首页/新闻资讯/正文详情

用 C++ 写 Web 服务实战(二):路由进阶、中间件链与文件上传

发布时间:2026/9/24 10:49:45 来源:云帆数科 栏目:资讯中心
用 C++ 写 Web 服务实战(二):路由进阶、中间件链与文件上传
用 C 写 Web 服务实战二路由进阶、中间件链与文件上传上一篇我们搭起了一个能跑的 Web 应用骨架——监听端口、注册 JSON 路由、绑定静态目录、开启多事件循环、写了一个文件日志中间件。这篇在这个骨架之上继续往上盖路由参数与通配符、中间件链的深度使用、请求体流式接收与文件上传。读完这篇你的 C Web 应用就能处理真实业务了。一、路由进阶不止是app.get(/hello)上篇只用了最简单的精确匹配路由。实际业务中你需要从 URL 中提取参数、匹配前缀、区分 HTTP 方法。uvcpp_web_router提供的模式路由覆盖了这些场景匹配优先级为静态 参数 通配。1.1 路径参数/user/:idapp.get(/user/:id,[](uvcpp_web_requestreq,uvcpp_web_responseresp,uvcpp_web_next next){conststd::string*idreq.param(id);resp.text(id!nullptr?*id:std::string());resp.end();});req.param(id)返回的是const std::string*参数不存在时返回nullptr——这比返回空字符串更明确不会把“参数没传”和“参数是空串”混在一起[reference:0]。多个参数可以混搭app.get(/api/v:version/users/:uid/posts/:pid,handler);1.2 通配符/files/*path*path匹配一段或多段路径捕获的内容同样通过req.param(path)获取app.get(/files/*path,[](uvcpp_web_requestreq,uvcpp_web_responseresp,uvcpp_web_next next){conststd::string*preq.param(path);// 请求 /files/docs/readme.txt → p docs/readme.txtresp.text(你请求了: *p);resp.end();});通配符至少吃一段。/files/*path匹配/files/a但不匹配/files——如果连前缀本身也要处理需要单独注册/files的路由。serve_static内部就是靠这个机制工作的[reference:1]。1.3 HTTP 方法app.get()、app.post()、app.put()、app.del()分别注册对应方法的路由。同一个路径可以注册多个方法uvcpp_web_router会正确区分app.get(/resource,get_handler);app.post(/resource,post_handler);当请求方法不匹配已注册的路由时框架自动返回 405 并填充Allow头OPTIONS请求自动生成允许的方法列表。这些语义是框架保证的不需要你手写[reference:2]。二、中间件链Express 风格的next()模型上篇的日志中间件已经展示了.use()的基本用法。这一节深入中间件链的执行模型。2.1 中间件就是“会调 next() 的 handler”uvcpp_web_middlewareauth_middleware(){return[](uvcpp_web_requestreq,uvcpp_web_responseresp,uvcpp_web_next next){conststd::string*tokenreq.header(Authorization);if(tokennullptr||!verify_token(*token)){resp.status(401).json_str({\error\:\unauthorized\});resp.end();return;// 不调 next()链到此结束}next();// 放行进入下一个中间件或路由 handler};}中间件和路由 handler 的类型完全相同uvcpp_web_handler。区别只在行为不调next()就是这一环直接结束链不再往下走[reference:3]。2.2 执行顺序与“前后包裹”.use()的注册顺序就是执行顺序。next()调用之前的代码在“请求进入”阶段执行之后的代码在“响应完成”阶段执行——这是 Express/Koa 的经典洋葱模型app.use([]{/* ① 请求进入 */next();/* ③ 响应完成后 */});app.use([]{/* ② 请求进入 */next();/* ② 响应完成后 */});app.get(/x,handler);// ④ 最后执行上篇的日志中间件正是利用了这个特性next()之前记录时间戳之后记录状态码。2.3 异步中间件与hold()/release()如果中间件需要做异步操作查数据库、调外部 API不能直接next()后返回——框架可能在这一环还没完成时就结束了请求。uvcpp_web_context提供hold()和release()来显式声明“我还没完成”app.use([](uvcpp_web_requestreq,uvcpp_web_responseresp,uvcpp_web_next next){uvcpp_web_context*ctxreq.context();ctx-hold();// 告诉框架这一环还在进行中别往下走// 异步操作比如查数据库do_async_query(req.path(),[ctx,next](boolallowed){if(allowed){ctx-release();// 解除 holdnext();// 放行}else{// 拒绝请求ctx-release();// ...写响应...}});});hold()的本质是给上下文加一个引用计数release()减一。计数归零时框架才认为这一环完成。这在异步中间件中是必须的——不 hold 直接返回框架会在异步回调执行前就结束请求。2.4 内置中间件libuvcpp 自带了一些常用中间件app.use(web_middleware_access_log());// 访问日志app.use(web_middleware_cors());// CORS 跨域web_middleware_access_log()会写框架标准格式的访问日志。但如果你需要自定义格式比如上篇那样输出到本地文件自己写中间件更灵活。web_middleware_cors()处理跨域预检请求和Access-Control-*头前端调试时很省心。2.5 中间件挂载建议推荐的注册顺序// 1. 日志最先注册记录一切app.use(my_log_middleware());// 2. CORS在鉴权之前预检请求不应被鉴权拦截app.use(web_middleware_cors());// 3. 鉴权app.use(auth_middleware());// 4. 业务路由app.get(/api/...,handler);// 5. 静态文件最后注册避免拦截 API 路由app.serve_static(/assets,./public);三、请求体流式接收默认情况下框架会把整个请求体缓冲在内存中handler 触发时req.body()已经完整可用。这对于小体积 JSON 或表单请求完全够用。但大文件上传或流式数据不能走这条路——一个 1 GB 的上传会把整个请求体拉进内存。uvcpp_web_stream提供了流式接收能力通过on_data/on_end回调逐块处理请求体app.post(/upload/stream,[](uvcpp_web_requestreq,uvcpp_web_responseresp,uvcpp_web_next next){auto*streamreq.stream();stream-on_data([](constchar*data,size_t len){// 逐块到达的数据可以直接写入磁盘或做增量处理// 注意data 只在回调期间有效需要持久化就拷贝或立刻写入});stream-on_end([](){// 请求体全部接收完毕});// 流式模式下handler 返回后框架不会自动结束请求// 需要在 on_end 中调用 resp.end()});stream-pause()和stream-resume()可以在消费端忙不过来时暂停接收形成背压。这在写入磁盘较慢的场景下很有用——暂停接收比在内存中无限堆积要安全得多。四、文件上传multipart 流式落盘uvcpp_web_upload/uvcpp_web_multipart构建在流式接收之上专门处理multipart/form-data请求。4.1 基本用法app.post(/upload,[](uvcpp_web_requestreq,uvcpp_web_responseresp,uvcpp_web_next next){auto*uploadreq.upload();// 设置落盘目录和大小上限upload-set_upload_dir(./uploads);upload-set_max_file_size(50*1024*1024);// 单文件 50 MBupload-set_max_total_size(200*1024*1024);// 总大小 200 MBupload-on_file([](constuvcpp_web_uploaded_filefile){// 每个文件落盘完成后触发// file.field_name —— 表单字段名// file.original_name —— 原始文件名// file.saved_path —— 实际保存路径随机叶子名// file.size —— 文件大小});upload-on_end([resp](){resp.json_str({\status\:\ok\});resp.end();});upload-on_error([](interr){// 上传过程中出错超限、磁盘写入失败等});});4.2 大小限制与安全策略uvcpp_web_upload内置了六条大小上限分别覆盖限制项作用单文件大小防止单个超大文件打满磁盘总请求大小防止大量小文件组合成 DoS文件数量防止 multipart 边界攻击字段名长度防止超长字段名消耗内存文件名长度同上part 头部大小防止畸形 multipart 头落盘策略是随机叶子名 流式写入文件名在服务端生成随机名不使用客户端提供的原始文件名作为磁盘路径——这直接封死了路径穿越攻击../../etc/passwd这类。原始文件名只作为元数据保留在original_name中。4.3 与流式接收的区别场景用uvcpp_web_stream用uvcpp_web_uploadraw body 流式处理✅❌multipart 表单❌✅需要解析字段名/文件名❌✅自己控制落盘逻辑✅❌框架落盘如果上传的是非 multipart 格式的原始数据比如直接 PUT 一个文件用uvcpp_web_stream自己处理如果是标准 HTML 表单上传用uvcpp_web_upload更省事。五、请求对象操作速查uvcpp_web_request封装了日常业务最常用的访问方式// 方法 路径req.method()// http_method 枚举req.method_str()// GET / POST ...req.path()// /user/123req.query()// 查询串不含 ?// 路径参数req.param(id)// const std::string*// 查询串参数req.query_param(page)// const std::string*// 请求头req.header(Content-Type)// const std::string*// Cookiereq.cookie(session_id)// const std::string*// 表单字段application/x-www-form-urlencodedreq.form(username)// const std::string*// 请求体req.body()// uvcpp_buf完整请求体响应侧resp.status(200);resp.set_header(X-Custom,value);resp.json_str({\ok\:true});resp.text(plain text);resp.html(h1Hello/h1);resp.redirect(/new-location);resp.send_file(./path/to/file.pdf);resp.end();六、完整示例一个带鉴权、上传和路由参数的 API 服务#includewebapp/uvcpp_web_app.h#includefstream#includemutex#includectime#includeiostreamusingnamespaceuvcpp;staticstd::ofstream g_log;staticstd::mutex g_log_mutex;staticstd::stringnow_str(){std::time_t tstd::time(nullptr);charbuf[64];std::strftime(buf,sizeof(buf),%Y-%m-%d %H:%M:%S,std::localtime(t));returnbuf;}// 简单鉴权中间件uvcpp_web_middlewareauth_middleware(){return[](uvcpp_web_requestreq,uvcpp_web_responseresp,uvcpp_web_next next){conststd::string*tokenreq.header(Authorization);if(tokennullptr||token-empty()){resp.status(401).json_str({\error\:\missing token\});resp.end();return;}next();};}intmain(){g_log.open(access.log,std::ios::app);uvcpp_web_app app;app.set_host(0.0.0.0).set_port(8080);constintrcapp.set_loops(4);if(rc!0){std::cerrset_loops: rcstd::endl;return1;}// ---- 中间件链 ----app.use([](uvcpp_web_requestreq,uvcpp_web_responseresp,uvcpp_web_next next){conststd::string tsnow_str();next();std::lock_guardstd::mutexlock(g_log_mutex);if(g_log.is_open()){g_log[ts] req.method_str() req.path() - static_castint(resp.status_code())\n;g_log.flush();}});app.use(web_middleware_cors());// ---- 公开路由 ----app.get(/hello,[](uvcpp_web_request,uvcpp_web_responseresp,uvcpp_web_next){resp.json_str({\hello\:\world\});resp.end();});// ---- 路径参数 ----app.get(/user/:id,[](uvcpp_web_requestreq,uvcpp_web_responseresp,uvcpp_web_next){conststd::string*idreq.param(id);resp.json_str({\user_id\:\(id?*id:)\});resp.end();});// ---- 通配符 ----app.get(/files/*path,[](uvcpp_web_requestreq,uvcpp_web_responseresp,uvcpp_web_next){conststd::string*preq.param(path);resp.text(requested: (p?*p:std::string()));resp.end();});// ---- 鉴权中间件只作用于 /api 前缀 ----// 实际项目中可以用路径前缀路由组这里简化处理app.use(auth_middleware());// ---- 文件上传 ----app.post(/api/upload,[](uvcpp_web_requestreq,uvcpp_web_responseresp,uvcpp_web_next){auto*uploadreq.upload();upload-set_upload_dir(./uploads);upload-set_max_file_size(50*1024*1024);upload-set_max_total_size(200*1024*1024);upload-on_file([](constuvcpp_web_uploaded_filef){std::cout收到文件: f.original_name (f.size bytes) - f.saved_pathstd::endl;});upload-on_end([resp](){resp.json_str({\status\:\uploaded\});resp.end();});});// ---- 异步 handler ----app.get(/api/slow,[app](uvcpp_web_request,uvcpp_web_responseresp,uvcpp_web_next next){uvcpp_web_response*rpresp;auto*wnewuvcpp_work();w-init();w-queue_work(app.loop(),[](uvcpp_work*){std::this_thread::sleep_for(std::chrono::milliseconds(20));},[next,rp](uvcpp_work*done,int){rp-json_str({\slow\:true});rp-end();next();deletedone;});});// ---- 静态资源 ----app.serve_static(/assets,./public,true);app.start();std::coutServer on :8080, 4 loopsstd::endl;app.join();return0;}几个关键点app.use(auth_middleware())放在公开路由之后这样/hello、/user/:id、/files/*path不需要鉴权而后续注册的/api/upload和/api/slow会经过鉴权。中间件的“路径前缀”作用域通过注册顺序来控制——先注册的中间件作用于所有后续路由。异步 handler 中next按值捕获。框架看到next被捕获到异步回调中就知道这一环会异步恢复不会提前结束请求。如果只是同步处理next可以直接调用[reference:4]。resp.end()是“我写完了”的标记不是发送。框架在 handler 返回后统一收尾时才真正把响应写出去。七、本篇小结与下一步这篇覆盖了从“Hello World”到真实业务的关键跨越路由进阶——路径参数:id、通配符*path、HTTP 方法区分匹配优先级静态 参数 通配。中间件链——next()洋葱模型异步中间件用hold()/release()声明生命周期web_middleware_cors()开箱即用。流式请求体——uvcpp_web_stream的on_data/on_end配合pause()/resume()实现背压。文件上传——uvcpp_web_upload的 multipart 流式落盘、随机叶子名、六条大小上限。请求对象——param()/query_param()/header()/cookie()/form()的统一访问入口。下一篇会进入WebSocket 实时通信uvcpp_web_ws端点的创建、连接生命周期管理、广播与房间模型以及与 HTTP 路由共存在同一个应用中的实践。如果你在实践过程中遇到问题欢迎在 GitHub Issues 提出。

相关推荐

nvm 速查表:Node.js 多版本安装、切换与镜像配置实战指南
nvm 速查表:Node.js 多版本安装、切换与镜像配置实战指南

文档知识库教程开发工具 【免费下载链接】reference 为开发人员分享快速参考备忘清单(速查表) 项目地址: https://gitcode.com/jaywcjlove/reference 点击查看 免费下载 本文基于开源仓库 reference 的 nvm 备忘清单 整理而成,聚焦 Node Version Manage… · 2026/9/24 10:49:45

Work Agent深度解读:AI如何完成长程复杂任务
Work Agent深度解读:AI如何完成长程复杂任务

AI交互形态正在经历一轮底层转变,从单次问答的对话窗口,逐步进化为可以自主推进多步骤工作的智能执行主体。早期大模型产品的核心交互形态是单轮问答,用户提出问题,模型即时返回一段文本结果;随着工具调用能力成熟&… · 2026/9/24 10:49:39

Mockery 教程:在 Laravel 项目中模拟 Demeter 链与流式接口(Demeter Chains Mocking)
Mockery 教程:在 Laravel 项目中模拟 Demeter 链与流式接口(Demeter Chains Mocking)

示例工程数据库教程后端 【免费下载链接】sql-server-samples Azure Data SQL Samples - Official Microsoft GitHub Repository containing code samples for SQL Server, Azure SQL, Azure Synapse, and Azure SQL Edge 项目地址: https://gitcode.com/gh_mirrors… · 2026/9/24 10:49:33

紫三角CAD翻译 市政工程图纸专业译制方案
紫三角CAD翻译 市政工程图纸专业译制方案

引言市政工程出海项目中,图纸翻译已成为跨境协作的核心效率瓶颈。传统人工译制周期长、成本高,PDF图像级翻译无法编辑,专业术语错译率高,多专业图纸切换工具的混乱进一步加剧协作内耗。本文从行业痛点诊断、紫三角CAD译制方案的技… · 2026/9/24 11:18:42

【大数据毕设精品】基于大数据的职业人群体检数据分析与可视化,附源码_数据可视化_数据分析_数据挖掘_Hadoop_spark_文档指导_毕设指导
【大数据毕设精品】基于大数据的职业人群体检数据分析与可视化,附源码_数据可视化_数据分析_数据挖掘_Hadoop_spark_文档指导_毕设指导

💖💖作者:计算机毕业设计杰瑞 💙💙个人简介:曾长期从事计算机专业培训教学,本人也热爱上课教学,语言擅长Java、微信小程序、Python、Golang、安卓Android等,开发项目包括… · 2026/9/24 11:18:35

SG3525推挽谐振软开关设计实战:高效率中功率电源的工程落地路径
SG3525推挽谐振软开关设计实战:高效率中功率电源的工程落地路径

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 11:18:35

RFID标签“假写入“故障排查:90%项目都踩过的5个隐蔽坑
RFID标签“假写入“故障排查:90%项目都踩过的5个隐蔽坑

某汽车零部件厂的产线 RFID 项目,上线第二天开始批量漏读。 操作员把 RFID 标签从打印机里拿出来的瞬间,设备日志显示"写入成功"。肉眼检查没有任何异常。但标签贴到金属工装上、走过读写工位时,三成左右的数据读不出来——读不到的… · 2026/9/24 11:18:29

FX5U与汇川伺服Modbus-RTU通讯实战指南
FX5U与汇川伺服Modbus-RTU通讯实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 11:18:29

无限回调微信登录接口API源码免公众号微信登录号主出租公众号收益+站长付费调用
无限回调微信登录接口API源码免公众号微信登录号主出租公众号收益+站长付费调用

一、这个项目到底是干什么的 一句话概括:这是一个"微信登录能力"的撮合平台。 有些网站、H5页面想在微信里做"用微信一键登录",但微信官方有个硬规矩:要做网页授权登录,必须自己有一个"已认证的服务号&a… · 2026/9/24 11:18:22

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13

1D-CNN时间序列建模实战:从Conv1d原理到工业落地
1D-CNN时间序列建模实战:从Conv1d原理到工业落地

简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26

柔软的L:汉语语流中被忽视的舌肌张力控制
柔软的L:汉语语流中被忽视的舌肌张力控制

1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码