libwebsockets HTTPS 服务端实现机制详解
概览
- 代码根路径:libwebsockets`
- 涉及模块:HTTP 监听与解析、TLS 初始化与握手、HTTPS 管道整合与连接管理、性能优化与缓存
模块流程图
HTTP 连接初始化
lws_create_context lws_create_vhost _lws_vhost_init_server AF _lws_vhost_init_server_af lws_plat_set_socket_options lws_socket_bind listen insert listen wsi accept lws_adopt_descriptor_vhost enter HTTP/WS role
HTTP/1 解析状态机
method line continue end START WSI_TOKEN_NAME_PART URI / method WSI_TOKEN_HTTP WSI_TOKEN_SKIPPING CRLF WSI_PARSING_COMPLETE lws_handshake_server
TLS 握手(服务端)
App LWS SSL Client lws_create_context 1 SSL_CTX_new + configure 2 load certs / key 3 accept() 4 SSL_new + set_fd + set_nbio 5 SSL_accept (non-blocking) 6 WANT_READ / WANT_WRITE 7 handshake done + ALPN 8 enter HTTP/WS callbacks 9 App LWS SSL Client
HTTPS 管道整合
yes no yes no accept SSL SSL_new SSL_accept done HTTP parse lws_handshake_server protocol callback
关键数据结构
struct lws_protocols
- 定义位置:
include/libwebsockets/lws-protocols-plugins.h:44 - 关键字段:
name、callback、per_session_data_size、rx_buffer_size、id、user、tx_packet_size - 作用:声明协议名与对应回调,服务器在路由后通过该结构触发应用层处理(HTTP/WS 等)。
struct lws_vhost
- 定义位置:
lib/core-net/private-lib-core-net.h:425 - 关键字段:
protocols:协议数组引用listen_wsi:该 vhost 的监听 wsi 列表listen_port、iface:监听端口与绑定接口tls:TLS 相关上下文(含SSL_CTX*、策略、缓存等)options:行为选项位(如允许共享监听、IPv6 配置、TLS 选项)
struct lws
- 定义位置:
lib/core-net/private-lib-core-net.h:634 - 关键字段:
a.context、a.vhost、a.protocol:上下文、虚拟主机、协议绑定http.ah:HTTP 头解析缓冲与状态机tls.ssl:SSL 会话句柄与握手状态desc.sockfd:底层套接字 fd
HTTP 实现细节
监听套接字创建与接受
- 上下文创建入口:
lib/core/context.c:393中的lws_create_context - vhost 初始化调用:
lib/core-net/vhost.c:1030调用_lws_vhost_init_server - 地址族与监听创建:
lib/roles/http/server/server.c:449、89_lws_vhost_init_server/_af - 绑定与监听:
bind()封装:lib/core-net/network.c:274lws_socket_bindlisten():lib/roles/http/server/server.c:403
- 接受连接:
lib/roles/listen/ops-listen.c:28rops_handle_POLLIN_listen,调用accept()后经lws_adopt_descriptor_vhost进入角色与协议处理(147)。
HTTP/1 解析器状态机
- 入口:
lib/roles/h1/ops-h1.c:41lws_read_h1 - 服务器握手驱动:
lib/roles/http/server/server.c:2285lws_handshake_server - 解析核心:
lib/roles/http/parsers.c:1018lws_parse - 机制要点:
- 单字节状态机,逐字节推进,支持分片与未知头处理
- 方法行解析:请求行遇空格切换到
WSI_TOKEN_HTTP版本解析 - 头名/值解析:利用
lextable_h1做头名识别,存储片段索引 - 结束检测:CRLF 进入
WSI_PARSING_COMPLETE,随后进行路由与协议确认
路由与协议回调触发
- 服务器握手后路由:
lib/roles/http/server/server.c:2240起,lws_bind_protocol绑定协议,进入LRS_ESTABLISHED - 默认 HTTP 处理:
lib/roles/http/server/server.c:2530lws_http_action - 升级处理:检测
Upgrade头,进入 WS 或 H2C(2499、2536),并发出LWS_CALLBACK_HTTP_CONFIRM_UPGRADE - 应用回调枚举:
include/libwebsockets/lws-callbacks.h:217LWS_CALLBACK_HTTP、HTTP_BODY、HTTP_BODY_COMPLETION等
TLS 握手实现
服务器握手流程分阶段
- 数据报文与握手消息概览(以TLS1.2/1.3为准)
- ClientHello:客户端发起,包含支持的协议版本、随机数、支持的密码套件、扩展(如SNI、ALPN)。
- ServerHello:服务器选择版本与套件(TLS1.3还会下发
EncryptedExtensions、Certificate、CertificateVerify、Finished)。TLS1.2通常下发ServerKeyExchange、Certificate、ServerHelloDone等。 - 证书链与验证:服务器发送证书链;若要求客户端证书,客户端在后续发送其证书供验证。
- 秘钥协商:基于选定套件完成密钥交换(ECDHE等),双方派生主密钥与会话密钥。
- Finished:双方发送
Finished报文确认握手完整性并切换至应用数据加密通道。
- 初始化阶段(配置SSL_CTX)
lws_tls_server_vhost_backend_init:创建SSL_CTX(SSLv23_server_method),关闭SSLv2/3并禁用压缩,启用SSL_OP_CIPHER_SERVER_PREFERENCE;配置ECDH/DH自动参数;按需设置cipher_list(TLS1.2)与ciphersuites(TLS1.3);注册SNI回调以在握手时根据servername切换SSL_CTX。- 证书链与私钥加载:
lws_tls_server_certs_load支持从文件或内存(DER/PEM)载入,并通过SSL_CTX_check_private_key完成匹配校验;可选加载CA用于客户端证书验证。
- 接入阶段(TCP层)
- 监听fd上
accept()得到会话fd,应用通用套接字选项(非阻塞、TCP_NODELAY等)。
- 监听fd上
- 会话建立阶段(SSL对象创建与非阻塞BIO)
lws_tls_server_new_nonblocking:SSL_new(ctx)创建SSL对象;SSL_set_fd(fd)关联底层fd;SSL_get_rbio/wbio+BIO_set_nbio(1)将读写BIO均设为非阻塞;可启用SSL_set_info_callback捕获握手事件。
- 握手推进阶段(非阻塞驱动)
lws_tls_server_accept:调用SSL_accept,如返回WANT_READ/WRITE,通过lws_change_pollfd切换事件关注(POLLIN/POLLOUT),等待下一次可读/可写再继续调用SSL_accept。- 握手成功后:
- 提取对端证书信息(如
Common Name); - 记录协商的套件与ALPN;
- 若
SSL_pending()存在握手期间缓冲的明文,加入TLS缓冲队列并合成POLLIN以尽快消费。
- 提取对端证书信息(如
- ALPN选择:在服务器端,
lws_context_init_alpn设置SSL_CTX_set_alpn_select_cb,握手过程中由OpenSSL在alpn_cb基于vhost配置选择协议(如h2或http/1.1);完成后通过lws_role_call_alpn_negotiated让对应角色执行协商后逻辑(例如h2切换为HTTP/2状态机)。
- 失败与关闭阶段
- 错误(
SSL_ERROR_SYSCALL/SSL_ERROR_SSL)则中止;正常关闭使用SSL_shutdown并释放资源。
- 错误(
客户端证书与SNI
- 客户端证书校验:当启用
LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT时,SSL_CTX_set_verify结合自定义回调OpenSSL_verify_callback把验证结果报告到LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION,用户代码可参与决策。 - SNI(Server Name Indication):
lws_ssl_server_name_cb在握手阶段读取servername,基于端口与名称选择匹配的vhost并切换其SSL_CTX,实现同端口多证书与多虚拟主机。
加密/解密数据路径
- 加密写路径
- 应用层通过
lws_write提交明文;底层在TLS会话活跃时,写入由SSL_write完成,将明文经记录层分片、MAC(或AEAD)与加密后通过套接字发送;非阻塞下若WANT_WRITE,由POLLOUT继续驱动直到完成。
- 应用层通过
- 解密读路径
- 网络数据进入读BIO;调用
SSL_read将密文经记录层处理、解密与完整性校验得到明文;若内部有缓存(SSL_pending),通过合成POLLIN尽快继续上送;明文最终进入HTTP/WS解析路径(lws_read_h1/lws_ws_rx_sm)。
- 网络数据进入读BIO;调用
会话复用与性能
- 会话缓存:
openssl-session.c基于vhost维护LRU缓存,lws_tls_session_new_cb在新会话建立时插入条目,lws_tls_reuse_session在新连接时尝试复用,必要时延长会话有效期;TTL到期由定时器清理。 - 连接层优化:监听侧
SO_REUSEPORT便于多线程负载分担;TCP_FASTOPEN缩短连接建立延迟;TCP_NODELAY减少Nagle影响。TLS层采用服务器优先套件以选择更高性能的组合(如AES-GCM/CHACHA20-Poly1305)。
HTTPS 通信整合与连接管理
HTTP over TLS 管道
- TLS 监听接入:
lws_server_socket_service_ssl()在握手状态LRS_SSL_INIT/ACK_PENDING间推进(lib/tls/tls-server.c:128起) - 可选回退与混用:支持
LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT等选项在同端口区分非 TLS 访问并执行重定向或回退(tls-server.c:190起)
vhost 与 wsi 关系与多路复用
struct lws_vhost管理监听、协议集与 TLS 上下文(private-lib-core-net.h:425)- 每个新连接为一个
struct lws(wsi),绑定到 vhost 并根据角色(HTTP/WS/H2)驱动状态机 - HTTP/2 与 WS 在 wsi 上具备子流/扩展能力,靠
mux与角色 ops 管理(同头文件)
性能优化措施
- TLS 会话缓存:
- 复用与缓存管理:
lib/tls/openssl/openssl-session.c:77lws_tls_reuse_session、210lws_tls_session_new_cb - 基于 vhost 的 LRU 缓存与 TTL 过期回调,控制条目上限(
178、152)
- 复用与缓存管理:
- OCSP Stapling:未检索到直接集成入口(代码库中未匹配 OCSP/stapling API),可通过 OpenSSL 回调在用户层拓展;当前以会话缓存与 SNI/ALPN 优化为主。
最小 HTTPS 服务器示例
c
#include <libwebsockets.h>
static int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) {
switch (reason) {
case LWS_CALLBACK_HTTP: {
unsigned char buf[LWS_PRE + 512], *p = &buf[LWS_PRE], *end = &buf[sizeof(buf) - 1];
const char *body = "hello";
if (lws_add_http_common_headers(wsi, 200, "text/plain", (lws_filepos_t)5, &p, end)) return 1;
if (lws_finalize_write_http_header(wsi, &buf[LWS_PRE], &p, end)) return 1;
if (lws_write(wsi, (unsigned char *)body, 5, LWS_WRITE_HTTP_FINAL) < 0) return 1;
return 1;
}
default:
break;
}
return 0;
}
static const struct lws_protocols protocols[] = {
{ "http", callback_http, 0, 0, 0, NULL, 0 },
LWS_PROTOCOL_LIST_TERM
};
int main(void) {
struct lws_context_creation_info info;
memset(&info, 0, sizeof(info));
info.port = 8443;
info.protocols = protocols;
info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
info.ssl_cert_filepath = "localhost-100y.cert";
info.ssl_private_key_filepath = "localhost-100y.key";
struct lws_context *cx = lws_create_context(&info);
if (!cx) return 1;
while (lws_service(cx, 0) >= 0) { }
lws_context_destroy(cx);
return 0;
}
- 运行前准备:在工作目录放置
localhost-100y.cert与localhost-100y.key(库自带多个 minimal 示例使用同名证书,可参照minimal-examples-lowlevel/http-server/minimal-http-server-tls/README.md的生成命令)。 - 编译示例命令(根据环境调整):
gcc -o minimal-https minimal-https.c -lwebsockets- 或使用 CMake 将该文件加入目标并链接
libwebsockets。
代码参考索引
lib/core/context.c:393lws_create_contextlib/core-net/vhost.c:1030lws_create_vhost内部触发_lws_vhost_init_serverlib/roles/http/server/server.c:449_lws_vhost_init_serverlib/roles/http/server/server.c:89_lws_vhost_init_server_aflib/core-net/network.c:274lws_socket_bindlib/roles/http/server/server.c:403listen()调用处lib/roles/listen/ops-listen.c:28rops_handle_POLLIN_listen接受连接lib/roles/http/parsers.c:1018lws_parseHTTP 状态机lib/roles/h1/ops-h1.c:41lws_read_h1读入驱动include/libwebsockets/lws-protocols-plugins.h:44struct lws_protocolsinclude/libwebsockets/lws-callbacks.h:217回调枚举lib/tls/openssl/openssl-server.c:483lws_tls_server_vhost_backend_initlib/tls/openssl/openssl-server.c:585lws_tls_server_certs_load调用点lib/tls/openssl/openssl-server.c:594lws_tls_server_new_nonblockinglib/tls/openssl/openssl-server.c:655lws_tls_server_acceptlib/tls/openssl/openssl-session.c:77lws_tls_reuse_sessionlibwebsockets.dox:857INPUT包含READMEs
TLS1.2 与 TLS1.3差异要点
- TLS1.3:握手消息经过加密阶段分层下发(如
EncryptedExtensions),秘钥派生流程简化;套件仅定义对称算法组合(AEAD),密钥交换由组参数控制(如X25519)。 - TLS1.2:握手明文阶段较长,套件同时定义密钥交换与对称算法;服务器可能下发
ServerKeyExchange等独立消息。 - 在libwebsockets中,具体差异由OpenSSL后端管理,lws侧通过
SSL_CTX配置套件、ALPN与回调,握手推进统一由SSL_accept与事件循环驱动。
TLS 握手时序(细粒度)
Client libwebsockets OpenSSL HTTP/WS TCP connect (accept) 1 SSL_new + set_fd + set_nbio 2 WANT_READ / WANT_WRITE 3 resume SSL_accept 4 loop [SSL_accept non-blocking] ClientHello parsed 5 SNI select (servername) 6 ALPN select (h2 / http/1.1) 7 ServerHello 8 EncryptedExtensions 9 Certificate / CertificateVerify 10 Finished 11 opt [TLS1.3] ServerHello / Certificate / ServerKeyExchange / ServerHelloDone 12 ClientKeyExchange / ChangeCipherSpec / Finished 13 opt [TLS1.2] handshake done 14 route to HTTP/WS (lws_handshake_server) 15 application data over TLS 16 Client libwebsockets OpenSSL HTTP/WS
对应代码实现关键位置
- 监听与接受:
lib/roles/listen/ops-listen.c:28接受连接并调用lws_adopt_descriptor_vhost - 创建SSL与非阻塞BIO:
lib/tls/openssl/openssl-server.c:594lws_tls_server_new_nonblocking - 推进握手:
lib/tls/openssl/openssl-server.c:655lws_tls_server_accept(WANT_READ/WRITE 驱动) - 客户端证书验证回调:
lib/tls/openssl/openssl-server.c:39OpenSSL_verify_callback - SNI选择:
lib/tls/openssl/openssl-server.c:100lws_ssl_server_name_cb - ALPN选择注册:
lib/tls/tls.c:228SSL_CTX_set_alpn_select_cb;协商后通知:lib/core-net/vhost.c:113lws_role_call_alpn_negotiated - 握手后进入HTTP解析:
lib/roles/http/server/server.c:2285lws_handshake_server;H1解析状态机:lib/roles/http/parsers.c:1018lws_parse