解析HTTP请求报文(GET、POST)
目的:
一个WEB服务器需要解析客户端(浏览器)发来的请求,两种常见的请求方式是GET和POST。
GET的请求格式:
- GET请求没有请求体只有请求头
- GET请求的请求参数放在URL后加上一个"?"的后面,参数以
key=value
的形式传递,参数与参数之间使用"&"进行连接。
GET /signin?next=%2F HTTP/2\r\nHost: www.zhihu.com\r\nUser-Agent: Mozilla/5.0\r\nAccept: **\r\nAccept-Language: zh-CN\r\nAccept-Encoding: gzip, deflate\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 29\r\nConnection: keep-alive\r\n\r\nusername=root&password=123456
- 请求头中每一行的后面都要加"\r\n"结尾;
- 第一行是状态行,分别是请求方法(POST)、请求路径(/login)、协议版本(HTTP/1.1);
- 请求头内的剩余内容均以XXX: XXXX的格式表示;
- 请求头最后需要一个"\r\n"的空行作为结束的标志;
- 放在请求体内的请求参数以
key=value
的形式传递,参数与参数之间使用"&"进行连接。
功能介绍:
使用状态机和正则表达式完成了对HTTP请求报文的解析,支持解析GET报文和POST报文(仅限Content-Type: application/x-www-form-urlencoded)。
由于计划完成的web服务器需要实现展示主页(GET)、用户登录(POST)、用户注册(POST)、获取图片(GET)、获取视频(GET)五个功能,所以web服务器的请求解析模块满足:
若为GET请求,可根据状态行信息,完成对请求内容地址的转换,以及请求头内其他内容的提取。
若为POST请求,可根据请求参数,完成登录和注册这两个功能(登录:根据后台数据库表中的信息判断用户名与密码是否正确;注册:向后台数据库表中插入符合条件的新用户名和密码)。
状态机流程:
enum PARSE_STATE { REQUEST_LINE, HEADERS, BODY, FINISH };
如果为GET请求: REQUEST_LINE——>HEADERS——>FINISH;
如果为POST请求:REQUEST_LINE——>HEADERS——>BODY——>FINISH。
用到的正则表达式:
1、^([^ ]*) ([^ ]*) HTTP/([^ ]*)$ 匹配状态行
2、^([^:]*): ?(.*)$ 匹配请求头内的XXX: XXXX字段
3、(?!&)(.*?)=(.*?)(?=&|$) 匹配POST的请求参数
HttpRequest类结构 httprequest.h
#ifndef HTTPREQUEST_H#define HTTPREQUEST_H#include #include #include #include #include #include #include #include "buffer.h"#include "log.h"#include "sqlconnpool.h"using std::string;class HttpRequest{public: enum PARSE_STATE//解析流程的状态 { REQUEST_LINE, HEADERS, BODY, FINISH }; HttpRequest(); ~HttpRequest()=default; bool parse(Buffer& buffer);//解析全过程 const string& getMethod() const; const string& getPath() const; const string& getVersion() const; bool isKeepAlive() const;private: void parseRequestLine(const string& line);//解析状态行 void parseHeader(const string& line);//解析请求头 void parseBody(const string& line);//解析请求体 void parsePath();//解析请求路径 void parsePost();//解析POST请求 void parseUrlencoded();//解析POST请求的请求参数 bool userVertify(const string& username,const string& password,int tag);//身份验证 PARSE_STATE state; string method; string path; string version; string body; std::unordered_map header;//存储请求头字段 std::unordered_map post; //存储POST请求参数 static const std::unordered_set DEFAULT_HTML; static const std::unordered_map DEFAULT_HTML_TAG;};#endif // !HTTPREQUEST_H
HttpRequest类实现 httprequest.cpp
#include "httprequest.h"const std::unordered_set HttpRequest::DEFAULT_HTML={"/home","/register","/login","/video","/picture"};const std::unordered_map HttpRequest::DEFAULT_HTML_TAG={{"/register.html", 0},{"/login.html", 1}};HttpRequest::HttpRequest(){ Log::getInstance()->init(); init();}void HttpRequest::init(){ method=""; path=""; version=""; body=""; state = REQUEST_LINE; header.clear(); post.clear();}bool HttpRequest::parse(Buffer& buffer){ if(buffer.readableBytes()<=0) return false; while(buffer.readableBytes()&&state!=FINISH) { const char CRLF[3]="\r\n"; const char* lineEnd=std::search(buffer.peek(),static_cast(buffer.beginWrite()),CRLF,CRLF+2); string line(buffer.peek(),lineEnd); switch (state) { case REQUEST_LINE: parseRequestLine(line); parsePath(); break; case HEADERS: parseHeader(line); break; case BODY: parseBody(line); break; default: break; } if(lineEnd==buffer.beginWrite())//解析完请求体(不由"\r\n"结尾) break; buffer.retrieveUntil(lineEnd+2);//解析完一行Headers(由"\r\n"结尾) } return true;}void HttpRequest::parsePath(){ if(path=="/") path="/home.html"; else if(DEFAULT_HTML.count(path)) path+=".html";}void HttpRequest::parseRequestLine(const string& line){ std::regex patten("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$"); std::smatch match; if(!std::regex_match(line,match,patten)) { LOG_ERROR("%s","Parse RequestLine Error"); } method=match[1]; path=match[2]; version=match[3]; state=HEADERS;}void HttpRequest::parseHeader(const string& line){ std::regex patten("^([^:]*): ?(.*)$"); std::smatch match; if(std::regex_match(line,match,patten)) { header[match[1]]=match[2]; } else { state=BODY; }}void HttpRequest::parseBody(const string& line){ body=line; parsePost(); state=FINISH;}void HttpRequest::parsePost(){ if(method=="POST"&&header["Content-Type"]=="application/x-www-form-urlencoded") { parseUrlencoded(); if(DEFAULT_HTML_TAG.count(path)) { int tag=DEFAULT_HTML_TAG.find(path)->second; if(userVertify(post["username"],post["password"],tag)) { path="/home.html"; } else { path="/error.html"; } } }}void HttpRequest::parseUrlencoded(){std::regex patten("(?!&)(.*?)=(.*?)(?=&|$)"); std::smatch match; string::const_iterator begin=body.begin(); string::const_iterator end=body.end(); while(std::regex_search(begin,end,match,patten)) { post[match[1]]=match[2]; begin=match[0].second; }}bool HttpRequest::userVertify(const string& username,const string& password,int tag){ SqlConnPool* pool = SqlConnPool::getInstance(); std::shared_ptr conn=pool->getConn(); string order1="SELECT username,password FROM user WHERE username='"+username+"' LIMIT 1"; string order2="INSERT INTO user(username, password) VALUES('"+username+"','"+password+"')"; MYSQL_RES* res=conn->query(order1); string user; string pwd; MYSQL_ROW row=nullptr; while((row=mysql_fetch_row(res))!=nullptr) { user=row[0]; pwd=row[1]; } if(tag)//登录 { if(pwd!=password)//密码错误 { LOG_ERROR("%s","Password Error"); return false; } LOG_INFO("%s Login Success",username); } else//注册 { if(!user.empty())//用户名已被使用 { LOG_ERROR("%s","Username Used"); return false; } if(!conn->update(order2))//数据库插入失败 { LOG_ERROR("%s","Insert Error"); return false; } LOG_INFO("%s Register Success",username); } mysql_free_result(res); return true;}const string& HttpRequest::getMethod() const{ return method;}const string& HttpRequest::getPath() const{ return path;}const string& HttpRequest::getVersion() const{ return version;}bool HttpRequest::isKeepAlive() const{ if(header.count("Connection")) { return header.find("Connection")->second=="keep-alive"; } return false;}
测试程序 testHttpRequest.cpp
分别解析GET请求和POST请求,根据解析内容进行判断。
#include "httprequest.h"#include using namespace std;void testPost(){ HttpRequest request; Buffer input; input.append("POST /login HTTP/1.1\r\n" "Host: 127.0.0.1:8888\r\n" "User-Agent: Mozilla/5.0\r\n" "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,**;q=0.8\r\n" "Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\r\n" "Accept-Encoding: gzip, deflate, br\r\n" "Connection: keep-alive\r\n" "Upgrade-Insecure-Requests: 1\r\n" "Cache-Control: max-age=0\r\n" "TE: trailers\r\n" "\r\n"); request.parse(input); cout<<"method:"<
运行结果:
由日志信息可以判断,对GET和POST的请求解析正确。
附:
Makefile
CXX = g++CFLAGS = -std=c++14 -O2 -Wall -g TARGET = testHttpRequestOBJS = buffer.cpp log.cpp blockqueue.h\sqlconn.cpp sqlconnpool.cpp httprequest.cpp\ testHttpRequest.cppall: $(OBJS)$(CXX) $(CFLAGS) $(OBJS) -o $(TARGET) -pthread -L/usr/lib64/mysql -lmysqlclientclean:rm -rf $(OBJS) $(TARGET)
数据库连接池(C++11实现)_{(sunburst)}的博客-CSDN博客
同步+异步日志系统(C++实现)_{(sunburst)}的博客-CSDN博客_c++ 异步日志
缓冲区Buffer类的设计(参考Muduo实现)_{(sunburst)}的博客-CSDN博客
基于C++11实现的阻塞队列(BlockQueue)_{(sunburst)}的博客-CSDN博客_c++11 阻塞队列
来源地址:https://blog.csdn.net/weixin_50437588/article/details/128570178
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341