
🔥小叶-duck:个人主页
❄️个人专栏:《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《Linux系统从入门到实践》《Linux网络从入门到实践》
《Qt 方寸极境》 《MySQL》
✨未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游
目录
4.3 示例三:静态资源目录挂载 set_mount_point
前言
在 C++ 服务端开发中,如果想要快速搭建简易 HTTP 服务,完整从零手写一套 HTTP 协议栈成本很高,需要自行处理协议解析、连接管理、请求分发等大量底层工作。而 httplib 是一款单头文件、轻量级的 C++ HTTP 库,无需复杂编译链接,就可以快速实现 HTTP 服务端与客户端能力,非常适合用来快速开发调试接口、搭建静态网页服务。
本文将从 httplib 基础介绍、环境安装开始,依次拆解库内核心结构体与核心类,结合多个可直接运行的代码示例进行实战演示。我们会学习基础 GET 动态接口、正则路由的使用,同时掌握静态资源目录挂载,区分动态业务接口和静态文件托管的适用场景。
一、httplib 介绍
在 C++ 后端开发中,如果想快速搭建简易 HTTP 服务、开发轻量 http 接口,或者实现 HTTP 客户端发起网络请求,不需要从零手写 socket、解析 HTTP 报文。
cpp-httplib(简称 httplib)就是一款非常适合的单头文件轻量级 HTTP 库。它仅需要引入一个头文件就能使用,无需复杂链接,是 C++ 项目快速开发 HTTP 服务的常用工具。
1.1 httplib 核心特性
- 单头文件,轻量化:整个库仅单个头文件,无任何基础外部依赖,引入头文件即可使用,编译部署简单。
- 跨平台:支持 Linux、Windows、macOS,一套代码可以在多系统编译运行。
- 同时支持服务端 + 客户端:既可以快速搭建 HTTP 服务器,注册接口路由;也可以作为客户端,向外发送 HTTP 请求。
- 完整 HTTP/1.1 协议支持:支持长连接、管道化,GET、POST 等常用请求方法。
- 丰富请求类型:原生支持
multipart/form-data,可以直接实现文件上传功能。 - 支持 HTTPS/WSS:搭配 OpenSSL/mbedTLS 库,即可开启加密通信。
- API 简洁易用:路由注册、请求参数获取、响应构造接口设计直观,上手门槛低。
- 支持同步、异步两种编程模型,适配不同业务场景。
二、httplib 安装
方式 1:源码克隆安装(Ubuntu / CentOS 通用,推荐)
# 拉取源码仓库
git clone https://github.com/yhirose/cpp-httplib.git
cd cpp-httplib
# 将头文件 httplib.h 复制到系统头文件目录,全局可用
sudo cp httplib.h /usr/include/
方式 2:包管理器安装
Ubuntu 直接命令安装:
sudo apt update
sudo apt install libcpp-httplib-dev
CentOS 直接命令安装:
# CentOS8+/RockyLinux
sudo dnf install cpp-httplib-devel
说明:CentOS 的包名后缀是
-devel,和 Ubuntu 的-dev命名习惯不一样。
验证是否安装成功:
- 查看头文件
ls /usr/include/httplib.h
安装完成结果:

三、httplib 使用
2.1 头文件包含
#include <httplib.h>
cpp-httplib 是单头文件库,只需要引入这一个头文件,就可以使用全部的服务端、客户端能力。
4.2 核心接口与核心结构体解析
4.2.1 HTTP 协议基础回顾
HTTP 是基于明文字符串的应用层协议,分为请求报文和响应报文两部分,这也是 Request 和Response 两个结构体设计的底层依据。
请求报文格式:
- 请求行:请求方法,URI,协议版本 GET /index.html?s=key HTTP/1.1
- 请求头部:一组 key:value 键值对,描述请求附加信息
- 空行:用于分隔头部与请求正文
- 正文:请求携带的载荷数据(POST 提交的数据、表单、JSON 等)
响应报文格式:
- 响应行:协议版本 状态码 状态描述 HTTP/1.1 200 OK
- 响应头部:一组 key:value 键值对,描述返回资源信息
- 空行:分隔头部与响应正文
- 正文:服务器返回的有效数据,如 html、json、文件二进制内容
httplib 库内部已经完成 HTTP 报文的解析与组装,我们不用手动处理字符串拆分,直接使用封装好的Request、Response对象读写数据。
4.2.2 Request 请求结构体
Request 用来存放客户端发来的完整请求解析结果,在路由回调函数中作为入参传入。
struct Request {
std::string method; // HTTP请求方法: GET / POST / PUT / DELETE
std::string path; // 请求路径,如 /hi
Headers headers; // 请求头,键值对
std::string body; // 请求正文,POST提交的json、文本数据存在这里
Params params; // URL查询参数,即 ?key=value 部分
MultipartFormDataMap files; // 文件上传的表单数据,multipart/form-data
Ranges ranges;
Match matches; // 正则路由捕获的路径参数
// for client
ResponseHandler response_handler;
ContentReceiverWithProgress content_receiver;
};
using ResponseHandler = std::function<bool(const Response &response)>;
using ContentReceiverWithProgress =
std::function<bool(const char *data,
size_t data_length,
uint64_t offset,
uint64_t total_length)>;
method:获取请求方法,判断是 GET 还是 POST 请求。path:客户端访问的请求路径。headers:存放全部请求头,例如Content-Type、User-Agent。params:url 问号后的查询参数,例如/hi?name=zhangsan,可以直接拿到 name 的值。body:POST 请求的请求体,常用于接收 JSON 报文。files:专门处理文件上传场景,读取 multipart 表单上传的文件。matches:正则路由捕获的分组内容,例如/numbers/(\d+),捕获到的数字就存放在 matches。-
response_handler:客户端发起请求后,用于异步接收并处理服务端响应的回调函数。当服务端返回状态码、响应头和响应体时触发,常用于非阻塞的请求场景。
类型为包装器类型:std::function<bool(const Response &response)> -
content_receiver:客户端接收服务端返回数据块(如大文件下载)时的流式接收与进度回调。不仅能分块处理数据避免内存溢出,还能实时获取下载进度。
类型为包装器类型:std::function<bool(const char *data, size_t data_length, uint64_t offset, uint64_t total_length)>
4.2.3 Response 响应结构体
Response 用来构造服务器返回给客户端的报文内容,在回调函数中填充内容。
struct Response {
std::string version;
int status = -1; // HTTP状态码,200成功、404找不到资源、500服务器错误
std::string reason;
Headers headers; // 响应头键值对
std::string body; // 响应正文,返回给浏览器/客户端的数据
void set_content(const std::string &s, const std::string &content_type);
void set_header(const std::string &key, const std::string &val);
};
status:设置 HTTP 状态码,rsp.status = 200代表正常返回。set_content():封装好的接口,一次性设置响应正文和Content-Type。set_header():自定义添加响应头信息。
4.2.4 Server 服务端核心类
Server 是用来搭建 HTTP 服务的核心类,负责监听端口、维护路由表、分发请求到对应的回调函数。
class Server {
using Handler = std::function<void(const Request &, Response &)>;
bool set_mount_point(const std::string &mount_point, const std::string &dir, Headers headers = Headers());
Server &Get(const std::string &pattern, Handler handler);
Server &Post(const std::string &pattern, Handler handler);
Server &Put(const std::string &pattern, Handler handler);
Server &Delete(const std::string &pattern, Handler handler);
bool listen(const std::string &host, int port);
};
Get/Post/Put/Delete:注册路由回调。
第一个参数是匹配路径,第二个是请求处理函数。 底层会把路径 + 请求方法作为 key,存入内部哈希路由表。当客户端请求到达,Server 查找路由表,找到匹配的 handler 回调执行,传入 Request、Response 对象。set_mount_point():静态资源挂载接口。
映射本地磁盘目录到 http 访问路径,不需要手写回调。listen(host, port):启动服务器,绑定 IP 与端口,开始阻塞等待客户端连接。

4.2.5 Client 客户端核心类
Client 用于作为 HTTP 客户端,在代码里主动向外发起 HTTP 请求,调用远端 HTTP 接口。
class Client {
explicit Client(const std::string &host, int port);
Result Get(const std::string &path, const Headers &headers);
Result Get(const std::string &path, const Params ¶ms, const Headers &headers, Progress progress = nullptr);
Result Post(const std::string &path,
const Headers &headers,
const std::string &body,
const std::string &content_type) //常用
Result Post(const std::string &path, const std::string &body, const std::string &content_type);
Result Post(const std::string &path, const Params ¶ms);
Result Post(const std::string &path, const Headers &headers, const Params ¶ms);
Result Post(const std::string &path, const MultipartFormDataItems &items);
Result Client::send(const Request &req)
bool Client::send(Request &req, Response &res, Error &error)
Result Put(const std::string &path, const std::string &body, const std::string &content_type);
Result Delete(const std::string &path, const std::string &body, const std::string &content_type);
};
- 构造函数传入目标服务的主机和端口。
Get、Post等成员函数用于发起对应类型 HTTP 请求。- 返回值为
Result类型,用来判断请求是否成功,获取远端返回的 Response。
4.2.6 Result 返回结果类
Result 是 Client 发起请求后的返回包装对象,重载了布尔运算符,可以直接判断请求是否成功,也可以用 -> 直接访问 Response 成员。
class Result {
operator bool() const { return res_ != nullptr; }
const Response &value() const { return *res_; }
Response &value() { return *res_; }
const Response &operator*() const { return *res_; }
Response &operator*() { return *res_; }
const Response *operator->() const { return res_.get(); }
Response *operator->() { return res_.get(); }
};
- 支持
if(ret)判断请求是否成功。 ret->body直接拿到响应正文,简化客户端的代码书写。
四、完整示例讲解
4.1 示例 1:Get 注册动态接口
本示例演示使用 Server::Get() 注册 GET 路由回调,读取请求全部信息,构造 HTML 响应返回给浏览器。
#include <iostream>
#include <httplib.h>
// GET请求对应的回调处理函数
// req: 入参,封装客户端发来的全部HTTP请求数据
// resp: 出参,我们填充内容,httplib会把它组装成HTTP响应报文返回客户端
void Hello_world(const httplib::Request &req, httplib::Response &resp)
{
// 打印请求方法、请求路径、请求正文
std::cout << req.method << std::endl;
std::cout << req.path << std::endl;
std::cout << req.body << std::endl;
std::cout << std::endl;
// 遍历打印所有请求头(headers是键值对容器)
for (auto &it : req.headers)
{
std::cout << it.first << " = " << it.second << std::endl;
}
std::cout << std::endl;
// 遍历打印URL查询参数,即url中 ?key1=val1&key2=val2 部分
for (auto &it : req.params)
{
std::cout << it.first << " = " << it.second << std::endl;
}
// 构造返回的html页面正文
std::string html_body = "<html><body><h1>Hello World</h1></body></html>";
// 设置响应正文与Content-Type,告诉浏览器这是html文本
resp.set_content(html_body, "text/html");
// 设置HTTP响应状态码,200代表请求成功
resp.status = 200;
}
int main()
{
// 实例化http服务对象
httplib::Server server;
// 注册GET路由:访问路径为 /hello 时,执行Hello_world回调函数
server.Get("/hello", Hello_world);
// 启动服务,监听本机所有网卡的8080端口,阻塞等待连接
server.listen("0.0.0.0", 8080);
return 0;
}
代码分析:
- 回调函数参数含义
const httplib::Request &req:只读的请求对象,httplib 框架在收到 HTTP 报文后自动解析、填充。我们可以从中读取请求方法、路径、请求头、url 查询参数 params、请求体 body。
httplib::Response &resp:响应对象,由我们填充内容。函数执行结束后,httplib 内部读取 resp,组装成标准 HTTP 响应报文发回浏览器。 -
URL 查询参数 req.params 浏览器访问地址:47.98.198.24:8080/hello?ply=123456&login=abcd
路径部分:/hello,匹配我们注册的路由 /hello
? 后面的 ply=123456&login=abcd,会被库自动解析,存入 req.params(multimap 键值对),循环遍历就能取出这两组参数。这就是 GET 请求传递参数最常用的方式。
- 响应构造
resp.set_content(内容, MIME类型):一次性设置响应 body 和 Content-Type 响应头。这里 text/html 告诉浏览器返回的是 HTML,浏览器会自动渲染页面,而不是直接打印原始文本。
resp.status = 200:设置 HTTP 状态码,200 OK 表示请求处理成功。
运行结果展示:


4.2 示例 1 优化:正则路由
问题:httplib 的 Get 接口为什么原生支持正则表达式?
httplib 在设计路由模块时,内部会区分两种路由字符串:普通固定字符串路由 和 正则路由。
- 当你调用 server.Get(路由串, 回调),库内部会判断传入的路由字符串是不是正则表达式。
- 底层使用 C++11 标准库 <regex> 做正则匹配。当路由字符串是合法正则,httplib 不会做简单字符串相等比对,而是执行正则匹配;如果正则带有捕获括号(),匹配成功之后,会自动把捕获分组结果存入 req.matches 容器,在回调里直接读取。
设计动机:
- 资源路径经常有一类相同业务逻辑的请求,例如文件下载、用户信息查询(
/user/1001、/user/1002)。如果没有正则路由,每一个资源都要单独注册路由,代码极度冗余,不好维护。- 区分路径参数和查询参数。查询参数放在
?后面,而资源标识直接放在 URL 路径里,RESTful 风格接口常用这种写法。- 统一在框架层做路径解析,业务代码不用自己手写字符串切割,减少手写解析带来的 bug。
/*
使用HTTPLIB搭建HTTP服务器
1、包含头文件
2、实例化Server对象
3、注册路由信息 -- 告诉httplib收到哪个请求应该由哪个接口处理
4、启动服务器
*/
#include <iostream>
#include <httplib.h>
// GET请求对应的回调处理函数
// req: 入参,封装客户端发来的全部HTTP请求数据
// resp: 出参,我们填充内容,httplib会把它组装成HTTP响应报文返回客户端
void Hello_world(const httplib::Request &req, httplib::Response &resp)
{
// 打印请求方法、请求路径、请求正文
std::cout << req.method << std::endl;
std::cout << req.path << std::endl;
std::cout << req.body << std::endl;
std::cout << std::endl;
// 遍历打印所有请求头(headers是键值对容器)
for (auto &it : req.headers)
{
std::cout << it.first << " = " << it.second << std::endl;
}
std::cout << std::endl;
// 遍历打印URL查询参数,即url中 ?key1=val1&key2=val2 部分
for (auto &it : req.params)
{
std::cout << it.first << " = " << it.second << std::endl;
}
// 构造返回的html页面正文
std::string html_body = "<html><body><h1>Hello World</h1></body></html>";
// 设置响应正文与Content-Type,告诉浏览器这是html文本
resp.set_content(html_body, "text/html");
// 设置HTTP响应状态码,200代表请求成功
resp.status = 200;
}
// 下载接口回调函数
void DownloadHandler(const httplib::Request &req, httplib::Response &resp)
{
// 打印请求方法、请求完整路径
std::cout << req.method << std::endl;
std::cout << req.path << std::endl;
// 遍历输出正则捕获到的全部分组内容
// matches[0] 完整匹配的整条路径,matches[1] 捕获组提取的文件名
for (auto &it : req.matches)
{
std::cout << it << std::endl;
}
}
int main()
{
// 实例化http服务对象
httplib::Server server;
// 注册固定路由:精准匹配 /hello,访问路径为 /hello 时,执行Hello_world回调函数
server.Get("/hello", Hello_world);
// 注册正则路由:匹配 /download/文件名,限定文件名只能是大小写字母、数字、下划线、点、短横线
server.Get(R"(/download/([A-Za-z0-9._-]+))", DownloadHandler);
// 启动服务,监听本机所有网卡的8080端口,阻塞等待连接
server.listen("0.0.0.0", 8080);
return 0;
}
代码分析:
-
正则表达式 [A-Za-z0-9._-]+
字符集 [A-Za-z0-9._-]:仅识别大小写英文字母、数字、点.、下划线_、短横线-,不会匹配斜杠/。
量词+:要求匹配内容至少包含 1 个字符,不会匹配空字符串。
整体效果:只能匹配单层文件名,无法匹配带有子目录分隔符/的路径。 req.matches遍历打印
访问 /download/test.txt:
matches[1]:test.txt(括号捕获的文件名)
matches[0]:/download/test.txt(完整匹配的路径)
直接循环遍历req.matches,方便调试查看正则捕获结果,开发阶段用来验证正则是否符合预期。- 多条路由共存
- 固定路由 /hello 和正则路由 /download/xxx 可以同时注册在同一个 server 实例中。
- 匹配优先级:固定精确路由 > 正则路由,当请求路径同时命中多条规则时,优先走固定路由。
运行结果展示:

4.3 示例三:静态资源目录挂载 set_mount_point
#include <iostream>
#include <httplib.h>
int main()
{
httplib::Server server;
// 第一个参数:url访问前缀;第二个:本地文件夹目录
// 含义:所有以 / 开头的请求,都会去本地 ./wwwroot/ 目录寻找对应的资源
server.set_mount_point("/", "./wwwroot");
server.listen("0.0.0.0", 8080);
return 0;
}
代码分析:
- set_mount_point("/", "./wwwroot") 为整目录挂载。当访问路径以 / 开头时,httplib 会自动把请求路径拼接至本地 ./wwwroot 目录下查找资源。
- 若 ./wwwroot 路径下存在多个 html 文件,也无需为每一个 html、图片文件单独注册接口,一行代码完成整个网站静态资源的发布,子目录资源也会自动识别。
- 如果请求的文件不存在,httplib 会直接返回 404 状态码。
运行结果展示:

4.4 动态路由接口与静态资源挂载的区别
httplib 提供了两种使用方式,分别适配动态业务处理和静态资源分发场景。
- 动态路由(
svr.Get()注册回调)
浏览器或者客户端访问我们注册好的接口路径时,可以携带查询参数、请求体等报文数据。服务器收到请求后,会执行我们自定义的回调函数,在回调内读取客户端传来的数据,执行业务逻辑(查询信息、处理提交数据等),动态组装响应内容返回给客户端。 典型场景:用户提交表单、查询个人信息、传递 JSON 报文。返回的内容是代码实时计算生成,不同请求可能返回不一样结果。
典型场景:提交表单、查询个人信息、提交 JSON 报文,每次返回的内容是根据用户请求动态计算出来的。
- 静态资源挂载(
set_mount_point)
该接口将服务器本地磁盘目录映射到 HTTP 访问路径。当客户端发起请求,httplib 库内部自动去指定文件夹寻找对应的文件,直接读取文件内容返回,不需要我们手写回调函数。文件内容预先写在磁盘上,不会随请求动态变化。 典型场景:首页 index.html、前端页面、图片、js、css 这类固定不变的静态资源。
典型场景:首页 index.html、前端页面、图片资源,文件内容是固定不变的静态资源。
补充:二者可以共存于同一个 HTTP 服务。请求到来时,优先匹配手动注册的动态路由;动态路由匹配失败,才会尝试走静态文件挂载逻辑。
结束语
本篇我们完整学习了 httplib 这款轻量级 C++ HTTP 库,从库的特性、环境安装,到 Request/Response、Server 等核心类的原理剖析,再到动态 GET 路由、正则路由、静态资源挂载三个实战示例,逐步体会了 httplib 单头文件、开箱即用的优势。
动态路由适合用来处理业务逻辑、接口计算,而静态资源挂载专门用于托管 html、图片这类文件,二者各司其职,在实际项目中经常搭配使用。需要注意 httplib 更适合做小型服务、接口调试、本地网页服务;在高并发、大规模线上业务场景,还需要结合性能需求评估选型。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2501_91275995/article/details/168142459




