You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache mod_proxy_wstunnel代理WebSocket报AH01991错误排查

问题:Apache mod_proxy_wstunnel代理WebSocket时异常重置连接

我使用Apache的mod_proxy_wstunnel模块在客户端站点与后端WebSocket服务器之间做数据代理,但Apache会异常重置套接字连接。
当前我的服务端与客户端代码逻辑非常简单:

客户端JS代码

var socket = new WebSocket("wss://local-test.mysite.com/wss/", "basic-protocol");

socket.onopen = function(e) {
    console.log('open: ', e);
};
socket.onerror = function(e) {
    console.log('error: ', e);
};
socket.onmessage = function(e) {
    console.log('message: ', e);
};
socket.onclose = function(e) {
    console.log('close: ', e);
};

服务端核心代码(C++)

为精简内容已省略部分struct细节:

int server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bind(server, ...); // 绑定到4433端口
listen(server, 1024);
int client = accept(server, ...);

char buff[4096];
long r = recv(client, buffer, sizeof(buffer), 0);

故障现象

握手请求接收流程可正常运行:JS加载完成并创建WebSocket实例后,服务端可正常接收到如下HTTP请求报文:

GET /wss/ HTTP/1.1
Host: localhost:4433
Pragma: no-cache
Cache-Control: no-cache
User-Agent: { user agent string }
Origin: https://local-test.mysite.com
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: { cookie data here }
Sec-WebSocket-Key: {base64 key}
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Protocol: basic-protocol
X-Forwarded-For: 192.168.11.1
X-Forwarded-Host: local-test.mysite.com
X-Forwarded-Server: local-test.mysite.com
Connection: Upgrade
Upgrade: websocket

我解析报文中的Sec-WebSocket-Key字段,拼接WebSocket标准魔数("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"),计算SHA1哈希值后做base64编码,向客户端返回如下握手响应:

HTTP/1.1 101 Switching Protocols\r\n
Connection: Upgrade\r\n
Upgrade: websocket\r\n
Sec-WebSocket-Accept: {hashed response key}\r\n
Sec-WebSocket-Protocol: basic-protocol\r\n\r\n

返回响应后服务端回到recv调用等待后续前端发送的数据(通过页面DOM按钮触发发送),但立刻抛出ECONNRESET错误;同时前端JS WebSocket连接以1006错误码(异常终止)关闭,Apache日志输出如下报错:

[ssl:info] (70014)End of file found: [client 192.168.11.1:54673] AH01991: SSL input filter read failed.

我无法确定问题根因是Apache代理配置错误还是服务端握手响应不符合规范,以下是对应虚拟主机的Apache配置内容:

Listen local-test.mysite.com:80
Listen local-test.mysite.com:443
<VirtualHost local-test.mysite.com:80>
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L,QSA]
</VirtualHost>
<VirtualHost local-test.mysite.com:443>
    ServerAdmin admin@mysite
    DocumentRoot "/Code/source/dev/web"
    ErrorLog /var/log/httpd/web/error.log
    CustomLog /var/log/httpd/web/access.log common
    ServerName local-test.mysite.com 
    ServerAlias local-test
    SSLEngine on
    SSLOptions +StrictRequire
    SSLCertificateFile /etc/httpd/ssl/mysite.crt
    SSLCertificateKeyFile /etc/httpd/ssl/mysite.key

    <Directory /Code/source/dev/web>
        RewriteEngine On
        RewriteBase /
        RewriteRule ^/index\.php$ - [L,NC]
        RewriteCond %{REQUEST_URI} !\.(png|jpg|gif|jpeg|bmp|svg|ico)$
        RewriteCond %{REQUEST_URI} !\.(css|js|map|ttf|woff|woff2|eot)$
        RewriteRule . index.php [L]
    </Directory>

    # Websocket config
    SSLProxyEngine on

    ProxyPreserveHost On
    ProxyRequests Off
    ProxyPass /wss ws://localhost:4433 keepalive=On enablereuse=On connectiontimeout=1800
    ProxyPassReverse /wss ws://localhost:4433

    RewriteEngine on
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule /(.*) ws://localhost:4433%{REQUEST_URI} [P,L]
</VirtualHost>

我已尝试调整ProxyPass与Rewrite规则的多种组合配置,均未解决问题。

当前运行环境

  • Apache/2.4.51 (Fedora)
  • Chrome 版本 103.0.5060.53

回答

问题由两处错误共同导致,按顺序修改即可解决:

1. 服务端握手响应格式错误

你写的响应字符串里手动加了\r\n换行符,但代码里每一行响应头直接做了物理换行,实际输出时会在每个\r\n前额外多一个LF字符,相当于响应头段提前出现了空行,Apache代理校验HTTP响应格式不通过,会直接主动断开连接。
正确的响应要保证所有头行仅以\r\n作为行结尾,不要在字符串内硬敲物理换行,参考写法:

const char* ws_resp = "HTTP/1.1 101 Switching Protocols\r\n"
"Connection: Upgrade\r\n"
"Upgrade: websocket\r\n"
"Sec-WebSocket-Accept: {计算后的正确accept值}\r\n"
"Sec-WebSocket-Protocol: basic-protocol\r\n"
"\r\n";

同时校验Sec-WebSocket-Accept的计算逻辑:必须是Sec-WebSocket-Key的值拼接魔数字符串后,取SHA1计算得到的20字节原始二进制结果做base64编码,不要把SHA1输出的十六进制字符串拿去做base64,值错误同样会导致握手失败。

2. Apache配置存在规则冲突

你同时配置了ProxyPass和RewriteRule的WebSocket转发规则,两条规则会重复匹配请求,导致代理链路异常。另外enablereuse=On参数是给普通HTTP长连接连接池复用设计的,和WebSocket升级后的长连接存在兼容性问题,会导致连接被代理提前回收。
删掉多余的Rewrite转发规则,修正ProxyPass的参数和路径匹配即可,修改后的WebSocket配置段如下:

# Websocket config
SSLProxyEngine on
ProxyPreserveHost On
ProxyRequests Off
# 统一路径结尾斜杠,去掉不兼容的enablereuse参数
ProxyPass /wss/ ws://localhost:4433/wss/ keepalive=On connectiontimeout=1800
ProxyPassReverse /wss/ ws://localhost:4433/wss/

改完后重启Apache服务,再测试WebSocket连接即可正常建立,不会再出现1006错误码和连接重置问题。


内容的提问来源于stack exchange,提问作者txtechhelp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 13:45:31