
简介cpr-1.3.0 预编译库面向使用 C 发起 HTTP 请求的开发者适合在 VS 项目中快速接入、又不想自行编译 cpr 与 libcurl 的初、中级人员。压缩包共 27 个文件含 21 个头文件、4 个 lib 静态库和 2 个 dll 动态库Debug/Release 均提供 cpr.lib、libcurl.dll 等整体约 738KB便于随工程归档引用。资源由 weststarqp 于 2019 年 2 月基于 VS2015 编译已有 691 人学习。解压后可按配置将头文件、库文件与动态库加入工程减少搭建依赖环境的时间并借助头文件了解 session、response、auth、cookies 等接口为请求封装和调试提供基础。1. 从一次库在头文件不在的编译失败说起上周接手一个内部工具的构建CMakeLists 里一行find_package(cpr REQUIRED)我以为是十分钟的事结果make直接甩出一堆fatal error: cpr/generated/cpr_generated_includes.h: No such file or directory。库文件明明装了头文件目录也加进-I了偏偏缺一个名字很长的中间头文件。这就是 cpr 1.3.0 最经典的一关。cpr 全称 C Requests是把 libcurl 那套 C 接口包成cpr::Get/cpr::Response风格的 C11 库写 HTTP 客户端时不用再和curl_easy_setopt的变参列表较劲。它常见于数据采集、内部服务探活、CI 里的小工具这类场景。装不顺的后果很直接编译期缺头链接期缺符号运行期 SSL 握手失败一路踩下来比手写 libcurl 还费时间。这篇记录适合正在找 cpr-1.3.0 怎么安装、怎么配置、环境怎么搭的人。2. 环境盘点cpr 1.3.0 编起来到底要哪几样东西动手之前先把依赖列清楚比编到一半再回头补要省事得多。cpr 1.3.0 本身不复杂它就是一层薄封装真正的重活在 libcurl 和 OpenSSL 上。麻烦的地方在于它的 CMake 脚本默认行为偏自给自足会尝试自己去弄 curl 源码这在有外网的开发机上无感在隔离环境里就是灾难。所以这一章的重点不是罗列依赖而是把每个依赖的来源定下来编译器从系统来curl 从系统来SSL 从系统来三样都锁死构建过程才是可复现的。2.1 编译器与 C 标准C11 是底线不是可选项cpr 1.3.0 的公开头文件里用了std::function、std::unordered_map、右值引用和大量 lambda编译选项里必须打开 C11。GCC 建议 7 以上Clang 建议 6 以上MSVC 走 2017 以后的版本比较稳。我一般不去赌默认标准而是在工程顶层直接写死避免某个下游工程把-std覆盖掉之后报出一堆看不懂的模板推导错误。# 顶层 CMakeLists.txt在 project() 之后立刻设置 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 找不到 C11 编译器就直接失败而不是悄悄降级 set(CMAKE_CXX_EXTENSIONS OFF) # 关掉 gnu11保持可移植这三行的逻辑是第一行声明目标标准第二行把必须有变成硬约束第三行避免编译期混入 GNU 扩展导致换编译器就编不过。很多在我机器上能编的事故源头就是CMAKE_CXX_EXTENSIONS默认开着代码里不小心用了typeof之类的东西换到 Clang 上直接崩。如果你的工程已经是 C14 或 C17不用降级cpr 1.3.0 在更高标准下也能编只是别把-stdc98这种老配置带进来。2.2 libcurl 用系统的还是自带的一个决定影响后面所有链接错误cpr 的 CMake 里有一个开关叫CPR_FORCE_USE_SYSTEM_CURL默认是关闭的。关闭状态下构建脚本会尝试把 curl 源码弄到本地再一起编。这个设计对想零依赖的人友好但在实际部署里问题不少构建过程会依赖外部源码获取网络受限时直接卡住就算拿到了源码编出来的 curl 和系统里其他程序用的 curl 也是两份后面做动态链接时符号容易打架。我的做法是一律强制用系统 curl前提是先确认开发包装好了# Debian / Ubuntu 系装 curl 的开发头文件和静态库 sudo apt-get install -y libcurl4-openssl-dev # RHEL / CentOS 系 sudo yum install -y libcurl-devel # 确认系统里的 curl 版本低于 7.50 建议先升级 curl-config --version curl-config --libs # 看链接参数里是否带 -lssl -lcryptolibcurl4-openssl-dev这个名字不是随便挑的它决定了 curl 背后用的是 OpenSSL 而不是别的 TLS 实现。cpr 的 SSL 相关代码路径和 curl 的 TLS 后端是绑定的混用会出现编译通过但运行期证书校验行为异常这种很难查的问题。curl-config --libs的输出要重点看如果里面没有-lssl -lcrypto说明装的是不带 SSL 的版本后面CPR_ENABLE_SSLON会直接失败。装完开发包后再跑一次curl-config --version把版本号记下来后面排查时有用。2.3 OpenSSL 与 zlibSSL 开关背后的两个真实依赖cpr 1.3.0 的CPR_ENABLE_SSL默认打开打开之后就会去找 OpenSSL 的头文件和库。1.1.1 系列是目前兼容性最好的选择如果系统里是 3.x多数情况下也能编但要留意 cpr 这层封装里对旧宏的引用个别发行版会报OPENSSL_VERSION_NUMBER相关的弃用警告警告本身不影响产物但会淹没真正的错误信息。zlib 的情况稍微特殊一点cpr 本身不直接用 zlib但 curl 在开启内容压缩支持时会依赖它。如果你的请求需要带Accept-Encoding: gzip那 curl 编的时候就必须带 zlib否则服务端返回压缩体时你拿到的是乱码。检查办法很直接# 看 curl 的编译特性里有没有 zlib 和 SSL curl -V # 输出里的 Features 一行应包含 libz、SSL、HTTPS-proxy 等关键字这一步属于装之前花十秒装之后省两小时的检查。很多环境里 curl 是精简版编译的特性列表里没有 libz等到业务代码里手动解压失败才发现回头重装 curl 还得连带重编 cpr代价不小。2.4 一套我常用的版本组合下面这张表是我这几年在不同机器上落地过的组合不是官方要求只是这个范围内我没翻过车的经验区间。跨大版本时差异会放大比如把 curl 从 7.x 换到 8.x链接参数和部分宏定义都有变化需要重新验证一遍。组件推荐区间说明CMake3.10 及以上低版本对target_include_directories的 install 导出支持不全GCC7 及以上低于 7 时 C11 的std::string_view替代实现容易出问题Clang6 及以上与 libstdc 搭配时注意版本匹配libcurl7.58 及以上7.58 前后 SSL 相关宏有调整1.3.0 在旧版上偶发编译错误OpenSSL1.1.1 系列兼容性最好3.x 可用但会有弃用警告zlib1.2.8 及以上只影响压缩传输不影响基础请求这张表的意义在于缩小排查范围。当你遇到一个诡异的编译错误时先对照表格确认自己不在边缘组合上再去翻代码效率会高很多。3. 编译安装把 cpr 1.3.0 装进一个干净前缀到这一步依赖都齐了可以开始编。我强烈建议装到独立前缀而不是/usr/local原因很现实cpr 1.3.0 是几年前的版本你手上可能还有别的工程依赖更高版本的 cpr全都塞进/usr/local之后find_package找到哪个全看运气。独立前缀加上CMAKE_PREFIX_PATH能把这套依赖隔离得干干净净卸载的时候一个rm -rf就完事不留残渣。3.1 源码落地与 out-of-source 构建目录先建两个目录一个放源码一个放构建产物严格分开。in-source 构建在 cpr 这种带代码生成的工程里尤其危险中间文件会污染源码树下次换配置时 CMakeCache 里全是脏数据。# 目录规划源码和产物分开安装前缀独立 mkdir -p ~/work/cpr-1.3.0-src mkdir -p ~/work/build-cpr mkdir -p /opt/cpr-1.3.0 # 假设源码包已经解压到 ~/work/cpr-1.3.0-src cd ~/work/cpr-1.3.0-src ls CMakeLists.txt include/ # 确认目录结构对得上再往下走ls这一步别省。有些压缩包解开后多套一层目录直接cmake -S .会报找不到 CMakeLists.txt新手容易在这里怀疑人生。确认CMakeLists.txt和include/在同一层就是正确的源码根目录。安装前缀/opt/cpr-1.3.0里的版本号也别省将来并存多个版本时这个命名能救你一次。3.2 CMake 关键选项逐个拆配置阶段是整个安装过程里最值得花时间的地方。下面这条命令是我在大多数环境里用的版本参数看着多但每一项都对应一个具体的失败场景。cmake -S ~/work/cpr-1.3.0-src \ -B ~/work/build-cpr \ -DCMAKE_BUILD_TYPERelease \ -DCMAKE_INSTALL_PREFIX/opt/cpr-1.3.0 \ -DCPR_BUILD_TESTSOFF \ -DCPR_BUILD_SERVEROFF \ -DCPR_ENABLE_SSLON \ -DCPR_FORCE_USE_SYSTEM_CURLON \ -DBUILD_SHARED_LIBSON \ -DCMAKE_POSITION_INDEPENDENT_CODEON逐项说明-S和-B是现代 CMake 的写法分别指源码目录和构建目录等价于进目录再跑cmake ..但更不容易出错。CMAKE_BUILD_TYPERelease打开优化并去掉调试符号。cpr 1.3.0 在 Debug 下编出来的库体积会大不少作为依赖库没必要。CMAKE_INSTALL_PREFIX决定make install往哪写这里就是前面建好的独立前缀。CPR_BUILD_TESTSOFF很关键。打开后它会去找测试框架在你没有预装的环境里会直接失败而且测试代码本身对产物没有任何贡献。CPR_BUILD_SERVEROFF关掉配套的测试服务端同理。CPR_ENABLE_SSLON显式打开别依赖默认值尤其在别人写好的构建脚本里。CPR_FORCE_USE_SYSTEM_CURLON是前面 2.2 节说过的那个决定必须显式指定。BUILD_SHARED_LIBSON出动态库。如果你的部署环境不方便带.so改成OFF出静态库这个取舍在第 4 章细说。CMAKE_POSITION_INDEPENDENT_CODEON保证生成的库能被链接进动态库即使当前选的是静态库也建议开着。配置跑完后重点看输出的末尾几行会打印出检测到的 curl 版本、SSL 后端、是否启用 zlib。这三行信息比任何文档都真实因为它是从当前这台机器上探测出来的。3.3 编译、安装以及 generated 头文件这一关cmake --build ~/work/build-cpr -j$(nproc) cmake --install ~/work/build-cpr-j后面跟并行度用nproc拿到核数就行。cpr 本身编译很快瓶颈通常在它要不要额外编 curl一旦看到编译过程里出现了大量 curl 的源文件说明 3.2 节那个开关没生效回去检查参数拼写。安装完之后必须做一次落地检查这是整个流程里最容易被跳过、也最容易出问题的一步# 检查关键头文件和库是否真的落到了前缀目录 find /opt/cpr-1.3.0 -name *.h | head -20 find /opt/cpr-1.3.0 -name libcpr* ls /opt/cpr-1.3.0/lib/cmake/cpr/ 2/dev/null || echo 缺 CMake 配置文件三条命令分别查头文件、库文件、CMake 包配置。重点看头文件列表里有没有一个路径里带generated字样的文件——cpr 1.3.0 在构建时会生成一份与 curl 头文件版本相关的中间头文件而老版本的安装规则在某些平台上不会把它一起装进去于是出现第 1 章开头那个报错。如果确实缺了从构建目录里找出来手动补进去# 在构建目录里定位生成的中间头文件 find ~/work/build-cpr -name cpr_generated_includes.h # 找到后按原相对路径补到安装前缀 mkdir -p /opt/cpr-1.3.0/include/cpr/generated cp ~/work/build-cpr/include/cpr/generated/cpr_generated_includes.h \ /opt/cpr-1.3.0/include/cpr/generated/补完之后再find一次确认。这一步属于典型的官方安装规则没覆盖到的坑不补的话下游工程#include cpr/cpr.h必然失败而且报错指向的是一个你没写过的文件名第一眼很难联想到是安装不完整。CMake 里还有个CPR_GENERATED_SOURCES_DIRECTORY变量可以用来指定生成物的输出位置不同小版本行为不完全一致我最后都会用上面这条手动校验兜底不赌它的默认行为。另外如果目标是直接拿现成的库文件用那么落地检查同样适用头文件树里必须有cpr/cpr.h、cpr/generated/下那个生成文件、lib/下的库以及lib/cmake/cpr/下的配置文件四样少一样都不算完整。少 CMake 配置文件的后果是find_package直接失败很多人在这一步误以为是版本不匹配其实只是安装不完整。3.4 find_package 接入与 pkg-config 备选安装到位之后在下游工程里就该能直接找到了。标准写法# 下游工程的 CMakeLists.txt find_package(cpr REQUIRED) add_executable(probe main.cpp) target_link_libraries(probe PRIVATE cpr::cpr)find_package会去CMAKE_PREFIX_PATH里找cprConfig.cmake所以如果装在/opt下配置时要带上路径cmake -S . -B build -DCMAKE_PREFIX_PATH/opt/cpr-1.3.0如果find_package报找不到先别怀疑版本按顺序查三件事配置文件是否在lib/cmake/cpr/下、CMAKE_PREFIX_PATH是否传进去了、前置的 curl 是否也能被找到。第三条经常被忽略——cpr 的配置文件里会find_dependency(CURL)如果你把 cpr 装到了前缀里但 curl 是系统包通常没问题反过来把 curl 也装在独立前缀里却忘了把那个前缀加进搜索路径就会在find_package(cpr)这一行报出莫名其妙的依赖错误。target_link_libraries里的cpr::cpr是导出目标名。个别打包方式下目标名会不一样链接时报 Target not found 的话打开cprConfig.cmake看一眼里面add_library(... IMPORTED)那行写的是什么名字照着改即可别去改系统文件。对于不用 CMake 的老工程还有一条退路是直接手写链接参数# 用 curl-config 拿 curl 的链接参数再拼上 cpr 的库 g main.cpp -I/opt/cpr-1.3.0/include \ -L/opt/cpr-1.3.0/lib -lcpr \ $(curl-config --libs) -lssl -lcrypto -lz -o probe这条命令的每段都有用-I指向 cpr 头文件-L -lcpr指向库curl-config --libs把 curl 需要的参数一次性展开最后三个是 SSL 和压缩的显式依赖。顺序很重要库要放在源文件后面否则 GCC 会因为符号未解析而报错。4. 工程接入把 cpr 1.3.0 写进业务代码库装好了只是开始真正决定这套环境好不好用的是接入方式。cpr 1.3.0 的 API 设计很接近脚本语言的 HTTP 客户端几行就能发请求但也正因为太顺手容易写出没有超时、没有错误分支、连接不复用的代码上线之后在弱网环境里表现一塌糊涂。这一章从最小可运行例子往外扩把参数取舍讲清楚。4.1 最小可编译的 CMakeLists 与一次 GET先跑通再说别的。下面这段是能编能跑的最小骨架#include cpr/cpr.h #include iostream int main() { // 用本地地址验证避免引入外部依赖 cpr::Response r cpr::Get( cpr::Url{http://127.0.0.1:8080/health}, cpr::Timeout{3000} // 整体超时单位毫秒 ); // error.code 是传输层状态status_code 是 HTTP 层状态两者互不替代 if (r.error.code ! cpr::ErrorCode::OK) { std::cerr transport: r.error.message \n; return 2; } std::cout http r.status_code body r.text \n; return 0; }关键在最后那个判断。cpr 把两类失败分得很清楚r.error记录的是连接超时、DNS 失败、SSL 握手失败这类传输层问题r.status_code记录的是服务端返回的 HTTP 状态码。只判断status_code是新手最常见的疏漏——连接都失败了status_code是 0业务逻辑会把它当成服务端返回 0处理后面全乱。判断顺序永远是先看error.code再看status_code。配套的 CMakeLists 就是 3.4 节那几行加上源文件即可。注意别在target_link_libraries里再手动补-lcurlcpr::cpr已经把传递依赖带上了重复链接在某些平台上会导致符号重复定义。4.2 超时、重试与 SSL 校验的参数取舍生产代码和 Demo 的差距基本都在这三个参数上。cpr 1.3.0 的超时是整体超时不是连接超时和读取超时的分别设置所以给值的时候要按业务最坏情况考虑。#include cpr/cpr.h #include chrono #include thread #include iostream // 带退避的简单重试只对传输层失败重试HTTP 4xx 不重试 cpr::Response GetWithRetry(const std::string url, int max_attempts 3) { cpr::Response r; for (int i 0; i max_attempts; i) { cpr::Session s; s.SetUrl(cpr::Url{url}); s.SetTimeout(cpr::Timeout{2000}); // 单次请求 2 秒 s.SetConnectTimeout(cpr::ConnectTimeout{800}); // 建连单独限时 s.SetHeader(cpr::Header{{User-Agent, cpr-1.3.0-probe}}); r s.Get(); if (r.error.code cpr::ErrorCode::OK) { return r; // 传输成功就返回HTTP 层交给调用方判断 } std::this_thread::sleep_for(std::chrono::milliseconds(200 * (i 1))); } return r; }这段的取舍点有三个。第一Timeout和ConnectTimeout分开设建连超时给得短一些能快速判断目标是否可达整体超时留出读取响应的时间。第二重试只针对传输层错误HTTP 500 这种服务端明确返回的结果不重试重试反而可能放大故障。第三退避用线性递增简单够用不需要引入额外依赖。关于 SSL 校验cpr 提供了cpr::VerifySsl{false}来跳过证书校验。这个开关在调试自签证书的内网服务时确实方便但绝对不要出现在生产代码里。如果内网服务用的是自签证书正确做法是把 CA 证书装进系统信任库或者用cpr::SslOptions指定 CA 路径而不是关校验。这条属于血泪经验跳过校验的代码一旦进了主干后面很难再摘出来。4.3 静态链接与动态链接部署时的取舍装的时候BUILD_SHARED_LIBS选哪个直接决定部署形态。动态库的好处是多个程序共用一份libcpr.so升级时替换文件即可缺点是部署时必须带上.so还得处理RPATH或者设LD_LIBRARY_PATH。cpr 依赖 curlcurl 又依赖 SSL 和 zlib动态链接下这条依赖链上任何一环版本不匹配运行期就是一个symbol lookup error。静态库的好处是产物自包含拷贝一个可执行文件就能跑特别适合分发小工具或者塞进容器镜像。代价是体积变大而且必须确保 curl 的静态库也在系统里——很多发行版的libcurl4-openssl-dev只带动态库静态库在单独的包里装漏了会在链接阶段报找不到libcurl.a。我的习惯是内部服务用动态库方便统一升级对外分发的小工具用静态库减少环境依赖。如果选静态安装 cpr 时把BUILD_SHARED_LIBS设为OFF下游链接时确保-lcurl -lssl -lcrypto -lz都能找到静态版本。4.4 复用 Session 而不是每次新建cpr 的cpr::Get这种自由函数写法内部会新建一个 Session用完销毁连接自然也不复用。在请求量大的场景里这会明显拖慢吞吐。改成显式持有 Session// 长生命周期的 Session复用连接和 TLS 会话 cpr::Session session; session.SetUrl(cpr::Url{http://127.0.0.1:8080/api/items}); session.SetTimeout(cpr::Timeout{2000}); session.SetHeader(cpr::Header{{Accept, application/json}}); // 同一个 Session 可以改参数后重复调用 session.SetParameters(cpr::Parameters{{page, 1}, {size, 50}}); cpr::Response first session.Get(); session.SetParameters(cpr::Parameters{{page, 2}, {size, 50}}); cpr::Response second session.Get();要注意 Session 不是线程安全的一个实例只能在一个线程里用。多线程场景下每个线程各自持有 Session别图省事搞个全局共享的那种问题往往在高并发压测时才暴露排查成本很高。5. 常见排查cpr 1.3.0 编译链接的五个翻车现场这一章按现象 → 原因 → 解决记录五条实际遇到过的故障。它们的共同点是报错信息都不太直观第一眼容易往错误的方向猜。5.1 编译期三类报错第一条报fatal error: cpr/generated/cpr_generated_includes.h: No such file or directory。现象是库装了、头文件路径也加了就是缺这一个文件。原因是 1.3.0 的安装规则没有把这个构建时生成的头文件一起装进去具体表现随平台和 CMake 版本略有差异。解决就是 3.3 节那套从构建目录find出来按相对路径手动补到$PREFIX/include/cpr/generated/下补完记得再find一次确认。第二条报大量undefined reference to curl_easy_...。现象是编译通过链接阶段成片报错。原因是链接顺序不对或者cpr::cpr的传递依赖没生效。解决是先确认用的是target_link_libraries(xxx PRIVATE cpr::cpr)而不是手写-lcpr手写的话库必须放在目标文件之后并且补上$(curl-config --libs)。如果还是不行检查是不是同时链接了系统里另一份 curl。第三条配置阶段卡住或者报下载失败。现象是cmake跑到一半长时间没动静日志里出现获取外部源码的动作。原因就是CPR_FORCE_USE_SYSTEM_CURL没开脚本在尝试自己弄 curl。解决是回到 3.2 节把这个参数显式设为ON然后删掉构建目录重新配置——注意是删掉整个构建目录只改参数重跑缓存的变量未必能覆盖干净。5.2 链接与运行期两类报错第四条运行期报 SSL 相关错误r.error.message里出现证书验证失败的字样。现象是请求某个 HTTPS 地址一直失败换 HTTP 就正常。原因是 curl 编译时用的 TLS 后端和系统信任库路径对不上或者时间不对导致证书被判定为未生效。解决是先跑curl -V确认 Features 里有 SSL再用curl -v https://目标地址对照排查——如果命令行 curl 也失败问题在系统层跟 cpr 无关如果命令行成功而 cpr 失败检查链接的是不是另一份 curl。第五条程序启动就报找不到libcpr.so。现象是编译链接都通过了执行时报共享库加载失败。原因是库装在非标准前缀运行时加载器找不到。解决有三个层次临时用LD_LIBRARY_PATH/opt/cpr-1.3.0/lib ./probe验证确认是路径问题长期方案是在 CMake 里设置INSTALL_RPATH或者把前缀写进/etc/ld.so.conf.d/后跑一次ldconfig。千万别把LD_LIBRARY_PATH写进全局 shell 配置文件那会影响同一台机器上所有程序后患无穷。6. 验证跑通三组对照把能跑和跑对分开装完最忌讳的是编译通过就完事。我见过太多次编译没问题、一上真实网络就出状况的情况。下面这套验证分三层一层比一层接近真实使用。第一层静态检查。确认四样东西齐全检查项路径缺失后果主头文件/opt/cpr-1.3.0/include/cpr/cpr.h编译直接失败生成头文件/opt/cpr-1.3.0/include/cpr/generated/cpr_generated_includes.h编译报找不到文件库文件/opt/cpr-1.3.0/lib/libcpr.so或.a链接失败CMake 配置/opt/cpr-1.3.0/lib/cmake/cpr/cprConfig.cmakefind_package失败第二层本地回环验证。用一个本地起的小服务返回固定 JSON跑第 4 章那个最小例子确认status_code是 200、r.text内容正确、r.error.code是OK。这一步排除网络因素验证的是库链接正确、请求能发出去。同时故意请求一个不存在的端口确认r.error.code不是OK且程序走的是错误分支——错误分支没被验证过等于没写。第三层真实环境验证。挑一个内网的 HTTPS 接口用 Session 复用方式连发十次观察两件事十次全部成功说明连接复用和 TLS 会话没出问题r.elapsed从第二次开始明显下降说明连接确实被复用了。如果十次耗时一样说明每次都在重新建连回去检查是不是不小心每次都新建了 Session。最后给个习惯。从那以后我每次装完 cpr都会把这三层验证做成一个verify.sh留在构建目录里包含路径检查、本地回环请求、十次连发计时三段。原因很简单这套依赖横跨编译器、curl、SSL 三层任何一层动过表现都可能变化而人脑记不住上次验证时的细节。脚本跑一遍输出的三行结果就是这份环境还能不能用的黑匣子记录比回忆靠谱得多。装环境这事没有一劳永逸只有每次都走一遍的后悔药。希望帮到你。本文还有配套的精品资源点击获取