- 后端
- 网络
- 通信
【免费下载链接】libwebsockets
canonical libwebsockets.org networking library
本篇围绕 libwebsockets 仓库中的 minimal-http-client-custom-headers 最小示例展开,讲解 HTTP 客户端如何发送自定义请求头(如dnt: 1)以及如何在收到响应后查询服务器下发的自定义头(如test-custom-header)。读完本文,你将掌握lws_add_http_header_by_name()、lws_hdr_custom_length()/lws_hdr_custom_copy()这组自定义头 API 的完整用法、对应的回调时机,以及该能力仅支持 HTTP/1 的原因和源码层面的实现原理。
一、示例定位与能力边界
该示例位于 minimal-examples-lowlevel/http-client/minimal-http-client-custom-headers/,其 README 明确说明两件事:
- 应用演示如何发送和接收自定义头;
- 目前只在 HTTP/1 上工作,因此即使构建时启用了 h2,应用也会强制使用 HTTP/1.1(源码中通过
i.alpn = "http/1.1"实现,见下文)。
从 顶层 README 的示例列表 也可以确认其定位:"Shows how to send and receive custom headers (h1 only)"。
二、构建与命令行用法(完整继承原文档)
构建方式(README 原文):
$ cmake . && make该示例的 CMakeLists.txt 通过require_lws_config检查三项依赖,未满足则直接不构建目标:
require_lws_config(LWS_ROLE_H1 1 requirements) require_lws_config(LWS_WITH_CLIENT 1 requirements) require_lws_config(LWS_WITH_CUSTOM_HEADERS 1 requirements)其中LWS_WITH_CUSTOM_HEADERS是启用自定义头存储与查询的编译开关,在 顶层 CMakeLists.txt 中定义为 ON(默认开启),其注释为 "Store and allow querying custom HTTP headers (H1 only)"——从这一措辞可直接印证 README 中 "H1 only" 的限制。
命令行参数(README 参数表):
| Commandline option | Meaning |
|---|---|
| -d | Debug verbosity in decimal, eg, -d15 |
| -l | Connect to https://localhost:7681 and accept selfsigned cert |
| -n | no TLS |
对应源码中的struct lws_switches定义(minimal-http-client-custom-headers.c):
static const struct lws_switches switches[] = { [LWS_SW_D] = { "-d", "Debug logs (e.g. -d 15)" }, [LWS_SW_L] = { "-l", "Enable -l feature" }, [LWS_SW_N] = { "-n", "Enable -n feature" }, [LWS_SW_HELP] = { "--help", "Show this help information" }, };默认情况下(不带-l),客户端连接warmcat.com:443;带-l时改连localhost:7681并加LCCSCF_ALLOW_SELFSIGNED接受自签证书;带-n时清除LCCSCF_USE_SSL走明文 http。相关分支逻辑在 main() 中:
if (!lws_cmdline_option(argc, argv, switches[LWS_SW_N].sw)) i.ssl_connection = LCCSCF_USE_SSL; if (lws_cmdline_option(argc, argv, switches[LWS_SW_L].sw)) { i.port = 7681; i.address = "localhost"; i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; } else { i.port = 443; i.address = "warmcat.com"; }三、运行效果:README 中的两条验证路径
路径 1:访问 warmcat.com 读取服务器自定义头
$ ./lws-minimal-http-client-custom-headers [2019-03-11 05:46:45:7582] USER: LWS minimal http client Custom Headers [-d<verbosity>] [-l] [--h1] [2019-03-11 05:46:45:7671] NOTICE: created client ssl context for default [2019-03-11 05:46:46:7812] USER: Connected with server response: 200 [2019-03-11 05:46:46:7812] NOTICE: callback_http: custom header: 'hello' [2019-03-11 05:46:46:7814] USER: RECEIVE_CLIENT_HTTP_READ: read 1024 ...应用查找的是服务器返回的test-custom-header(warmcat.com 固定下发值hello)。
路径 2:本地回环验证"请求侧"自定义头
README 指出:使用-n -l让应用以明文 http 连接 localhost:7681,可以用 tcpdump 确认dnt:1头确实发出;或者在本机 :7681 端口运行 test server 并加-d1151日志级别,观察解析器逐字节解析该头的过程:
[2019-03-11 05:48:53:6806] PARSER: WSI_TOKEN_NAME_PART 'd' 0x64 (role=0x20000000) wsi->lextable_pos=0 [2019-03-11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART 'n' 0x6E (role=0x20000000) wsi->lextable_pos=567 [2019-03-11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART 't' 0x74 (role=0x20000000) wsi->lextable_pos=-1 [2019-03-11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART ' ' 0x20 (role=0x20000000) wsi->lextable_pos=-1 [2019-03-11 05:48:53:6807] PARSER: WSI_TOKEN_NAME_PART '1' 0x31 (role=0x20000000) wsi->lextable_pos=-1这两条路径分别验证了"响应侧读自定义头"与"请求侧发自定义头"两个方向,与标题中的 send/receive 完全对应。
四、发送自定义头:LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER
发送自定义头的标准做法是在客户端握手请求头尚未写出的回调LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER中追加。示例代码(minimal-http-client-custom-headers.c):
case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: { unsigned char **p = (unsigned char **)in, *end = (*p) + len; /* * How to send a custom header in the request to the server */ if (lws_add_http_header_by_name(wsi, (const unsigned char *)"dnt", (const unsigned char *)"1", 1, p, end)) return -1; break; }要点:
in是unsigned char **类型的写指针,end = (*p) + len为缓冲区末尾;lws_add_http_header_by_name()成功后会把*p前移,失败返回非 0,此时应返回 -1 终止连接;- 头名可以不带冒号(如
"dnt"),libwebsockets 会按已知头/未知头自动处理分隔与拼装,最终线上格式即dnt: 1(路径 2 的 PARSER 日志逐字符印证了这一点); - 该 API 声明于 lws-http.h,实现位于 lib/sansio/http/header.c,它把
name: value\r\n追加进 wsi 的附加头区(ah),供后续写 socket 时统一发出。
五、接收自定义头:LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP 中查询
收到响应头后(LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP),用两个查询 API 读取未知头:
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: status = (int)lws_http_client_http_response(wsi); lwsl_user("Connected with server response: %d\n", status); /* * How to query custom headers (http 1.x only at the momemnt) * * warmcat.com sends a custom header "test-custom-header" for * testing, it has the fixed value "hello". */ n = lws_hdr_custom_length(wsi, "test-custom-header:", 19); if (n < 0) lwsl_notice("%s: Can't find test-custom-header\n", __func__); else { if (lws_hdr_custom_copy(wsi, val, sizeof(val), "test-custom-header:", 19) < 0) lwsl_notice("%s: custom header too long\n", __func__); else lwsl_notice("%s: custom header: '%s'\n", __func__, val); } break;这两个 API 的文档(lws-http.h)说明了核心语义:
- libwebsockets 认识约 100 个常见 HTTP 头,遇到已知头会解析成 token 索引(可用
lws_hdr_copy()按WSI_TOKEN_*枚举查询);遇到不认识的自定义头,则把名字和值原样存起来,用lws_hdr_custom_length()/lws_hdr_custom_copy()查询; name参数要求带结尾冒号("test-custom-header:",长度 19)。源码实现 lws_hdr_custom_length() 和 lws_hdr_custom_copy() 通过strncmp与存储的名字做区分大小写的精确匹配,这就是为什么必须带上:且大小写要与服务器下发一致(另有一个lws_hdr_custom_name_foreach()回调式遍历 API 可枚举全部自定义头名,见 lws-http.h);lws_hdr_custom_length()返回 -1(不存在)或值部分长度;lws_hdr_custom_copy()要求缓冲区能容纳值外加一个结尾 NUL,否则返回 -1;- 使用这些 API 的前提是库以
LWS_WITH_CUSTOM_HEADERS编译(默认开启),源码中所有实现都被#if defined(LWS_WITH_CUSTOM_HEADERS)包裹,见 parsers.c。
六、从源码看:未知头是如何被存储的
"为什么只有 H1" 可以从 H1 解析器对未知头的存储机制中理解。lib/sansio/http/parsers.c 中定义了未知头(UHO)在 ah 缓冲区内的条目布局,偏移量定义在 private-lib-roles-http.h:
#define UHO_NLEN 0 #define UHO_VLEN 2 #define UHO_LL 4 #define UHO_NAME 8即每个未知头是一条变长记录:name 长度(2B) | value 长度(2B) | 下一条指针(4B) | 名字+值。当 H1 解析器发现某个头名不在已知表中时,lws_h1_unknown_name_ended()会把当前ah->unk_pos登记进unk_ll_head开头的单向链表(UHO_LL存 next 指针),随后进入WSI_TOKEN_UNKNOWN_VALUE_PART状态收集值直到下一个 CRLF。上述lws_hdr_custom_length()等查询 API 正是沿这条链表做逐节点匹配——这解释了 README "http 1.x only" 的限制来源:这条"未知头链表"机制是 H1 解析路径(WSI_TOKEN_*状态机)特有的;H2/H3 的头在 hpack/HPACK 解码后走的是另一套 header 表示,查询路径与数据布局不同(hpack 侧虽有LWS_WITH_CUSTOM_HEADERS分支,但该 minimal 示例通过固定i.alpn = "http/1.1"主动规避了 h2 场景,见 main() 注释)。
七、响应体的读取与回调闭环
示例还展示了 H1 客户端读取响应体的标准两段式:
/* 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); return 0; /* don't passthru */ /* 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拿到原始内容缓冲,需手动调lws_http_client_read()推进;- 库剥掉分块/头部后,通过
LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ逐块回调干净的负载(这就是运行日志中read 1024的来源); LWS_CALLBACK_COMPLETED_CLIENT_HTTP/LWS_CALLBACK_CLOSED_CLIENT_HTTP中标记client_wsi = NULL、按status != 200置bad,并lws_cancel_service()唤醒主循环退出。
主循环是最小化的:
lws_client_connect_via_info(&i); while (n >= 0 && client_wsi) n = lws_service(context, 0);另外值得注意的两个细节:info.port = CONTEXT_PORT_NO_LISTEN表明纯客户端不监听任何端口;info.fd_limit_per_thread = LWS_FD_LIMIT_PER_THREAD_MIN + 1告知 libwebsockets 该 context 同时只有一路连接,从而缩小 fd 表分配;在 mbedTLS/wolfSSL 下还需显式info.client_ssl_ca_filepath = "./warmcat.com.cer"(该证书文件随示例提供),而 OpenSSL 直接使用系统信任库。
八、小结
| 场景 | 关键 API / 回调 | 所在文件 |
|---|---|---|
| 请求侧发自定义头 | lws_add_http_header_by_name()@LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER | header.c |
| 响应侧查询自定义头长度 | lws_hdr_custom_length(wsi, "name:", nlen) | parsers.c |
| 响应侧拷贝自定义头值 | lws_hdr_custom_copy() | parsers.c |
| 枚举全部自定义头名 | lws_hdr_custom_name_foreach() | parsers.c |
| 编译开关 | LWS_WITH_CUSTOM_HEADERS(默认 ON,H1 only) | CMakeLists.txt |
需要记住的实操要点:查询时头名必须带冒号且大小写一致;自定义头查询当前仅 HTTP/1 路径可用,如示例所示可用i.alpn = "http/1.1"强制降级到 H1;验证请求侧是否发出自定义头,可参照 README 用 tcpdump 或高日志级别(-d1151)的 test server 观察WSI_TOKEN_NAME_PART解析日志。
- 后端
- 网络
- 通信
【免费下载链接】libwebsockets
canonical libwebsockets.org networking library
相关推荐
libwebsockets 自定义 HTTP 响应头实战:minimal-http-server-custom-headers 示例深度解析
libwebsockets 自定义 HTTP 响应头实战:minimal http server custom headers 示例深度解析 导读 本篇文章以
人工智能AI Agent多模态语音AI 应用基于 libwebsockets 的 HTTP 自定义请求/响应头处理实战:minimal-http-client-custom-headers 源码剖析
基于 libwebsockets 的 HTTP 自定义请求/响应头处理实战:minimal http client custom headers 源码剖析 导读
人工智能AI Agent多模态语音AI 应用libwebsockets 自定义事件循环集成实战:解析 lws-minimal-http-server-eventlib-custom 示例
libwebsockets 自定义事件循环集成实战:解析 lws minimal http server eventlib custom 示例 导读 lws m
人工智能AI Agent多模态语音AI 应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考