简介:面向 Windows 平台 C++ 开发者的 HTTP/HTTPS 请求库资源,基于 libcurl 与 WinHTTP 两种接口,分别封装成 CHttpClient 和 WinHttpClient 类,均支持 GET/POST,可直接用于 Visual Studio 工程。压缩包共 54 个文件,大小 6.24MB,含 17 个头文件、12 个静态库、4 个 cpp 源文件,以及解决方案、配置文件、构建日志、辅助脚本等,结构清晰。示例提供可运行 demo 与清理脚本,便于对照调试和二次改造,对需要快速集成网络请求的开发者是很好的参考模板。已有 484 人学习下载,也可作为两种封装思路的对比学习资料。
1. 为什么我同时维护 curl 和 WinHTTP 两套 HTTP 客户端
做 Windows 上的自动化接口请求时,我手里一直保留两套方案:一套是 curl 命令行,一套是系统自带的 WinHTTP API。原因很简单——curl 适合临时验证和脚本,WinHTTP 适合嵌进 C++ 工具里不引第三方依赖。很多人在两者之间摇摆,问得最多的是:同一个 POST 请求,为什么用 curl 一把就过,换成 WinHTTP 却各种翻车?这篇笔记会把 GET/POST 在 HTTP 和 HTTPS 下的完整发送链路拆开,把 curl 和 WinHTTP 的对应参数、证书处理、超时设置和常见坑位都摆出来。适合正在写内部工具、接口巡检小程序,或者被服务端要求严格调试请求头的开发。
2. HTTP/HTTPS 请求的关键环节:头、体、状态码与选型依据
2.1 一个 HTTP 请求到底由什么构成
先说一个我自己踩过的认知误区:很久以前我以为 POST 就是把数据塞到请求体里,然后服务端就能自动读到。后来排查问题才发现,请求行、请求头、空行、请求体一个都不能少,服务端对“缺胳膊少腿”的请求非常敏感。
一个标准 HTTP 请求有这样的骨架:
- 请求行:比如
POST /api/user HTTP/1.1,包含方法、路径(不包含域名)、协议版本。 - 请求头:一堆键值对,每行一个,比如
Host: api.example.com、Content-Type: application/json、User-Agent。请求头在服务端会被序列化成专门的字典结构,缺了关键头会导致服务端无法判断请求体格式。 - 空行:请求头结束后必须有一个空行,表示头部结束。这个空行很多人会忽略,但协议要求就是这样。
- 请求体:GET 请求一般没有,POST/PUT 可以有。请求体最开始可以是 JSON、表单、二进制文件等,取决于
Content-Type。
响应也是同样的结构:状态行(HTTP/1.1 200 OK)、响应头、空行、响应体。拿到响应后,先判断状态码,再解析头部和正文。实际写代码时,大部分库会帮你处理这些细节,但遇到 400、413、415 这类状态码时,还是要回到这个结构上找原因。
下面是 GET 和 POST 的核心差异,我做了张表:
| 对比项 | GET | POST |
|---|---|---|
| 请求体 | 通常没有 | 有,放在空行之后 |
| 参数位置 | URL querystring,比如?page=1 | 请求体,也可以部分放 URL |
| 幂等性 | 语义上幂等 | 非幂等 |
| 服务器端默认处理 | 读 querystring | 读 body |
| 常见使用场景 | 查询、抓取 | 提交、写业务数据 |
在实际项目里,我最常看到的错误是:GET 请求硬把参数塞进-d传递,或者 POST 请求用-X GET强行覆盖方法名。这些都会导致服务端拿到一个不符合预期的请求,返回 400 或者 301 重定向。
2.2 为什么我在 Windows 上只留 curl 和 WinHTTP
选型这件事,不是“哪个熟用哪个”,而是要看请求要跑在哪里。我在 Windows 上最终只保留两套,不是没用过别的,是实践证明这两套能覆盖九成场景。
第一套是 curl。curl 是独立可执行文件,不依赖运行时,出了故障能用命令行直接复现;它在 Windows 上支持 HTTP/HTTPS、Cookie、重定向、压缩、代理(企业环境下需要走代理时的兜底方案)等能力,脚本里写起来非常直观。第二套是 WinHTTP。WinHTTP 是操作系统自带的 HTTP 客户端栈,C/C++ 直接调用,不需要引入第三方库,对内存和依赖控制更严格,适合写常驻服务、系统级工具、开机自启动检查器这一类的程序。
有人会问:为什么不用 libcurl?libcurl 确实强大,但它在 Windows 下要么自己编译,要么引入第三方 DLL,在一些只允许发布纯净 exe 的环境里很麻烦。WinHTTP 不需要额外部署任何东西,编译时就链接系统库。当然,代价是 API 比较繁琐,而且没有 curl 命令行那种“一条命令出结果”的爽快感。
选择建议是这样的:
| 选型点 | curl | WinHTTP |
|---|---|---|
| 使用场景 | 调试、脚本、临时测试 | C++ 程序内嵌请求逻辑 |
| 依赖 | 需 curl.exe,一般系统自带 | 系统自带,只需链接 winhttp.lib |
| 上手难度 | 低,看参数即可 | 中等,要理解句柄生命周期 |
| 证书处理 | 参数化灵活 | 代码设置安全标志 |
| 错误信息 | 字符串直出 | 错误码映射,需要查表 |
| 执行效率 | 每条命令独立进程 | 复用句柄,适合高并发循环 |
我看过不少项目在正式代码里调 curl.exe 去读接口,这种做法不是不行,但每次都会拉起一个子进程,性能和异常处理都比较脆。反过来,也见过有人用 WinHTTP 写个一次性测接口的小工具,结果代码量比业务逻辑还大。所以我的习惯是:能用 curl 快速验证的,就先用 curl;要长期跑的、要集成进程序的,再落到 WinHTTP。
3. curl 版本的 GET/POST 实现:从命令行参数到生产级调用
3.1 一条 curl 命令的完整拆解:GET、POST、JSON、文件字段
curl 最常用的场景就是手动模拟请求。先说 GET。普通 GET 什么都不用加,直接用 URL 就行:
curl "http://127.0.0.1:8080/api/health"这是最基础的写法,但问题来了:如果参数里有中文、特殊字符或者空格,直接拼接 URL 会导致服务端解析错误。所以我一般会用-G和--data-urlencode组合:
curl -G "http://127.0.0.1:8080/api/user" \ --data-urlencode "page=1" \ --data-urlencode "name=张三"逻辑说明:-G会让后面用--data-urlencode指定的参数自动拼接到 URL 的 querystring 上,并做 URL 编码。“name=张三”会被编码成%E5%BC%A0%E4%B8%89这样的形式,服务端才会正确识别。如果不加-G,--data-urlencode会被当成请求体发送,服务端就会在 querystring 里找不到参数。
参数说明:
-G:将-d或--data-urlencode的数据作为 GET 方法的查询参数。-X POST:显式指定方法,但注意-X主要改的是请求行里的方法名字,不是“加上方法就能发请求”。-H "Content-Type: application/json":设置请求头,覆盖默认头。-d '{"name":"test"}':发送请求体,默认会把请求头改为Content-Type: application/x-www-form-urlencoded。
写 POST JSON 时,比较稳的写法是:
curl -X POST "http://127.0.0.1:8080/api/user" \ -H "Content-Type: application/json" \ -d '{"name":"A同学","role":"developer"}'逻辑说明:-X POST指定方法为 POST,-H告诉服务端请求体是 JSON,-d则是请求体内容。需要注意:-d默认不带Content-Type: application/json时,curl 会自己加一个表单头,服务端如果用@RequestParam接收,就会拿不到 JSON 里的字段。因此先写上-H才是关键。
如果是要上传文件表单字段,可以用-F:
curl -X POST "http://127.0.0.1:8080/api/upload" \ -F "file=@D:\report.docx" \ -F "note=月度报告"逻辑说明:-F会自动设置Content-Type: multipart/form-data,@D:\report.docx表示读取本地文件作为字段file的值。这个方法在处理附件上传时很好用,但如果路径里有空格,需要把整个-F参数用双引号包住,否则会被拆成两个字段。
3.2 HTTPS 和证书:什么时候用 -k,什么时候必须指定 CA
HTTPS 场景下,curl 默认会校验证书链。测试环境里经常是自签名证书,这时候 curl 会直接报错:
curl: (60) SSL certificate problem: self-signed certificate遇到这种提示,很多人第一反应是加-k跳过校验。这个操作在本地临时测试没问题,但放到生产环境就是灾难——等于把 HTTPS 的防护全部关掉了。更要命的是,我看过有人把-k写死在脚本里,后来上了生产环境,被安全扫描一查一个准。
正确做法是:如果服务端用的是自建 CA 签发的证书,把 CA 文件下载下来,用--cacert指定它:
curl --cacert ./demo-ca.crt "https://127.0.0.1:8443/api/health"逻辑说明:--cacert让 curl 使用指定文件作为受信任的 CA 证书。这样既保留了证书校验,又能信任自签场景。如果服务端支持客户端证书双向验证,加--cert client.crt和--key client.key:
curl --cert ./client.crt --key ./client.key \ --cacert ./demo-ca.crt \ "https://127.0.0.1:8443/api/user"参数说明:
--cacert:指定 CA 证书文件路径。--cert:指定客户端证书文件路径。--key:指定客户端私钥文件路径。-k:禁用证书校验,只能在可控的测试网络里用。
还有一个细节:如果服务器证书已经过期,--cacert也救不了,curl 会报出certificate has expired。这时候要么在服务器端换证书,要么在本地环境变量里临时设置CURL_CA_BUNDLE,但生产方式不要这么干。
3.3 超时、重试和调试:把 curl 当生产工具用
curl 不只是手动敲命令用的,我经常把它写进计划任务里做接口健康巡检。这时候必须把超时、重试这些参数配齐,否则一条请求卡住,整个脚本都被拖死。
curl --connect-timeout 5 --max-time 15 \ --retry 3 --retry-delay 2 \ -w "%{http_code}\n" \ "https://127.0.0.1:8443/api/health"逻辑说明:--connect-timeout 5限制 TCP 连接阶段的超时时间为 5 秒,--max-time 15限制整个请求最多 15 秒,--retry 3表示网络错误时重试三次,--retry-delay 2表示重试间隔 2 秒,-w "%{http_code}\n"在请求结束后输出状态码。这些参数组合起来,才能保证定时任务不会因为一次卡死的请求而停摆。
调试时最有用的参数是-v和-i:
curl -v "http://127.0.0.1:8080/api/user"-v会输出请求头、响应头、TLS 握手过程等详细信息。遇到 401、403、415 这类状态码,先用-v看一遍,基本能定位是头的问题还是 body 的问题。-i则是在输出响应体之前先输出响应头,适合快速看Set-Cookie和Content-Type。
参数说明:
-w:输出自定义信息,比如-w "%{time_total}"可以打印总耗时。-A "Mozilla/5.0":指定 User-Agent,有些服务端会针对 UA 做拦截。--compressed:让 curl 申请并自动解压 gzip 响应,避免收到乱码。
我曾经在排查一个 401 问题时,把-v输出拉到日志里,发现是请求头里少了Cookie字段,导致服务端认为未登录。这种问题如果不看详细输出,纯靠猜,能折腾一天。
4. WinHTTP 版本的 GET/POST 实现:C++ 里的原生 HTTP 栈
4.1 WinHTTP 同步请求的五个步骤
WinHTTP 的同步请求流程可以拆成五个典型步骤:初始化会话、建立连接、创建请求句柄、发送并接收响应、读取数据。下面这个例子是一个最简单的 GET 请求:
#include <windows.h> #include <winhttp.h> #include <stdio.h> #include <string> #pragma comment(lib, "winhttp.lib") std::string HttpGet(const std::wstring& host, int port, const std::wstring& path) { BOOL ok = FALSE; std::string result; // 1. 打开 WinHTTP 会话句柄 HINTERNET hSession = WinHttpOpen( L"WinHttpDemo/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hSession) { printf("WinHttpOpen failed: %lu\n", GetLastError()); return result; } // 2. 连接目标主机 HINTERNET hConnect = WinHttpConnect( hSession, host.c_str(), (INTERNET_PORT)port, 0); if (!hConnect) { printf("WinHttpConnect failed: %lu\n", GetLastError()); WinHttpCloseHandle(hSession); return result; } // 3. 创建 GET 请求 HINTERNET hRequest = WinHttpOpenRequest( hConnect, L"GET", path.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, port == 443 ? WINHTTP_FLAG_SECURE : 0); if (!hRequest) { printf("WinHttpOpenRequest failed: %lu\n", GetLastError()); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return result; } // 4. 发送请求 ok = WinHttpSendRequest( hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); if (!ok) { printf("WinHttpSendRequest failed: %lu\n", GetLastError()); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return result; } // 5. 接收响应 ok = WinHttpReceiveResponse(hRequest, NULL); if (!ok) { printf("WinHttpReceiveResponse failed: %lu\n", GetLastError()); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return result; } // 读取响应体 DWORD bytesRead = 0; char buffer[4096] = {}; while (WinHttpReadData(hRequest, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) { result.append(buffer, bytesRead); bytesRead = 0; } // 清理句柄,顺序与打开顺序相反 WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return result; }逻辑说明:WinHttpOpen创建一个全局会话,参数WINHTTP_ACCESS_TYPE_DEFAULT_PROXY表示使用系统默认代理设置,这在企业内网场景里很有必要。WinHttpConnect建立与目标主机的连接句柄,这里只做连接,不会真正发起 TCP 通信。WinHttpOpenRequest创建请求句柄,最后一个参数如果是 HTTPS 端口就传入WINHTTP_FLAG_SECURE,告知协议栈用 TLS。WinHttpSendRequest负责把请求行、请求头和请求体发送出去。WinHttpReceiveResponse会阻塞直到收到响应头,然后WinHttpReadData循环读取响应体。
参数说明:
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY:跟随系统代理配置,适合公司网络环境;如果是直连网络,可以用WINHTTP_ACCESS_TYPE_NO_PROXY。WINHTTP_DEFAULT_ACCEPT_TYPES:表示接受所有响应类型,也可以传L"application/json"这样的具体类型。WINHTTP_FLAG_SECURE:启用 HTTPS,此时 WinHTTP 会进行证书链校验。
注意:同步模式下,WinHTTP 句柄是线程安全的,但同一个会话句柄不宜被多个线程同时调用。如果要做并发请求,要么每个线程创建独立的 session,要么用异步回调模式。我早期偷懒,一个hSession开十几个线程调 GET,结果遇到ERROR_WINHTTP_INCORRECT_HANDLE_STATE,排查了半天才意识到是句柄复用出了问题。
4.2 POST 数据:把 JSON 交给 WinHTTP
POST 请求相比 GET,核心改动在WinHttpSendRequest的最后几个参数:传入请求体指针和请求体长度,同时在请求头里显式声明Content-Type。
std::string HttpPost(const std::wstring& host, int port, const std::wstring& path, const std::string& jsonBody) { HINTERNET hSession = WinHttpOpen( L"WinHttpDemo/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hSession) return ""; HINTERNET hConnect = WinHttpConnect(hSession, host.c_str(), (INTERNET_PORT)port, 0); if (!hConnect) { WinHttpCloseHandle(hSession); return ""; } HINTERNET hRequest = WinHttpOpenRequest( hConnect, L"POST", path.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, port == 443 ? WINHTTP_FLAG_SECURE : 0); if (!hRequest) { WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return ""; } // 设置 Content-Type,这里手动拼了一个请求头字符串 LPCWSTR headers = L"Content-Type: application/json\r\n"; BOOL ok = WinHttpSendRequest( hRequest, headers, wcslen(headers), (LPVOID)jsonBody.c_str(), (DWORD)jsonBody.length(), (DWORD)jsonBody.length(), 0); if (!ok) { printf("WinHttpSendRequest failed: %lu\n", GetLastError()); WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return ""; } ok = WinHttpReceiveResponse(hRequest, NULL); std::string result; if (ok) { DWORD bytesRead = 0; char buffer[4096] = {}; while (WinHttpReadData(hRequest, buffer, sizeof(buffer), &bytesRead) && bytesRead > 0) { result.append(buffer, bytesRead); bytesRead = 0; } } WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return result; }逻辑说明:WinHttpSendRequest的第三个参数是请求头字符串长度,第五个参数是请求体指针,第六个参数是请求体总长度,第七个参数是请求体的实际发送长度。对于普通字符串,第六个和第七个可以相同;如果涉及分块发送,则需要区分 totalsize 与 send size。请求体是std::string,用.c_str()取内存地址,用.length()取长度。这里有一个细节:std::string::c_str()末尾还有一个\0,但 HTTP 请求体通常不需要把\0算进去,所以长度用.length()而不是strlen。
参数说明:
- 头字符串必须用
\r\n结尾,否则 WinHTTP 拼接多个头时可能出错。 jsonBody.length()是字节长度,WinHTTP 默认把它同时作为 Content-Length 发送,服务端可以据此判断请求体是否读完。WINHTTP_FLAG_SECURE同样适用于 POST,注意传参位置在WinHttpOpenRequest。
4.3 HTTPS 证书校验与错误处理
WinHTTP 默认对 HTTPS 证书做完整校验,包括有效期、证书链、域名是否匹配。测试环境里如果用了自签名证书,第一次运行会得到ERROR_WINHTTP_SECURE_FAILURE。处理方式不是全局跳过,而是只在你明确信任指定主机时绕过对应校验项。
// 在 WinHttpSendRequest 之前,针对这个 Request 句柄设置安全选项 DWORD flags = 0; DWORD flagsSize = sizeof(flags); WinHttpQueryOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &flags, &flagsSize); // 按需开启忽略项 flags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA; // 忽略未知 CA flags |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID; // 忽略域名不匹配 flags |= SECURITY_FLAG_IGNORE_CERT_DATE_INVALID; // 忽略过期(仅测试时使用) WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &flags, flagsSize);逻辑说明:WINHTTP_OPTION_SECURITY_FLAGS是 WinHTTP 的安全标志位集合,默认值为 0,表示严格校验。先WinHttpQueryOption读出当前值,再按需置位。这个设置是作用在单个请求句柄上的,也就是说不同请求可以有不同的校验策略,而不会污染全局。生产环境不建议开启这些忽略项,否则和 curl 的-k没有区别。
错误处理有一个常见误区:GetLastError()拿到的错误码,很多并不是 WinHTTP 的ERROR_WINHTTP_*码,而是底层 Socket 错误。更好用的方式是在WinHttpSendRequest失败后调用WinHttpQueryOption去查WINHTTP_OPTION_BYPASS之外的项,或者直接调用GetLastError()后对照WINHTTP_ERROR_BASE表。我一般会在日志里同时输出错误码和WinHttpReceiveResponse的返回值,避免被表面现象误导。
5. 避坑与排查:十个我踩过的 HTTP 请求坑位
5.1 五个典型的 GET/POST 坑位
先说现象,再讲原因,最后给解决方式。
坑位一:curl POST JSON,服务端却收到表单格式
- 现象:请求返回 415 或字段全部为 null。
- 原因:
curl -d '{}'时,curl 默认将请求头设为Content-Type: application/x-www-form-urlencoded,服务端按 JSON 解析时失败。 - 解决:显式加
-H "Content-Type: application/json",并在调试时用-v确认请求头已经覆盖成功。代码里如果用了库封装,还要注意库里是否会自动追加默认 Content-Type。
坑位二:Windows cmd 下 curl 的 JSON 引号被吞
- 现象:在 cmd 窗口执行
curl -d "{"name":"test"}",服务端收到的 body 变成了{name:test}或直接报语法错误。 - 原因:Windows cmd 不识别单引号作为字符串定界符,双引号又会被 shell 做转义处理,导致 JSON 里的引号丢失。
- 解决:在 cmd 下将内部的 JSON 双引号换成反斜杠转义,例如
curl -d "{\"name\":\"test\"}"。在 PowerShell 下则反过来,单引号更安全。如果经常写脚本,建议在 Git Bash 或 WSL 里跑 curl,少受这套罪。
坑位三:WinHTTP POST 返回 400,且服务端日志显示 Content-Length 异常
- 现象:同样的 JSON,用 curl 发送成功,用 WinHTTP 发送就报 400。
- 原因:
WinHttpSendRequest的dwTotalLength参数传入了字符串strlen(jsonBody.c_str()),但 body 里有中文字符,strlen是按字节计算的,和length()结果一致才对。真正的问题是部分开发者把sizeof(jsonBody)写进去,结果把整个 string 对象的内存当成了请求体。 - 解决:使用
jsonBody.size()或jsonBody.length(),并且不能把WINHTTP_NO_REQUEST_DATA和0混用在同一个调用里。前面代码里第六、第七个参数统一传(DWORD)jsonBody.length()就稳了。
坑位四:自签名证书导致 curl 和 WinHTTP 都失败
- 现象:HTTPS 请求报证书错误,测试环境没办法改证书。
- 原因:证书链不受信任,或证书域名与访问的 URL 不匹配。
- 解决:curl 用
--cacert指定自建 CA,WinHTTP 用WINHTTP_OPTION_SECURITY_FLAGS开启忽略未知 CA 的选项。不要一上来就全局跳过证书校验,否则哪天 URL 写错域名都发现不了。
坑位五:中文内容在 GET 和 POST 中乱码
- 现象:服务端拿到的中文是乱码,或者 URL querystring 里的中文变成了空字符串。
- 原因:客户端没有做 URL 编码,或者请求体编码和服务端期望不一致。
- 解决:GET 参数用
curl --data-urlencode或 WinHTTP 里封装UrlEscape函数;POST body 统一使用 UTF-8 编码,并设置Content-Type: application/json; charset=utf-8。注意std::string存中文时,要确保它是 UTF-8 字节流,不能是 GBK 混进去。
5.2 排查请求问题:先定层,再定因
HTTP 请求问题排查,我最怕的是“眉毛胡子一把抓”。拿到一个失败请求,先按四层定位:第一层是网络层,确认端口能通、TLS 能握手;第二层是协议层,看请求头是否完整、状态码是否符合预期;第三层是业务层,看响应体里的错误码和 message;第四层是环境层,看是不是有防火墙或系统代理拦截。
curl 排查时,-v输出已经包含了大部分信息,重点看这几行:
> POST /api/user HTTP/1.1 > Host: 127.0.0.1:8080 > Content-Type: application/json如果> Host之后没有> Content-Type,说明请求头没有加上。响应侧看< HTTP/1.1 400,再往后翻是服务端返回的响应体,一般会写明错误原因。
WinHTTP 排查时,我会先把WinHttpSendRequest之后的所有关键调用返回值打日志,再配合抓包工具查看实际发送的字节流。WinHTTP 里最容易出现的问题是请求头和请求体拼接顺序错误,导致服务端解析不了。用抓包工具能看到原始请求,能直接确认是不是多了一个\n或少了一个\r。
日志规范也提一下:建议每一条请求都记录时间戳、方法、URL、状态码、耗时、响应体前 200 个字符。这些信息足够覆盖绝大多数线上问题。
6. 把请求封装成通用模块:从可用到好用的距离
前五章写的是“跑通”,最后一章聊聊“复用”。我自己在多个项目里反复踩过重复路之后,最终把 curl 和 WinHTTP 都封装成了统一入口。curl 封装在 shell 脚本里,WinHTTP 封装在 C++ 类里,对外暴露的接口保持一致。
# curl 封装示例:http_request.sh request_post() { local url="$1" local json="$2" curl --connect-timeout 5 --max-time 15 \ -H "Content-Type: application/json" \ -d "$json" \ -w "\n%{http_code}\n" \ "$url" }// WinHTTP 封装示例:只暴露一个 Class class HttpRequest { public: static std::string Post(const std::wstring& host, int port, const std::wstring& path, const std::string& jsonBody, int timeoutMs = 10000); static std::string Get(const std::wstring& host, int port, const std::wstring& path, int timeoutMs = 10000); };封装的核心不是写更多函数,而是把超时、证书策略、错误码转换成统一的日志格式。curl 脚本里-w输出的状态码要和正文分开,方便上游脚本判断。C++ 封装里,WinHttpSetOption传超时时间,然后WinHttpReceiveResponse失败时可以区分是超时还是网络错误。
还有一个验证习惯:每写完一个封装模块,先用本地回环服务跑一遍 HTTP 和 HTTPS 两种模式,确认 GET、POST、JSON、文件字段、证书错误这五个维度全部通过,再提交到业务代码。在本地服务和测试接口上把坑都踩一遍,比直接在生产环境翻车要划算得多。从那以后,我每次写完新的请求函数,第一件事就是用一个本地回环服务跑一遍 GET 和 POST,确认状态码不是 200 就绝不往生产环境上丢。希望帮到你。
本文还有配套的精品资源,点击获取