秋雨De blog

  • 首页
  • 留言板
  • 关于
  • rss
秋雨De blog
一个技术小白的个人博客
  1. 首页
  2. 未分类
  3. 正文

手写 C++ Web 服务器:HTTP 请求解析

2026年3月12日 77点热度 0人点赞 0条评论

在上一篇文章中,我们已经实现了一个最基础的 Web 服务器:

  • 使用 Boost.Asio 接收 TCP 连接
  • 为每个连接创建一个 Session
  • 通过 async_read 读取客户端数据

但此时服务器其实还没有真正理解 HTTP 请求。

服务器收到的其实只是 一段原始的字符串数据。

例如浏览器发来的请求可能是:

POST /api/login HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Content-Length: 34{"username":"admin","password":"123"}

如果我们想要处理这个请求,就必须完成几件事情:

  1. 解析 请求行
  2. 解析 HTTP Header
  3. 读取 HTTP Body
  4. 构造 HTTP Response

本篇是手写 C++ Web 服务器系列第二篇,这一篇我们就来实现一个最基础的 HTTP 请求解析器。源码地址:https://github.com/Fall-Rain/asio_web_service


一、Session 的基本结构

在服务器中,每一个客户端连接都会对应一个 Session 对象。

构造函数如下:

Session::Session(boost::asio::ip::tcp::socket &socket): client_socket_(std::move(socket)) {}

这里我们将 socket 移动到 Session 中保存。

这样每个 Session 都拥有一个独立的 TCP 连接。

当连接建立后,服务器会调用:

void Session::start() {
    do_read();
}

开始读取客户端请求。


二、读取 HTTP 请求头

HTTP 请求头的结束标志是:

\r\n\r\n

因此可以使用 Asio 的:

boost::asio::async_read_until

代码如下:

boost::asio::async_read_until(
    client_socket_,
    client_buffer_,
    "\r\n\r\n",
    callback
);

这里的意思是:

一直读取 socket 数据,直到遇到 \r\n\r\n。

当读取完成后,就会进入回调函数。


三、解析请求头字符串

读取完成后,我们先把 buffer 转换成字符串:

std::string request_header(
    boost::asio::buffers_begin(self->client_buffer_.data()),
    boost::asio::buffers_begin(self->client_buffer_.data()) +
    bytes_transferred
);

此时 request_header 内容类似:

POST /login HTTP/1.1
Host: localhost
Content-Type: application/json
Content-Length: 34

接下来需要按行拆分:

std::vector<string> headers;boost::split(
    headers,
    request_header,
    boost::is_any_of("\r\n"),
    boost::token_compress_on
);

拆分后:

headers[0] = 请求行
headers[1] = Host
headers[2] = Content-Type
headers[3] = Content-Length

四、解析请求行

HTTP 第一行叫做 Request Line:

POST /login HTTP/1.1

代码:

std::vector<string> line;
boost::split(line, headers[0], boost::is_any_of(" "));

拆分后:

line[0] = method
line[1] = uri
line[2] = http_version

于是我们得到:

method = POST
uri = /login
http_version = HTTP/1.1

五、解析 HTTP Header

HTTP 头格式是:

Key: Value

例如:

Content-Type: application/json

代码:

std::for_each(
    headers.begin() + 1,
    headers.end() - 1,
    [&](std::string v)
{
    std::vector<string> header;    boost::split(header, v, boost::is_any_of(":"));    self->header_map.insert(
        std::pair<string,string>(
            header[0],
            header[1]
        )
    );
});

最终保存到:

header_map

例如:

Host -> localhost
Content-Type -> application/json
Content-Length -> 34

为了方便调试,我还打印了请求信息:

std::cout << "请求头" << std::endl;
std::cout << "method:" << method << std::endl;
std::cout << "uri:" << uri << std::endl;

输出示例:

请求头
method:POST
uri:/login
http_version:HTTP/1.1
Host => localhost
Content-Length => 34

六、读取 HTTP Body

如果请求头中存在:

Content-Length

说明请求包含 Body。

代码:

auto content_length = self->header_map.find("Content-Length");

如果存在,就需要继续读取 body。

首先计算当前 buffer 中多出来的数据:

std::size_t excess_data_length = self->client_buffer_.size() - bytes_transferred;

因为有时候 async_read_until 读取的数据 可能已经包含了部分 body。

于是我们先把这部分提取出来:

std::vector<char> excess_data(excess_data_length);boost::asio::buffer_copy(
    boost::asio::buffer(excess_data),
    self->client_buffer_.data() + bytes_transferred
);

然后转换为字符串:

self->request_body = string(excess_data.data(), excess_data_length);

七、继续读取剩余 Body

接下来根据 Content-Length 判断还需要读取多少数据:

size_t content_length = std::stoi(self->header_map.find("Content-Length")->second);

如果 body 没读完:

if (content_length - self->request_body.size() > 0)

就继续读取:

boost::asio::read(
    self->client_socket_,
    boost::asio::buffer(buffer),
    boost::asio::transfer_exactly(
        content_length - self->request_body.size()
    )
);

最终得到完整 body:

{"username":"admin","password":"123"}

并打印:

std::cout << "请求体:" << std::endl;
std::cout << self->request_body << std::endl;

八、返回 HTTP 响应

最后服务器返回一个简单的 HTTP 响应:

response_stream << "HTTP/1.1 200 OK\r\n";
response_stream << "Content-Type: text/html\r\n";
response_stream << "Content-Length:"
                << self->request_body.size()
                << "\r\n";
response_stream << "\r\n";
response_stream << self->request_body;

这个服务器会:

直接把客户端发送的 body 原样返回。

例如客户端发送:

hello

服务器响应:

HTTP/1.1 200 OK
Content-Length:5hello

最后通过 async_write 发送:

boost::asio::async_write(
    client_socket_,
    boost::asio::buffer(response_stream.str()),
    callback
);

九、小结

这一篇我们实现了一个最基础的 HTTP 请求解析流程:

TCP Socket
│
▼
读取 HTTP Header
│
▼
解析 Request Line
│
▼
解析 Header
│
▼
读取 Body
│
▼
返回 HTTP Response

虽然这个服务器还非常简单,但已经具备了 Web 服务器的核心能力:

  • 解析 HTTP 协议
  • 读取请求体
  • 构造响应

在下一篇文章中,我们将进一步完善服务器的能力,例如:

  • URL 参数解析
  • Content-Type 处理
  • JSON / 表单数据解析

让服务器逐渐演变成一个真正的 C++ Web 框架。

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2026年3月14日

fallrain

种一棵树最好的时间是十年前,其次是现在。

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

fallrain

种一棵树最好的时间是十年前,其次是现在。

文章目录
  • 一、Session 的基本结构
  • 二、读取 HTTP 请求头
  • 三、解析请求头字符串
  • 四、解析请求行
  • 五、解析 HTTP Header
  • 六、读取 HTTP Body
  • 七、继续读取剩余 Body
  • 八、返回 HTTP 响应
  • 九、小结
友情连接
猫饭范文泉博客迎風别葉CODING手艺人ScarSu博友圈
归档
  • 2026 年 3 月
  • 2025 年 11 月
  • 2025 年 5 月
  • 2025 年 4 月
  • 2025 年 3 月
  • 2024 年 12 月
  • 2024 年 10 月
  • 2024 年 5 月
  • 2023 年 2 月
  • 2022 年 11 月
  • 2022 年 3 月
  • 2021 年 12 月
  • 2021 年 8 月
  • 2021 年 5 月
  • 2021 年 4 月
  • 2021 年 3 月
  • 2020 年 12 月
  • 2020 年 11 月
  • 2020 年 8 月
  • 2020 年 5 月
  • 2019 年 12 月
  • 2019 年 3 月

吉ICP备18007356号

吉公网安备22020302000184号

Theme Kratos Made By Seaton Jiang

COPYRIGHT © 2026 秋雨De blog ALL RIGHTS RESERVED