☰
libwebsockets HTTP 客户端实战:用超过 2KB 的超长 URL 发起 HTTPS 请求(minimal-http-client-hugeurl)
2026/10/14 17:19:13 网站建设 项目流程
  • 后端
  • 网络
  • 通信

【免费下载链接】libwebsockets

canonical libwebsockets.org networking library

项目地址:https://gitcode.com/gh_mirrors/li/libwebsockets
点击查看免费下载

本文基于 minimal-examples-lowlevel/http-client/minimal-http-client-hugeurl/README.md 及其配套源码,讲解 libwebsockets 中lws-minimal-http-client-hugeurl示例的构建与运行方式、超长 URL 的组织技巧、HTTP 客户端回调链(LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP→LWS_CALLBACK_RECEIVE_CLIENT_HTTP→LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ→LWS_CALLBACK_COMPLETED_CLIENT_HTTP)的工作机理,以及上下文参数(pt_serv_buf_size、fd_limit_per_thread、超时等)的配置依据,帮助读者完整复现这个"用 > 2.5KB 的 URL 访问服务器并接收整页响应"的最小化 HTTP/HTTPS 客户端。

示例定位:它验证什么

minimal-http-client-hugeurl位于 lowlevel(低层 API)风格的 http-client 示例目录下,在 http-client 目录索引 中的官方描述是:

minimal-http-client-hugeurl — Sends a > 2.5KB URL to warmcat.com

与目录里"连接并读取 warmcat.com 首页"的 minimal-http-client 不同,这个示例的核心差异点只有一个:把 URL 的查询串撑大到 2KB 以上(/?fakeparam=<2KB>),验证 libwebsockets 客户端在发送一个远超常规长度的请求行(request line)时,能否正确建立 TLS 连接、把完整 URL 无截断地发出去,并稳定收完整个页面响应。README 中给出的预期行为正是如此:

The application goes to https://warmcat.com/?fakeparam=<2KB> and receives the page data.

源码解析:2KB 的 URL 是怎么写进 C 代码的

核心文件 minimal-http-client-hugeurl.c 首先用一个字符串字面量拼接常量构造了这条超长 URI:

static const char * const uri = "/?fakeparam=" "00000000000000000000000000000000000000000000000000" "0000000000000000000000000000000000000000000000000000" ... "3333333333333333333333333333333333333333333333333333" /* 2000 */ ;

利用 C 语言"相邻字符串字面量自动拼接"的特性,把 2000 个字符分块写成每行约 50 个字符的形式,并在 500/1000/1500/2000 处标注了位置注释,既保证源码可读,又直观证明查询参数总长度达到 2KB。这个uri随后被原样赋给lws_client_connect_info的path字段:

i.path = uri; i.host = i.address; i.origin = i.address; i.method = "GET"; i.protocol = protocols[0].name; /* "http" */ i.pwsi = &client_wsi; lws_client_connect_via_info(&i);

值得注意的是host与origin都指向i.address,而address默认是libwebsockets.org(加-l本地模式时为localhost),即 README 里历史输出中的warmcat.com已被更新为当前域名,示例逻辑不变。

构建与运行

构建

README 给出的构建方式(在示例目录下执行,前提是系统已安装 libwebsockets 开发包):

$ cmake . && make

其 CMakeLists.txt 通过find_package(libwebsockets CONFIG REQUIRED)探测已安装的库,并用require_lws_config声明了三个硬性构建前提:

require_lws_config(LWS_ROLE_H1 1 requirements) require_lws_config(LWS_WITH_CLIENT 1 requirements) require_lws_config(LWS_WITH_TLS 1 requirements)

即:libwebsockets 必须启用 HTTP/1.x 角色(LWS_ROLE_H1)、客户端支持(LWS_WITH_CLIENT)与 TLS(LWS_WITH_TLS),否则不会生成可执行文件。也可以直接在仓库根目录用cmake . && make编译,该示例会由 lowlevel 示例的 CMake 体系统一纳入构建。

命令行参数

源码定义了四个开关(lws_switches数组 +lws_cmdline_option解析):

选项含义
-l本地模式:连接localhost:7681,并追加LCCSCF_ALLOW_SELFSIGNED允许自签证书
--server <addr>指定要连接的服务器地址(覆盖默认libwebsockets.org/localhost)
-p <port>指定端口,源码中显式校验必须在 16 位范围(1..65535),越界直接报错退出
--help打印帮助

此外,lws_cmdline_option_handle_builtin还会处理 libwebsockets 内建全局选项(如日志详细度),LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT保证全局 SSL 上下文完成初始化。

运行与预期输出

README 中记录的一次典型运行日志(连接建立 → 收到 26520 字节响应体 → 按块读出 → 事务完成):

$ ./lws-minimal-http-client [2018/03/04 14:43:20:8562] USER: LWS minimal http client hugeurl [2018/03/04 14:43:20:8571] NOTICE: Creating Vhost 'default' port -1, 1 protocols, IPv6 on [2018/03/04 14:43:20:8616] NOTICE: created client ssl context for default [2018/03/04 14:43:20:8617] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com [2018/03/04 14:43:21:1496] NOTICE: lws_client_connect_2: 0x1814dc0: address warmcat.com [2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: incoming content length 26520 [2018/03/04 14:43:22:0154] NOTICE: lws_client_interpret_server_handshake: client connection up [2018/03/04 14:43:22:0169] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 ...(按 ~1KB 的块持续读出,最后一块 read 974) [2018/03/04 14:43:22:3022] NOTICE: lws_http_client_read: transaction completed says -1 [2018/03/04 14:43:23:3042] USER: Completed

几个可以对照日志理解的要点:

  • port -1表示CONTEXT_PORT_NO_LISTEN——纯客户端进程,不监听任何端口;
  • 响应以约 1024 字节的粒度多次触发RECEIVE_CLIENT_HTTP_READ(块大小由下文pt_serv_buf_size与 TLS 记录边界共同决定),最后一次读出 974 字节后,lws_http_client_read返回完成信号(日志中transaction completed says -1,见 client-http.c 中lws_http_transaction_completed_client的判定路径);
  • 程序退出码由status != 200决定:bad变量在COMPLETED/CLOSED回调里按最终 HTTP 状态码赋值,main返回它,因此该示例天然带"是否 200"的成功判定。

回调链:HTTP 客户端必须"手动抽干"接收数据

示例的协议回调callback_http处理了 HTTP 客户端的四个关键 reason,这也是 lws-callbacks.h 文档化的标准用法:

1. 连接建立:捕获响应状态码

case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: status = (int)lws_http_client_http_response(wsi); lwsl_user("Connected with server response: %d\n", status); break;

lws-client.h 特别强调:lws_http_client_http_response()必须在LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP中捕获状态码,因为"之后再存储相关头部的内存会被释放,这个值就会丢失"。

2. 收到未解释数据:必须调用 lws_http_client_read

/* uninterpreted http content */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: { char buffer[1024 + LWS_PRE]; char *px = buffer + LWS_PRE; int lenx = sizeof(buffer) - LWS_PRE; if (lws_http_client_read(wsi, &px, &lenx) < 0) return -1; } return 0; /* don't passthru */

LWS_CALLBACK_RECEIVE_CLIENT_HTTP(= 46)的语义是"通知你有数据到了,但并不会替你读走它"。头文件注释明确警告:如果你在 HTTP 客户端中不处理它、不调用lws_http_client_read(),事件循环会反复被告知"同一批数据可读"而陷入空转。buffer预留了LWS_PRE头部空间(lws 可能向前写入的预区),长度 1024 与日志中每次read 1024正好对应。

3. 解帧后的载荷:RECEIVE_CLIENT_HTTP_READ

/* chunks of chunked content, with header removed */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: lwsl_user("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len); #if 0 /* enable to dump the html */ ... #endif return 0; /* don't passthru */

lws_http_client_read()的契约(见 lws-client.h)是:

  • 非 chunked 连接:把最多len字节的正文直接填入你的缓冲,返回前把len改写为实际填充量;
  • chunked 连接:线性缓冲里混着 chunk 头,无法整块交出,因此该函数会回调LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,in指向 chunk 起点、len为 chunk 长度,有多少 chunk(或部分 chunk)就回调多少次——这正是"chunked 帧无法一步处理完"的原因,也是本例日志里大量read 1024行的来源。

从源码结构看,这个抽干过程实现在 client-http.c:每次读走n字节后,若rx_chunked且chunk_remaining归零则推进 chunk 状态机;若已知Content-Length,则递减rx_content_remain,剩余量耗尽且content_length_given为真时走到completed:标签调用lws_http_transaction_completed_client(wsi),产生日志里的transaction completed。源码中还保留了#if 0注释的"逐字符 dump 页面 HTML"代码块(可打印 ASCII、非 ASCII 打印为.),把注释改回即可看到完整页面内容——这是 README 里"receives the page data"说法对应的可验证手段。

4. 事务收尾:退出事件循环

case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: client_wsi = NULL; bad = status != 200; lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ break; case LWS_CALLBACK_CLOSED_CLIENT_HTTP: client_wsi = NULL; bad = status != 200; lws_cancel_service(lws_get_context(wsi)); /* abort poll wait */ break;

LWS_CALLBACK_COMPLETED_CLIENT_HTTP(= 47)表示客户端事务完成;当前版本下它与"关闭"等价,因为客户端侧尚不支持事务管线化(头文件注释原话)。两个回调都置空client_wsi并调用lws_cancel_service()打断阻塞中的lws_service()等待——main的循环条件while (n >= 0 && client_wsi)随即退出,程序进入lws_context_destroy()收尾并打印Completed: OK/failed。

上下文与连接参数:为什么这么配

main()里的上下文配置是本示例最值得学习的部分:

lws_context_info_defaults(&info, NULL); lws_cmdline_option_handle_builtin(argc, argv, &info); info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; info.pt_serv_buf_size = 8192; info.timeout_secs = 10; info.connect_timeout_secs = 30; info.fd_limit_per_thread = LWS_FD_LIMIT_PER_THREAD_MIN + 1;

逐项含义:

  • LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT:全局一次性初始化 TLS 库,后续客户端连接复用该全局状态;
  • CONTEXT_PORT_NO_LISTEN:纯客户端,日志中port -1即来源于此;
  • pt_serv_buf_size = 8192:每个 service 循环的接收缓冲尺寸,直接决定单次lws_http_client_read最多能抽走的数据量(示例实际取 1024 的子缓冲);
  • timeout_secs = 10/connect_timeout_secs = 30:分别为连接建立后与建连阶段的超时控制,避免异常网络下进程永久挂起;
  • fd_limit_per_thread = LWS_FD_LIMIT_PER_THREAD_MIN + 1:这是针对"单客户端连接"进程的关键调优。lws-context-vhost.h 解释:默认 fd 表要按ulimit -n规模分配,而本例永远只有一个客户端 wsi/fd,告诉 lws 按LWS_FD_LIMIT_PER_THREAD_MIN(内部保留 + 8 的连接开销)再加 1 个连接来分配即可,显著降低内存占用;
  • mbedTLS/WolfSSL 特例:OpenSSL 走系统信任库,而 mbedTLS 必须显式告知信任哪个 CA,因此源码在LWS_WITH_MBEDTLS或USE_WOLFSSL下设置info.client_ssl_ca_filepath = "./warmcat.com.cer",对应随附的 warmcat.com.cer 文件。

连接信息侧则只需标记i.ssl_connection = LCCSCF_USE_SSL(本地-l模式再或上LCCSCF_ALLOW_SELFSIGNED),协议名取protocols[0].name(即"http"),pwsi回传client_wsi供主循环判断生命周期。

测试体系:默认不访问公网

示例的 CMakeLists.txt 同时把该二进制接入了 ctest 回归:它会先用ctest-background.sh后台拉起仓库自带的test-server(固定随机端口,FIXTURES_SETUP hc_huge_srv),再运行两条用例:

add_test(NAME http-client-hugeurl-warmcat COMMAND lws-minimal-http-client-hugeurl -l --server ${LWS_CTEST_SERVER_RESOLVE} -p ${PORT_HC_HUGE_SRV}) add_test(NAME http-client-hugeurl-warmcat-h1 COMMAND lws-minimal-http-client-hugeurl -l --server ${LWS_CTEST_SERVER_RESOLVE} -p ${PORT_HC_HUGE_SRV} --h1)

即:-l走本地自签服务验证 2KB+ URL 的收发正确性,--h1用例则强制 HTTP/1.x 角色再验证一次;测试用例工作目录设为示例自身所在目录(保证能找到warmcat.com.cer),超时 60 秒,并在并行 CI(SAI_OVN)下通过sai_resource申请连接资源租约。从这条测试命令还能确认--h1是lws_cmdline_option_handle_builtin提供的内建选项之一。

小结

minimal-http-client-hugeurl用一个"超长 URL"的压力点,展示了 libwebsockets HTTP 客户端的完整最小闭环:

  1. 用字符串字面量拼接出 2KB 级path,交给lws_client_connect_via_info()发起 TLS GET;
  2. 在ESTABLISHED_CLIENT_HTTP捕获响应码、在RECEIVE_CLIENT_HTTP里用lws_http_client_read()持续抽干数据、在RECEIVE_CLIENT_HTTP_READ里拿到已剥除 chunk 帧的载荷、在COMPLETED/CLOSED_CLIENT_HTTP里判定成败并用lws_cancel_service()结束服务循环;
  3. 上下文按单连接客户端调优(CONTEXT_PORT_NO_LISTEN、pt_serv_buf_size、fd_limit_per_thread = LWS_FD_LIMIT_PER_THREAD_MIN + 1、双超时),TLS 信任链按所选 TLS 库区分处理(系统信任库 vs 显式client_ssl_ca_filepath);
  4. 构建上要求LWS_ROLE_H1+LWS_WITH_CLIENT+LWS_WITH_TLS,ctest 用例默认通过本地test-server后台服务完成验证,不依赖公网。

掌握这个示例后,把它当作模板替换uri、address/port、回调中的载荷处理,即可快速搭建任意 HTTPS 拉取型客户端。

  • 后端
  • 网络
  • 通信

【免费下载链接】libwebsockets

canonical libwebsockets.org networking library

项目地址:https://gitcode.com/gh_mirrors/li/libwebsockets
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询