解析HTTP请求报文(GET、POST)

news2024/11/28 7:43:54

目的:

一个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():state(REQUEST_LINE)
{
    Log::getInstance()->init();
}

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);//解析完一行请求头(由"\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];
        LOG_INFO("%s: %s",match[1].str().c_str(),match[2].str().c_str());
    }        
    else
    {
        state=BODY;
    }
}

void HttpRequest::parseBody(const string& line)//解析请求体
{
    body=line;
    parsePost();//解析POST请求的请求体
    state=FINISH;
}

void HttpRequest::parsePost()//解析POST请求的请求体
{
    if(method=="POST"&&header["Content-Type"]=="application/x-www-form-urlencoded")
    {
        parseUrlencoded();//解析POST请求的请求参数
        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()//解析POST请求的请求参数
{
	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;
        LOG_INFO("%s=%s",match[1].str().c_str(),match[2].str().c_str());
    }
}

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;
        }
    }
    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");
    }
    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//检查isKeepAlive是否开启
{
    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.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 阻塞队列

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/142872.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

时序图文献精度——1.2018-KDD-Embedding Temporal Network via Neighborhood Formation

Embedding Temporal Network via Neighborhood Formation Abstract 作者发现&#xff0c;在现有的研究中&#xff0c;以节点间顺序交互事件为特征的网络的完整时间形成过程还很少被建模&#xff0c;在这边文章中&#xff0c;作者引入邻域形成序列的概念来描述节点的演化&…

第四篇 - 对象的深层劫持

一&#xff0c;前言 上篇&#xff0c;主要介绍了在 Vue 的数据初始化流程中&#xff0c;对象属性的单层劫持是如何实现的 回顾一下&#xff0c;主要涉及以下几个核心点&#xff1a; data 为函数和对象的处理&#xff0c;及当 data 为函数时的 this 指向问题Observer 类&…

一文弄清楚vue中的computed和methods

1.实现业务我们现在需要实现一个业务&#xff0c;就是我们有一个输入框&#xff0c;之后我们修改输入框的值&#xff0c;就在此时输入框的值会呈现到页面中的h1标签上去2.利用Vue中的插值语法实现业务2.1什么是插值语法&#xff1f;插值语法就是vue中用来存放预留值得方法&…

【前端】CSS盒子模型

五、盒子模型 1.1盒子模型的介绍 盒子的概念 页面中的每一个标签&#xff0c;都可看做是一个“盒子”&#xff0c;通过盒子的视角方便的进行布局浏览器在渲染&#xff08;显示&#xff09;网页时&#xff0c;会奖网页中的元素看作是一个个的矩形区域&#xff0c;我们也形象地…

<Python的函数(1)>——《Python》

目录 1. 函数 2. 语法格式 2.1 创建函数/定义函数 ​2.2 调用函数/使用函数 3. 函数参数 4. 函数返回值 5. 变量作用域 后记&#xff1a;●由于作者水平有限&#xff0c;文章难免存在谬误之处&#xff0c;敬请读者斧正&#xff0c;俚语成篇&#xff0c;恳望指教&…

Android input 事件分发 -- inputReader

inputReaderinputReaderinputReader 这个章节主要是围绕inputReader 、inputReaderThread进行的&#xff0c;老规矩先上时序图通过前面我们已经知道了InputReader和InputReaderThread都是在InputManager的构造函数里面new出来的&#xff0c;然后InputReaderThread的启动是在Sys…

图文详解:内存总是不够,我靠HBase说服了Leader为新项目保驾护航

最近在工作中用到了 Hbase 这个数据库&#xff0c;也顺便做了关于 Hbase 的知识记录来分享给大家。其实 Hbase的内容体系真的很多很多&#xff0c;这里介绍的是小羽认为在工作中会用到的一些技术点&#xff0c;希望可以帮助到大家。 可以这么说互联网都是建立在形形色色的数据…

剑指offer----C语言版----第十天

目录 1. 二进制中 1 的个数 1.1 题目描述 1.2 可能引起错误的解法 1.3 常规解法 1.4 思路优化 1. 二进制中 1 的个数 原题链接: 剑指 Offer 15. 二进制中1的个数 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/er-jin-zhi-zhong-1de-ge-shu-lcof…

电路方案分析(十五)符合 EMC 标准的汽车制动灯和尾灯设计方案

符合 EMC 标准的汽车制动灯和尾灯设计方案 tips&#xff1a;TI设计方案参考分析&#xff1a;TI Designs&#xff1a;TIDA-01374 1.系统描述 1.1关键参数 2.系统概述 2.1系统框图 2.2关键元器件 3.设计原理 3.1双重亮度设计 3.2 电荷泵设计 3.4 LED故障设计 3.3 MOSFET驱动电…

LeetCode Hot 100 笔记

文章目录链表21. 合并两个有序链表栈20. 有效的括号Java栈链表 链表的题目一般都不太难&#xff0c;画图&#xff0c;别怕麻烦 21. 合并两个有序链表 解法一&#xff1a;迭代 用一个指针cur跟踪当前节点&#xff0c;每次从list1和list2中选取小的节点&#xff0c;链接起来建…

什么是轻量化,轻量化模型is all your need hhh

其实学了几个小月&#xff0c;我们肯定知道&#xff0c;MLP有多deeper &#xff0c;卷积层有多少层呀 抑或是Transformer架构&#xff0c;大量的参数&#xff0c;只能用huge 来描述&#xff0c; 可实际上我们的设备&#xff0c;有时候并没有服务器那么厉害&#xff0c;所以人…

阿里云认证为什么那么多人考?考试内容难不难?

我国人口密集&#xff0c;每年有大量的毕业生涌进社会&#xff0c;除此之外还有很多进入社会很久的打工人&#xff0c;想要跳槽&#xff0c;到更加挣钱的岗位&#xff0c;待遇更好的公司去。为了能够早日买房、买车&#xff0c;很多人会选择社会热门行业去学习&#xff0c;甚至…

sqli-labs 第七关 多命通关攻略

sqli-labs 第七关 多命通关攻略描述字符串与数值之间的转换判断注入类型返回结果正常输入不正常输入错误输入总结判断注入类型判断是否为字符型注入判断是否为单引号字符型注入判断是否为双引号字符型注入判断是否为数值型注入总结判断注入类型&#xff08;修正版&#xff09;字…

二十六、Docker (2)

&#x1f33b;&#x1f33b; 目录一、Docker的常用命令 (阶段A)1.1 帮助命令1.2 镜像命令1.3 容器命令1.3.1 新建容器并启动1.3.2 列出所有运行的容器1.3.3 退出容器1.3.4 删除容器1.3.5 启动和停止容器的操作1.4 常用的其它命令1.4.1 后台启动容器1.4.2 查看日志1.4.3 查看容器…

如何搭建私域流量?

如今已经进入存量用户时代&#xff0c;越来越多的企业也明白了存量用户的重要性&#xff0c;因此企业都非常重视私域流量的搭建&#xff0c;以挖掘客户的价值。 前言 如今已经进入存量用户时代&#xff0c;越来越多的企业也明白了存量用户的重要性&#xff0c;因此企业都非常重…

安卓搭建好的模拟机,为调试准备

​ 这一节直接分享制作好的虚拟机&#xff0c;镜像系统&#xff0c;以及安卓源码&#xff0c;直接节省你的时间去配置&#xff0c;编译。 下来我来分享下搭建步骤&#xff1a; 1 虚拟机下载vm 12 pro &#xff0c;这个网上百度就可以&#xff0c;原则13,14也都是可以的。 2 下…

移动魔百盒CM311-3-YST-晨星MSO9385-语音首页正常-TTL刷机包

移动魔百盒CM311-3-YST-晨星MSO9385-语音首页正常-TTL刷机包 固件特点&#xff1a; 1、三网通用&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、无开机广告&#xff0c;无系统更新&#xff0c;不在被强制升级&#xff1b; 4、大量精简内置的没用…

熬夜搞了 17000 字,终于把你这个 ES 玩明白了

平常经常用ES(ElasticSearch), 觉得这真是个好玩意儿&#xff0c;所以来分享一篇文章&#xff0c;希望通过这篇文章能让读者大致了解ES是做什么的以及它的使用和基本原理。 可能有的读者航海不知道ES是个啥玩儿&#xff0c;别着急&#xff0c;看完本文后&#xff0c;相信你会了…

import...from... 和 require 如何找到模块位置?

import Vue from "vue"; 为什么不用写相对地址和绝对地址就能够导出 Vue 呢&#xff1f;似乎也没有配置路径&#xff1f;也没有配置映射&#xff0c;那么究竟 from "vue"; 对应的究竟是那个路径呢&#xff1f; 先提出两个可能的方案 1.VS Code/WebStorm …

【每天学习一点新知识】nmap端口扫描

nmap所识别的6个端口状态open(开放的)应用程序正在该端口接收TCP 连接或者UDP报文。发现这一点常常是端口扫描 的主要目标。安全意识强的人们知道每个开放的端口 都是攻击的入口。攻击者或者入侵测试者想要发现开放的端口。 而管理员则试图关闭它们或者用防火墙保护它们以免妨碍…