一、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;
}