【Azure 应用服务】App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)
问题描述
使用 python websockets 模块作为Socket的服务端,发布到App Service for Linux环境后,发现Docker Container无法启动。错误消息为:
2021-10-28T02:39:51.812Z INFO - docker run -d -p 1764:8000 --name test_0_c348bc62 -e WEBSITE_SITE_NAME=sockettest -e WEBSITE_AUTH_ENABLED=False -e WEBSITE_ROLE_INSTANCE_ID=0 -e WEBSITE_HOSTNAME=sockettest.chinacloudsites.cn -e WEBSITE_INSTANCE_ID=08307498aa991c84523184617d17f074bad5139bd2c0710fdf2b1a0ad3d3a9b7 -e HTTP_LOGGING_ENABLED=1 appsvc/python:3.8_20210709.2 python socket_server.py 2021-10-28T02:39:55.922Z INFO - Initiating warmup request to container test_0_c348bc62 for site sockettest
2021-10-28T02:40:11.177Z INFO - Waiting for response to warmup request for container test_0_c348bc62. Elapsed time = 15.2556084 sec
...
2021-10-28T02:43:33.439Z INFO - Waiting for response to warmup request for container test_0_c348bc62. Elapsed time = 217.5175373 sec
2021-10-28T02:43:46.644Z ERROR - Container test_0_c348bc62 for site sockettest did not start within expected time limit. Elapsed time = 230.7221775 sec
2021-10-28T02:43:46.645Z ERROR - Container test_0_c348bc62 didn't respond to HTTP pings on port: 8000, failing site start. See container logs for debugging.
2021-10-28T02:43:46.672Z INFO - Stopping site sockettest because it failed during startup.
PS:应用上云的需求。
问题解决
这是因为App Service Linux使用Container的启动需要Python代码中对HTTP进行正确的响应,否则Site无法启动。而这次的Python代码并不包含对HTTP请求的响应(需要Web框架),所以无法正确启动。
在Azure App Service for Linux - Python的文档中,主要介绍的两种Web框架为 Flask 和 Django。 接下来,就通过Flask 和SocketIO来实现WebSocket功能。
实现 Python SocketIO 代码及步骤
1)创建 app.py 文件,并复制以下内容,作为Socket的服务端及Flask应用的启动
from flask import Flask, render_template, session, copy_current_request_context
from flask_socketio import SocketIO, emit, disconnect
from threading import Lock
import os async_mode = None
app = Flask(__name__) app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, async_mode=async_mode)
thread = None
thread_lock = Lock() ## Used by App Service For linux
PORT = os.environ["PORT"]
serverIP = "0.0.0.0" # # Used by Local debug.
# PORT = 5000
# serverIP = "127.0.0.1" @app.route('/')
def index():
return render_template('index.html', async_mode=socketio.async_mode) @socketio.on('my_event', namespace='/test')
def test_message(message):
print('receive message:' + message['data'],)
session['receive_count'] = session.get('receive_count', 0) + 1
emit('my_response',
{'data': message['data'], 'count': session['receive_count']}) @socketio.on('my_broadcast_event', namespace='/test')
def test_broadcast_message(message):
print('broadcast message:' + message['data'],)
session['receive_count'] = session.get('receive_count', 0) + 1
emit('my_response',
{'data': message['data'], 'count': session['receive_count']},
broadcast=True) @socketio.on('disconnect_request', namespace='/test')
def disconnect_request():
@copy_current_request_context
def can_disconnect():
disconnect() session['receive_count'] = session.get('receive_count', 0) + 1
emit('my_response',
{'data': 'Disconnected!', 'count': session['receive_count']},
callback=can_disconnect) if __name__ == '__main__':
socketio.run(app,port=PORT, host=serverIP, debug=True)
print('socket io start')
2)创建 template/index.html,并复制以下内容,作为Socket的客户端,验证WebSocket的正常工作
<!DOCTYPE HTML>
<html>
<head>
<title>Socket-Test</title>
<script src="//code.jquery.com/jquery-1.12.4.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() { namespace = '/test';
var socket = io(namespace); socket.on('connect', function() {
socket.emit('my_event', {data: 'connected to the SocketServer...'});
}); socket.on('my_response', function(msg, cb) {
$('#log').append('<br>' + $('<div/>').text('logs #' + msg.count + ': ' + msg.data).html());
if (cb)
cb();
});
$('form#emit').submit(function(event) {
socket.emit('my_event', {data: $('#emit_data').val()});
return false;
});
$('form#broadcast').submit(function(event) {
socket.emit('my_broadcast_event', {data: $('#broadcast_data').val()});
return false;
});
$('form#disconnect').submit(function(event) {
socket.emit('disconnect_request');
return false;
});
});
</script>
</head>
<body style="background-color:white;"> <h1 style="background-color:white;">Socket</h1>
<form id="emit" method="POST" action='#'>
<input type="text" name="emit_data" id="emit_data" placeholder="Message">
<input type="submit" value="Send Message">
</form>
<form id="broadcast" method="POST" action='#'>
<input type="text" name="broadcast_data" id="broadcast_data" placeholder="Message">
<input type="submit" value="Send Broadcast Message">
</form> <form id="disconnect" method="POST" action="#">
<input type="submit" value="Disconnect Server">
</form>
<h2 style="background-color:white;">Logs</h2>
<div id="log" ></div>
</body>
</html>
3)创建 requirements.txt 文件,并包含以下module及版本,如果版本不适合,可以适当修改。
Flask==1.0.2
Flask-Login==0.4.1
Flask-Session==0.3.1
itsdangerous==1.1.0
Jinja2==2.10
MarkupSafe==1.1.0
six==1.11.0
Werkzeug==0.14.1
Flask-SocketIO==4.3.1
python-engineio==3.13.2
python-socketio==4.6.0
eventlet==0.30.2
以上三个就是整个项目的源文件,VS Code中的文件结构为:

4)在VS Code中使用az webapp up来部署Python Web应用
#设置登录环境为中国区Azure
az cloud set -n AzureChinaCloud
az login #部署代码,如果pythonlinuxwebsocket01不存在,则自动创建定价层位B1的App Service
az webapp up --sku B1 --name pythonlinuxwebsocket01
效果展示:

5)修改App Service的启动命令
gunicorn --worker-class eventlet -w 1 app:app

注:为了避免 flask-socketIO 服务器部署 400 Bad Request 问题,所以需要使用 eventlet 作为工作进程。详细说明可见:https://blog.csdn.net/weixin_43958804/article/details/109024348
6) 开启WebSocket, 启用HTTP, 设置PORT参数

注:修改后,重启App Service。如果重启后使用HTTP请求,但是发生了302跳转到HTTPS的情况,就可以考虑重新部署依次站点。使用第四步方法,az webapp up然container重新生成项目信息。
7)验证Web Socket
使用HTTP访问刚刚部署的App Service URL。

附录一:解决flask-socketIO 服务器部署 400 Bad Request 问题
使用eventlet,设置启动命令:gunicorn --worker-class eventlet -w 1 app:app
附录二:Gunicorn ImportError: cannot import name 'ALREADY_HANDLED' from 'eventlet.wsgi' in docker
Installing older version of eventlet solved the problem: pip install eventlet==0.30.2
参考资料:
Implement a WebSocket Using Flask and Socket-IO(Python): https://medium.com/swlh/implement-a-websocket-using-flask-and-socket-io-python-76afa5bbeae1
解决flask-socketIO 服务器部署 400 Bad Request 问题:https://blog.csdn.net/weixin_43958804/article/details/109024348
【Azure 应用服务】App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)的更多相关文章
- 【Azure 应用服务】Azure App Service For Linux 上实现 Python Flask Web Socket 项目 Http/Https
问题描述 在上篇博文"[Azure 应用服务]App Service for Linux 中实现 WebSocket 功能 (Python SocketIO)"中,实现了通过 HT ...
- 【Azure 应用服务】App Service For Linux 部署Java Spring Boot应用后,查看日志文件时的疑惑
编写Java Spring Boot应用,通过配置logging.path路径把日志输出在指定的文件夹中. 第一步:通过VS Code创建一个空的Spring Boot项目 第二步:在applicat ...
- 【应用服务 App Service】快速获取DUMP文件(App Service for Windows(.NET/.NET Core))
问题情形 当应用在Azure 应用服务App Service中运行时,有时候出现CPU,Memory很高,但是没有明显的5XX错误和异常日志,有时就是有异常但是也不能明确的指出具体的代码错误.当面临这 ...
- 【Azure 应用服务】PHP应用部署在App Service for Linux环境中,上传文件大于1MB时,遇见了413 Request Entity Too Large 错误的解决方法
问题描述 在PHP项目部署在App Service后,上传文件如果大于1MB就会遇见 413 Request Entity Too Large 的问题. 问题解决 目前这个问题,首先需要分析应用所在的 ...
- 【Azure 应用服务】App Service For Linux 部署PHP Laravel 项目,如何修改首页路径为 wwwroot\public\index.php
问题描述 参考官方文档部署 PHP Laravel 项目到App Service for Linux环境中,但是访问应用时候遇见了500 Server Error 错误. 从部署的日志中,可以明确看出 ...
- 【Azure 应用服务】App Service For Linux 如何在 Web 应用实例上住抓取网络日志
问题描述 在App Service For Windows的环境中,我们可以通过ArmClient 工具发送POST请求在Web应用的实例中抓取网络日志,但是在App Service For Linu ...
- 【Azure 应用服务】部署Jar到App Service for Linux,因启动命令路径配置错误而引起:( Application Error 问题
问题描述 App Service for Linux 资源创建完成后,通过FTP方式把 .jar包(logdemo.jar)包上传到 /site/wwwroot/ 文件夹后,在App Service的 ...
- 【应用服务 App Service】在Azure App Service中使用WebSocket - PHP的问题 - 如何使用和调用
问题描述 在Azure App Service中,有对.Net,Java的WebSocket支持的示例代码,但是没有成功的PHP代码. 以下的步骤则是如何基于Azure App Service实现PH ...
- 【应用服务 App Service】App Service中抓取网络日志
问题描述 众所周知,Azure App Service是一种PaaS服务,也就是说,IaaS层面的所有内容都由平台维护,所以使用App Service的我们根本无法触碰到远行程序的虚拟机(VM), 所 ...
随机推荐
- Django学习day08随堂笔记
今日考题 """ 今日考题 1.聚合查询,分组查询的关键字各是什么,各有什么特点或者注意事项 2.F与Q查询的功能,他们的导入语句是什么,针对Q有没有其他用法 3.列举常 ...
- nginx 禁止某IP访问
首先建立下面的配置文件放在nginx的conf目录下面,命名为blocksip.conf: deny 95.105.25.181; 保存一下. 在nginx的配置文件nginx.conf中加入:inc ...
- Spring Boot中如何配置线程池拒绝策略,妥善处理好溢出的任务
通过之前三篇关于Spring Boot异步任务实现的博文,我们分别学会了用@Async创建异步任务.为异步任务配置线程池.使用多个线程池隔离不同的异步任务.今天这篇,我们继续对上面的知识进行完善和优化 ...
- Linux系列(21) - 光盘、U盘挂载
挂载光盘 mount命令.umount命令 step-1 建立挂载点 原理:相当于建立盘符,建个目录读取光盘内容 命令:[root@localhost ~]# mkdir /mnt/cdrom/ 备注 ...
- Ubuntu怎么开启/关闭防火墙
在Ubuntu中,使用sudo ufw status命令查看当前防火墙状态. 不活动,是关闭状态. 在Ubuntu中,使用sudo ufw enable命令来开启防火墙. 在Ubuntu中,使用sud ...
- javascript 高阶函数 currying & uncurrying
* currying var currying = function(fn) { var args = []; return function() { if (arguments.length === ...
- python三种导入模块的方法
做为python初学者,有时候搞不清楚导入模块的作用. 直接导入模块 通常模块为一个文件,直接使用import来导入就好了.可以作为module的文件类型有".py"." ...
- python学习笔记(十二)-网络编程
本文结束使用 Requests 发送网络请求.requests是一个很实用的Python HTTP客户端库,编写爬虫和测试服务器响应数据时经常会用到.可以说,Requests 完全满足如今网络的需求. ...
- 重新嫁接rm命令
### 重定义rm命令 #### 定义回收站目录trash_path='~/.trash'# 判断 $trash_path 定义的文件是否存在,如果不存在,那么就创建 $trash_path.if [ ...
- CF666E-Forensic Examination【广义SAM,线段树合并】
正题 题目链接:https://www.luogu.com.cn/problem/CF666E 解题思路 给出一个串\(S\)和\(n\)个串\(T_i\).\(m\)次询问\(S_{a\sim b} ...