1. 原理
https://blog.csdn.net/2302_80372340/article/details/151611390?spm=1011.2415.3001.5331
上面这篇文章已经详细讨论过HTTP协议是如何运作的了。简单来说,我们要在我们的服务器上做下面几件事:
- 接收来自客户端(浏览器)的HTTP请求
- 反序列化HTTP请求
- 读取请求行的URI字段,读取客户端要求的网页对象文件
- 将网页对象文件的数据包装为HTTP响应报文
- 序列化响应报文,回送给客户端
网页对象文件通常就是HTML、JavaScript、CSS以及图片、声音、文本等文件。
总之,我们要做的就是用代码来实现HTTP协议。
2. 协议的实现
2.1 报文的定义
首先,我们要根据HTTP协议描述的请求报文与响应报文,定义出结构化的请求报文和响应报文,并实现二者的序列化与反序列化方法。
如果只考虑服务端的话,我们只需要实现请求报文的反序列化方法和响应报文的序列化方法。
我们注意到,请求报文与响应报文最大的区别在于首行,而其他部分均大同小异,所以我们可以首先定义出HttpMessage类作为二者的父类,实现二者共同的部分:
cpp
class HttpMessage
{
public:
// 向_headers中添加键值对
bool AddHeader(const std::string &header)
{
auto pos = header.find(_HeaderSep);
if (pos == std::string::npos)
{
return false;
}
std::string key = header.substr(0, pos);
std::string value = header.substr(pos + _HeaderSep.size());
_headers[key] = value;
return true;
}
bool AddHeader(const std::string &key, const std::string &value)
{
_headers[key] = value;
return true;
}
void SetData(const std::string &data)
{
_data = data;
}
const std::string &Data() const
{
return _data;
}
void SetVersion(const std::string &version)
{
_version = version;
}
bool Headers(const std::string &key, std::string value)
{
if(!_headers.count(key))
return false;
value = _headers[key];
return true;
}
protected:
static const std::string _Space; // 空格
static const std::string _LineBreak; // 换行符
static const std::string _BlankLine; // 空行
static const std::string _HeaderSep; // 报头分割符
std::string _version; // Http版本
std::unordered_map<std::string, std::string> _headers; // 请求/响应报头
std::string _data; // 请求/响应正文
};
const std::string HttpMessage::_Space = " ";
const std::string HttpMessage::_LineBreak = "\r\n";
const std::string HttpMessage::_BlankLine = "\r\n";
const std::string HttpMessage::_HeaderSep = ": ";
2.1.1 请求报文
相比于HttpMessage,请求报文有两个特有的字段:HTTP请求方法和URI。
要完成请求报文的反序列化,我们需要一个函数来帮助我们逐行提取请求的内容:
cpp
class Util
{
public:
static bool ReadLine(std::string &message, const std::string &sep, std::string &one_line)
{
auto pos = message.find(sep);
if (pos == std::string::npos)
{
LOG(LogLevel::WARNING) << "Util::ReadLine: 未能提取出一行! ";
return false;
}
one_line = message.substr(0, pos);
message.erase(0, pos + sep.size());
return true;
}
};
当我们遇到空行(即两个连续的换行符)时,就认为收到了一个完整的报头。
在解析完报头之后,我们需要判断是否存在 "Content-Length" 字段,若存在则需继续读取正文部分。
cpp
// 请求行: 方法 + 空格 + URI + 空格 + 版本 + 换行符
class HttpRequest : public HttpMessage
{
private:
void PraseRequestLine(const std::string &request_line)
{
std::stringstream buffer(request_line);
buffer >> _method >> _uri >> _version;
}
// 读取报头
bool GetHead(std::string &request_str)
{
static std::string end = _LineBreak + _BlankLine;
// 判断是否存在一个完整报头
if (request_str.find(end) == std::string::npos)
{
return false;
}
// 读取请求行
std::string request_line;
Util::ReadLine(request_str, _LineBreak, request_line);
PraseRequestLine(request_line);
// 读取请求报头
std::string header;
do
{
Util::ReadLine(request_str, _LineBreak, header);
if (header.size() > 0)
{
AddHeader(header);
}
} while (header.size() > 0); // 读取到空行结束
// 如果URI包含参数则拆出
auto pos = _uri.find(_URIArgsSep);
if (pos != std::string::npos)
{
_uri_args = _uri.substr(pos + _URIArgsSep.size());
_uri = _uri.substr(0, pos);
}
return true;
}
public:
HttpRequest() {}
// 序列化
// std::string Serialize() {}
// 反序列化
bool Deserialize(std::string &request_str)
{
static bool is_continue = false;
if (!is_continue)
{
if (!GetHead(request_str))
return false;
}
is_continue = true;
if (_headers.count("Content-Length"))
{
int size = std::stoi(_headers["Content-Length"]);
if (request_str.size() < size)
return false;
_data = request_str.substr(0, size);
request_str.erase(0, size);
}
is_continue = false;
return true;
}
const std::string &Uri() const { return _uri; }
const std::string &Method() const { return _method; }
~HttpRequest() {}
private:
std::string _method; // Http请求方法
std::string _uri; // URI
std::string _uri_args; // URI中的参数
static const std::string _URIArgsSep; // URI参数分割符
};
const std::string HttpRequest::_URIArgsSep = "?";
当然,我们只是做一个简单的服务器,虽然请求报头部分我们也做了序列化,但是我们并不打算对这部分做处理。
2.1.2 响应报文
相比于HttpMessage,响应报文有也有两个特有的字段:状态码与状态码描述。除此之外,我们还需要动用HttpMessage的_data字段来存储要发给客户端的对象。
响应报文的序列化看起来是较为简单的,只要按照响应报文的格式拼接字符串即可。
但是要使我们回复给客户端的数据被正确解析,我们还需要两个重要的响应报头字段:"Content-Length"(响应正文的长度)、"Content-Type"(响应正文的数据类型)。
响应报文的长度可以由如下方法进行获取:
cpp
class Util
{
public:
static size_t FileSize(const std::string &path)
{
// 以二进制模式打开文件(避免文本模式下的换行符转换影响计算)
std::ifstream file(path, std::ios::in | std::ios::binary);
if (!file.is_open())
{
LOG(LogLevel::ERROR) << "无法打开文件: " << path << strerror(errno);
return -1; // 打开失败返回-1
}
// 将读指针移动到文件末尾
file.seekg(0, std::ios::end);
// 获取当前指针位置(即文件大小)
size_t size = file.tellg();
return size;
}
};
Content-Type可以通过提取文件的后缀名,并通过一个映射集来进行映射的方式获取:
cpp
// 状态行: 版本 + 空格 + 状态码 + 空格 + 状态码描述 + 换行符
class HttpResponse : public HttpMessage
{
public:
HttpResponse()
: _status(200)
{
}
// 序列化
std::string Serialize()
{
std::string status_line = _version + _Space + std::to_string(_status) + _Space + _StatusDesc[_status] + _LineBreak;
std::string response_headers;
for (auto &header : _headers)
{
response_headers += header.first + _HeaderSep + header.second + _LineBreak;
}
std::string message = status_line + response_headers + _BlankLine + _data;
return message;
}
const std::string &GetMineType(const std::string &extension)
{
if (_MineType.count(extension) == 0)
{
LOG(LogLevel::ERROR) << "ExtensionToType: 未知的拓展名! [" << extension << "]";
return _MineType[""];
}
return _MineType[extension];
}
void SetStatus(int status)
{
_status = status;
}
// 反序列化
// bool Deserialize(const std::string request_str) {}
~HttpResponse() {}
private:
int _status; // 状态码
static std::unordered_map<int, std::string> _StatusDesc; // 状态码描述
static std::unordered_map<std::string, std::string> _MineType; // 后缀转HTTP数据类型
};
std::unordered_map<int, std::string> HttpResponse::_StatusDesc = {
{200, "OK"},
{404, "Not Found"},
{301, "Moved Permanently"},
{302, "See Other"}};
std::unordered_map<std::string, std::string> HttpResponse::_MineType = {
{"", "text/plain"},
{"txt", "text/plain"},
{"html", "text/html"},
{"htm", "text/html"},
{"xml", "text/xml"},
{"gif", "image/gif"},
{"jpg", "image/jpeg"},
{"png", "image/png"}};
2.2 HTTP协议的定义
2.2.1 重定向

如果HTTP响应报文的状态码为3xx,则表明服务端希望客户端跳转到另一个网页(同一个网站或其他网站都有可能)。
此时,我们需要在响应报文的头部加上 "Location: 新网址" 报头。客户端在收到这样的一条响应报文之后,就会重新向这个新网址发起请求。
我们可以设计如下的一个字段来记录重定向关系,即当用户在请求某个URI指向的资源时,我们将其重定向到对应的新的网址:
cpp
std::unordered_map<std::string, std::string> _Redirect; // 临时重定向
// 注册重定向
void RegisterRedirect(const std::string &uri, const std::string &dest)
{
_Redirect[uri] = dest;
}
2.2.2 RESTful API
REST 是 Representational State Transfer的缩写,中文译为 "表现层状态转移",由计算机科学家 Roy Fielding 在 2000 年的博士论文中提出。
它不是一种协议(如 HTTP),而是一套 架构设计原则,用于指导分布式系统(如前后端分离、跨服务通信)的 API 设计。
简单来说,就是将所有数据 / 服务都视为 "资源"(如用户、订单、商品),并通过唯一的 URI(统一资源标识符) 来定位。
除了返回数据,我们还可以为用户提供一些微服务,就比如登录、注册等。
当客户端提供的URI为我们注册了微服务的值时,我们就可以转去执行这个微服务,而不是简单地读取文件数据,然后返回。例如如下这些常见的微服务:
|-------------|----------|---------------|--------------------------------|
| HTTP 方法 | 对应操作 | 语义说明 | 示例 URI |
| GET | 查询 | 获取资源,无副作用(只读) | GET /users(查所有用户) |
| POST | 创建 | 新增资源,会改变服务器状态 | POST /users(新增用户) |
| PUT | 全量更新 | 替换资源的全部属性 | PUT /users/123(更新用户 123) |
| PATCH | 部分更新 | 仅修改资源的部分属性 | PATCH /users/123(改用户 123 的手机号) |
| DELETE | 删除 | 删除资源,会改变服务器状态 | DELETE /users/123(删用户 123) |
同样地,我们可以定义如下的字段来记录URI和微服务之间的映射方法:
cpp
using microService_t = std::function<void(const HttpRequest &, HttpResponse &)>;
std::unordered_map<std::string, microService_t> _Route; // 微服务路由
// 注册微服务
void RegisterMicroService(const std::string &uri, microService_t service)
{
_Route[uri] = service;
}
在制作响应报文的时候,我们就可以按照如下的步骤来完成:
cpp
std::string MakeResponse(const HttpRequest &request, HttpResponse &response)
{
response.SetVersion(_Version);
std::string uri = request.Uri();
if (_Redirect.count(uri)) // 重定向
{
response.SetStatus(302);
response.AddHeader("Location", _Redirect[uri]);
}
else if (_Route.count(uri)) // uri为微服务
{
_Route[uri](request, response);
}
else // 获取静态资源
{
GetStaticSource(request, response);
}
return response.Serialize();
}
2.2.4 微服务参数的传递
既然是调用微服务,那么就免不了需要参数。就比如我们在处理用户登录时,就需要一个微服务,此时我们就需要用户提交的用户名与密码。
那么,HTTP协议是如何传参的呢?
-
GET方法 :在RUI后拼接上 "?参数1=value1&参数2=value2..." :
bash例如: [2025-09-13 16:03:41] [INFO] [146862] [Http.hpp] [270] - 来自[210.41.99.103:53299]的Http请求报文: GET /login?username=shishen&password=123456 HTTP/1.1 Host: 110.41.32.137:8888 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9
-
POST方法 :将参数存放在正文部分:
bash[2025-09-13 16:05:09] [INFO] [146893] [Http.hpp] [270] - 来自[210.41.99.103:53341]的Http请求报文: POST /login HTTP/1.1 Host: 110.41.32.137:8888 Connection: keep-alive Content-Length: 32 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 username=shishen&password=123456
2.2.5 完整实现
cpp
class Http
{
private:
void SetContentLength(HttpResponse &response, const std::string &path)
{
size_t filesize = Util::FileSize(path);
response.AddHeader("Content-Length", std::to_string(filesize));
}
void SetContentType(HttpResponse &response, const std::string &path)
{
static std::string point = ".";
auto pos = path.rfind(point);
std::string extension;
if (pos == std::string::npos)
{
extension = "";
}
else
{
extension = path.substr(pos + point.size());
}
response.AddHeader("Content-Type", response.GetMineType(extension));
}
void GetStaticSource(const HttpRequest &request, HttpResponse &response)
{
std::string path, data;
if (request.Uri() == "/")
path = _HomePage;
else
path = _WebRoot + request.Uri();
if (!Util::ReadFile(path, data))
{
response.SetStatus(404);
LOG(LogLevel::ERROR) << "Http: 获取资源失败! [" << path << "]";
path = _404Page;
Util::ReadFile(path, data);
}
response.SetData(std::move(data));
SetContentLength(response, path);
SetContentType(response, path);
}
std::string MakeResponse(HttpRequest &request, HttpResponse &response)
{
response.SetVersion(_Version);
std::string connection;
if(request.Headers("Connection", connection) && connection == "Keep-alive")
response.AddHeader("Connection", "Keep-alive");
std::string uri = request.Uri();
if (_Redirect.count(uri)) // 重定向
{
response.SetStatus(302);
response.AddHeader("Location", _Redirect[uri]);
}
else if (_Route.count(uri)) // uri为微服务
{
_Route[uri](request, response);
}
else // 获取静态资源
{
GetStaticSource(request, response);
}
return response.Serialize();
}
public:
// TCPServer的回调函数
void RequestHandler(const std::shared_ptr<TCPConnectSocket> &con_socket)
{
std::string cli_message, buffer;
while (con_socket->Receive(buffer) > 0)
{
cli_message += buffer;
LOG(LogLevel::INFO) << "来自[" << con_socket->addr().Info() << "]的Http请求报文:\n\r" << buffer;
HttpRequest request;
if (!request.Deserialize(cli_message))
continue;
LOG(LogLevel::DEBUG) << "request 反序列化成功";
HttpResponse response;
std::string message = MakeResponse(request, response);
con_socket->Send(message);
}
}
// 注册重定向
void RegisterRedirect(const std::string &uri, const std::string &dest)
{
_Redirect[uri] = dest;
}
// 注册微服务
using microService_t = std::function<void(const HttpRequest &, HttpResponse &)>;
void RegisterMicroService(const std::string &uri, microService_t service)
{
_Route[uri] = service;
}
private:
static const std::string _Version; // Http版本
static const std::string _WebRoot; // 网页根目录
static const std::string _HomePage; // 首页
static const std::string _404Page; // 404页面
std::unordered_map<std::string, std::string> _Redirect; // 临时重定向
std::unordered_map<std::string, microService_t> _Route; // 微服务路由
};
const std::string Http::_Version = "HTTP/1.1";
const std::string Http::_WebRoot = "/home/shishen/113code/linux-c/Http协议/WebRoot";
const std::string Http::_HomePage = Http::_WebRoot + "/index.html";
const std::string Http::_404Page = Http::_WebRoot + "/404.html";
3. 整个服务端的实现
bash
.
├── HttpServer
│ ├── Common.hpp
│ ├── Http.hpp
│ ├── InetAddr.hpp
│ ├── Log.hpp
│ ├── main.cpp
│ ├── Makefile
│ ├── Mutex.hpp
│ ├── Socket.hpp
│ ├── TCPServer.hpp
│ ├── testHttp
│ └── Util.hpp
└── WebRoot
├── 404.html
├── image
│ └── 666.jpg
├── index.html
├── login.html
├── login-success.html
└── register.html
3 directories, 17 files
3.1 main.cpp
cpp
#include "Http.hpp"
#include "Common.hpp"
#include <memory>
#include <unistd.h>
#include <signal.h>
int main(int argc, char* args[])
{
if(argc != 2)
{
LOG(LogLevel::FATAL) << "Usage: " << args[0] << " port";
exit(USAGE_ERROR);
}
// USE_FILE_STRATEGY();
Http http;
http.RegisterRedirect("/qq", "https://qq.com");
http.RegisterMicroService("/login", [](const HttpRequest& req, HttpResponse& res){
// 仅作测试,不管req的参数了
res.SetStatus(302);
res.AddHeader("Location", "/login-success.html");
});
in_port_t port = std::stoi(args[1]);
TCPServer server(port, [&http](const std::shared_ptr<TCPConnectSocket>& con_socket){
http.RequestHandler(con_socket);
});
// daemon(0, 0);
signal(SIGCHLD, SIG_IGN);
server.Run();
return 0;
}
3.2 Http.hpp
cpp
#pragma once
#include "TCPServer.hpp"
#include "Util.hpp"
#include <unordered_map>
#include <memory>
#include <sstream>
class HttpMessage
{
public:
// 向_headers中添加键值对
bool AddHeader(const std::string &header)
{
auto pos = header.find(_HeaderSep);
if (pos == std::string::npos)
{
return false;
}
std::string key = header.substr(0, pos);
std::string value = header.substr(pos + _HeaderSep.size());
_headers[key] = value;
return true;
}
bool AddHeader(const std::string &key, const std::string &value)
{
_headers[key] = value;
return true;
}
void SetData(const std::string &data)
{
_data = data;
}
const std::string &Data() const
{
return _data;
}
void SetVersion(const std::string &version)
{
_version = version;
}
bool Headers(const std::string &key, std::string value)
{
if(!_headers.count(key))
return false;
value = _headers[key];
return true;
}
protected:
static const std::string _Space; // 空格
static const std::string _LineBreak; // 换行符
static const std::string _BlankLine; // 空行
static const std::string _HeaderSep; // 报头分割符
std::string _version; // Http版本
std::unordered_map<std::string, std::string> _headers; // 请求/响应报头
std::string _data; // 请求/响应正文
};
const std::string HttpMessage::_Space = " ";
const std::string HttpMessage::_LineBreak = "\r\n";
const std::string HttpMessage::_BlankLine = "\r\n";
const std::string HttpMessage::_HeaderSep = ": ";
// 请求行: 方法 + 空格 + URI + 空格 + 版本 + 换行符
class HttpRequest : public HttpMessage
{
private:
void PraseRequestLine(const std::string &request_line)
{
std::stringstream buffer(request_line);
buffer >> _method >> _uri >> _version;
}
// 读取报头
bool GetHead(std::string &request_str)
{
static std::string end = _LineBreak + _BlankLine;
// 判断是否存在一个完整报头
if (request_str.find(end) == std::string::npos)
{
return false;
}
// 读取请求行
std::string request_line;
Util::ReadLine(request_str, _LineBreak, request_line);
PraseRequestLine(request_line);
// 读取请求报头
std::string header;
do
{
Util::ReadLine(request_str, _LineBreak, header);
if (header.size() > 0)
{
AddHeader(header);
}
} while (header.size() > 0); // 读取到空行结束
// 如果URI包含参数则拆出
auto pos = _uri.find(_URIArgsSep);
if (pos != std::string::npos)
{
_uri_args = _uri.substr(pos + _URIArgsSep.size());
_uri = _uri.substr(0, pos);
}
return true;
}
public:
HttpRequest() {}
// 序列化
// std::string Serialize() {}
// 反序列化
bool Deserialize(std::string &request_str)
{
static bool is_continue = false;
if (!is_continue)
{
if (!GetHead(request_str))
return false;
}
is_continue = true;
if (_headers.count("Content-Length"))
{
int size = std::stoi(_headers["Content-Length"]);
if (request_str.size() < size)
return false;
_data = request_str.substr(0, size);
request_str.erase(0, size);
}
is_continue = false;
return true;
}
const std::string &Uri() const { return _uri; }
const std::string &Method() const { return _method; }
~HttpRequest() {}
private:
std::string _method; // Http请求方法
std::string _uri; // URI
std::string _uri_args; // URI中的参数
static const std::string _URIArgsSep; // URI参数分割符
};
const std::string HttpRequest::_URIArgsSep = "?";
// 状态行: 版本 + 空格 + 状态码 + 空格 + 状态码描述 + 换行符
class HttpResponse : public HttpMessage
{
public:
HttpResponse()
: _status(200)
{
}
// 序列化
std::string Serialize()
{
std::string status_line = _version + _Space + std::to_string(_status) + _Space + _StatusDesc[_status] + _LineBreak;
std::string response_headers;
for (auto &header : _headers)
{
response_headers += header.first + _HeaderSep + header.second + _LineBreak;
}
std::string message = status_line + response_headers + _BlankLine + _data;
return message;
}
const std::string &GetMineType(const std::string &extension)
{
if (_MineType.count(extension) == 0)
{
LOG(LogLevel::ERROR) << "ExtensionToType: 未知的拓展名! [" << extension << "]";
return _MineType[""];
}
return _MineType[extension];
}
void SetStatus(int status)
{
_status = status;
}
// 反序列化
// bool Deserialize(const std::string request_str) {}
~HttpResponse() {}
private:
int _status; // 状态码
static std::unordered_map<int, std::string> _StatusDesc; // 状态码描述
static std::unordered_map<std::string, std::string> _MineType; // 后缀转HTTP数据类型
};
std::unordered_map<int, std::string> HttpResponse::_StatusDesc = {
{200, "OK"},
{404, "Not Found"},
{301, "Moved Permanently"},
{302, "See Other"}};
std::unordered_map<std::string, std::string> HttpResponse::_MineType = {
{"", "text/plain"},
{"txt", "text/plain"},
{"html", "text/html"},
{"htm", "text/html"},
{"xml", "text/xml"},
{"gif", "image/gif"},
{"jpg", "image/jpeg"},
{"png", "image/png"}};
class Http
{
private:
void SetContentLength(HttpResponse &response, const std::string &path)
{
size_t filesize = Util::FileSize(path);
response.AddHeader("Content-Length", std::to_string(filesize));
}
void SetContentType(HttpResponse &response, const std::string &path)
{
static std::string point = ".";
auto pos = path.rfind(point);
std::string extension;
if (pos == std::string::npos)
{
extension = "";
}
else
{
extension = path.substr(pos + point.size());
}
response.AddHeader("Content-Type", response.GetMineType(extension));
}
void GetStaticSource(const HttpRequest &request, HttpResponse &response)
{
std::string path, data;
if (request.Uri() == "/")
path = _HomePage;
else
path = _WebRoot + request.Uri();
if (!Util::ReadFile(path, data))
{
response.SetStatus(404);
LOG(LogLevel::ERROR) << "Http: 获取资源失败! [" << path << "]";
path = _404Page;
Util::ReadFile(path, data);
}
response.SetData(std::move(data));
SetContentLength(response, path);
SetContentType(response, path);
}
std::string MakeResponse(HttpRequest &request, HttpResponse &response)
{
response.SetVersion(_Version);
std::string connection;
if(request.Headers("Connection", connection) && connection == "Keep-alive")
response.AddHeader("Connection", "Keep-alive");
std::string uri = request.Uri();
if (_Redirect.count(uri)) // 重定向
{
response.SetStatus(302);
response.AddHeader("Location", _Redirect[uri]);
}
else if (_Route.count(uri)) // uri为微服务
{
_Route[uri](request, response);
}
else // 获取静态资源
{
GetStaticSource(request, response);
}
return response.Serialize();
}
public:
// TCPServer的回调函数
void RequestHandler(const std::shared_ptr<TCPConnectSocket> &con_socket)
{
std::string cli_message, buffer;
while (con_socket->Receive(buffer) > 0)
{
cli_message += buffer;
LOG(LogLevel::INFO) << "来自[" << con_socket->addr().Info() << "]的Http请求报文:\n\r" << buffer;
HttpRequest request;
if (!request.Deserialize(cli_message))
continue;
LOG(LogLevel::DEBUG) << "request 反序列化成功";
HttpResponse response;
std::string message = MakeResponse(request, response);
con_socket->Send(message);
}
}
// 注册重定向
void RegisterRedirect(const std::string &uri, const std::string &dest)
{
_Redirect[uri] = dest;
}
// 注册微服务
using microService_t = std::function<void(const HttpRequest &, HttpResponse &)>;
void RegisterMicroService(const std::string &uri, microService_t service)
{
_Route[uri] = service;
}
private:
static const std::string _Version; // Http版本
static const std::string _WebRoot; // 网页根目录
static const std::string _HomePage; // 首页
static const std::string _404Page; // 404页面
std::unordered_map<std::string, std::string> _Redirect; // 临时重定向
std::unordered_map<std::string, microService_t> _Route; // 微服务路由
};
const std::string Http::_Version = "HTTP/1.1";
const std::string Http::_WebRoot = "/home/shishen/113code/linux-c/Http协议/WebRoot";
const std::string Http::_HomePage = Http::_WebRoot + "/index.html";
const std::string Http::_404Page = Http::_WebRoot + "/404.html";
3.3 Util.hpp
cpp
#pragma once
#include <iostream>
#include <fstream>
#include "Log.hpp"
using namespace LogModule;
class Util
{
public:
static bool ReadFile(const std::string &file_path, std::string &content)
{
// 必须要按照二进制的方式读取数据,否则非文本数据无法传输
std::ifstream file(file_path, std::ios::binary);
if (!file.is_open())
{
LOG(LogLevel::ERROR) << "Util::ReadFile: 无法打开文件[" << file_path << "]! " << strerror(errno);
return false;
}
size_t file_size = FileSize(file_path);
content.resize(file_size);
// 5. 读取二进制数据到字符串
if (!file.read(&content[0], file_size))
{
LOG(LogLevel::ERROR) << "Util::ReadFile: 读取文件失败[" << file_path << "]! ";
return false;
}
file.close();
return true;
}
static bool ReadLine(std::string &message, const std::string &sep, std::string &one_line)
{
auto pos = message.find(sep);
if (pos == std::string::npos)
{
LOG(LogLevel::WARNING) << "Util::ReadLine: 未能提取出一行! ";
return false;
}
one_line = message.substr(0, pos);
message.erase(0, pos + sep.size());
return true;
}
static size_t FileSize(const std::string &path)
{
// 以二进制模式打开文件(避免文本模式下的换行符转换影响计算)
std::ifstream file(path, std::ios::in | std::ios::binary);
if (!file.is_open())
{
LOG(LogLevel::ERROR) << "无法打开文件: " << path << strerror(errno);
return -1; // 打开失败返回-1
}
// 将读指针移动到文件末尾
file.seekg(0, std::ios::end);
// 获取当前指针位置(即文件大小)
size_t size = file.tellg();
return size;
}
};
其余的hpp文件在以往的文章当中都有,就不再重复贴出了,读者也可以到仓库中找。
https://gitee.com/da-guan-mu-lao-sheng/linux-c/tree/master/Http%E5%8D%8F%E8%AE%AE/HttpServer
至于网页对象,可以让ai帮忙生成几个html对象用于调试。
4. 效果

这样一来,我们的Web服务器基本上就能完成大部分浏览器的请求了。
但是,我们目前还没有对请求方法进行判断处理,对于报头的处理也以忽略为主,总之我们的服务器还是非常简单的版本。