我已将 WebSocket 服务部署到 Azure 容器应用程序,乍一看似乎没有问题。从客户的角度来看,这项服务似乎运行得非常完美。
但是,当查看容器的日志时,似乎充满了以下不断重复的错误。
opening handshake failed
Traceback (most recent call last):
File "/usr/local/lib/python3.12/site-packages/websockets/http11.py", line 134, in parse
request_line = yield from parse_line(read_line)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/websockets/http11.py", line 380, in parse_line
line = yield from read_line(MAX_LINE_LENGTH)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/websockets/streams.py", line 46, in read_line
raise EOFError(f"stream ends after {p} bytes, before end of line")
EOFError: stream ends after 0 bytes, before end of line
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/local/lib/python3.12/site-packages/websockets/asyncio/server.py", line 353, in conn_handler
await connection.handshake(
File "/usr/local/lib/python3.12/site-packages/websockets/asyncio/server.py", line 204, in handshake
raise self.protocol.handshake_exc
File "/usr/local/lib/python3.12/site-packages/websockets/server.py", line 551, in parse
request = yield from Request.parse(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/websockets/http11.py", line 136, in parse
raise EOFError("connection closed while reading HTTP request line") from exc
EOFError: connection closed while reading HTTP request line
当容器在我的机器上本地运行时,这不是问题。
我的猜测是,Azure 容器应用程序会自动调用一些我的 WebSocket 服务器不支持的端点,但我无法弄清楚到底是什么以及为什么。如果有人能指出我可以改变什么的正确方向,我将不胜感激。
在浏览了 Azure 容器应用程序的文档后,我在这里找到了解决方案:Azure 容器应用程序中的运行状况探针。
似乎当没有为容器应用程序定义特定的运行状况探测器时,探测器会自动且静默地为您定义,即使它们在查看容器应用程序的配置时不可见。
这些默认探针是:
探头类型 | 默认值 |
---|---|
启动 | 协议:TCP 端口:入口目标端口 超时:3秒 周期:1秒 初始延迟:1秒 成功阈值:1 失败阈值:240 |
准备就绪 | 协议:TCP 端口:入口目标端口 超时:5秒 周期:5秒 初始延迟:3秒 成功阈值:1 失败阈值:48 |
活力 | 协议:TCP 端口:入口目标端口 |
这里的问题是,对服务器根路径的 TCP 请求将不起作用,并且活性探测永远不会成功。
解决方案只是向服务器添加对简单 HTTP
/health
端点的支持,该端点会返回 200 响应。然后,有必要使用 HTTP 协议、这个新端点和正确的端口显式地定义容器的活性探针。
活性探针是必需的,并且不能被忽略,但以这种方式设置它,将确保日志不会因错误而混乱。
当
EOFError: EOF when reading a line
函数尝试读取一行输入时,Python 中的 input()
错误就会发生。有关更多详细信息,请参阅此文档来解决 EOF 错误。
以下是在 Azure 容器应用程序上创建和部署 WebSocket 的步骤:
app.py
:
from flask import Flask, render_template
from flask_socketio import SocketIO
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
socketio = SocketIO(app, cors_allowed_origins="*")
@app.route('/')
def index():
return render_template('index.html')
@socketio.on('message')
def handle_message(msg):
print('Received message: ' + msg)
socketio.emit('message', msg)
if __name__ == '__main__':
socketio.run(app)
模板/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask WebSocket Example</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
</head>
<body>
<h1>Flask WebSocket Example</h1>
<input type="text" id="message" placeholder="Enter a message">
<button onclick="sendMessage()">Send Message</button>
<p id="response"></p>
<script>
const socket = io('http://localhost:5000');
socket.on('message', (msg) => {
console.log('Received message: ' + msg);
document.getElementById('response').textContent = 'Received: ' + msg;
});
function sendMessage() {
const message = document.getElementById('message').value;
socket.emit('message', message);
}
</script>
</body>
</html>
需求.txt:
flask
Flask-SocketIO
flask-cors
eventlet
gunicorn
Docker:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
EXPOSE 5000
#CMD ["python", "app.py"]
CMD ["gunicorn", "-k", "eventlet", "-w", "1", "app:app", "--bind", "0.0.0.0:5000"]
使用以下命令构建图像:
docker build -t websocket-chat .
然后标记图像以匹配 ACR 存储库格式:
docker tag websocket-chat AzureRegisterName.azurecr.io/websocket-chat:latest
和
通过以下命令登录到您的 Azure 容器注册表:
az acr login -n
现在,将标记的映像推送到您的 Azure 容器注册表:
docker push AzureRegisterName.azurecr.io/websocket-chat:latest
如果是 Web 应用程序,请确保在“配置”>“Web 套接字”>“打开”中打开“Web 套接字”选项。确保端口 80 和 443。
转到容器注册表中的“存储库”部分,选择镜像,然后单击“运行实例”。
然后就开始创建Container了。输入Container名称,选择Public IP地址为Yes,并配置端口。
使用 IP 地址浏览您的容器应用程序。