前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >【Linux网络编程】应用层:HTTP协议 | URL | 简单实现一个HTTP服务器 | 永久重定向与临时重定向

【Linux网络编程】应用层:HTTP协议 | URL | 简单实现一个HTTP服务器 | 永久重定向与临时重定向

作者头像
南桥
发布2024-12-12 08:38:16
发布2024-12-12 08:38:16
17500
代码可运行
举报
文章被收录于专栏:南桥谈编程南桥谈编程
运行总次数:0
代码可运行

前言

实现一个简单的HTTP服务器点击链接获取源码

HTTP协议

应用层有很多协议,其中HTTP协议就是其中一个,HTTP协议(超文本传输协议)也是较为重要的一个协议。

在互联网世界中, HTTP(HyperText Transfer Protocol, 超文本传输协议) 是一个至关重要的协议。 它定义了客户端(如浏览器) 与服务器之间如何通信, 以交换或传输超文本(如 HTML 文档)。

HTTP 协议是客户端与服务器之间通信的基础。 客户端通过 HTTP 协议向服务器发送请求, 服务器收到请求后处理并返回响应。 HTTP 协议是一个无连接、 无状态的协议, 即每次请求都需要建立新的连接, 且服务器不会保存客户端的状态信息。

URL

说到URL大家可能不熟悉,但是说到“网址”大家就一目了然,实际上,“网址”就是URL。

  • 域名会自动转化成IP地址,称为DNS
  • 协议名称和端口号是强关联的,在浏览器这个网址中将端口号默认忽略了,当浏览器发起请求时会自动拼接端口号80(指明端口)

HTTP做的工作:用户在网络上获取资源(图片、文本、视频等)时是在服务器端获取,所有的资源都是在服务器端。通过某种协议(如http协议或者https协议)来标识用户所需要的资源,然后返回给用户。

我们知道Linux适合做后端开发,那么这些服务器端的资源是在Linux操作系统中。而Linux操作系统一切皆文件,必须得找到对应的资源,需要通过路径来标识,因此在URL后半部分就是路径。

因此,URL就是统一资源定位符,域名+文件路径,标识互联网中唯一的文件资源。

urlencode 和 urldecode(了解)

/ ? : 等这样的字符, 已经被 url 当做特殊意义理解了. 因此这些字符不能随意出现。比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义。 转义的规则如下: 将需要转码的字符转为 16 进制, 然后从右到左, 取 4 位(不足 4 位直接处理), 每 2 位做一位, 前面加上%, 编码成%XY 格式。

HTTP协议请求与响应格式

HTTP请求

在HTTP请求中,格式如下:

  • 首行:称之为请求行,格式为 方法(POST、GET、HEAD…)+URI(即URL后半部分—>用户访问资源的路径)+版本号
  • HTTP的方法:

HTTP响应

在HTTP请求中,格式如下:

  • 这里的DATA是用户需要的资源内容:网页,图片的二进制、视频的二进制、音频的二进制等

HTTP中如何将报头和有效载荷进行分离(封装)? 通过空行即\r\n

如何HTTP的请求和响应读到一个完整的报头?通过换行符可以知道读到一个完整的报文。

如果请求和响应中包含了正文(DATA),如何保证读到一个完整的报头?怎么知道正文部分的长度?在之前的网络版本计算器中只有一个字段,拥有有效载荷长度的字段。在HTTP的请求和应答中报头属性中有一个公共属性叫做Content-Length: xxx,无论是请求还是响应,如果有正文部分,那么这个字段一定包含,这样就能读到一个完整的报文。

简单实现一个HTTP服务器

HTTP请求及反序列化

HTTP请求的基本框架

在HTTP请求中,需要基本的属性(请求行、请求报头、空行、请求正文)以及进一步显示出具体属性(使用的方法、使用的URL、使用的HTTP版本、请求报头以KV形式显示)

代码语言:javascript
代码运行次数:0
复制
class HttpRequest
{
public:
    HttpRequest():_blank_line(base_sep) 
    {}

    void Deserialize(std::string &reqstr)
    {}
    
    ~HttpRequest() {}
private:
    // 请求的基本格式
    std::string _req_line;
    std::vector<std::string> _req_handers;
    std::string _blank_line;
    std::string _body_text;

    //具体属性
    std::string _method;
    std::string _url;
    std::string _version;
    std::unordered_map<std::string,std::string> _headers_kv;
};

封装反序列化

在反序列化中:

  1. 首先需要通过Getlinereqstr中获取请求行
  2. 获取请求报头:通过一个 do-while 循环来读取所有的请求头部信息。每次循环调用 Getline(reqstr) 获取一行数据,并将它存储在 header
  3. 获取请求正文
  4. 进一步解析请求行和请求头
代码语言:javascript
代码运行次数:0
复制
void Deserialize(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_handers.push_back(header);
    }while(true);
    
    if(!reqstr.empty())
        _body_text=reqstr;
    
    //进一步反序列化
    ParseReqLine();
    ParseReqHeader();
}

获取每一行:Getline

寻找base_sep的位置,如果找不到说明没有分隔符,是一个空串;如果找到了base_sep就分割从0base_sep之间的字符串。然后更新reqstr,即删除已经提取的部分。如果 line 是空字符串(即没有有效的行内容),则返回 base_sep,表示一个空行或分隔符。否则,返回提取的有效行内容 line

代码语言:javascript
代码运行次数:0
复制
const static std::string base_sep="\r\n";

std::string Getline(std::string &reqstr)
{
    auto pos=reqstr.find(base_sep);
    if(pos==std::string::npos) 
        return std::string();
    std::string line=reqstr.substr(0,pos);
    reqstr.erase(0,line.size()+base_sep.size());

    return line.empty()?base_sep:line;
}

进一步解析请求行

解析一个请求行,并将解析的结果存储到类的成员变量 _method_url _version 中。

代码通过 std::stringstream 提供的流提取操作符 (>>) 来从 ss 中依次提取数据并赋值给 _method_url _version

  • 第一个 >> _method:将从流中提取的第一个单词(如 GET)赋值给 _method。通常情况下,_method 代表 HTTP 请求的方法(例如 GET、POST 等)。
  • 第二个 >> _url:提取流中的第二个单词(如 /index.html),并将其赋值给 _url。这个字符串通常代表请求的 URL 路径。
  • 第三个 >> _version:提取流中的第三个单词(如 HTTP/1.1),并将其赋值给 _version。这个字符串代表请求的 HTTP 版本。
代码语言:javascript
代码运行次数:0
复制
void ParseReqLine()
{
    std::stringstream ss(_req_line);
    ss>>_method>>_url>>_version;
}
进一步解析请求报头

未解析的解析报头是以: 分割的,因此这里使用: 作为分隔符。

将每个请求头的键值对存储到 _headers_kv 容器中。它通过遍历 _req_handers,提取出每个请求头的键和值,并将它们插入到一个键值对容器中。

代码语言:javascript
代码运行次数:0
复制
const static std::string line_sep=": ";

void ParseReqHeader()
 {
     for(auto &header:_req_handers)
     {
         auto pos=header.find(line_sep);
         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.insert(std::make_pair(k,v));
     }
 }

测试

HTTP响应及序列化

HTTP响应基本框架
代码语言:javascript
代码运行次数:0
复制
class HttpResponse
{
public:
    HttpResponse():_version(httpversion),_blank_line(base_sep)
    {}

    void AddCode(int code)
    {
        _status_code=code;
        _desc="OK";
    }

    void AddHeader(const std::string &k,const std::string &v)
    {
        _headers_kv[k]=v;
    }

    void AddBodyText(const std::string body_text)
    {
        _resp_body_text=body_text;
    }
    
    std::string Serialize()
    {
    }

    ~HttpResponse()
    {}

private:
    // 基本属性
    std::string _version;
    int _status_code;
    std::string _desc;
    std::unordered_map<std::string,std::string> _headers_kv;
    // 响应的基本格式
    std::string _status_line;
    std::vector<std::string> _resp_handers;
    std::string _blank_line;
    std::string _resp_body_text;
};
封装序列化

在HTTP响应序列化中:

  1. 构建状态行,并将其存储在 _status_line 字符串中
  2. 构建响应头,遍历 _headers_kv 容器,它是一个存储 HTTP 响应头的键值对容器。每一行头部字符串会被 push_back _resp_handers 容器中,最终该容器保存了所有响应头的字符串
  3. 正式序列化,将之前构建的各部分拼接成最终的响应报文字符串 responsestr
代码语言:javascript
代码运行次数:0
复制
std::string Serialize()
{
    // 构建状态行
    _status_line=_version+spacesep+std::to_string(_status_code)+spacesep+_desc+base_sep;
    // 构建应答报头
    for(auto &hander:_headers_kv)
    {
        std::string header_line=hander.first+line_sep+hander.second+base_sep;
        _resp_handers.push_back(header_line);
    }
    // 空行和正文
    // 空行已经初始化,正文已经保存在_resp_body_text中

    // 正式序列化
    std::string responsestr=_status_line;
    for(auto &line:_resp_handers)
    {
        responsestr+=line;
    }
    responsestr+=_blank_line;
    responsestr+=_resp_body_text;
    
    return responsestr;
}
使用telnet进行抓包

telnet 127.0.0.1 8888

通过这个测试,得到响应

完整代码
代码语言:javascript
代码运行次数:0
复制
class HttpResponse
{
public:
    HttpResponse():_version(httpversion),_blank_line(base_sep)
    {}

    void AddCode(int code)
    {
        _status_code=code;
        _desc="OK";
    }

    void AddHeader(const std::string &k,const std::string &v)
    {
        _headers_kv[k]=v;
    }

    void AddBodyText(const std::string body_text)
    {
        _resp_body_text=body_text;
    }
    
    std::string Serialize()
    {
        // 构建状态行
        _status_line=_version+spacesep+std::to_string(_status_code)+spacesep+_desc+base_sep;
        // 构建应答报头
        for(auto &hander:_headers_kv)
        {
            std::string header_line=hander.first+line_sep+hander.second+base_sep;
            _resp_handers.push_back(header_line);
        }
        // 空行和正文
        // 空行已经初始化,正文已经保存在_resp_body_text中

        // 正式序列化
        std::string responsestr=_status_line;
        for(auto &line:_resp_handers)
        {
            responsestr+=line;
        }
        responsestr+=_blank_line;
        responsestr+=_resp_body_text;
        
        return responsestr;
    }

    ~HttpResponse()
    {}

private:
    // 基本属性
    std::string _version;
    int _status_code;
    std::string _desc;
    std::unordered_map<std::string,std::string> _headers_kv;
    // 响应的基本格式
    std::string _status_line;
    std::vector<std::string> _resp_handers;
    std::string _blank_line;
    std::string _resp_body_text;
};

html

代码语言:javascript
代码运行次数:0
复制
<!DOCTYPE html>
<html>
<head>
    <title>南桥几晴秋(gwj.cn)</title>        
    <meta charset="UTF-8">
</head>
<body>
    <div id="container" style="width:800px">
    <div id="header" style="background-color:#FFA500;">
    <h1 style="margin-bottom:0;">南桥几晴秋</h1></div>
    <div id="menu" style="background-color:#FFD700;height:200px;width:100px;float:left;">
    <b>Menu</b><br>
    HTML<br>
    CSS<br>
    JavaScript</div>
    <div id="content" style="background-color:#EEEEEE;height:200px;width:700px;float:left;">
    这是一个测试</div>
    <div id="footer" style="background-color:#FFA500;clear:both;text-align:center;">
    Copyright © gwj.com</div>
    </div>

    <div>
        <img src="/image/1.jpg" alt="一张图片">
        
    </div>
</body>
</html>
测试

在浏览器中输入:http://119.3.220.34:8888/,得到如下界面:

为什么只访问首页时但是依然有下面的图片

获得一个完整的网页,浏览器要先得到html,根据html的标签检测出我们还要获取的其他资源,浏览器会继续发起HTTP请求。

理解网站页面跳转

我们在使用网站时,点击某个按钮进行跳转,实际上是在访问wwwroot中的文件

在我自己的HTTP服务器中显示如下:

在现代Web开发中,HTML文件的内容通常属于前端部分,而通过后端调用和渲染这些内容是常见的架构设计。

HTTP 常见 Header

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

HTTP 中的 Connection 字段是 HTTP 报文头的一部分, 它主要用于控制和管理客户端与服务器之间的连接状态。

  • 管理持久连接: Connection 字段还用于管理持久连接(也称为长连接) 。 持久连接允许客户端和服务器在请求/响应完成后不立即关闭 TCP 连接, 以便在同一个连接上发送多个请求和接收多个响应。
  • HTTP/1.1: 在 HTTP/1.1 协议中, 默认使用持久连接。 当客户端和服务器都不明确指定关闭连接时, 连接将保持打开状态, 以便后续的请求和响应可以复用同一个连接。
  • HTTP/1.0: 在 HTTP/1.0 协议中, 默认连接是非持久的。 如果希望在 HTTP/1.0上实现持久连接, 需要在请求头中显式设置 Connection: keep-alive

语法格式Connection: keep-alive: 表示希望保持连接以复用 TCP 连接。 • Connection: close: 表示请求/响应完成后, 应该关闭 TCP 连接。

HTTP的状态码

最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定 向), 504(Bad Gateway)

永久重定向与临时重定向

什么叫做重定向?

用户浏览器想目标服务器发起请求,但是目标服务器想用户浏览器推送了一个服务器,此时用户浏览器向推送的服务器进行请求,重定向的地址在location字段中。

所谓的永久与临时无非就是字面意思,永久即下次再请求时直接去新的服务器,会修改客户的地址,临时不对客户做任何影响。

永久重定向:当你决定将一个页面永久迁移到新地址时,使用永久重定向是正确的做法。例如,网站改版、页面URL结构优化或域名更换时。

临时重定向:当你需要在短期内将页面临时指向另一个URL时使用,比如进行系统维护、A/B测试或者临时更新页面内容时。

代码语言:javascript
代码运行次数:0
复制
if(req.Path()=="wwwroot/redir")
{
   std::string reire_path="https://www.qq.com";
   resp.AddCode(302,_code_to_desc[302]);
   resp.AddHeader("Location",reire_path);
}

HTTP请求方法

HTTP常见方法
GET方法
  • 用途:用于请求URL指定的资源
  • 特性:指定资源经服务器端解析后返回响应内容

GET一般用来获取静态资源,也可以通过URL向服务器传递参数

代码语言:javascript
代码运行次数:0
复制
std::string Method()
{
    LOG(DEBUG,"Client request method is %s\n",_method.c_str());
    return _method;
}
POST方法
  • 用途:用于传输实体的主体, 通常用于提交表单数据
  • 特性:可以发送大量的数据给服务器, 并且数据包含在请求体中

POST方法可以通过httprequest的正文来进行参数传递

POST方法传递参数比GET方法更私密,登录信息不回显示在浏览器中。但是都不安全。要想保证安全必须对http的参数部分进行加密。

完整HTTP代码

代码语言:javascript
代码运行次数:0
复制
#pragma once

#include<iostream>
#include<string>
#include<vector>
#include<functional>
#include<sstream>
#include<fstream>
#include<unordered_map>

const static std::string base_sep="\r\n";
const static std::string line_sep=": ";
const static std::string prefixpath="wwwroot"; // web根目录
const static std::string homepage="index.html"; // web根目录
const static std::string httpversion="HTTP/gwj_1.0";
const static std::string spacesep=" ";
const static std::string suffixsep=".";
const static std::string html_404="404.html";
const static std::string arg_sep="?";

class HttpRequest
{
private:
    std::string Getline(std::string &reqstr)
    {
        auto pos=reqstr.find(base_sep);
        if(pos==std::string::npos) 
            return std::string();
        std::string line=reqstr.substr(0,pos);
        reqstr.erase(0,line.size()+base_sep.size());

        return line.empty()?base_sep:line;
    }

    void ParseReqLine()
    {
        std::stringstream ss(_req_line);
        ss>>_method>>_url>>_version;

        if(strcasecmp(_method.c_str(),"GET")==0)
        {
            auto pos=_url.find(arg_sep);
            if(pos!=std::string::npos)
            {
                _body_text=_url.substr(pos+arg_sep.size());
                _url.resize(pos);  // 只保留了?前面的字符
            }
        }

        _path+=_url;
        if(_path[_path.size()-1]=='/')
        {
            _path+=homepage;
        }
        
        auto pos=_path.rfind(suffixsep);
        if(pos!=std::string::npos)
        {
            _suffix=_path.substr(pos);
        }
        else
        {
            _suffix=".default";
        }

    }

    void ParseReqHeader()
    {
        for(auto &header:_req_handers)
        {
            auto pos=header.find(line_sep);
            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.insert(std::make_pair(k,v));
        }
    }

public:
    HttpRequest():_blank_line(base_sep),_path(prefixpath)
    {}

    void Deserialize(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_handers.push_back(header);
        }while(true);
        
        if(!reqstr.empty())
            _body_text=reqstr;
        
        //进一步反序列化
        ParseReqLine();
        ParseReqHeader();
    }

    std::string Url()
    {
        LOG(DEBUG,"Client Want path %s\n",_path.c_str());
        return _url;
    }

    std::string Path()
    {
        LOG(DEBUG,"Client Want %s\n",_url.c_str());
        return _path;
    }

    std::string Suffix()
    {
        return _suffix;
    }

    std::string Method()
    {
        LOG(DEBUG,"Client request method is %s\n",_method.c_str());
        return _method;
    }

    std::string GetRequestBody()
    {
        LOG(DEBUG,"Client request method is %s, args: %s, request path: %s\n",_method.c_str(),_body_text.c_str(),_path.c_str());
        return _body_text;
    }

    void Print()
    {
        std::cout<<"-----------------------------------------------"<<std::endl;
        std::cout<<"###"<<_req_line<<std::endl;
        for(auto &header:_req_handers)
        {
            std::cout<<"@@@"<<header<<std::endl;
        }
        std::cout<<"***"<<_blank_line;
        std::cout<<">>>"<<_body_text<<std::endl;

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

        for(auto header_kv:_headers_kv)
        {
            std::cout<<"< "<<header_kv.first<<" > : < "<<header_kv.second<<" >"<<std::endl;
        }
    }

    ~HttpRequest() {}
private:
    // 请求的基本格式
    std::string _req_line;
    std::vector<std::string> _req_handers;
    std::string _blank_line;
    std::string _body_text;

    //具体属性
    std::string _method;
    std::string _url;
    std::string _path;
    std::string _suffix; // 资源后缀
    std::string _version;
    std::unordered_map<std::string,std::string> _headers_kv;
};

class HttpResponse
{
public:
    HttpResponse():_version(httpversion),_blank_line(base_sep)
    {}

    void AddCode(int code,const std::string &desc)
    {
        _status_code=code;
        _desc=desc;
    }

    void AddHeader(const std::string &k,const std::string &v)
    {
        _headers_kv[k]=v;
    }

    void AddBodyText(const std::string body_text)
    {
        _resp_body_text=body_text;
    }
    
    std::string Serialize()
    {
        // 构建状态行
        _status_line=_version+spacesep+std::to_string(_status_code)+spacesep+_desc+base_sep;
        // 构建应答报头
        for(auto &hander:_headers_kv)
        {
            std::string header_line=hander.first+line_sep+hander.second+base_sep;
            _resp_handers.push_back(header_line);
        }
        // 空行和正文
        // 空行已经初始化,正文已经保存在_resp_body_text中

        // 正式序列化
        std::string responsestr=_status_line;
        for(auto &line:_resp_handers)
        {
            responsestr+=line;
        }
        responsestr+=_blank_line;
        responsestr+=_resp_body_text;
        
        return responsestr;
    }

    ~HttpResponse()
    {}

private:
    // 基本属性
    std::string _version;
    int _status_code;
    std::string _desc;
    std::unordered_map<std::string,std::string> _headers_kv;
    // 响应的基本格式
    std::string _status_line;
    std::vector<std::string> _resp_handers;
    std::string _blank_line;
    std::string _resp_body_text;
};

using func_t=std::function<HttpResponse(HttpRequest&)>;

class HttpServer
{
private:
    std::string GetFileContent(const std::string path)
    {
        std::ifstream in(path,std::ios::binary);
        if(!in.is_open()) return std::string();
        in.seekg(0,in.end);
        int filesize=in.tellg(); //读写偏移量,计算文件大小
        in.seekg(0,in.beg);

        std::string content;
        content.resize(filesize);
        in.read((char*)content.c_str(),filesize);

        in.close();

        return content;
    }

public:
    HttpServer()
    {
        _mime_type.insert(std::make_pair(".html","text/html"));
        _mime_type.insert(std::make_pair(".jpg","image/jpeg"));
        _mime_type.insert(std::make_pair(".png","image/png"));
        _mime_type.insert(std::make_pair(".default","text/html"));

        _code_to_desc.insert(std::make_pair(100,"Continue"));
        _code_to_desc.insert(std::make_pair(200,"OK"));
        _code_to_desc.insert(std::make_pair(201,"Created"));
        _code_to_desc.insert(std::make_pair(301,"Moved Permanently"));
        _code_to_desc.insert(std::make_pair(302,"Found"));
    }
// #define TEST
    std::string HandlerHttpRequest(std::string &reqstr) //reqstr被客户序列化过
    {
#ifdef TEST
        std::cout<<"------------------------------------------------------"<<std::endl;
        std::cout<<reqstr;

        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 gwj!</h1></html>";
        return responsestr;

#else
    std::cout << "---------------------------------------" << std::endl;
    std::cout << reqstr;
    std::cout << "---------------------------------------" << std::endl;

    HttpRequest req;
    HttpResponse resp;  

    req.Deserialize(reqstr);
    // req.Method();

    if(req.Path()=="wwwroot/redir")
    {
        std::string reire_path="https://www.qq.com";
        resp.AddCode(302,_code_to_desc[302]);
        resp.AddHeader("Location",reire_path);
    }
    else if (!req.GetRequestBody().empty())
    {
        if(IsServiceExists(req.Path()))
        {
            resp=_service_lists[req.Path()](req);
        }
    }
    
    else
    {
        std::string content = GetFileContent(req.Path());
        if (content.empty())
        {
            content = GetFileContent("wwwroot/404.html");
            resp.AddCode(404, _code_to_desc[404]);
            resp.AddHeader("Content-Length", std::to_string(content.size()));
            resp.AddHeader("Content-Type", _mime_type[".html"]);
            resp.AddBodyText(content);
        }
        else
        {
            resp.AddCode(200, _code_to_desc[200]);
            resp.AddHeader("Content-Length", std::to_string(content.size()));
            resp.AddHeader("Content-Type", _mime_type[req.Suffix()]);
            resp.AddBodyText(content);
        }
    }

    return resp.Serialize();

#endif

    }
    void InsertService(const std::string servicename,func_t f)
    {
        std::string s=prefixpath+servicename;
        _service_lists[s]=f;
    }

    bool IsServiceExists(const std::string &servicename)
    {
        auto iter=_service_lists.find(servicename);
        if(iter==_service_lists.end()) return false;
        else return true;
    }


    ~HttpServer() {}

private:
    std::unordered_map<std::string,std::string> _mime_type;
    std::unordered_map<int,std::string> _code_to_desc;
    std::unordered_map<std::string,func_t> _service_lists;
};
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-12-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • HTTP协议
  • URL
    • urlencode 和 urldecode(了解)
  • HTTP协议请求与响应格式
  • 简单实现一个HTTP服务器
    • HTTP请求及反序列化
      • HTTP请求的基本框架
      • 封装反序列化
      • 获取每一行:Getline
      • 进一步解析请求行
      • 进一步解析请求报头
      • 测试
    • HTTP响应及序列化
      • HTTP响应基本框架
      • 封装序列化
      • 使用telnet进行抓包
      • 完整代码
      • 测试
      • 为什么只访问首页时但是依然有下面的图片
    • 理解网站页面跳转
    • HTTP 常见 Header
      • 关于connection报头
    • HTTP的状态码
    • 永久重定向与临时重定向
    • HTTP请求方法
      • HTTP常见方法
    • 完整HTTP代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档