ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于mongoose + MFC 搭建本地HTTP服务器:TaoToken 统一 Key 接入与 curl 联调配置

基于mongoose + MFC 搭建本地HTTP服务器:TaoToken 统一 Key 接入与 curl 联调配置 1. 为什么要在 MFC 里塞一个 HTTP 服务器如果你正在做 Windows 桌面端的小工具比如摄像头预览、身份证读卡器、扫码枪这类硬件集成应用大概率会遇到一个很别扭的需求前端页面H5、管理后台、甚至另一个本地程序要通过 HTTP 请求来调用你桌面程序里的能力。桌面程序本身是 GUI是 MFC 对话框它不是一个服务但业务又要求它对外暴露接口。我试过直接用 socket 手写 HTTP 解析拼接请求行、请求头、Content-Length、chunked 编码坑一个接一个协议细节稍微记错一点浏览器那边就是一直转圈或者 400。后来换成 mongoose一个 C 写的嵌入式 HTTP 库体量小、单文件、事件回调模型清晰配合 MFC 的对话框线程几百行就能把一个能用的本地 HTTP 服务跑起来。这篇文章要解决的核心链路是MFC 对话框程序启动时拉起 mongoose HTTP 服务服务端注册/api/camera、/api/idcard这类业务路由路由回调里再通过 TaoToken 的统一 Key 和 API 通道去调用大模型最后用 curl 和 jsoncpp 完成请求验证与响应解析。适合谁适合正在做 Windows 桌面 本地服务混合架构、又不想自己造 HTTP 协议轮子的开发者。下面从依赖准备到 curl 联调一步步给可复制的代码。2. TaoToken 前置统一 Key 与 API 通道准备在写 mongoose 回调之前先把大模型调用这条链路准备好。桌面程序里如果每个业务模块各自维护一套模型地址和 Key后期换模型、换通道会非常痛苦。TaoToken 的作用就是把这些统一成一个 Key、一个 API 入口服务端代码只认一个 base_url 和一个 api_key。你需要先拿到 Key。进入控制台创建 API Key地址是 https://taotoken.net/api-keys 创建后复制保存它只会完整显示一次。模型对话调试可以在 https://taotoken.net/model-chat 里先验证 Key 是否可用确认能正常返回再写进代码。如果你后续要做长期编码或 Agent 类任务可以了解 Coding Plan https://taotoken.net/coding-plan 。配置上服务端只需要记住两个值API 基地址https://taotoken.net/api以及你的 Key。注意 API 地址不要加 UTM 参数保持干净。接入文档在 https://taotoken.net/doc 里面有请求格式和返回结构说明写 jsoncpp 解析时对着看。注意Key 不要硬编码进提交到仓库的源码里。桌面程序可以用一个config.toml放在 exe 同目录运行时读取这样换 Key 不用重新编译。一个最小config.toml片段如下[server] port 6080 host 127.0.0.1 [llm] base_url https://taotoken.net/api api_key sk-你的Key model gpt-4o-mini timeout_ms 30000MFC 里读 toml 可以用现成的 header-only 库也可以先用简单的 ini 解析替代重点是让 Key 和 base_url 从配置文件进来而不是散落在各个 cpp 里。3. 可复制配置mongoose 事件回调骨架mongoose 的核心是mg_mgr事件循环加mg_connection连接对象。整个 HTTP 服务封装成一个HttpServer类对外提供Start()和Close()内部用一个独立线程跑mg_mgr_poll。下面是可直接复制的骨架。// HttpServer.h #pragma once #include string #include thread #include map #include functional #include mongoose.h using OnRspCallback std::functionvoid(mg_connection*, const std::string, int); using ReqHandler std::functionbool(const std::string, const std::string, mg_connection*, OnRspCallback); class HttpServer { public: bool Init(const std::string port); bool Start(); bool Close(); void AddHandler(const std::string url, ReqHandler handler); private: static void OnHttpEvent(mg_connection* c, int ev, void* data); static void HandleEvent(mg_connection* c, http_message* req); static void SendRsp(mg_connection* c, const std::string rsp, int state); mg_mgr m_mgr; std::string m_port; bool m_bExitSvr false; static std::mapstd::string, ReqHandler s_handler_map; };Start()里开线程跑事件循环Close()置退出标志并释放 mgrbool HttpServer::Start() { std::thread t([this]() { mg_mgr_init(m_mgr, NULL); mg_connection* c mg_bind(m_mgr, m_port.c_str(), OnHttpEvent); if (c NULL) return false; mg_set_protocol_http_websocket(c); printf(starting http server at port: %s\n, m_port.c_str()); while (!m_bExitSvr) { mg_mgr_poll(m_mgr, 500); } return true; }); t.detach(); return true; } bool HttpServer::Close() { m_bExitSvr true; Sleep(1000); mg_mgr_free(m_mgr); return true; }事件回调里只处理MG_EV_HTTP_REQUEST把请求交给HandleEvent做路由分发void HttpServer::OnHttpEvent(mg_connection* c, int ev, void* data) { if (ev MG_EV_HTTP_REQUEST) { HandleEvent(c, (http_message*)data); } } void HttpServer::HandleEvent(mg_connection* c, http_message* req) { std::string url(req-uri.p, req-uri.len); std::string body(req-body.p, req-body.len); auto it s_handler_map.find(url); if (it ! s_handler_map.end()) { it-second(url, body, c, SendRsp); } else { SendRsp(c, {\state\:404,\msg\:\not found\}, 404); } }响应构造要注意先发 header再用 chunked 方式发 body最后发一个空 chunk 结束void HttpServer::SendRsp(mg_connection* c, const std::string rsp, int state) { mg_printf(c, %s, HTTP/1.1 200 OK\r\n Content-Type: application/json\r\n Transfer-Encoding: chunked\r\n\r\n); mg_printf_http_chunk(c, {\state\:%d,\result\:%s}, state, rsp.c_str()); mg_send_http_chunk(c, , 0); }MFC 对话框初始化时注册路由并启动服务业务回调里再调 TaoTokenvoid CCtWebCameraDlgApp::startHttpSvr() { http_server std::make_sharedHttpServer(); http_server-Init(6080); http_server-AddHandler(/api/camera, [](const std::string url, const std::string body, mg_connection* c, OnRspCallback rsp) { int state 200; std::string result init data.; CCtWebCameraDlgDlg* dlg (CCtWebCameraDlgDlg*)theApp.m_pMainWnd; if (dlg ! nullptr) { std::string type getImgType(); dlg-getImgBase64(type.c_str(), result, state); } rsp(c, result, state); return true; }); http_server-Start(); }这段骨架跑起来后http://127.0.0.1:6080/api/camera就能被访问。业务回调里如果要调大模型把result换成 TaoToken 返回的内容即可Key 从config.toml读。4. 验证请求curl 联调与 jsoncpp 解析服务起来后先用 curl 验证基本连通性。Windows 下用编译好的curl.exe命令行直接发 POSTcurl -X POST http://127.0.0.1:6080/api/camera \ -H Content-Type: application/x-www-form-urlencoded \ -d image_type1time1700000000如果服务端正常会返回类似{state:200,result:base64data...}文件上传用-F表单方式注意后面跟文件路径curl -F file1.jpg -F image_type1 http://127.0.0.1:6080/api/upload服务端接收文件时mongoose 的http_message里 body 是原始数据multipart 需要自己按 boundary 切分或者用 curl 的 formadd 在客户端构造好。实测下来客户端用curl_formadd拼 multipart服务端按 boundary 解析比手写 socket 稳得多。响应解析用 jsoncpp。假设 TaoToken 返回的是标准 JSON解析代码如下#include json/json.h bool ParseLlmResponse(const std::string raw, std::string content) { Json::CharReaderBuilder builder; Json::Value root; std::string errs; std::unique_ptrJson::CharReader reader(builder.newCharReader()); if (!reader-parse(raw.c_str(), raw.c_str() raw.size(), root, errs)) { printf(json parse error: %s\n, errs.c_str()); return false; } if (root.isMember(choices) root[choices].isArray()) { content root[choices][0][message][content].asString(); return true; } return false; }调用 TaoToken 的请求可以用 curl 在服务端发起把base_url /v1/chat/completions作为 URLHeader 里带Authorization: Bearer keybody 是 JSON。返回的 raw 字符串丢给上面的ParseLlmResponse取出 content 再通过 mongoose 的SendRsp回给前端。跨域场景下前端可能传callbackwhenreadover服务端把响应包成 JSONP 格式即可std::string callback GetQueryVar(req, callback); if (!callback.empty()) { rsp callback ( jsonBody ); }这样浏览器端就能正常拿到数据不会被同源策略拦住。5. 本篇常见错排查端口被占用mg_bind返回 NULL先检查 6080 是否被其他进程占用命令行netstat -ano | findstr 6080查一下换端口或结束占用进程。响应一直转圈多半是 header 没发对。chunked 模式下必须先发Transfer-Encoding: chunked再用mg_printf_http_chunk发 body最后mg_send_http_chunk(c, , 0)结束。少最后一步浏览器会一直等。curl 返回 400检查 Content-Type 和 body 格式是否匹配。表单用application/x-www-form-urlencodedJSON 用application/jsonmultipart 用-F让 curl 自动生成 boundary。jsoncpp 解析失败先打印 raw 字符串看实际返回常见是返回里带了 BOM 或者前后有空白parse 前 trim 一下。另外确认reader-parse的起止指针正确。MFC 回调里访问对话框崩溃theApp.m_pMainWnd可能为空或者跨线程访问 UI 对象。业务数据先在服务线程准备好UI 更新用PostMessage抛回主线程。TaoToken 请求 401Key 没读到或者格式不对。检查config.toml路径是否在 exe 同目录Key 是否带了多余空格Header 里Bearer后面有没有漏空格。6. 接入文档与后续调试入口服务端骨架跑通后下一步是把大模型调用真正接进业务回调。TaoToken 的接入文档在 https://taotoken.net/doc 里面有完整的请求示例和返回字段说明对着改ParseLlmResponse里的字段路径即可。如果你要长期做编码类或 Agent 类任务Coding Plan 页面 https://taotoken.net/coding-plan 有对应的通道说明。Key 管理和新建在 https://taotoken.net/api-keys 模型对话快速验证在 https://taotoken.net/model-chat 。建议先把 curl 联调跑通确认服务端能稳定返回 JSON再往 MFC 业务里塞模型调用逻辑这样排障时能快速定位是 HTTP 层的问题还是模型层的问题。
返回列表