C++有限状态自动机解析HTTP协议

2023-05-16

一、HTTP请求报文格式

 

 HTTP请求报文主要由四部分组成,分别为请求头、请求行、空行、请求体

请求方法

请求方法包括GET、HEAD、PUT、POST、TRACE、OPTIONS、DELETE等;

(1)GET

  •   当客户端要从服务器中读取文档时,当点击网页上的链接或者通过在浏览器的地址栏输入网址来浏览网页的,使用的都是GET方式。GET方法要求服务器将URL定位的资源放在响应报文的数据部分,会送给客户端。使用GET方法时,请求参数和对应的值附加在URL后面,利用一个问号‘?’代表URL的结尾与请求参数的开始,传递参数长度受限制。例如,/index.jsp?id=100&op=bind。通过GET方式传递的数据直接放在地址中,所以GET方式的请求一般不包含“请求内容”部分,请求数据以地址的形式表现在请求行。地址中‘?’之后的部分就是通过GET发送的请求数据,各个数据之间用‘&’符号隔开。显然这种方式不适合传送私密数据。另外,由于不同的浏览器对地址的字符限制也有所不同,一半最多只能识别1024个字符,所以如果需要传送大量数据的时候,也不适合使用GET方式。如果数据是英文字母/数字,原样发送;如果是空格,转换为+;如果是中文/其他字符,则直接把字符串用BASE64加密,得出:%E4%BD%A0%E5%A5%BD,其中%XX中的XX为该符号以16进制表示的ASCII。

(2)POST

  • 允许客户端给服务器提供信息较多。POST方法将请求参数封装在HTTP请求数据中,以名称/值的形式出现,可以传输大量数据,这样POST方式对传送的数据大小没有限制,而且也不会显示在URL中。POST方式请求行中不包含数据字符串,这些数据保存在“请求内容”部分,各数据之间也是使用‘&’符号隔开。POST方式大多用于页面的表单中。因为POST也能完成GET的功能,因此多数人在设计表单的时候一律都使用POST方式,其实这是一个误区。GET方式也有自己的特点和优势,我们应该根据不同的情况来选择是使用GET还是使用POST。

URL

  • URL:统一资源定位符,是一种资源位置的抽象唯一识别方法。
  • 组成:<协议>://<主机>:<端口>/<路径>
  • 端口和路径有事可以省略(HTTP默认端口号是80)

  

协议

协议版本的格式:HTTP/主协议号.次版本号,常用的有HTTP/1.0和HTTP1.1。

请求头

请求头为一系列键值对的形式表示,格式为:key:value,常见的请求头如下:

请求头说明
Accept主要是告诉服务器客户端可以接收什么类型的响应
Content-Length表示请求体的长度
Cookie传递的cookie
User-Agent发送请求的应用程序信息

二、有限状态自动机解析HTTP协议

有限状态自动机拥有有限个数量的状态,每个状态可以迁移到零个或多个状态,输入字符决定执行哪个状态的迁移。有限状态自动机可以表示为一个有向图。在协议的解析上有广泛的应用!

#ifndef _http_hpp
#define _http_hpp

#include <iostream>
#include <string>
#include <unordered_map>

#define CR '\r' //回车
#define LF '\n' //换行

/*字符串buf,只存储对应的指针,不存储实际的内容*/
struct StringBuffer
{
    char* begin=NULL;//字符串起始位置
    char* end=NULL;  //字符串结束位置
    operator std::string()const{//转移operator,返回类实例化得到string类型的
        return std::string(begin,end);
    }
};

/*HTTP请求行的状态*/
enum class HttpRequestDecodeState
{
    INVALID,                //无效
    INVALID_METHOD,         //无效请求方法
    INVALID_URI,            //无效的请求路径
    INVALID_VERSION,        //无效的协议版本号
    INVALID_HEADER,         //无效请求头

    START,                  //请求行开始
    METHOD,                 //请求方法

    BEFORE_URI,             //请求连接前的状态,需要'/'开头
    IN_URI,                 //url处理
    BEFORE_URI_PARAM_KEY,   //URL请求参数键之前
    URI_PARAM_KEY,          //URL请求参数键
    BEFORE_URI_PARAM_VALUE, //URL的参数值之前
    URI_PARAM_VALUE,        //URL请求参数值

    BEFORE_PROTOCAL,        //协议解析之前
    PROTOCAL,               //协议

    BEFORE_VERSION,         //版本开始之前
    VERSION_SPLIT,          //版本分割符号'.'
    VERSION,                //版本

    HEADER_KEY,
    HEADER_BEFORE_COLON,    //请求头冒号之前
    HEADER_AFTER_COLON,     //请求头冒号之后
    HEADER_VALUE,           //值

    WHEN_CR,                //遇到一个回车
    CR_LF,                  //回车换行
    CR_LF_CR,               //回车换行之后

    BODY,                   //请求体
    COMPLEIE                //完成
};

/*HTTP请求类*/
class HttpRequest
{
public:
    void tryDecode(const std::string& buf);
    const std::string& getMethod() const;
    const std::string& getUrl() const;
    const std::unordered_map<std::string,std::string>& getRequestParams() const;
    const std::string& getProtocol() const;
    const std::string& getVersion() const;
    const std::unordered_map<std::string,std::string>& getHeaders() const;
    const std::string& getBody() const;

private:
    void parseInternal(const char* buf,int size);

private:
    std::string _method;                                       //请求方法
    std::string _url;                                          //请求路径(不包含参数)
    std::unordered_map<std::string,std::string> _requestParams;//请求参数
    std::string _protocol;                                     //协议
    std::string _version;                                      //版本
    std::unordered_map<std::string,std::string> _header;       //所有的请求头
    std::string _body;                                         //请求体
    int _nextPos=0;                                            //下一个位置的
    HttpRequestDecodeState _decodeState=HttpRequestDecodeState::START;//解析状态
};
#endif

具体实现细节:

#include "http.hpp"
#include <vector>
#include <string.h>
#include <stdio.h>
#include <ctype.h>

/*协议解析*/
void HttpRequest::tryDecode(const std::string& buf) 
{
    this->parseInternal(buf.c_str(),buf.size());
}

/*请求方法*/
const std::string& HttpRequest::getMethod() const
{
    return _method;
}

/*请求URL*/
const std::string& HttpRequest::getUrl() const
{
    return _url;
}

/*请求头key->value*/
const std::unordered_map<std::string,std::string>& HttpRequest::getRequestParams() const
{
    return _requestParams;
}

/*请求URL参数*/
const std::string& HttpRequest::getProtocol() const
{
    return _protocol;
}

/*请求协议版本*/
const std::string& HttpRequest::getVersion() const
{
    return _version;
}

/*请求头*/
const std::unordered_map<std::string,std::string>& HttpRequest::getHeaders() const
{
    return _header;
}

/*请求主体*/
const std::string& HttpRequest::getBody() const
{
    return _body;
}

/*解析请求行*/
void HttpRequest::parseInternal(const char* buf,int size) 
{
    StringBuffer method;
    StringBuffer url;

    StringBuffer requestParamsKey;
    StringBuffer requestParamsValue;

    StringBuffer protocol;
    StringBuffer version;

    StringBuffer headerKey;
    StringBuffer headerValue;

    int bodyLength=0;
    char* p0=const_cast<char*>(buf+_nextPos);//去掉const限制
    while(_decodeState!=HttpRequestDecodeState::INVALID
           &&_decodeState!=HttpRequestDecodeState::INVALID_METHOD
           &&_decodeState!=HttpRequestDecodeState::INVALID_URI
           &&_decodeState!=HttpRequestDecodeState::INVALID_VERSION
           &&_decodeState!=HttpRequestDecodeState::INVALID_HEADER
           &&_decodeState!=HttpRequestDecodeState::COMPLEIE
           &&_nextPos<size
         )
    {
        char ch=*p0;    //当前的字符
        char* p=p0++;   //指针偏移 
        int scanPos=_nextPos++;//下一个指针往后偏移

        switch(_decodeState)
        {
            case HttpRequestDecodeState::START:
            {
                //空格、换行、回车都继续
                if(ch==CR||ch==LF||isblank(ch)){}
                else if(isupper(ch)) //判断是不是大写字符
                {
                    method.begin=p;//请求方法的起始点
                    _decodeState=HttpRequestDecodeState::METHOD;//遇到第一个字符开始解析方法
                }
                else
                {
                    _decodeState=HttpRequestDecodeState::INVALID;//无效的字符
                }
                break;
            }
            case HttpRequestDecodeState::METHOD:
            {
                //请求方法需要大写,大写字母就继续
                if(isupper(ch)){}
                else if(isblank(ch))//空格说明请求方法解析结束,下一步解析URI
                {
                    method.end=p;//请求方法解析结束
                    _method=method;
                    _decodeState=HttpRequestDecodeState::BEFORE_URI;
                }
                else
                {
                    _decodeState=HttpRequestDecodeState::INVALID_METHOD;//无效的请求方法
                }
                break;
            }
            case HttpRequestDecodeState::BEFORE_URI:
            {
                /*请求URL连接前的处理,需要'/'开头*/
                if(isblank(ch)){}
                else if(ch=='/')
                {
                    url.begin=p;
                    _decodeState=HttpRequestDecodeState::IN_URI;
                }
                else
                {
                    _decodeState=HttpRequestDecodeState::INVALID_URI;
                }
                break;
            }
            case HttpRequestDecodeState::IN_URI:
            {
                /*开始处理请求路径URL的字符串*/
                if(isblank(ch))
                {
                    url.end=p;
                    _url=url;
                    _decodeState=HttpRequestDecodeState::BEFORE_PROTOCAL;
                }
                else if(ch=='?')
                {
                    url.end=p;
                    _url=url;
                    _decodeState=HttpRequestDecodeState::BEFORE_URI_PARAM_KEY;
                }
                else{}
                break;
            }
            case HttpRequestDecodeState::BEFORE_URI_PARAM_KEY:
            {
                if(isblank(ch)||ch==LF||ch==CR)//'?'后面是空格、回车、换行则是无效的URL
                {
                    _decodeState=HttpRequestDecodeState::INVALID_URI;
                }
                else
                {
                    requestParamsKey.begin=p;
                    _decodeState=HttpRequestDecodeState::URI_PARAM_KEY;//URL的参数key
                }
                break;
            }
            case HttpRequestDecodeState::URI_PARAM_KEY:
            {
                if(ch=='=')
                {
                    requestParamsKey.end=p;
                    _decodeState=HttpRequestDecodeState::BEFORE_URI_PARAM_VALUE;//开始解析参数值
                }
                else if(isblank(ch))
                {
                    _decodeState=HttpRequestDecodeState::INVALID_URI;//无效的请求参数
                }
                else{}
                break;
            }
            case HttpRequestDecodeState::BEFORE_URI_PARAM_VALUE:
            {
                if(isblank(ch)||ch==LF||ch==CR)
                {
                    _decodeState=HttpRequestDecodeState::INVALID_URI;
                }
                else
                {
                    requestParamsValue.begin=p;
                    _decodeState=HttpRequestDecodeState::URI_PARAM_VALUE;
                }
                break;
            }
            case HttpRequestDecodeState::URI_PARAM_VALUE:
            {
                if(ch=='&')
                {
                    requestParamsValue.end=p;
                    //收获一个请求参数
                    _requestParams.insert({requestParamsKey,requestParamsValue});
                    _decodeState=HttpRequestDecodeState::BEFORE_URI_PARAM_KEY;
                }
                else if(isblank(ch))
                {
                    requestParamsValue.end=p;
                    //空格也收获一个请求参数
                    _requestParams.insert({requestParamsKey,requestParamsValue});
                    _decodeState=HttpRequestDecodeState::BEFORE_PROTOCAL;
                }
                else{}
                break;
            }
            case HttpRequestDecodeState::BEFORE_PROTOCAL:
            {
                if(isblank(ch)){}
                else
                {
                    protocol.begin=p;
                    _decodeState=HttpRequestDecodeState::PROTOCAL;
                }
                break;
            }
            case HttpRequestDecodeState::PROTOCAL:
            {
                //解析请求协议
                if(ch=='/')
                {
                    protocol.end=p;
                    _protocol=protocol;
                    _decodeState=HttpRequestDecodeState::BEFORE_VERSION;
                }
                else{}
                break;
            }
            case HttpRequestDecodeState::BEFORE_VERSION:
            {
                if(isdigit(ch))
                {
                    version.begin=p;
                    _decodeState=HttpRequestDecodeState::VERSION;
                }
                else
                {
                    _decodeState=HttpRequestDecodeState::INVALID_VERSION;
                }
                break;
            }
            case HttpRequestDecodeState::VERSION:
            {
                //协议版本解析,如果不是数字,则协议版本不对
                if(ch==CR)
                {
                    version.end=p;
                    _version=version;
                    _decodeState=HttpRequestDecodeState::WHEN_CR;//遇到一个回车
                }
                else if(ch=='.')
                {
                    //遇到版本分割
                    _decodeState=HttpRequestDecodeState::VERSION_SPLIT;
                }
                else if(isdigit(ch)){}
                else
                {
                    _decodeState=HttpRequestDecodeState::INVALID_VERSION;
                }
                break;
            }
            case HttpRequestDecodeState::VERSION_SPLIT:
            {
                //遇到版本分隔符之后的字符必须是数字,其它情况都是错误的
                if(isdigit(ch))
                {
                    _decodeState=HttpRequestDecodeState::VERSION;
                }
                else
                {
                    _decodeState=HttpRequestDecodeState::INVALID_VERSION;
                }
            }
            case HttpRequestDecodeState::HEADER_KEY:
            {
                //冒号前后可能有空格
                if(isblank(ch))
                {
                    headerKey.end=p;
                    _decodeState=HttpRequestDecodeState::HEADER_BEFORE_COLON;//冒号之前的状态
                }
                else if(ch==':')
                {
                    headerKey.end=p;
                    _decodeState=HttpRequestDecodeState::HEADER_AFTER_COLON;//冒号之后的状态
                }
                else{}
                break;
            }
            case HttpRequestDecodeState::HEADER_BEFORE_COLON:
            {
                if(isblank(ch)){}
                else if(ch==':')
                {
                    _decodeState=HttpRequestDecodeState::HEADER_AFTER_COLON;
                }
                else
                {
                    //冒号之前的状态不能是空格之外的其他字符
                    _decodeState=HttpRequestDecodeState::INVALID_HEADER;
                }
                break;
            }
            case HttpRequestDecodeState::HEADER_AFTER_COLON:
            {
                if(isblank(ch)){}
                else
                {
                    headerValue.begin=p;
                    _decodeState=HttpRequestDecodeState::HEADER_VALUE;//开始处理值
                }
                break;
            }
            case HttpRequestDecodeState::HEADER_VALUE:
            {
                if(ch==CR)
                {
                    headerValue.end=p;
                    _header.insert({headerKey,headerValue});
                    _decodeState=HttpRequestDecodeState::WHEN_CR;//回车
                }
                break;
            }
            case HttpRequestDecodeState::WHEN_CR:
            {
                if(ch==LF)
                {
                    //前面是回车,如果当前是换行,可换成下一个
                    _decodeState=HttpRequestDecodeState::CR_LF;
                }
                else
                {
                    _decodeState=HttpRequestDecodeState::INVALID;
                }
                break;
            }
            case HttpRequestDecodeState::CR_LF:
            {
                if(ch==CR)
                {
                    //如果在CR_LF状态之后还是CR,可能是到请求体了
                    _decodeState=HttpRequestDecodeState::CR_LF_CR;
                }
                else if(isblank(ch))
                {
                    _decodeState=HttpRequestDecodeState::INVALID;
                }
                else
                {
                    //如果不是,那么就可能又是请求头了
                    headerKey.begin=p;
                    _decodeState=HttpRequestDecodeState::HEADER_KEY;
                }
                break;
            }
            case HttpRequestDecodeState::CR_LF_CR:
            {
                if(ch==LF)
                {
                    //如果是\r接着\n,那么判断是不是需要解析请求体
                    if(_header.count("Content-Length"))
                    {
                        bodyLength=atoi(_header["Content-Length"].c_str());
                        if(bodyLength>0)
                        {
                            _decodeState=HttpRequestDecodeState::BODY;//解析请求体
                        }
                        else
                        {
                            _decodeState=HttpRequestDecodeState::COMPLEIE;//完成了
                        }
                    }
                    else
                    {
                        if(scanPos<size)
                        {
                            bodyLength=size-scanPos;
                            _decodeState=HttpRequestDecodeState::BODY;//解析请求体
                        }
                        else
                        {
                            _decodeState=HttpRequestDecodeState::COMPLEIE;
                        }
                    }
                }
                else
                {
                    _decodeState=HttpRequestDecodeState::INVALID_HEADER;
                }
                break;
            }
            case HttpRequestDecodeState::BODY:
            {
                //解析请求体
                _body.assign(p,bodyLength);
                _decodeState=HttpRequestDecodeState::COMPLEIE;
                break;
            }
            default:break;
        }
    }
}

测试代码:

#include "http.hpp"
#include "http.cpp"

int main(int argc, char *argv[]){

	std::string  str="POST /audiolibrary/music?ar=1595301089068&n=1p1 HTTP/1.1\r\n"
		"Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, application/x-silverlight, application/x-shockwave-flash\r\n"
		"Referer: http://www.google.cn\r\n"
		"Accept-Language: zh-cn\r\n"
		"Accept-Encoding: gzip, deflate\r\n"
		"User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; TheWorld)\r\n"
		"content-length:28\r\n"
		"Host: www.google.cn\r\n"
		"Connection: Keep-Alive\r\n"
		"Cookie: PREF=ID=80a06da87be9ae3c:U=f7167333e2c3b714:NW=1:TM=1261551909:LM=1261551917:S=ybYcq2wpfefs4V9g; NID=31=ojj8d-IygaEtSxLgaJmqSjVhCspkviJrB6omjamNrSm8lZhKy_yMfO2M4QMRKcH1g0iQv9u\r\n"
		"\r\n"
		"hl=zh-CN&source=hp&q=domety";


	HttpRequest req;
	req.tryDecode(str);

	std::cout<<"[method]"<<req.getMethod()<<std::endl;
	std::cout<<std::endl;
	std::cout<<"[url]"   <<req.getUrl()<<std::endl;
	std::cout<<std::endl;
	std::cout<<"[request params]"<<std::endl;
	std::cout<<std::endl;
	for(auto& p:req.getRequestParams()) 
    {
		std::cout<<"   key: "  << p.first<<std::endl;
		std::cout<<"   value: "<< p.second<<std::endl;
	}
	std::cout<<std::endl;
	std::cout<<"[protocol]"<<req.getProtocol() <<std::endl;
	std::cout<<"[version]" <<req.getVersion()  <<std::endl;
	std::cout<<std::endl;
	std::cout<<"[request headers]" << std::endl;
	for(auto& h : req.getHeaders()) {
		std::cout<<"   key: "   <<h.first  <<std::endl;
		std::cout<<"   value: " <<h.second <<std::endl;
	}
	std::cout<<std::endl;
	std::cout<<"[body]"<<req.getBody()<<std::endl;
	return 0;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++有限状态自动机解析HTTP协议 的相关文章

随机推荐

  • windows比cmd更强大的 WMIC命令使用详解

    文章目录 什么是wmic WMIC能做什么 WMIC命令使用帮助文档WMIC命令使用实例wmic的运行方式可以有两种法1 显示进程的详细信息2 停止 暂停和运行服务功能3 显示出BIOS信息4 停止进程的操作5 连接远程电脑6 BIOS 基
  • 编程意识——宏定义封装多个函数参数

    作者 釜薪君 公众号 嵌入式杂牌军 文章目录 前言一 这种意识的来源二 实现源码分析1 函数调用2 宏定义部分3 函数实现4 宏替换后的函数调用 总结 前言 今天带小伙伴们分析一段不错的代码 xff0c 学习一下关于宏封装的一种意识 xff
  • DSP28335的SCI的FIFO中断使用心得

    自学了一段时间的DSP28335的串口设置 xff0c 写下来帮助更多的新手 xff0c 遇到了很多问题也记录一些解决办法 以下全都是我个人的理解 xff0c 可能说的不对 xff0c 大家讨论 1 关于为什么必须用FIFO 一般的DSP系
  • 51单片机堆栈深入剖析

    用C语言进行MCS51系列单片机程序设计是单片机开发和应用的必然趋势 Keil公司的C51编译器支持经典8051和8051派生产品的版本 xff0c 通称为Cx51 应该说 xff0c Cx51是C语言在MCS51单片机上的扩展 xff0c
  • 基于ros_arduino_bridge的智能小车----上位机篇

    基于ros arduino bridge的智能小车 上位机篇 基于ros arduino bridge的智能小车 硬件篇 基于ros arduino bridge的智能小车 下位机篇 ros arduino bridge文件系统 xff08
  • 基于ros_arduino_bridge的智能小车----下位机篇

    基于ros arduino bridge的智能小车 下位机篇 参考文章 xff1a 基于ros arduino bridge的智能小车 上位机篇 基于ros arduino bridge的智能小车 硬件篇 下位机部分实际上可以视作完全独立的
  • 【命令】Python执行命令超时控制【原创】

    目录 参考 概要 方案 方案一 xff1a os system 方案二 xff1a os popen 方案三 xff1a subprocess check output 方案四 xff1a subprocess Popen 方案五 xff1
  • nRF52 Mesh开发 (2) SDK例程Light_switch server 添加一个element控制开发板其他LED灯

    server文件结构 xff1a 使用SEGGER编译的话直接打开 emProject文件即可 xff1b img文件中包含程序运行过程图 xff1b include文件包含该例程下的头文件 xff1b 2 具体操作 xff1a 在main
  • nRF52 Mesh开发 (3) MESH Sensor Server/Client Models详解与实现

    MESH Sensor Model 实现 MESH Spec规定的 Sensor Model 标准传感器状态传感器描述传感器参数设置传感器cadence传感器数据 传感器可发送和接收的消息Sensor Server Client Model
  • Telink Mesh 开发(1)调试log打印

    Telink Mesh SDK 调试log打印 Telink 官网论坛建议使用GPIO模拟串口打印log xff0c 推荐阅读Telink官网发布的最新SDK使用手册 xff0c 更新了不少东西 一 使用串口打印log1 使能uart lo
  • 蓝牙Mesh基础(3)蓝牙Mesh协议--总览

    蓝牙Mesh协议 总览Bearer Layer xff08 承载层 xff09 Network Layer xff08 网络层 xff09 Low Transport Layer xff08 下层传输层 xff09 Upper Transp
  • 蓝牙Mesh基础(9)设备配网

    设备配网 xff08 启动配置 xff09 设备配网过程配网PDU配网PDU如何传输呢 设备配网过程 首先 xff0c 需要配网的设备先进行未配网广播 xff0c 这个广播不同于普通的ble广播 xff0c 广播数据结构类型 xff08 A
  • 弱网络环境模拟--树莓派搭建ATC

    弱网络环境模拟 树莓派搭建ATC 1 硬件和系统2 搭建过程3 遇到的问题1 Failed to start hostapd service Unit hostapd service is masked2 django python版本问题
  • OpenCV双目相机测距程序

    本文主要分享一个双目测距的实现程序 xff0c 用的bumblebee2相机 使用的OpenCV自带的BM算法 在OpenCV3中 xff0c StereoBM算法发生了比较大的变化 xff0c StereoBM被定义为纯虚类 xff0c
  • stm32 printf 串口输出

    在使用STM32调试时 xff0c 经常使用串口发送信息 xff0c 为了方便调试与串口发送信息 xff0c 用printf xff08 xff09 函数实现通过串口打印信息 1 添加包含printf xff08 xff09 函数的头文件
  • 【slighttpd】基于lighttpd架构的Server项目实战(7)—http-parser

    对于http服务器 xff0c http request的解析是比较麻烦的 xff0c 由于我们的重点并不在这上面 xff0c 所以这一部分不打算自己编写 xff0c 而是使用开源的http parser库 xff0c 下面我们将使用该库来
  • C#实现以图搜图

    朋友们 xff0c 如需转载请标明出处 xff1a http blog csdn net jiangjunshow 前言 最近在逛淘宝时发现了淘宝的图片搜索功能 xff0c 可能是我太Low了这个技术点已经实现很长时间了 想想自己能不能实现
  • 床长人工智能教程 - 前言

    朋友们 如需转载请标明出处 xff1a http blog csdn net jiangjunshow 人工智能被认为是一种拯救世界 终结世界的技术 毋庸置疑 xff0c 人工智能时代就要来临了 xff0c 科幻电影中的场景将成为现实 xf
  • 如何做接口测试呢?接口测试有哪些工具【小白都会系列】

    回想入职测试已经10年时间了 xff0c 初入职场的我对于接口测试茫然不知 后来因为业务需要 xff0c 开始慢慢接触接口测试 从最开始使用工具进行接口测试到编写代码实现接口自动化 xff0c 到最后的测试平台开发 回想这一路走来感触颇深
  • C++有限状态自动机解析HTTP协议

    一 HTTP请求报文格式 HTTP请求报文主要由四部分组成 xff0c 分别为请求头 请求行 空行 请求体 xff1b 请求方法 请求方法包括GET HEAD PUT POST TRACE OPTIONS DELETE等 xff1b xff