Java实现一个解析CURL脚本小工具

news2024/11/19 16:48:11

该工具可以将CURL脚本中的Header解析为KV Map结构;获取URL路径、请求类型;解析URL参数列表;解析Body请求体:Form表单、Raw Body、KV Body、XML/JSON/TEXT结构体等。

使用示例

获取一个http curl脚本:

curl --location --request POST 'https://cainiao-inc.com?param_1=value_1&param_2=value_2' \
--header 'Cookie: USER_COOKIE' \
--header 'Content-Type: application/json' \
--data-raw '{
    "appName": "link",
    "apiId": "TEST_API",
    "content": {
        "address": "Cainiao Home",
        "city": "Hangzhou"
    }
}'

执行解析例子:

实现原理

实现原理很简单:基于Java正则 + 责任链设计模式,按照Curl脚本的常见语法去匹配、解析即可~

按照Curl语法结构,可以将其拆分为 5 个部分:

  • URL路径:http://cainiao.com

  • URL参数列表:?param_1=valie_1&param_2=valie_2

  • 请求方法类型: 例如 POST、GET、DELETE、PUT...... 需要正则匹配-X --request等标识符

  • Header请求头:例如 Cookie、Token、Content-Type...... 需要正则匹配-H --header等标识符

  • Body请求体:可以分为form-data/-formdata-rawdata-urlencode-d--datakvbody等。格式可能包含JSON、XML、文本、KV键值对,二进制流(暂不支持解析)等等。

具体实现

流程简图:

类关系图:

CurlParserUtil

Curl解析工具类:

public class CurlParserUtil {
    /**
     * 该方法是用来解析CURL的入口。
     *
     * @param curl 输入的CURL文本字符串
     * @return 返回解析后生成的CURL实体对象
     */
    public static CurlEntity parse(String curl) {
        CurlEntity entity = CurlEntity.builder().build();
        ICurlHandler<CurlEntity, String> handlerChain = CurlHandlerChain.init();

        // 如需扩展其他解析器,继续往链表中add即可
        handlerChain.next(new UrlPathHandler())
                .next(new UrlParamsHandler())
                .next(new HttpMethodHandler())
                .next(new HeaderHandler())
                .next(new HttpBodyHandler());

        handlerChain.handle(entity, curl);
        return entity;
    }
}

CurlEntity

解析后得到的Curl实体类(这里分了5个部分)

@Data
@Builder
public class CurlEntity {
    /**
     * URL路径
     */
    private String url;

    /**
     * 请求方法类型
     */
    private Method method;

    /**
     * URL参数
     */
    private Map<String, String> urlParams;

    /**
     * header参数
     */
    private Map<String, String> headers;

    /**
     * 请求体
     */
    private JSONObject body;

    public enum Method {
        GET,
        POST,
        PUT,
        DELETE
    }
}

ICurlHandler

责任链链表结构定义:

public interface ICurlHandler<R, S> {

    ICurlHandler<CurlEntity, String> next(ICurlHandler<CurlEntity, String> handler);

    void handle(CurlEntity entity, String curl);
}

CurlHandlerChain

责任链载体:

public abstract class CurlHandlerChain implements ICurlHandler<CurlEntity, String> {

    ICurlHandler<CurlEntity, String> next;

    @Override
    public ICurlHandler<CurlEntity, String> next(ICurlHandler<CurlEntity, String> handler) {
        this.next = handler;
        return this.next;
    }

    @Override
    public abstract void handle(CurlEntity entity, String curl);

    /**
     * for subclass call
     */
    protected void nextHandle(CurlEntity curlEntity, String curl) {
        if (next != null) {
            next.handle(curlEntity, curl);
        }
    }

    protected void validate(String curl) {
        if (StringUtils.isBlank(curl)) {
            throw new IllegalArgumentException("Curl script is empty");
        }

        Matcher matcher = CURL_BASIC_STRUCTURE_PATTERN.matcher(curl);
        if (!matcher.find()) {
            throw new IllegalArgumentException("Curl script is invalid");
        }
    }

    public static CurlHandlerChain init() {
        return new CurlHandlerChain() {
            @Override
            public void handle(CurlEntity entity, String curl) {
                this.validate(curl);

                // 替换掉可能存在的转译
                curl = curl.replace("\\", "");

                if (next != null) {
                    next.handle(entity, curl);
                }
            }
        };
    }

    public void log(Object... logParams) {
        // Write log for subclass extensions
    }
}

UrlPathHandler

URL路径解析:

public class UrlPathHandler extends CurlHandlerChain {

    @Override
    public void handle(CurlEntity entity, String curl) {
        String url = parseUrlPath(curl);
        entity.setUrl(url);

        this.log(url);
        super.nextHandle(entity, curl);
    }

    /**
     * 该方法用于解析URL路径。
     *
     * @param curl 需要解析的URL,以字符串形式给出
     * @return URL中的路径部分。如果找不到,将返回null
     */
    private String parseUrlPath(String curl) {
        Matcher matcher = CurlPatternConstants.URL_PATH_PATTERN.matcher(curl);
        if (matcher.find()) {
            return matcher.group(1) != null ? matcher.group(1) : matcher.group(3);
        }
        return null;
    }

    @Override
    public void log(Object... logParams) {
        LogPrinter.info("UrlPathHandler execute: url={}", logParams);
    }
}

HttpMethodHandler

请求类型解析:

public class HttpMethodHandler extends CurlHandlerChain {

    @Override
    public void handle(CurlEntity entity, String curl) {
        CurlEntity.Method method = parseMethod(curl);
        entity.setMethod(method);

        this.log(method);
        super.nextHandle(entity, curl);
    }

    private CurlEntity.Method parseMethod(String curl) {
        Matcher matcher = CurlPatternConstants.HTTP_METHOD_PATTERN.matcher(curl);
        Matcher defaultMatcher = CurlPatternConstants.DEFAULT_HTTP_METHOD_PATTERN.matcher(curl);
        if (matcher.find()) {
            String method = matcher.group(1);
            return CurlEntity.Method.valueOf(method.toUpperCase());
        } else if (defaultMatcher.find()) {
            // 如果命令中包含 -d 或 --data,没有明确请求方法,默认为 POST
            return CurlEntity.Method.POST;
        } else {
            // 没有明确指定请求方法,默认为 GET
            return CurlEntity.Method.GET;
        }
    }

    @Override
    public void log(Object... logParams) {
        LogPrinter.info("HttpMethodHandler execute: method={}", logParams);
    }
}

UrlParamsHandler

URL参数列表解析:

public class UrlParamsHandler extends CurlHandlerChain {

    @Override
    public void handle(CurlEntity entity, String curl) {
        String url = extractUrl(curl);
        Map<String, String> urlParams = parseUrlParams(url);
        entity.setUrlParams(urlParams);

        this.log(urlParams);
        super.nextHandle(entity, curl);
    }

    private String extractUrl(String curl) {
        Matcher matcher = CurlPatternConstants.URL_PARAMS_PATTERN.matcher(curl);
        if (matcher.find()) {
            return matcher.group(1);
        }
        return null;
    }

    private Map<String, String> parseUrlParams(String url) {
        if (StringUtils.isBlank(url)) {
            return Collections.emptyMap();
        }

        Map<String, String> urlParams = new HashMap<>();
        // 提取URL的查询参数部分
        String[] urlParts = url.split("\\?");
        if (urlParts.length > 1) {
            // 只处理存在查询参数的情况
            String query = urlParts[1];
            // 解析查询参数到Map
            String[] pairs = query.split("&");
            for (String pair : pairs) {
                int idx = pair.indexOf("=");
                if (idx != -1 && idx < pair.length() - 1) {
                    String key = pair.substring(0, idx);
                    String value = pair.substring(idx + 1);
                    urlParams.put(key, value);
                } else {
                    // 存在无值的参数时
                    urlParams.put(pair, null);
                }
            }
        }
        return urlParams;
    }

    @Override
    public void log(Object... logParams) {
        LogPrinter.info("UrlParamsHandler execute: urlParams={}", logParams);
    }
}

HeaderHandler

Http Header解析:

public class HeaderHandler extends CurlHandlerChain{
    
    @Override
    public void handle(CurlEntity entity, String curl) {
        Map<String, String> headers = parseHeaders(curl);
        entity.setHeaders(headers);

        this.log(headers);
        super.nextHandle(entity, curl);
    }

    private Map<String, String> parseHeaders(String curl) {
        if (StringUtils.isBlank(curl)) {
            return Collections.emptyMap();
        }

        Matcher matcher = CurlPatternConstants.CURL_HEADERS_PATTERN.matcher(curl);
        Map<String, String> headers = new HashMap<>();
        while (matcher.find()) {
            String header = matcher.group(1);
            String[] headerKeyValue = header.split(":", 2);
            if (headerKeyValue.length == 2) {
                // 去除键和值的首尾空白字符
                headers.put(headerKeyValue[0].trim(), headerKeyValue[1].trim());
            }
        }

        return headers;
    }

    @Override
    public void log(Object... logParams) {
        LogPrinter.info("HeaderHandler execute: headers={}", logParams);
    }
}

HttpBodyHandler

Request Body请求体解析:

  • form-data/-form

  • data-urlencode

  • data-raw

  • default/-d/--data

格式可能包含JSON、XML、文本、KV键值对,二进制流(暂不支持解析)等等。

public class HttpBodyHandler extends CurlHandlerChain {
    
    @Override
    public void handle(CurlEntity entity, String curl) {
        JSONObject body = parseBody(curl);
        entity.setBody(body);

        this.log(body);
        super.nextHandle(entity, curl);
    }

    private JSONObject parseBody(String curl) {
        Matcher formMatcher = CurlPatternConstants.HTTP_FROM_BODY_PATTERN.matcher(curl);
        if (formMatcher.find()) {
            return parseFormBody(formMatcher);
        }

        Matcher urlencodeMatcher = CurlPatternConstants.HTTP_URLENCODE_BODY_PATTERN.matcher(curl);
        if (urlencodeMatcher.find()) {
            return parseUrlEncodeBody(urlencodeMatcher);
        }

        Matcher rawMatcher = CurlPatternConstants.HTTP_ROW_BODY_PATTERN.matcher(curl);
        if (rawMatcher.find()) {
            return parseRowBody(rawMatcher);
        }

        Matcher defaultMatcher = CurlPatternConstants.DEFAULT_HTTP_BODY_PATTERN.matcher(curl);
        if (defaultMatcher.find()) {
            return parseDefaultBody(defaultMatcher);
        }

        return new JSONObject();
    }

    private JSONObject parseDefaultBody(Matcher defaultMatcher) {
        String bodyStr = "";
        if (defaultMatcher.group(1) != null) {
            // 单引号包裹的数据
            bodyStr = defaultMatcher.group(1);
        } else if (defaultMatcher.group(2) != null) {
            // 双引号包裹的数据
            bodyStr = defaultMatcher.group(2);
        } else {
            // 无引号的数据
            bodyStr = defaultMatcher.group(3);
        }

        // 特殊Case: username=test&password=secret
        Matcher kvMatcher = CurlPatternConstants.DEFAULT_HTTP_BODY_PATTERN_KV.matcher(bodyStr);
        if (kvMatcher.find()) {
            return parseKVBody(bodyStr);
        }

        return JSONObject.parseObject(bodyStr);
    }

    private JSONObject parseKVBody(String kvBodyStr) {
        JSONObject json = new JSONObject();
        String[] pairs = kvBodyStr.split("&");
        for (String pair : pairs) {
            int idx = pair.indexOf("=");
            String key = URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8);
            String value = URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8);
            json.put(key, value);
        }
        return json;
    }

    private JSONObject parseFormBody(Matcher formMatcher) {
        JSONObject formData = new JSONObject();

        // 重置指针匹配的位置
        formMatcher.reset();
        while (formMatcher.find()) {
            // 提取表单项
            String formItem = formMatcher.group(1) != null ? formMatcher.group(1) : formMatcher.group(2);

            // 分割键和值
            String[] keyValue = formItem.split("=", 2);
            if (keyValue.length == 2) {
                String key = keyValue[0];
                String value = keyValue[1];

                // 检测文件字段标记
                // PS: 理论上文件标记字段不需要支持
                if (value.startsWith("@")) {
                    // 只提取文件名,不读取文件内容
                    formData.put(key, value.substring(1));
                } else {
                    // 放入表单数据
                    formData.put(key, value);
                }
            }
        }

        return formData;
    }

    private JSONObject parseUrlEncodeBody(Matcher urlencodeMatcher) {
        JSONObject urlEncodeData = new JSONObject();

        // 重置指针匹配的位置
        urlencodeMatcher.reset();
        while (urlencodeMatcher.find()) {
            // 提取键值对字符串
            String keyValueEncoded = urlencodeMatcher.group(1);

            // 分隔键和值
            String[] keyValue = keyValueEncoded.split("=", 2);
            if (keyValue.length == 2) {
                String key = keyValue[0];
                String value = keyValue[1];

                // 对值进行URL解码
                String decodedValue = URLDecoder.decode(value, StandardCharsets.UTF_8);

                // 存入数据到JSON对象
                urlEncodeData.put(key, decodedValue);
            }
        }

        return urlEncodeData;
    }

    private JSONObject parseRowBody(Matcher rowMatcher) {
        String rawData = rowMatcher.group(1);

        if (isXML(rawData)) {
            // throw new IllegalArgumentException("Curl --data-raw content cant' be XML");
            return xml2json(rawData);
        }

        try {
            return JSON.parseObject(rawData);
        } catch (Exception e) {
            throw new IllegalArgumentException("Curl --data-raw content is not a valid JSON");
        }
    }

    public static boolean isXML(String xmlStr) {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            InputSource is = new InputSource(new StringReader(xmlStr));
            builder.parse(is);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private JSONObject xml2json(String xmlStr) {
        try {
            org.json.JSONObject orgJsonObj = XML.toJSONObject(xmlStr);
            String jsonString = orgJsonObj.toString();
            return JSON.parseObject(jsonString);
        } catch (JSONException e) {
            throw new LinkConsoleException("Curl --data-raw content xml2json error", e);
        }
    }

    @Override
    public void log(Object... logParams) {
        LogPrinter.info("HttpBodyHandler execute: body={}", logParams);
    }
}

CurlPatternConstants

正则匹配常量定义:

public interface CurlPatternConstants {

    /**
     * CURL基本结构校验
     */
    Pattern CURL_BASIC_STRUCTURE_PATTERN = Pattern.compile("^curl (\\S+)");

    /**
     * URL路径匹配
     */
    Pattern URL_PATH_PATTERN =
            Pattern.compile("(?:\\s|^)(?:'|\")?(https?://[^?\\s'\"]*)(?:\\?[^\\s'\"]*)?(?:'|\")?(?:\\s|$)");

    /**
     * 请求参数列表匹配
     */
    Pattern URL_PARAMS_PATTERN = Pattern.compile("(?:\\s|^)(?:'|\")?(https?://[^\\s'\"]+)(?:'|\")?(?:\\s|$)");

    /**
     * HTTP请求方法匹配
     */
    Pattern HTTP_METHOD_PATTERN = Pattern.compile("(?:-X|--request)\\s+(\\S+)");

    /**
     * 默认HTTP请求方法匹配
     */
    Pattern DEFAULT_HTTP_METHOD_PATTERN = Pattern.compile(".*\\s(-d|--data|--data-binary)\\s.*");

    /**
     * 请求头匹配
     */
    Pattern CURL_HEADERS_PATTERN = Pattern.compile("(?:-H|--header)\\s+'(.*?:.*?)'");

    /**
     * -d/--data 请求体匹配
     */
    Pattern DEFAULT_HTTP_BODY_PATTERN = Pattern.compile("(?:--data|-d)\\s+(?:'([^']*)'|\"([^\"]*)\"|(\\S+))");
    Pattern DEFAULT_HTTP_BODY_PATTERN_KV = Pattern.compile("^([^=&]+=[^=&]+)(?:&[^=&]+=[^=&]+)*$");

    /**
     * --data-raw 请求体匹配
     */
    Pattern HTTP_ROW_BODY_PATTERN = Pattern.compile("--data-raw '(.+?)'(?s)", Pattern.DOTALL);

    /**
     * --form 请求体匹配
     */
    Pattern HTTP_FROM_BODY_PATTERN = Pattern.compile("--form\\s+'(.*?)'|-F\\s+'(.*?)'");


    /**
     * --data-urlencode 请求体匹配
     */
    Pattern HTTP_URLENCODE_BODY_PATTERN = Pattern.compile("--data-urlencode\\s+'(.*?)'");

}

有问题可以留言讨论,刷流量评论定期删除!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1838390.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

备忘录文字颜色怎么改 备忘录改变字体颜色方法

在日常的工作和生活中&#xff0c;备忘录已经成为我不可或缺的好帮手。但是&#xff0c;面对满满当当的备忘录&#xff0c;有时候不同的任务和信息都混在一起&#xff0c;让人眼花缭乱。我常常想&#xff0c;如果能改变备忘录中的文字颜色&#xff0c;用以区分不同类别的事项&a…

HTML李峋同款跳动的爱心代码(双爱心版)

目录 写在前面 跳动的爱心 完整代码 代码分析 系列推荐 最后想说 写在前面 在浩瀚的网络世界中&#xff0c;总有一些小惊喜能触动我们的心弦。今天&#xff0c;就让我们用HTML语言&#xff0c;探索既神秘又浪漫的李峋同款跳动的爱心代码吧。 首先&#xff0c;让我们一起…

【NOI-题解】1431. 迷宫的第一条出路

文章目录 一、前言二、问题问题&#xff1a;1431. 迷宫的第一条出路 三、感谢 一、前言 二、问题 问题&#xff1a;1431. 迷宫的第一条出路 类型&#xff1a;深度搜索、回溯、路径打印 题目描述&#xff1a; 已知一 NN 的迷宫&#xff0c;允许往上、下、左、右四个方向行走…

网卡故障但bond0不切换原因及处理、脚本监控bond0网卡状态并做相应操作

文章目录 故障说明监控脚本脚本编写脚本测试正常场景异常场景1异常场景2脚本准备和修改网卡名脚本拷贝到所有需要监控主机修改网卡名批量执行脚本故障说明 在一次交换机升级的割接中,主备交换机重启的时候,我们发现了一个问题,有几台宿主机会中断【ping不通】,交换机重启完…

类的继承性(Java)

本篇学习面向对象语言的第二特性——继承性。 1 .为什么需要继承 我们来举个例子&#xff1a;我们知道动物有很多种&#xff0c;是一个比较大的概念。在动物的种类中&#xff0c;我们熟悉的有猫(Cat)、狗(Dog)等动物&#xff0c;它们都有动物的一般特征&#xff08;比如能够吃…

web安全渗透测试十大常规项(一):web渗透测试之XML和XXE外部实体注入

#详细点: XML被设计为传输和存储数据,XML文档结构包括XML声明、DTD文档类型定义(可选)、文档元素,其焦点是数据的内容,其把数据从HTML分离,是独立于软件和硬件的信息传输工具。等同于JSON传输。XXE漏洞XML External Entity Injection,即xml外部实体注入漏洞,XXE漏洞发…

Prometheus告警Alertmanager部署

Prometheus告警Alertmanager部署 资源监控一般离不开预警&#xff0c;因为我们不可能每时每刻都盯着某个资源监控看&#xff0c;而且在实际的工作中当中我们搭建的解决方案涉及到的服务器是多台甚至数十台&#xff0c;所以更加不现实&#xff0c;因此资源告警是一个必不可少的…

男士内裤品牌哪个好?2024公认好穿的五款男士内裤分享

男士内裤作为大家每天都要长时间穿着的贴身衣物&#xff0c;它的重要性不言而喻。为了确保健康与卫生&#xff0c;专家和医生建议您每三个月更换一次内裤&#xff0c;避免细菌滋生&#xff0c;让身体更加清爽自在。而一款优质的内裤&#xff0c;不仅要有舒适的弹性&#xff0c;…

学生课程信息管理系统

摘 要 目前&#xff0c;随着科学经济的不断发展&#xff0c;高校规模不断扩大&#xff0c;所招收的学生人数越来越 多&#xff1b;所开设的课程也越来越多。随之而来的是高校需要管理更多的事务。对于日益增 长的学生相关专业的课程也在不断增多&#xff0c;高校对其管理具有一…

《无与伦比》Centos7 扩容到已有逻辑卷

命令可以查找硬盘和分区情况 fdisk -l lsblk

【会议征稿,CPS出版】第三届先进机械电子与电气工程国际学术会议(ICAMEE 2024,7月19-21)

第三届先进机械电子与电气工程国际学术会议&#xff08;ICAMEE 2024&#xff09;将于2024年7月19-21日在桂林隆重举行。会议主要围绕“机械电子”、“电气工程” 等研究领域展开讨论&#xff0c; 旨在为机械电子、电气工程等领域的专家学者、工程技术人员、技术研发人员提供一个…

项目文章 | ATAC-seq联合转录组探究牦牛肌肉的调控机制

还在苦恼没有研究方向&#xff1f;还在找寻开启调控表观调控大门的钥匙&#xff1f;拿到材料不知如何下手&#xff1f;两篇ATAC联合转录组的牦牛调控机制分析或许能给你一点方向。 为了研究不同牦牛品种之间以及牦牛在成熟过程中&#xff0c;骨骼肌的调控如何影响肉类品质&…

OpenStack快速入门

任务一 熟悉OpenStack图形界面操作 1.1 Horizon项目 •各OpenStack服务的图形界面都是由Horizon提供的。 •Horizon提供基于Web的模块化用户界面。 •Horizon为云管理员提供一个整体的视图。 •Horizon为终端用户提供一个自主服务的门户。 •Horizon由云管理员进行管理…

Datacom HCIE实验考试通过率90%!深圳智汇云校传来5月捷报!

坚持不懈地努力&#xff0c;才能取得成功的果实 这是不变的真理 深圳云校传来5月捷报 在Datacom HCIE实验考试中 共有10名学员应战 其中9名学员凭借出色的表现 一次性通过了考试 展现出了扎实的技术能力 通过率高达90% &#xff08;华为历年考试平均通过率约60%&#…

【Hudi】基础概念-数据写

目录 数据写写操作写流程(UPSERT)写流程(Insert)写流程(Insert Overwrite)Key生成策略删除策略 数据写 写操作 UPSERT&#xff1a;默认行为&#xff0c;数据先通过index打标&#xff0c;有一些启发式算法决定消息的组织以及优化文件的大小>CDC导入INSERT:跳过index,写入效…

Java历史

Java程序设计语言史 Java是一种面向对象的编程语言&#xff0c;由Sun Microsystems于1990年初由开发人员James Gosling、Mike Sheridan和Patrick Naughton开发。1991年&#xff0c;詹姆斯戈斯林和他的朋友们组成了一个名为“绿色团队”的团队&#xff0c;进一步致力于这个项…

如何使用nginx 将新老网站域名重定向?(亲测收藏版)

目录 1. 安装Nginx 2. 配置Nginx 3. 启用新配置 结论 最近公司需要推行海外的业务&#xff0c;原来公司的名字麦聪软件的官网maicongs.com暂时用不上了&#xff0c;想启用sqlynx.com&#xff0c;但因为历史流量的原因&#xff0c;有很多的业务是指向的maicongs.com&#xf…

小程序分页新写法

// pages/query/query.js import {request } from ../../utils/request; Page({/*** 页面的初始数据*/data: {tabClickIndex: ,page: 1,limit: 10,listData: []},/*** 生命周期函数--监听页面加载*/onLoad(options) {this.getList()},/*** 生命周期函数--监听页面初次渲染完成*…

Sylar C++高性能服务器学习记录23 【Http模块-知识储备篇】

早在19年5月就在某站上看到sylar的视频了&#xff0c;一直认为这是一个非常不错的视频。 由于本人一直是自学编程&#xff0c;基础不扎实&#xff0c;也没有任何人的督促&#xff0c;没能坚持下去。 每每想起倍感惋惜&#xff0c;遂提笔再续前缘。 为了能更好的看懂sylar&…

Python10 python多线程

1.什么是python多线程 Python的多线程指的是在一个Python程序中同时运行多个线程&#xff0c;以达到并发执行多个任务的目的。线程是操作系统能够进行运算调度的最小单位&#xff0c;它被包含在进程之中&#xff0c;是进程中的实际运作单位。 在Python中&#xff0c;多线程的…