目的:

一个WEB服务器需要解析客户端(浏览器)发来的请求,两种常见的请求方式是GETPOST

GET的请求格式:

  • GET请求没有请求体只有请求头
  • GET请求的请求参数放在URL后加上一个"?"的后面,参数以key=value的形式传递,参数与参数之间使用"&"进行连接。
GET /signin?next=%2F HTTP/2\r\n
Host: www.zhihu.com\r\n
User-Agent: Mozilla/5.0\r\n
Accept: */*\r\n
Accept-Language: zh-CN\r\n
Accept-Encoding: gzip, deflate\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
  1. 请求头中每一行的后面都要加"\r\n"结尾;
  2. 第一行是状态行,分别是请求方法(GET)、请求路径(/signin?next=%2F)、协议版本(HTTP/2);
  3. 其余所有行均以XXX: XXXX的格式表示;
  4. 最后需要一个"\r\n"的空行作为请求头结束的标志。

POST的请求格式:

  • POST请求传送的数据放在请求体中;
  • POST请求的请求参数放在请求体中,由请求头中的"Content-Type"字段决定其格式;
  • 如果是"Content-Type: application/x-www-form-urlencoded",则请求参数以key=value的形式传递,参数与参数之间使用"&"进行连接
  • 如果是"Content-Type: multipart/form-data",则使用boundary(分割线)充当参数与参数之间的连接(相当于&)
POST /login HTTP/1.1\r\n
Host: 127.0.0.1:8888\r\n
User-Agent: Mozilla/5.0\r\n
Accept: */*\r\n
Accept-Language: zh-CN\r\n
Accept-Encoding: gzip, deflate\r\n
Content-Type: application/x-www-form-urlencoded\r\n
Content-Length: 29\r\n
Connection: keep-alive\r\n
\r\n
username=root&password=123456
  1. 请求头中每一行的后面都要加"\r\n"结尾;
  2. 第一行是状态行,分别是请求方法(POST)、请求路径(/login)、协议版本(HTTP/1.1);
  3. 请求头内的剩余内容均以XXX: XXXX的格式表示;
  4. 请求头最后需要一个"\r\n"的空行作为结束的标志;
  5. 放在请求体内的请求参数以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 <unordered_set>
#include <unordered_map>
#include <string>
#include <regex>
#include <algorithm>
#include <memory>
#include <mysql/mysql.h>#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<string,string> header;//存储请求头字段std::unordered_map<string,string> post; //存储POST请求参数static const std::unordered_set<string> DEFAULT_HTML;static const std::unordered_map<string,int> DEFAULT_HTML_TAG;
};#endif // !HTTPREQUEST_H

HttpRequest类实现       httprequest.cpp

#include "httprequest.h"const std::unordered_set<string> HttpRequest::DEFAULT_HTML=
{"/home","/register","/login","/video","/picture"};const std::unordered_map<string,int> 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<const char*>(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";elseif(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<SqlConn> 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 <iostream>
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,application/signed-exchange;v=b3;q=0.9\r\n""Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6\r\n""Accept-Encoding: gzip, deflate\r\n""Content-Type: application/x-www-form-urlencoded\r\n""Content-Length: 29\r\n""Connection: keep-alive\r\n""\r\n""username=root&password=123456");request.parse(input);cout<<"method:"<<request.getMethod()<<endl;cout<<"path:"<<request.getPath()<<endl;cout<<"version:"<<request.getVersion()<<endl;if(request.isKeepAlive())   cout<<"isKeepAlive"<<endl;
}void testGet()
{HttpRequest request;Buffer input;input.append("GET /signin?next=%2F HTTP/2\r\n""Host: www.zhihu.com\r\n""User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:91.0) Gecko/20100101 Firefox/91.0\r\n""Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;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:"<<request.getMethod()<<endl;cout<<"path:"<<request.getPath()<<endl;cout<<"version:"<<request.getVersion()<<endl;if(request.isKeepAlive())   cout<<"isKeepAlive"<<endl;
}int main()
{cout<<"POST------------------------------------------"<<endl;testPost();cout<<"GET-------------------------------------------"<<endl;testGet();
}

运行结果:

由日志信息可以判断,对GET和POST的请求解析正确。

附:

 Makefile

CXX = g++
CFLAGS = -std=c++14 -O2 -Wall -g TARGET = testHttpRequest
OBJS =  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 阻塞队列

解析HTTP请求报文(GET、POST)相关推荐

  1. 【项目学习】C++实现高并发服务器——代码学习(二)存储解析HTTP请求报文,创建响应报文

    项目来源:WebServer 上一篇:Reactor高并发模型 本文介绍以下功能的代码实现 利用标准库容器封装char,实现自动增长的缓冲区: 利用正则与状态机解析HTTP请求报文,实现处理静态资源的 ...

  2. Web服务器踩坑之旅03:解析HTTP请求报文

    项目地址: 本文实现的文件在源码中的SimpleWebServer/http_parser目录下 本文内容 目标:解析HTTP报文,从而获取客户请求的文件的文件名及文件地址 浏览器与服务器间的通信过程 ...

  3. [计算机网络]httpserver--如何解析HTTP请求报文

    这个http server的实现源代码我放在了我的github上,有兴趣的话可以点击查看哦. 在上一篇文章中,讲述了如何编写一个最简单的server,但该程序只是接受到请求之后马上返回响应,实在不能更 ...

  4. 解析 http 请求 header 错误_详解http报文(2)-web容器是如何解析http报文的

    摘要 在详解http报文一文中,详细介绍了http报文的文本结构.那么作为服务端,web容器是如何解析http报文的呢?本文以jetty和undertow容器为例,来解析web容器是如何处理http报 ...

  5. html解析 英文,http请求报文解析(国外英文资料).doc

    http请求报文解析(国外英文资料) http请求报文解析(国外英文资料) HTTP request message sample: POST/pass/demo/requesttest.jsp HT ...

  6. 【计算机网络】应用层 : 万维网 和 HTTP 协议 ( 万维网概述 | HTTP 协议特点 | HTTP 协议连接方式 | HTTP 协议报文结构 | HTTP 请求报文 | HTTP 响应报文 )

    文章目录 一.万维网概述 ★ 二.HTTP 协议 ( 超文本传输协议 ) ★ 三.HTTP 协议特点 ★ 四.HTTP 连接方式 ★ 五.HTTP 协议报文结构 六.HTTP 请求报文 ★ 七.HTT ...

  7. web网络和http协议(了解域名和网页,制作第一个网页,了解http协议,流程和请求报文格式)

    文章目录 web网络和http协议 了解域名 早期使用HOST文件解析域名 现在DNS(Domain Name system 域名系统) 域名的概述 域名空间结构 域名介绍 域名注册 域名注册步骤 网 ...

  8. HTTP协议简介_请求消息/请求数据包/请求报文_响应消息/响应数据包/响应报文

    文章目录 HTTP 介绍 请求数据包/请求消息/请求报文 请求数据包解析 响应数据包/响应消息/响应报文 HTTP 介绍 概念:Hyper Text Transfer Protocol 超文本传输协议 ...

  9. http详解 请求报文格式和响应报文格式

    题外话: <Pi Network 免费挖矿国外热门项目 一个π币大约值3元到10元>相信过去BTC的人,信不信未来的PI,了解一下,唯一一个高度与之持平的项目 HTTP 工作原理 超文本传 ...

最新文章

  1. IIS8 添加配置 WCF服务
  2. 下次诺贝尔奖会是他吗?肠道微生物组领域开创者Jeffrey Gordon
  3. 如何快速将PointPoint导入CSDN?
  4. python学习 01 变量
  5. 澎湃新闻产品总监首度分享,如何快速在新闻类APP中异军突围?一年时间进入前4名...
  6. angularjs html编辑器,AngularJS集成wangeditor富文本编辑器
  7. c语言编写程序确定平年闰年,C语言平年闰年问题
  8. 【Qt】Qt数据库简介
  9. lua C++ wrapper
  10. 变量提升、作用域this实战(真题:看代码输出)
  11. paramiko学习笔记
  12. 在装有windows跟ubuntu的机器上重新安装windows后修复ubuntu的grub
  13. mysql中null与“空值”的坑
  14. mmap文件内存映射
  15. 极域电子教室豪华版v6.0.2021有哪些功能
  16. SpringBoot+H5微信登陆(网页)
  17. 华大基因首席运营官张凌离职
  18. ppd文件下载 linux,Linux系统R230,R270,R330,L380打印机驱动下载爱普生喷墨打印机PPD文件...
  19. 微信小程序开发初学者之入门步骤和体验
  20. 微信浏览器禁止下载APK文件 微信扫描二维码 下载app的方法

热门文章

  1. c语言——输入方式gets,scanf,fgets分析
  2. zigbee无线传感网技术与应用开发v2.0_物联网通讯协议——Zigbee
  3. ERP 系统维护的那些事
  4. 网银转账或者其他业务操作时,提醒签名失败,请检查证书
  5. ffmpeg 编码 png apng图片
  6. 如何理解奇偶校验位?
  7. SwitchySharpssh on Chrome-Linux
  8. 友情链接交易网站源码
  9. 使用Thunderbird管理多个邮件账号
  10. thunderbird 日历