目的:

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

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

请求头中每一行的后面都要加"\r\n"结尾;第一行是状态行,分别是请求方法(GET)、请求路径(/signin?next=%2F)、协议版本(HTTP/2);其余所有行均以XXX: XXXX的格式表示;最后需要一个"\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

请求头中每一行的后面都要加"\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,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:"<

cout<<"path:"<

cout<<"version:"<

if(request.isKeepAlive())

cout<<"isKeepAlive"<

}

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:"<

cout<<"path:"<

cout<<"version:"<

if(request.isKeepAlive())

cout<<"isKeepAlive"<

}

int main()

{

cout<<"POST------------------------------------------"<

testPost();

cout<<"GET-------------------------------------------"<

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.cpp

all: $(OBJS)

$(CXX) $(CFLAGS) $(OBJS) -o $(TARGET) -pthread -L/usr/lib64/mysql -lmysqlclient

clean:

rm -rf $(OBJS) $(TARGET)

数据库连接池(C++11实现)_{(sunburst)}的博客-CSDN博客

同步+异步日志系统(C++实现)_{(sunburst)}的博客-CSDN博客_c++ 异步日志

缓冲区Buffer类的设计(参考Muduo实现)_{(sunburst)}的博客-CSDN博客

基于C++11实现的阻塞队列(BlockQueue)_{(sunburst)}的博客-CSDN博客_c++11 阻塞队列

参考阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: