Python websockets模块搭配IIS反向代理连接异常问题排查
问题:IIS反向代理Python websockets服务器连接异常断开
环境与配置
Python WebSocket服务器代码
运行在localhost:5005,代码来自websockets官方文档:
#!/usr/bin/env python import asyncio import websockets import logging logger = logging.getLogger('websockets') logger.setLevel(logging.DEBUG) logger.addHandler(logging.StreamHandler()) async def echo(websocket): async for message in websocket: print(message) await websocket.send(message) async def main(): async with websockets.serve(echo, "localhost", 5005): await asyncio.Future() # run forever asyncio.run(main())
IIS 10反向代理配置
目标是让客户端通过ws://localhost/app访问Python服务器,配置步骤:
- 应用程序请求路由缓存 → 服务器代理设置 → 启用代理
- 站点 → 默认网站:添加名为
app的应用程序 - URL重写规则配置:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <rewrite> <rules> <rule name="ReverseProxy" stopProcessing="true"> <match url="(.*)" /> <action type="Rewrite" url="http://localhost:5005" logRewrittenUrl="true" /> </rule> </rules> </rewrite> </system.webServer> </configuration>
问题现象
客户端尝试连接ws://localhost/app时立即断开,Python服务器抛出以下异常:
= connection is CONNECTING < GET / HTTP/1.1 < Cache-Control: no-cache < Connection: Upgrade < Pragma: no-cache < Upgrade: websocket < Accept-Encoding: gzip, deflate, br, peerdist < Accept-Language: en-US,en;q=0.9 < Host: localhost:5005 < Max-Forwards: 10 < User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36 < Origin: null < Sec-WebSocket-Version: 13 < Sec-WebSocket-Key: /C9ah/z+wAo4mnoACSz2IA== < Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits < X-Original-URL: /app < X-Forwarded-For: [::1]:52798 < X-ARR-LOG-ID: 8c59749a-4e5d-43cf-8a5a-c4421cc4ecc8 < X-P2P-PeerDist: Version=1.1 < X-P2P-PeerDistEx: MinContentInformation=1.0, MaxContentInformation=2.0 > HTTP/1.1 101 Switching Protocols > Upgrade: websocket > Connection: Upgrade > Sec-WebSocket-Accept: W9Sttf7fHX3aB9zd4b/PVGt0Ldg= > Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=12; client_max_window_bits=12 > Date: Fri, 15 Jul 2022 19:14:32 GMT > Server: Python/3.7 websockets/10.3 connection open = connection is OPEN ! failing connection with code 1006 = connection is CLOSED x half-closing TCP connection connection handler failed Traceback (most recent call last): File "C:\Users\amin.ramezanifar\AppData\Roaming\Python\Python37\site-packages\websockets\legacy\protocol.py", line 945, in transfer_data message = await self.read_message() File "C:\Users\amin.ramezanifar\AppData\Roaming\Python\Python37\site-packages\websockets\legacy\protocol.py", line 1015, in read_message frame = await self.read_data_frame(max_size=self.max_size) File "C:\Users\amin.ramezanifar\AppData\Roaming\Python\Python37\site-packages\websockets\legacy\protocol.py", line 1090, in read_data_frame frame = await self.read_frame(max_size) File "C:\Users\amin.ramezanifar\AppData\Roaming\Python\Python37\site-packages\websockets\legacy\protocol.py", line 1149, in read_frame extensions=self.extensions, File "C:\Users\amin.ramezanifar\AppData\Roaming\Python\Python37\site-packages\websockets\legacy\framing.py", line 70, in read data = await reader(2) File "C:\Program Files\Python37\lib\asyncio\streams.py", line 677, in readexactly raise IncompleteReadError(incomplete, n) asyncio.streams.IncompleteReadError: 0 bytes read on a total of 2 expected bytes The above exception was the direct cause of the following exception: Traceback (most recent call last): File "C:\Users\amin.ramezanifar\AppData\Roaming\Python\Python37\site-packages\websockets\legacy\server.py", line 232, in handler await self.ws_handler(self) File "D:/Robotic Framework/RoboticApps/UI_Framework/server.py", line 12, in echo async for message in websocket: File "C:\Users\amin.ramezanifar\AppData\Roaming\Python\Python37\site-packages\websockets\legacy\protocol.py", line 482, in __aiter__ yield await self.recv() File "C:\Users\amin.ramezanifar\AppData\Roaming\Python\Python37\site-packages\websockets\legacy\protocol.py", line 553, in recv await self.ensure_open() File "C:\Users\amin.ramezanifar\AppData\Roaming\Python\Python37\site-packages\websockets\legacy\protocol.py", line 921, in ensure_open raise self.connection_closed_exc() websockets.exceptions.ConnectionClosedError: no close frame received or sent connection closed
验证情况
- 使用
simple-websocket-server库搭建的WebSocket服务器,通过IIS代理可正常连接 - 简单Node.js WebSocket服务器,通过IIS代理也可正常运行
排除IIS代理的基础配置问题,问题出在Python websockets库与IIS的交互上。
问题原因与解决方法
原因分析
客户端请求携带了Accept-Encoding头和permessage-deflate扩展,IIS默认开启的内容压缩功能会在WebSocket连接建立后,尝试对帧数据进行压缩,破坏了WebSocket的帧结构,导致Python websockets库无法正确解析帧,最终引发连接断开。
解决方法
方法1:关闭IIS站点的压缩功能
- 打开IIS管理器,找到目标站点
- 双击"压缩"功能
- 取消勾选"启用静态内容压缩"和"启用动态内容压缩"
- 保存配置并重启站点
方法2:修改URL重写规则,禁用压缩转发
修改URL重写的XML配置,添加WebSocket请求条件,并清空Accept-Encoding头,避免IIS对WebSocket数据进行压缩:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <system.webServer> <rewrite> <rules> <rule name="ReverseProxy" stopProcessing="true"> <match url="(.*)" /> <conditions> <add input="{HTTP_UPGRADE}" pattern="websocket" /> <add input="{HTTP_CONNECTION}" pattern="upgrade" /> </conditions> <action type="Rewrite" url="http://localhost:5005/{R:1}" logRewrittenUrl="true" /> <serverVariables> <set name="HTTP_X_ORIGINAL_ACCEPT_ENCODING" value="{HTTP_ACCEPT_ENCODING}" /> <set name="HTTP_ACCEPT_ENCODING" value="" /> </serverVariables> </rule> </rules> </rewrite> </system.webServer> </configuration>
- 添加条件确保仅转发WebSocket升级请求
- 清空
Accept-Encoding头,阻止IIS对WebSocket数据进行压缩 - 重写URL时保留原始路径(
{R:1}),避免路径不一致问题
方法3:禁用websockets库的permessage-deflate扩展
修改Python服务器代码,创建服务器时禁用该扩展:
async def main(): async with websockets.serve( echo, "localhost", 5005, extensions=[] # 禁用所有扩展,包括permessage-deflate ): await asyncio.Future() # run forever
内容的提问来源于stack exchange,提问作者afar
相关产品推荐
相关产品推荐

