本期我们就来进行最后一步
相关代码上传至作者gitee:仿muduo服务器: 本项目致力于实现一个仿造muduo库的简易并发服务器,为个人项目,参考即可
目录
HTTP服务器
设计思路
设计一张请求路由表:
表中记录针对哪个请求,应该使用哪个函数来进行业务处理的映射关系
当服务器收到了一个请求时,就在请求路由表中,查找有没有对应请求的处理函数,如果有,则执行对应的处理函数即可。
什么请求,怎么处理,由用户来设定,服务器收到了请求只需要执行函数即可。这样做的好处是:用户只需要实现业务处理函数,然后将请求与处理函数的映射关系,添加到服务器中
而服务器只需要接收数据,解析数据,查找路由表映射关系,执行业务处理函数即可。

要素:
-
GET请求的路由映射表
-
POST请求的路由映射表
-
PUT请求的路由映射表
-
DELETE请求的路由映射表 --- 路由映射表记录对应请求方法的请求的处理函数映射关系 --- 更多是功能性请求的处理
-
静态资源相对根目录 --- 实现静态资源请求的处理
-
高性能TCP服务器 --- 进行连接的IO操作
接口 :
服务器处理流程:
-
从socket接收数据,放到接收缓冲区
-
调用OnMessage回调函数进行业务处理
-
对请求进行解析,得到了一个HttpRequest结构,包含了所有的请求要素
-
进行请求的路由查找 -- 找到对应请求的处理方法
-
静态资源请求 --- 一些实体文件资源的请求,html,image......
将静态资源文件的数据读取出来,填充到HttpResponse结构中
- 功能性请求 --- 在请求路由映射表中查找处理函数,找到了则执行函数
具体的业务处理,并进行HttpResponse结构的数据填充
- 对静态资源请求/功能性请求进行处理完毕后,得到了一个填充了响应信息的HttpResponse对象,组织http格式响应,进行发送
源码
HttpServer.hpp
cpp
#pragma once
#include"TcpServer.hpp"
#include"HttpRequest.hpp"
#include"HttpResponse.hpp"
#include"HttpContext.hpp"
#include"LoopThreadPool.hpp"
namespace ImMuduo
{
class HttpServer
{
using Handler=std::function<void(const HttpRequest&,HttpResponse*)>;
public:
HttpServer(int port = 8080);
~HttpServer()=default;
//添加路由
void Get(const std::string& pattern,Handler handler);
void Post(const std::string& pattern,Handler handler);
void Put(const std::string& pattern,Handler handler);
void Delete(const std::string& pattern,Handler handler);
//设置静态资源根目录
void SetRootDir(const std::string& root_dir);
//设置线程数
void SetThreadCount(int thread_count);
//开启空闲连接释放
void EnableInactiveRelease();
//监听端口------服务器启动的接口
void Listen();
private:
//将HttpResponse中的要素按照http协议格式进行组织,发送
void WriteResponse(const HttpRequest& request,HttpResponse* response);
//判断是否为静态资源请求
bool IsFileHandler(const HttpRequest& request);
//静态资源的请求处理
void FileHandler(const HttpRequest& request,HttpResponse* response);
//功能性请求的分类处理
void Dispatcher(const HttpRequest& request,HttpResponse* response);
//路由处理
void Route(const HttpRequest& request,HttpResponse* response);
//设置上下文
void OnConnected(const ConnectionPtr& conn);
//缓冲区数据解析+处理
void OnMessage(const ConnectionPtr& conn,Buffer* buf);
//错误响应
void ErrorHandler(const HttpRequest& request,HttpResponse* response);
private:
TcpServer TcpServer_;
std::unordered_map<std::string, Handler> get_route_;//GET请求路由
std::unordered_map<std::string, Handler> post_route_;//POST请求路由
std::unordered_map<std::string, Handler> put_route_;//PUT请求路由
std::unordered_map<std::string, Handler> delete_route_;//DELETE请求路由
std::string root_dir_;//静态资源根目录
};
}
HttpServer.cpp
cpp
#include "HttpServer.hpp"
#include "Log.hpp"
#include "Util.hpp"
#include <sstream>
namespace ImMuduo
{
HttpServer::HttpServer(int port)
: TcpServer_(port)
{
TcpServer_.SetConnectedCallback(
[this](const ConnectionPtr& conn) { OnConnected(conn); });
TcpServer_.SetMessageCallback(
[this](const ConnectionPtr& conn, Buffer* buf) {
OnMessage(conn, buf);
});
}
// ========== 路由注册 ==========
void HttpServer::Get(const std::string& pattern, Handler handler)
{ get_route_[pattern] = std::move(handler); }
void HttpServer::Post(const std::string& pattern, Handler handler)
{ post_route_[pattern] = std::move(handler); }
void HttpServer::Put(const std::string& pattern, Handler handler)
{ put_route_[pattern] = std::move(handler); }
void HttpServer::Delete(const std::string& pattern, Handler handler)
{ delete_route_[pattern] = std::move(handler); }
// ========== 服务器设置 ==========
void HttpServer::SetRootDir(const std::string& root_dir)
{ root_dir_ = root_dir; }
void HttpServer::SetThreadCount(int thread_count)
{ TcpServer_.SetLoopThreadCount(thread_count); }
void HttpServer::EnableInactiveRelease()
{ TcpServer_.EnableInactiveRelease(10); }
void HttpServer::Listen()
{ TcpServer_.start(); }
// ========== 回调 ==========
void HttpServer::OnConnected(const ConnectionPtr& conn)
{
conn->SetContext(HttpContext());
DEBUG("New connection: %p", conn.get());
}
// Response conn->Send
static void SendResponse(const ConnectionPtr& conn,
const HttpRequest& req, HttpResponse* rsp)
{
// 1. 头部
if (!rsp->HasHeader("Connection"))
rsp->SetHeader("Connection",
req.IsShortLinkConnection() ? "close" : "keep-alive");
if (!rsp->HasHeader("Server"))
rsp->SetHeader("Server", "MuduoServer/1.0");
// 2. HTTP
std::ostringstream oss;
oss << req.version_ << " " << rsp->GetStatus() << " "
<< Util::StatDesc(rsp->GetStatus()) << "\r\n";
for (const auto& [k, v] : rsp->GetHeaders())
oss << k << ": " << v << "\r\n";
oss << "\r\n";
oss << rsp->GetBody();
// 3.
std::string data = oss.str();
conn->Send(data.data(), data.size());
}
void HttpServer::OnMessage(const ConnectionPtr& conn, Buffer* buf)
{
while (buf->ReadableSize() > 0)
{
std::any anyCtx_ = conn->GetContext();
auto* ctx = std::any_cast<HttpContext>(&anyCtx_);
if (ctx == nullptr) return;
ctx->RecvHttpRequest(buf);
if (ctx->RespStatu() >= 400)
{
HttpResponse rsp;
ErrorHandler(ctx->Request(), &rsp);
SendResponse(conn, ctx->Request(), &rsp);
conn->ShutDown();
return;
}
if (ctx->RecvStatu() != HttpRecvStatus::RECV_HTTP_OVER)
return;
HttpResponse rsp;
Route(ctx->Request(), &rsp);
SendResponse(conn, ctx->Request(), &rsp);
ctx->ReSet();
if (rsp.IsShortLinkConnection())
{
conn->ShutDown();
return;
}
}
}
// ========== 路由 ==========
void HttpServer::Route(const HttpRequest& request, HttpResponse* response)
{
if (IsFileHandler(request))
FileHandler(request, response);
else
Dispatcher(request, response);
}
void HttpServer::Dispatcher(const HttpRequest& request,
HttpResponse* response)
{
const std::unordered_map<std::string, Handler>* routes = nullptr;
if (request.method_ == "GET") routes = &get_route_;
else if (request.method_ == "POST") routes = &post_route_;
else if (request.method_ == "PUT") routes = &put_route_;
else if (request.method_ == "DELETE") routes = &delete_route_;
if (routes) {
auto it = routes->find(request.path_);
if (it != routes->end()) {
it->second(request, response);
return;
}
}
ErrorHandler(request, response);
}
// ========== 静态资源 ==========
bool HttpServer::IsFileHandler(const HttpRequest& request)
{
if (request.method_ != "GET") return false;
if (root_dir_.empty()) return false;
if (request.path_.empty() || request.path_[0] != '/') return false;
if (request.path_.find("..") != std::string::npos) return false;
return true;
}
void HttpServer::FileHandler(const HttpRequest& request,
HttpResponse* response)
{
std::string path = root_dir_ + request.path_;
if (path.back() == '/') path += "index.html";
if (!Util::ValidPath(path) || !Util::IsRegular(path))
{ ErrorHandler(request, response); return; }
std::string content;
if (!Util::ReadFile(path, &content))
{ ErrorHandler(request, response); return; }
response->SetContent(content, Util::ExtMime(path));
}
// ========== 错误 ==========
void HttpServer::ErrorHandler(const HttpRequest& request,
HttpResponse* response)
{
response->Reset();
response->SetStatus(404);
std::string desc = Util::StatDesc(404);
std::string body = "<html><head><title>" + desc + "</title></head>"
"<body><h1>404 " + desc + "</h1><p>" +
request.path_ + " not found.</p></body></html>";
response->SetContent(body, "text/html");
}
// WriteResponse ( )
void HttpServer::WriteResponse(const HttpRequest& request,
HttpResponse* response)
{
if (!response->HasHeader("Connection"))
response->SetHeader("Connection",
request.IsShortLinkConnection() ? "close" : "keep-alive");
if (!response->HasHeader("Server"))
response->SetHeader("Server", "MuduoServer/1.0");
}
}
测试源码
Http测试
cpp
#include "HttpRequest.hpp"
#include "HttpResponse.hpp"
#include "HttpContext.hpp"
#include "Util.hpp"
#include "Log.hpp"
#include "Socket.hpp"
#include <thread>
#include <chrono>
#include <cassert>
#include <cstring>
#include <atomic>
#include <fstream>
#include <sstream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
using namespace ImMuduo;
static std::string DoReq(const std::string& host, int port, const std::string& req)
{
Socket sock;
if (!sock.Create()) return "";
if (!sock.Connect(host, static_cast<uint16_t>(port))) return "";
sock.Send(req.data(), req.size());
char buf[65536] = {};
std::string resp;
for (int i = 0; i < 20; ++i) {
ssize_t n = sock.Recv(buf, sizeof(buf) - 1, 0);
if (n > 0) { buf[n] = '\0'; resp += buf; }
else break;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
sock.Close();
return resp;
}
// ==================== 1: Keep-Alive ====================
void Test_KeepAlive()
{
INFO("=== [1] Keep-Alive Test ===");
static const int P = 20001;
std::thread srv([](int port) {
Socket ls;
ls.CreateServer(static_cast<uint16_t>(port), "127.0.0.1");
for (int c = 0; c < 2; ++c) {
int fd = ::accept(ls.fd(), nullptr, nullptr);
if (fd < 0) { c--; continue; }
Socket cli(fd);
char buf[4096] = {};
ssize_t n = cli.Recv(buf, sizeof(buf) - 1, 0);
if (n > 0) {
buf[n] = '\0';
INFO(" Svr got: %s",
std::string(buf).find("keep-alive") != std::string::npos
? "keep-alive" : "close");
}
cli.Send("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK", 42);
cli.Close();
}
ls.Close();
}, P);
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::string req =
"GET / HTTP/1.1\r\nHost: 127.0.0.1\r\nConnection: keep-alive\r\n\r\n";
std::string r1 = DoReq("127.0.0.1", P, req);
std::string r2 = DoReq("127.0.0.1", P, req);
INFO(" Req1: %s Req2: %s",
r1.find("200") != std::string::npos ? "200 OK" : "FAIL",
r2.find("200") != std::string::npos ? "200 OK" : "FAIL");
assert(!r1.empty() && !r2.empty());
srv.join();
INFO("=== [1] Complete ===");
}
// ==================== 2: Timeout (skip) ====================
void Test_Timeout()
{
INFO("=== [2] Timeout (SKIP - needs 12s) ===");
}
// ==================== 3: Error Request ====================
void Test_ErrorRequest()
{
INFO("=== [3] Error Request Test ===");
HttpContext ctx;
Buffer buf;
buf.WriteAndPush("GARBAGE\r\n\r\n", 11);
ctx.RecvHttpRequest(&buf);
INFO(" Garbage: status=%d", ctx.RespStatu());
assert(ctx.RespStatu() >= 400);
ctx.ReSet();
Buffer buf2;
buf2.WriteAndPush("OPTIONS / HTTP/1.1\r\nHost: x\r\n\r\n", 33);
ctx.RecvHttpRequest(&buf2);
INFO(" OPTIONS method: status=%d", ctx.RespStatu());
assert(ctx.RespStatu() >= 400);
ctx.ReSet();
Buffer buf3;
buf3.WriteAndPush("GET /../etc/passwd HTTP/1.1\r\nHost: x\r\n\r\n", 42);
ctx.RecvHttpRequest(&buf3);
INFO(" Path traversal: method=%s path=%s",
ctx.Request().method_.c_str(), ctx.Request().path_.c_str());
std::string desc = Util::StatDesc(404);
assert(desc == "Not Found");
INFO(" 404 desc: %s", desc.c_str());
INFO("=== [3] Complete ===");
}
// ==================== 4: Business Timeout ====================
void Test_BusinessTimeout()
{
INFO("=== [4] Business Timeout Test ===");
std::atomic<bool> done(false);
std::thread worker([&]() {
INFO(" Slow task: sleeping 2s...");
std::this_thread::sleep_for(std::chrono::seconds(2));
done = true;
INFO(" Slow task: done");
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
assert(!done);
worker.join();
assert(done);
INFO("=== [4] Complete ===");
}
// ==================== 5: Concurrent Parse ====================
void Test_Concurrent()
{
INFO("=== [5] Concurrent Parse Test ===");
const int N = 10;
std::atomic<int> ok(0);
std::vector<std::thread> ths;
for (int i = 0; i < N; ++i)
ths.emplace_back([&, i]() {
HttpContext ctx;
Buffer buf;
std::string raw =
"GET /t" + std::to_string(i) + " HTTP/1.1\r\nHost: x\r\n\r\n";
buf.WriteAndPush(raw.data(), raw.size());
ctx.RecvHttpRequest(&buf);
if (ctx.RecvStatu() == HttpRecvStatus::RECV_HTTP_OVER) ok++;
});
for (auto& t : ths) t.join();
INFO(" %d/%d parsed OK", ok.load(), N);
assert(ok == N);
INFO("=== [5] Complete ===");
}
// ==================== 6: Large File ====================
void Test_LargeFile()
{
INFO("=== [6] Large File Test ===");
const std::string path = "/tmp/muduo_http_big.html";
{
std::ofstream ofs(path);
ofs << "<html><body>\n";
for (int i = 0; i < 10000; ++i)
ofs << "<p>Line " << i << ": Big file test for Muduo.</p>\n";
ofs << "</body></html>\n";
}
std::string content;
bool ok = Util::ReadFile(path, &content);
INFO(" File: %zu bytes, OK=%s", content.size(), ok ? "YES" : "NO");
assert(ok && content.find("Line 9999") != std::string::npos);
std::string mime = Util::ExtMime(path);
INFO(" MIME: %s", mime.c_str());
assert(mime == "text/html");
std::remove(path.c_str());
INFO("=== [6] Complete ===");
}
// ==================== 7: Stress ====================
void Test_Stress()
{
INFO("=== [7] Stress Parse Test ===");
const int N = 100;
std::atomic<int> ok(0);
std::vector<std::thread> ths;
for (int i = 0; i < N; ++i)
ths.emplace_back([&, i]() {
HttpContext ctx;
Buffer buf;
std::ostringstream oss;
oss << "GET /ping" << i << " HTTP/1.1\r\n"
<< "Host: 127.0.0.1\r\n"
<< "X-Request-ID: " << i << "\r\n"
<< "\r\n";
std::string raw = oss.str();
buf.WriteAndPush(raw.data(), raw.size());
ctx.RecvHttpRequest(&buf);
if (ctx.RecvStatu() == HttpRecvStatus::RECV_HTTP_OVER &&
ctx.Request().method_ == "GET")
ok++;
});
for (auto& t : ths) t.join();
double rate = ok * 100.0 / N;
INFO(" %d/%d OK, rate=%.1f%%", ok.load(), N, rate);
assert(rate >= 95.0);
INFO("=== [7] Complete ===");
}
int main()
{
INFO("========================================");
INFO(" Muduo HTTP Integration Tests");
INFO("========================================");
Test_KeepAlive(); std::this_thread::sleep_for(std::chrono::milliseconds(50));
Test_Timeout();
Test_ErrorRequest(); std::this_thread::sleep_for(std::chrono::milliseconds(50));
Test_BusinessTimeout(); std::this_thread::sleep_for(std::chrono::milliseconds(50));
Test_Concurrent(); std::this_thread::sleep_for(std::chrono::milliseconds(50));
Test_LargeFile(); std::this_thread::sleep_for(std::chrono::milliseconds(50));
Test_Stress(); std::this_thread::sleep_for(std::chrono::milliseconds(50));
INFO("========================================");
INFO(" All HTTP Tests Completed");
INFO("========================================");
return 0;
}
压力测试
cpp
#include "HttpRequest.hpp"
#include "HttpResponse.hpp"
#include "HttpContext.hpp"
#include "Util.hpp"
#include "Log.hpp"
#include <thread>
#include <chrono>
#include <cassert>
#include <cstring>
#include <atomic>
#include <vector>
#include <sstream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
using namespace ImMuduo;
// 发起 HTTP 请求,返回状态码
static int HttpGet(int port, const std::string& path)
{
int fd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) return -1;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(static_cast<uint16_t>(port));
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
if (::connect(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
::close(fd); return -1;
}
std::ostringstream req;
req << "GET " << path << " HTTP/1.1\r\n"
<< "Host: 127.0.0.1\r\nConnection: close\r\n\r\n";
std::string rs = req.str();
::send(fd, rs.data(), rs.size(), 0);
char buf[8192] = {};
std::string resp;
for (int i = 0; i < 10; ++i) {
ssize_t n = ::recv(fd, buf, sizeof(buf) - 1, 0);
if (n > 0) { buf[n] = '\0'; resp += buf; }
else break;
std::this_thread::sleep_for(std::chrono::milliseconds(20));
}
::close(fd);
auto pos = resp.find(' ');
if (pos == std::string::npos) return -1;
return std::stoi(resp.substr(pos + 1, 3));
}
int main()
{
INFO("============================================");
INFO(" HTTP Stress Test");
INFO("============================================");
const int kTotal = 500;
const int kClients = 10;
const int kPort = 20088;
INFO(" %d requests, %d concurrent, port %d", kTotal, kClients, kPort);
//
std::atomic<bool> srvRunning(true);
std::atomic<int> handled(0);
std::thread srv([&]() {
int ls = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
int opt = 1;
setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(static_cast<uint16_t>(kPort));
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
bind(ls, (struct sockaddr*)&addr, sizeof(addr));
listen(ls, 1024);
while (srvRunning) {
struct timeval tv = {1, 0}; // 1
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(ls, &rfds);
if (select(ls + 1, &rfds, nullptr, nullptr, &tv) <= 0) continue;
int fd = ::accept(ls, nullptr, nullptr);
if (fd < 0) continue;
handled++;
std::thread([fd]() {
char buf[4096] = {};
ssize_t n = ::recv(fd, buf, sizeof(buf) - 1, 0);
if (n > 0) {
const char* rsp =
"HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\npong";
::send(fd, rsp, strlen(rsp), 0);
}
::close(fd);
}).detach();
}
::close(ls);
});
std::this_thread::sleep_for(std::chrono::milliseconds(300));
// ====== ======
INFO(" Running...");
auto t1 = std::chrono::steady_clock::now();
std::atomic<int> ok(0), fail(0);
std::atomic<long> sumUs(0);
std::vector<std::thread> clients;
for (int c = 0; c < kClients; ++c) {
clients.emplace_back([&]() {
for (int i = 0; i < kTotal / kClients; ++i) {
auto a = std::chrono::steady_clock::now();
int code = HttpGet(kPort, "/ping");
auto b = std::chrono::steady_clock::now();
sumUs += std::chrono::duration_cast<std::chrono::microseconds>(b - a).count();
if (code == 200) ok++; else fail++;
}
});
}
for (auto& cl : clients) cl.join();
auto t2 = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
srvRunning = false;
srv.join();
// ====== ======
int total = ok + fail;
double rate = ok * 100.0 / (total > 0 ? total : 1);
double qps = total * 1000.0 / (ms > 0 ? ms : 1);
double avgUs = total > 0 ? sumUs * 1.0 / total : 0;
INFO("============================================");
INFO(" Results");
INFO("============================================");
INFO(" Total: %d", total);
INFO(" Success: %d (%.1f%%)", ok.load(), rate);
INFO(" Failure: %d", fail.load());
INFO(" Time: %ld ms", ms);
INFO(" Throughput: %.0f req/s", qps);
INFO(" Avg Latency: %.0f us (%.2f ms)", avgUs, avgUs / 1000.0);
INFO(" Server handled: %d", handled.load());
if (rate >= 99.0)
INFO(" [PASS] rate >= 99%%");
else if (rate >= 90.0)
INFO(" [WARN] rate %.1f%%", rate);
else
INFO(" [FAIL] rate %.1f%% < 90%%", rate);
INFO("============================================");
return 0;
}
封面图如下:
