Linux 49 HTTP请求与响应实战解析 带http模拟实现源码--万字长文解析

🔥个人主页: Milestone-里程碑

❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>

<<Git>><<MySQL>>

🌟心向往之行必能至

目录

一.网络传输:拷贝

[二 先简写大致框架,快速认识 http请求](#二 先简写大致框架,快速认识 http请求)

二.对请求进行解码,反序列化

[2.1 解码 Decode](#2.1 解码 Decode)

2.2反序列化

三.状态码

[3.1 重定向状态码](#3.1 重定向状态码)

[3.2 永久重定向和临时重定向](#3.2 永久重定向和临时重定向)

四.HTTP常见方法

[4.1 GET](#4.1 GET)

[4.2 POST](#4.2 POST)

[4.3 HEAD⽅法](#4.3 HEAD⽅法)

[4.4 DELETE⽅法(不常⽤)被禁用](#4.4 DELETE⽅法(不常⽤)被禁用)

[4.5 OPTIONS⽅法 被禁用](#4.5 OPTIONS⽅法 被禁用)

[五. 常见Header](#五. 常见Header)

5.1connection报头

5.1.2两种连接方式

[5.2 http是一个无连接,无状态的协议](#5.2 http是一个无连接,无状态的协议)

[无状态 = 服务器 "记不住你",每次请求都是 "陌生人对话"](#无状态 = 服务器 “记不住你”,每次请求都是 “陌生人对话”)

[5.3 解决http无状态带来的烦恼-Cookie](#5.3 解决http无状态带来的烦恼-Cookie)

5.4验证浏览器使用Cookie进行记录

[5.5 解决Cookie导致的问题 session](#5.5 解决Cookie导致的问题 session)

[六 源码](#六 源码)


在前面我们已经讲过了http的基本概念,接下来我们再看两张图,通过这两张图实现基本的http的请求和响应报头的组成

一.网络传输:拷贝

我们前面的学习学过,write 这些系统接口是写入内核级缓存区,而read这些系统接口是从内核级的缓存区读取数据

其实TCP和UDP都有缓存区,UDP无需发送缓存区(不可靠),而TCP则有,可靠

每次读取或者发送,其实都是把缓存区的内容拷贝出去,典型的生产者消费者模型

这也就是满了或者空了为什么会堵塞的原因了

二 先简写大致框架,快速认识 http请求

hhtp基本应答格式

Http.hpp

bash 复制代码
#pragma once

#include "Socket.hpp"
#include "TcpServer.hpp"
#include <iostream>
#include <string>
#include <memory>
#include <unordered_map>

using namespace SocketModule;

const std::string gspace = " ";
const std::string glinespace = "\r\n";

class HttpRequest
{
public:
    HttpRequest()
    {
    }
    std::string Serialize()
    {
        return std::string();
    }
    bool Deserialize(std::string &reqstr)
    {
        return true;
    }
    ~HttpRequest()
    {
    }

private:
    std::string _method;
    std::string _url;
    std::string _version;

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;
};
class HttpResponse
{
public:
    HttpResponse() {}
    ~HttpResponse() {}
    std::string Serialization(){
        std::string hrsp_str;
        hrsp_str+=_version+gspace+std::to_string(_code)+gspace+_decp+glinespace;
        for(auto &e:_handler)
        {
            hrsp_str+=e.first+e.second+glinespace;
        }
        _blankline=glinespace;
        hrsp_str+=_blankline+_text;
        return hrsp_str;
    }
    bool Deserialization(){
        return true;
    }
    

    std::string _version;
    int _code;         //  eg:404
    std::string _decp; // Nofoune

    std::unordered_map<std::string, std::string> _handler;
    std::string _blankline;
    std::string _text;
};
class Http
{
public:
    Http(uint16_t port) : tsvrp(std::make_unique<TcpServer>(port))
    {
    }
    void HandlerHttpRquest(std::shared_ptr<SocketModule::Socket> &sock, InetAddr &client)
    {
#ifndef DEBUG
#define DEBUG
        std::string httpreqstr;
        int n = sock->Recv(&httpreqstr); // 浏览器给我发过来的是一个大的http字符串
        if (n > 0)
        {
            HttpResponse resp;
            resp._version = "HTTP/1.1";
            resp._code = 200; // is success
            resp._decp = "OK";
            // 方法1:每行末尾加 \ (注意\后不能有空格)
            resp._text = "<!DOCTYPE html>\
<html lang=\"en\">\
<head>\
    <meta charset=\"UTF-8\">\
    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\
    <title>Hello World</title>\
</head>\
<body>\
    <h1>Hello World</h1>\
</body>\
</html>";
            //再将resp进行序列化
            std::string resp_str=resp.Serialization();
            sock->Send(resp_str);
        }
        std::cout << httpreqstr;
#endif
        // 对请求字符串,进行反序列化
    }
    void Start()
    {
        tsvrp->Start([this](std::shared_ptr<SocketModule::Socket> &sock, InetAddr &client)
                     { this->HandlerHttpRquest(sock, client); });
    }
    ~Http()
    {
    }

private:
    std::unique_ptr<TcpServer> tsvrp;
};

此时我们再启动服务器,再通过浏览器搜索,可以得到hello world,但我们发现会有两种情况

  1. 端口号未接路径,得到的是/2.端口号带路径,会带上路径

但其实这些目录都是Linux根目录,而是web根目录,当没有带路径时,会自动去先拼接index.html下查找

所以http申请的本质就是请求我们路径下的./wwwroot,目录下的特定资源,而url中的路径,就是我们要找的

因此修改

bash 复制代码
class Http
{
public:
    Http(uint16_t port) : tsvrp(std::make_unique<TcpServer>(port))
    {
    }
    void HandlerHttpRquest(std::shared_ptr<SocketModule::Socket> &sock, InetAddr &client)
    {
#ifndef DEBUG
#define DEBUG
        std::string httpreqstr;
        int n = sock->Recv(&httpreqstr); // 浏览器给我发过来的是一个大的http字符串
        if (n > 0)
        {
            HttpResponse resp;
            resp._version = "HTTP/1.1";
            resp._code = 200; // is success
            resp._decp = "OK";
            // 方法1:每行末尾加 \ (注意\后不能有空格)
            //再将resp进行序列化
            std::string filename=webroot+homepage;
             bool res = Util::ReadFileContent(filename, &(resp._text)); // 浏览器请求的资源,一定会存在吗?出错呢?
            (void)res;
            std::string resp_str=resp.Serialization();
            sock->Send(resp_str);
        }
        std::cout << httpreqstr;
#endif
        // 对请求字符串,进行反序列化
    }
    void Start()
    {
        tsvrp->Start([this](std::shared_ptr<SocketModule::Socket> &sock, InetAddr &client)
                     { this->HandlerHttpRquest(sock, client); });
    }
    ~Http()
    {
    }

private:
    std::unique_ptr<TcpServer> tsvrp;
};

二.对请求进行解码,反序列化

对字符串进行反序列化分3步

1.读取并找到完整报头[空行]

2.对报头进行反序列化,提取一个属性:Content-length的长度

3.再从剩下的字符串内容中,提取content-length个字符

2.1 解码 Decode

解码操作与上一篇协议的解码操作类似

bash 复制代码
 bool Decode(std::string &buffer,std::string *package,std::string & blankline)
    {
        size_t pos=buffer.find(blankline);
        if(pos==std::string ::npos)
            return false;
        size_t lenpos=buffer.find("Content-length");
        int x=std::stoi(buffer.substr(lenpos,lenpos+2));
        //再去换行符后面找
        if(buffer.size()<pos+x)
            return false;
        //完整
        *package=buffer.substr(pos,pos+x);
        return true;
    }

2.2反序列化

bash 复制代码
 bool Deserialize(std::string &reqstr)
    {
        std::string line;
        if(Util::ReadOneline(reqstr,&line,gliespace))
            return true;
        return false;
    }
bash 复制代码
   bool Deserialize(std::string &reqstr)
    {
        // 1. 提取请求行
        std::string reqline;
        bool res = Util::ReadOneLine(reqstr, &reqline, glinespace);
        LOG(LogLevel::DEBUG) << reqline;

        // 2. 对请求行进行反序列化
        ParseReqLine(reqline);

        LOG(LogLevel::DEBUG) << "_method: " << _method;
        LOG(LogLevel::DEBUG) << "_uri: " << _url;
        LOG(LogLevel::DEBUG) << "_version: " << _version;
        if(_url == "/")
            _url = webroot + _url + homepage; // ./wwwroot/index.html
        else
            _url = webroot + _url;// ./wwwroot/a/b/c.html
        return true;
    }

三.状态码

1开头:如网盘上传

4开头是客户端出错

5开头是服务器出错

3.1 重定向状态码

状态码 类型 方法是否保留 典型场景
301 永久重定向 可能改为 GET 域名更换
308 永久重定向 POST 请求永久迁移
302 临时重定向 可能改为 GET 临时跳转
307 临时重定向 POST 请求临时跳转
303 临时重定向 强制 GET POST 提交后跳转
304 缓存协商 - 资源未更新,使用缓存

其中我们重点讲一下永久重定向和临时重定向

3.2 永久重定向和临时重定向

维度 永久重定向 临时重定向
核心定义 资源永久迁移到新 URI,原地址不再使用 资源临时驻留在新 URI,原地址仍有效
核心状态码 301 Moved Permanently、308 Permanent Redirect 302 Found、307 Temporary Redirect、303 See Other
请求方法保留 301:可能被客户端转为 GET;308:严格保留(POST/PUT 等) 302:可能转为 GET;307:严格保留;303:强制转为 GET
搜索引擎行为 更新索引(将原地址权重转移到新地址) 不更新索引(原地址权重不变)
客户端行为 缓存重定向规则,后续直接请求新地址 不缓存(每次请求原地址后再跳转)
典型使用场景 域名更换、页面永久下线 / 迁移、HTTPS 强制跳转(永久) 临时维护、活动页面跳转、POST 提交后防重复提交、服务器负载均衡

临时重定向常 用于网页跳转,登录等,之后还会跳转回来的

永久重定向常用于企业的域名更改等

重定向有两种:

1.重定向到新的网站

2.重定向到自己的内部资源

四.HTTP常见方法

方法 幂等性 是否有请求体 核心用途
GET 通常无 获取资源
POST 创建 / 提交资源
PUT 全量更新 / 创建资源
DELETE 可选 删除资源
PATCH 部分更新资源
HEAD 获取资源头信息
OPTIONS 可选 查询资源支持的方法 / 预检

常用的有两种.其中一个为GET,另一个为POST

GET通常用来获得资源,但也可用来提交资源

POST则是用来提交资源

4.1 GET

⽤途:⽤于请求URL指定的资源。
⽰例: GET /index.html HTTP/1.1
特性:指定资源经服务器端解析后返回响应内容。
form表单: https://www.runoob.com/html/html-forms.html

4.2 POST

⽤途:⽤于传输实体的主体,通常⽤于提交表单数据。
⽰例: POST /submit.cgi HTTP/1.1
特性:可以发送⼤量的数据给服务器,并且数据包含在请求体中。
form表单: https://www.runoob.com/html/html-forms.html

GET如果进行参数的提交, 是通过url进行提交

POST:提交参数通过http request正文提交

GET会回显,POST不会,更加私密,但都不安全,可以被抓取,因为明文传送

为了安全,必须对报文进行加密,http协议

4.3 HEAD⽅法

⽤途:与GET⽅法类似,但不返回报⽂主体部分,仅返回响应头。
⽰例: HEAD /index.html HTTP/1.1 查看报头
特性:⽤于确认URL的有效性及资源更新的⽇期时间等

4.4 DELETE⽅法(不常⽤)被禁用

⽤途:⽤于删除⽂件,是PUT的相反⽅法。
⽰例: DELETE /example.html HTTP/1.1
特性:按请求URL删除指定的资源。

4.5 OPTIONS⽅法 被禁用

⽤途:⽤于查询针对请求URL指定的资源⽀持的⽅法。 ⽰例: OPTIONS * HTTP/1.1
特性:返回允许的⽅法,如GET、POST等。

五. 常见Header

Content-Type: 数据类型(text/html等)
Content-Length: Body的⻓度
Host: 客⼾端告知服务器, 所请求的资源是在哪个主机的哪个端⼝上;
User-Agent: 声明⽤⼾的操作系统和浏览器版本信息;
Referer: 当前⻚⾯是从哪个⻚⾯跳转过来的;
Location: 搭配3xx状态码使⽤, 告诉客⼾端接下来要去哪⾥访问;
Cookie: ⽤于在客⼾端存储少量信息. 通常⽤于实现会话(session)的功能

其中,除了最后一个,前面的 ,我们在源码都有使用和讲解,接下来注重讲解Cookie,在讲解之前,我们先讲解报头,谈谈连接问题

5.1connection报头

HTTP中的 Connection 字段是HTTP报⽂头的⼀部分,它主要⽤于控制和管理客⼾端与服务器之间
的连接状态
核心作用

管理持久连接: Connection 字段还⽤于管理持久连接(也称为⻓连接)。持久连接允许客⼾端
和服务器在请求/响应完成后不⽴即关闭TCP连接,以便在同⼀个连接上发送多个请求和接收多个响应。

5.1.2两种连接方式

短链接:HTTP/1.0:在HTTP/1.0协议中,默认连接是⾮持久的

一次请求对应一次 TCP 连接"。客户端发起请求,服务器建立连接并返回响应后,立刻关闭 TCP 连接,适用于早期互联网,加载资源较小时

如果1.0如要使用长连接,需要在报头加入Connection: keep-alive
长连接:

  • HTTP 1.1 的改进 :引入了 Connection: keep-alive 机制,允许 TCP 连接被复用,处理多个连续的请求。但这只是对 "无连接" 的优化,而非改变协议本质
    • 这个长连接是可选且有超时限制的,不是协议强制要求的;
    • 连接的复用是为了提升性能(减少 TCP 三次握手的开销),但协议层面依旧不要求连接长期保持;
    • 当没有新请求时,连接最终还是会被关闭,不会一直存在。

5.2 http是一个无连接,无状态的协议

http1.0和1.1有连接,为何http无连接

HTTP 协议 想象成 "打电话问问题" ,把 TCP 连接 想象成 "接通电话的这根线"

如1.0

你给客服打电话(发起请求),客服接起(建立 TCP 连接),你问一个问题,客服答完,立刻挂电话(关闭 TCP 连接)

1.1

你给客服打电话,问完第一个问题,客服没挂,说 "你还有别的问题吗?我等你 30 秒"(TCP 连接复用)。你接着问第二个、第三个问题,都不用重新拨号。但如果 30 秒内你没再说话,客服还是会挂电话 ;而且这个 "不挂线等你",是额外的优化选项,不是客服的,但本质都是立刻挂断

无状态:服务器不会保存任何客户端的上下文信息,每个请求之间没有关联,都是独立的个体

无状态 = 服务器 "记不住你",每次请求都是 "陌生人对话"

  • 第一次请求 :你打电话说 "麻烦查下我尾号 1234 的银行卡余额"。客服(服务器)需要你提供身份证号、银行卡号(请求参数),核实后告诉你 "余额 5000 元",然后结束对话(响应完成)。这个过程里,客服不会在本子上记下 "这个人叫 xx,尾号 1234,余额 5000"

  • 第二次请求 :你紧接着又打电话说 "那我再查下这张卡的最近交易记录"。对 HTTP 协议来说,这是一个全新的、独立的请求------ 客服完全不知道你就是上一个查余额的人,会再次要求你提供身份证号、银行卡号,才能帮你查询。

如果没有Cookie,你在视频软件刷完一个视频,都要登陆一次,

有了Cookie后,你第一次请求登录时,输入账号与密码,浏览器会进行保存

(Set-Cookie:name=XXX&&passward=XXXXXXX)

保存位置:

文件保存(可长期保存,关机一样)

内存里面(浏览器 也是一个进程,保存到自己的内存了)

5.4验证浏览器使用Cookie进行记录

我们可以发现我们登录b站的网页版后,之后无论是关掉当前页面,还是关掉浏览器,甚至是关机,下次打开时,都无需再进行登录,其实就是B站进行了保存,而且是保存在文件中

也确实如此

而如果不想浏览器记录,我们只需将文件删除即可

打开上面的Cookie,进行删除

5.5 解决Cookie导致的问题 session

但有了Cookie后,如果我们电脑中了木马病毒,当我们向服务端使用Cookie,存储的个人隐私信息向服务端发送请求时,黑客那边也可以拿到我们的Cookie文件,拿到我们的隐私信息, 并向服务器发送请求


解决办法:

以后我们进行登录等操作时,创建的Cookie文件放在服务器那边(比放本地更安全),而我们请求则创建一个session id,每个对应的服务器那边的一个数据,每次访问,都拿id进行访问

但看上面似乎还是无法解决黑客拿我们的信息访问啊

其实我们说的Cookie是有两个问题:1.被他人拿去访问 2.个人隐私信息泄漏

但有了session id后,我们的个人隐私信息存在了企业的服务器中,解决了该问题

而预防被他人非法登录

eg:微信 qq,当你登录时,上一秒ip还在北京,下一秒就在国外了,ip跨度太大了,一般企业都会进行检测

或者你的社交软件,一日内找了上百位许久未联系的人,也太过异常

等等

六 源码

bash 复制代码
#pragma once

#include "Socket.hpp"
#include "TcpServer.hpp"
#include "Util.hpp"
#include "Log.hpp"
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include <unordered_map>
#include <functional>
using namespace SocketModule;
using namespace LogModule;

const std::string gspace = " ";
const std::string glinespace = "\r\n";
const std::string glinesep = ": ";

const std::string webroot = "./wwwroot";
const std::string homepage = "index.html";
const std::string page_404 = "/404.html";
class HttpRequest
{
public:
    HttpRequest()
    {
    }
    std::string Serialize()
    {
        return std::string();
    }
    void ParseReqLine(std::string &reqline)
    {
        // GET / HTTP/1.1
        std::stringstream ss(reqline);
        ss >> _method >> _uri >> _version;
    }
    // 实现, 我们今天认为,reqstr是一个完整的http request string
    bool Deserialize(std::string &reqstr)
    {
        // 1. 提取请求行
        std::string reqline;
        bool res = Util::ReadOneLine(reqstr, &reqline, glinespace);
        LOG(LogLevel::DEBUG) << reqline;

        // 2. 对请求行进行反序列化
        ParseReqLine(reqline);

        LOG(LogLevel::DEBUG) << "_method: " << _method;
        LOG(LogLevel::DEBUG) << "_uri: " << _uri;
        LOG(LogLevel::DEBUG) << "_version: " << _version;
        const std::string temp = "?";
        auto pos = _uri.find(temp);
        if (pos == std::string::npos)
        {
            return true;
        }
        if (_uri == "/")
            _uri = webroot + _uri + homepage; // ./wwwroot/index.html
        else
            _uri = webroot + _uri; // ./wwwroot/a/b/c.html
        return true;
    }
    std::string Uri() { return _uri; }
    ~HttpRequest()
    {
    }

private:
    std::string _method;
    std::string _uri;
    std::string _version;

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;
};

class HttpResponse
{
public:
    HttpResponse() : _blankline(glinespace)
    {
    }
    // 实现: 成熟的http,应答做序列化,不要依赖任何第三方库!
    std::string Serialize()
    {
        std::string status_line = _version + gspace + std::to_string(_code) + gspace + _desc + glinespace;
        std::string resp_header;
        for (auto &header : _headers)
        {
            std::string line = header.first + glinesep + header.second + glinespace;
            resp_header += line;
        }

        return status_line + resp_header + _blankline + _text;
    }
    void SetTargetFile(const std::string &target)
    {
        _targetfile = target;
    }
    void Setcode(int code)
    {
        _code = code;
        switch (_code)
        {
        case 200:
            _desc = "OK";
            break;
        case 301:
            _desc = "Move Temp";
            break;
        case 302:
            _desc = "Set Other";
            break;
        case 404:
            _desc = "Not Found";
            break;
        default:
            break;
        }
    }
    void SetHeader(std::string key, std::string value)
    {
        auto iter = _headers.find(key);
        if (iter != _headers.end())
            return;
        _headers.insert(make_pair(key, value));
    }
    std::string UriTo(std::string x)
    {
        auto pos = x.rfind(".");
        if (pos != std::string ::npos)
        {
            return "text/html";
        }
        std::string suffix = x.substr(pos);
        if (suffix == ".html" || suffix == ".htm")
            return "text/html";
        else if (suffix == ".jpg")
            return "image/jpeg";
        else if (suffix == "png")
            return "image/png";
        else
            return "";
    }

    bool MakeResponse()
    {

        if (_targetfile == "./wwwroot/favicon.ico")
        {
            LOG(LogLevel::DEBUG) << "用户请求: " << _targetfile << "忽略它";
            return false;
        }
        if (_targetfile == "./wwwroot/redir_test")
        {
            Setcode(301);
            SetHeader("Location", "https://www.qq.com/");
            return true;
        }
        int filesize = 0;
        bool res = Util::ReadFileContent(_targetfile, &_text); // 浏览器请求的资源,一定会存在吗?出错呢?
        if (!res)
        {
            Setcode(404);
            _targetfile = webroot + page_404;
            filesize = Util::FileSize(_targetfile);
            SetHeader("Content-Length", std::to_string(filesize));
            bool res = Util::ReadFileContent(_targetfile, &_text); // 浏览器请求的资源,一定会存在吗?出错呢?
        }
        else
        {
            Setcode(200);
            filesize = Util::FileSize(_targetfile);
            SetHeader("Content-Type: ", UriTo(_targetfile));

            SetHeader("Content-Length: ", std::to_string(filesize));
            SetHeader("Set-Cookie:","username:lisi passwardz=123456;");
        }

        return true;
    }
    bool Deserialize(std::string &reqstr)
    {
        return true;
    }
    ~HttpResponse() {}

    // private:
public:
    std::string _version;
    int _code;         // 404
    std::string _desc; // "Not Found"

    std::unordered_map<std::string, std::string> _headers;
    std::string _blankline;
    std::string _text;

    // 其他属性
    std::string _targetfile;
};

using http_func_t = std::function<void(HttpRequest &req, HttpResponse &resp)>;

// 1. 返回静态资源
// 2. 提供动态交互的能力
class Http
{
public:
    Http(uint16_t port) : tsvrp(std::make_unique<TcpServer>(port))
    {
    }
    void HandlerHttpRquest(std::shared_ptr<Socket> &sock, InetAddr &client)
    {
        // 收到请求
        std::string httpreqstr;
        // 假设:概率大,读到了完整的请求
        // bug!
        int n = sock->Recv(&httpreqstr); // 浏览器给我发过来的是一个大的http字符串, 其实我们的recv也是有问题的。tcp是面向字节流的.
        if (n > 0)
        {
            // 对报文完整性进行审核 -- 缺
            HttpRequest req;
            req.Deserialize(httpreqstr);
            HttpResponse resp;
            resp.SetTargetFile(req.Uri());
            resp.MakeResponse();

            // HttpResponse resp;
            // resp._version = "HTTP/1.1";
            // resp._code = 200; // success
            // resp._desc = "OK";
            // //./wwwroot/a/b/c.html
            // LOG(LogLevel::DEBUG) << "用户请求: " << filename;
            // bool res = Util::ReadFileContent(filename, &(resp._text)); // 浏览器请求的资源,一定会存在吗?出错呢?
            // (void)res;
            std::string response_str = resp.Serialize();
            sock->Send(response_str);
        }

// #ifndef DEBUG
// #define DEBUG
#ifdef DEBUG
        // 收到请求
        std::string httpreqstr;
        // 假设:概率大,读到了完整的请求
        sock->Recv(&httpreqstr); // 浏览器给我发过来的是一个大的http字符串, 其实我们的recv也是有问题的。tcp是面向字节流的.
        std::cout << httpreqstr;

        // 直接构建http应答. 内存级别+固定
        HttpResponse resp;
        resp._version = "HTTP/1.1";
        resp._code = 200; // success
        resp._desc = "OK";

        std::string filename = webroot + homepage; // "./wwwroot/index.html";
        bool res = Util::ReadFileContent(filename, &(resp._text));
        (void)res;
        std::string response_str = resp.Serialize();
        sock->Send(response_str);
#endif
        // 对请求字符串,进行反序列化
    }
    void Start()
    {
        tsvrp->Start([this](std::shared_ptr<Socket> &sock, InetAddr &client)
                     { this->HandlerHttpRquest(sock, client); });
    }
    void RegisterService(const std::string name, http_func_t h)
    {
        std::string key = webroot + name; // ./wwwroot/login
        auto iter = _route.find(key);
        if (iter == _route.end())
        {
            _route.insert(std::make_pair(key, h));
        }
    }
    ~Http()
    {

        

    }

private:
    std::unique_ptr<TcpServer> tsvrp;
    std::unordered_map<std::string, http_func_t> _route;
};
相关推荐
ZVAyIVqt0UFji1 小时前
高可用虚拟IP(HaVip)技术详解:原理、设计与应用
开发语言·网络·网络协议·tcp/ip·perl
慧天城寻1 小时前
H3C巡检命令与避坑技巧
运维·网络·运维开发
油丶酸萝卜别吃1 小时前
从网络角度来看,用户从输入网址到网页显示,期间发生了什么?
网络
飞Link1 小时前
深度解析 TS2Vec:时序表示学习中的层次化建模(Hierarchical Contrastive Learning)
开发语言·python·学习·数据挖掘
IronMurphy1 小时前
【算法二十】 114. 寻找两个正序数组的中位数 153. 寻找旋转排序数组中的最小值
java·算法·leetcode
爱炸薯条的小朋友1 小时前
C#依赖注入和仿写Prism注入
开发语言·c#
M158227690551 小时前
SG-TCP-Profibus (M) ModbusTCP 转 Profibus DP 网关:工业双协议无缝互联的高效解决方案
网络·网络协议·tcp/ip
代码探秘者1 小时前
【Java集合】ArrayList :底层原理、数组互转与扩容计算
java·开发语言·jvm·数据库·后端·python·算法
寻见9032 小时前
10 分钟吃透 MyBatis 核心|从底层原理到实战技巧,Java 开发者必藏(无废话干货)
java·mysql·mybatis