【Linux网络】构建与优化HTTP请求处理 - HttpRequest从理解到实现

📢博客主页:https://blog.csdn.net/2301_779549673

📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson

📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!

📢本文由 JohnKi 原创,首发于 CSDN🙉

📢未来很长,值得我们全力奔赴更美好的生活✨

文章目录

  • [🏳️‍🌈一、HttpRequest 类](#🏳️‍🌈一、HttpRequest 类)
    • [1.1 基本结构](#1.1 基本结构)
    • [1.2 构造函数、析构函数](#1.2 构造函数、析构函数)
    • [1.3 反序列化函数 Descrialize](#1.3 反序列化函数 Descrialize)
    • [1.4 获取一行字符串 GetLine()](#1.4 获取一行字符串 GetLine())
    • [1.5 打印方法 Print](#1.5 打印方法 Print)
    • [1.6 解析请求行 PraseReqLine](#1.6 解析请求行 PraseReqLine)
    • [1.7 解析请求头 void PraseHeader();](#1.7 解析请求头 void PraseHeader();)
    • [1.8 增加路径字段](#1.8 增加路径字段)
    • [1.9 测试](#1.9 测试)
  • [🏳️‍🌈二 、整体代码](#🏳️‍🌈二 、整体代码)
  • 👥总结

🏳️‍🌈一、HttpRequest 类

1.1 基本结构

我们结合这张图,构建出 http请求 的基本结构,并定义一些基本方法

复制代码
const static std::string _base_sep = "\r\n";     // static 关键字使变量具有内部链接,仅当前翻译单元(源文件)可见。

class HttpRequest {
private:
    std::string GetLine(std::string& reqstr); // 获取一行信息
    void PraseReqLine();                      // 解析请求行
    void PraseHeader();                       // 解析请求头
public:
    HttpRequest();
    void Descrialize(std::string& reqstr);
    void Print();
    ~HttpRequest();

private:
    std::string _req_line;                 // 请求行
    std::vector<std::string> _req_headers; // 请求报头
    std::string _blank_line;               // 空行
    std::string _req_body;                 // 请求体
};

1.2 构造函数、析构函数

构造函数 初始化空行即可,因为空行是固定的,析构函数无需处理!

复制代码
HttpRequest() : _blank_line(_base_sep) {}
~HttpRequest() {}

1.3 反序列化函数 Descrialize

我们这里是需要解析获取到的请求,所以用的方法自然是 反序列化

复制代码
void Descrialize(std::string& reqstr) {
    // 基本的反序列化
    _req_line = GetLine(reqstr); // 读取第一行请求行
    // 请求报头
    std::string header;
    do {
        header = GetLine(reqstr);
        // 如果既不是空,也不是空行,就是请求报头,加入到请求报头列表中
        if (header.empty())
            break;
        else if (header == _base_sep)
            break;
        _req_headers.push_back(header);
    } while (true);

    // 正文
    if (!reqstr.empty())
        _req_body = reqstr;
}

1.4 获取一行字符串 GetLine()

复制代码
// 获取一行信息
std::string GetLine(std::string& reqstr) {
    auto pos = reqstr.find(_base_sep);
    if (pos == std::string::npos)
        return "";
    std::string line = reqstr.substr(0, pos);  // 截取一行有效信息
    reqstr.erase(0, pos + _base_sep.length()); // 删除有效信息和分隔符
    return line.empty() ? _base_sep
                        : line; // 有效信息为空则返回分隔符,否则返回有效信息
}

1.5 打印方法 Print

复制代码
void Print() {
    std::cout << "----------------------------------------" << std::endl;
    std::cout << "请求行: " << _req_line << std::endl;
    std::cout << "请求报头: " << std::endl;
    for (auto& header : _req_headers) {
        std::cout << header << std::endl;
    }
    std::cout << "空行: " << _blank_line << std::endl;
    std::cout << "请求体: " << _req_body << std::endl;
}

当我们使用浏览器访问我们的服务器,就能够成功地将我们需要地所有信息给序列化出来,这里没有请求,所以请求体为空


1.6 解析请求行 PraseReqLine

我们已经知道请求行的组成如下,所以我们可以进一步细分 HttpRequest 类,增加响应的请求行的成员变量

复制代码
std::string _method;  // 请求方法
std::string _url;     // 请求url
std::string _version; // 请求版本

将 _req_line(请求行)封装为字符串流,按空格分隔读取方法、路径、协议版本

复制代码
// 解析请求行
void PraseReqLine() {
    // 以空格为分隔符,不断读取
    std::stringstream ss(_req_line);
    ss >> _method >> _url >> _version;
}

1.7 解析请求头 void PraseHeader();


我们可以知道请求报头中存在类似哈希表的 KV 结构 ,因此我们可以是使用一个 unordered_map,存储每一个键值对

根据我们之前获取到的请求报头,可以知道分隔符是 ": ",可以根据这个进行解析请求报头

复制代码
// 解析请求头
void PraseHeader() {
    for (auto& header : _req_headers) {
        auto pos = header.find(':');
        if (pos == std::string::npos)
            continue;
        std::string k = header.substr(0, pos);
        std::string v = header.substr(pos + _line_sep.size());
        if (k.empty() || v.empty())
            continue;
        _headers_kv[k] = v;
    }
}

1.8 增加路径字段

**我们向服务器请求的时候,需要知道资源的路径,因此我们可以增加路径字段
**

  • 我们提供一个路径的前缀 wwwroot

  • 并且当这个用户访问的路径为 / 时,提供默认路径 default.html

    const static std::string _prefix_path = "wwwroot"; // 默认前缀路劲
    const static std::string _default_path = "default.html"; // 默认路径

我们可以在构造函数时,给路劲添加上默认前缀路径

复制代码
HttpRequest() : _blank_line(_base_sep), _path(_prefix_path) {}

我们在解析 请求行url 进行分析,判断是否为空,并且给 path 赋值

复制代码
void PraseReqLine(){    
                // 以空格为分隔符,不断读取
                std::stringstream ss(_req_line);
                ss >> _method >> _url >> _version;     
                
                _path += _url;
                // 处理url,如果是根目录,则返回默认路径
                if(_url == "/")
                    _path += _default_path;
            }

提供两个新方法用来获取当前的 url路径

复制代码
std::string Url() {
    LOG(LogLevel::INFO) << "client want url : " << _url;
    return _url;
}
std::string Path() {
    LOG(LogLevel::INFO) << "client want url : " << _path;
    return _path;
}

1.9 测试

我们运行服务端后,再用浏览器访问我们的服务器,成功捕捉到了两次请求,

  • ​第一次请求​(端口 3362):
    浏览器主动请求你输入的 URL(如 http://119.91.133.45:8080/),服务端返回页面/default.html
  • 第二次请求​(端口 3361):
    浏览器 ​自动请求网站图标​ /favicon.ico,用于在标签页、书签栏显示小图标。若服务端未显式处理该请求,浏览器仍会尝试获取。

🏳️‍🌈二 、整体代码

复制代码
#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <unordered_map>
#include "Log.hpp"

using namespace LogModule;

const static std::string _base_sep = "\r\n";     // static 关键字使变量具有内部链接,仅当前翻译单元(源文件)可见。
// const static std::string _base_sep = "\r\n";  // 默认具有外部链接,其他文件可通过 extern 引用。
const static std::string _line_sep = ": ";
const static std::string _prefix_path = "/wwwroot";          // 默认前缀路劲
const static std::string _default_path = "default.html";    // 默认路径



namespace HttpServer{
    class HttpRequest{
        private:
            // 获取一行信息
            std::string GetLine(std::string& reqstr){
                auto pos = reqstr.find(_base_sep);
                if(pos == std::string::npos) return "";
                std::string line = reqstr.substr(0, pos);       // 截取一行有效信息
                reqstr.erase(0, pos + _base_sep.length());      // 删除有效信息和分隔符
                return line.empty() ? _base_sep : line;         // 有效信息为空则返回分隔符,否则返回有效信息
            }

            // 解析请求行
            void PraseReqLine(){    
                // 以空格为分隔符,不断读取
                std::stringstream ss(_req_line);
                ss >> _method >> _url >> _version;     
                
                _path += _url;
                // 处理url,如果是根目录,则返回默认路径
                if(_url == "/")
                    _path = _default_path;
            }

            // 解析请求头
            void PraseHeader(){
                for(auto& header : _req_headers){
                    auto pos = header.find(':');
                    if(pos == std::string::npos)
                        continue;
                    std::string k = header.substr(0, pos);
                    std::string v = header.substr(pos + _line_sep.size());
                    if(k.empty() || v.empty()) continue;
                    _headers_kv[k] = v;
                }
            }
        public:
            HttpRequest() : _blank_line(_base_sep), _path(_prefix_path) {}

            void Descrialize(std::string& reqstr){
                // 基本的反序列化
                _req_line = GetLine(reqstr);    // 读取第一行请求行
                // 请求报头
                std::string header;
                do{
                    header = GetLine(reqstr);
                    // 如果既不是空,也不是空行,就是请求报头,加入到请求报头列表中
                    if(header.empty()) break;
                    else if(header == _base_sep) break;
                    _req_headers.push_back(header);
                }while(true);

                // 正文
                if(!reqstr.empty())
                    _req_body = reqstr;

                // 进一步反序列化请求行
                PraseReqLine();
                // 分割请求报头,获取键值对
                PraseHeader(); 
            }

            void Print(){
                std::cout << "----------------------------------------" <<std::endl;
                std::cout << "请求行: ###" << _req_line << std::endl;
                std::cout << "请求报头: " << std::endl;
                for(auto& header : _req_headers){
                    std::cout << "@@@" << header << std::endl;
                }
                std::cout << "空行: " << _blank_line << std::endl;
                std::cout << "请求体: " << _req_body << std::endl;

                std::cout << "Method: " << _method << std::endl;
                std::cout << "Url: " << _url << std::endl;
                std::cout << "Version: " << _version << std::endl;
            }

            std::string Url(){
                LOG(LogLevel::INFO) << "client want url : " << _url;  
                return _url;
            }
            std::string Path(){
                LOG(LogLevel::INFO) << "client want path : " << _path;  
                return _path;
            }

            ~HttpRequest() {}
        private:
            std::string _req_line;                      // 请求行
            std::vector<std::string> _req_headers;      // 请求报头
            std::string _blank_line;                    // 空行
            std::string _req_body;                      // 请求体

            std::string _method;                         // 请求方法
            std::string _path;   // 资源路径
            std::string _url;                            // 请求url
            std::string _version;                        // 请求版本

            std::unordered_map<std::string, std::string> _headers_kv; // 存储每行报文的哈希表
    };


    class HttpHandler{
        public:
            HttpHandler(){}
            std::string handle(std::string req){
                std::cout << "------------------------------------" << std::endl;
                std::cout << req;
    
                HttpRequest req_obj;
                req_obj.Descrialize(req);
                // req_obj.Print();

                std::string path = req_obj.Path();
                std::string url = req_obj.Url();
                
                std::string responsestr = "HTTP/1.1 200 OK\r\n";
                responsestr += "Content-Type: text/html\r\n";
                responsestr += "\r\n";
                responsestr += "<html><h1>hello linux,hello net!<h2></html>";
                return responsestr;
            }
            ~HttpHandler(){}
    };
}

👥总结

本篇博文对 【Linux网络】构建与优化HTTP请求处理 - 从HttpRequest到HttpServer 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

相关推荐
电鱼智能的电小鱼2 分钟前
EFISH-SBC-RK3588 —— 厘米级定位 × 旗舰算力 × 工业级可靠‌
linux·人工智能·嵌入式硬件·边缘计算
yuanlaile20 分钟前
Go全栈_Golang、Gin实战、Gorm实战、Go_Socket、Redis、Elasticsearch、微服务、K8s、RabbitMQ全家桶
linux·redis·golang·k8s·rabbitmq·gin
程序员JerrySUN25 分钟前
驱动开发硬核特训 · Day 22(上篇): 电源管理体系完整梳理:I2C、Regulator、PMIC与Power-Domain框架
linux·驱动开发·嵌入式硬件
IT运维爱好者37 分钟前
Ubuntu 22.04.4操作系统初始化详细配置
linux·运维·服务器·ubuntu
樂5021 小时前
关于 Web 服务器的五个案例
linux·服务器·经验分享
用户217516114381 小时前
【linux】重定向与缓冲区
linux
一键三联啊1 小时前
【FastJSON】的parse与parseObject
linux·前端·python
离凌寒1 小时前
一、linux系统启动过程操作记录
linux
qq_543248521 小时前
正则表达式三剑客之——grep和sed
linux·运维·正则表达式
码农新猿类2 小时前
信号量函数
linux·c++·visual studio