上回我們說到了一些小功能涧尿,這次再對(duì)flask應(yīng)用做一個(gè)補(bǔ)充系奉,交代flask的上下文變量,身份認(rèn)證等功能姑廉。
當(dāng)我們的flask應(yīng)用做好之后就不得不涉及到部署缺亮,本文將會(huì)討論flask-socketio的三種不同類型的服務(wù)器部署,nginx的反向代理桥言,外部進(jìn)程消息的處理等
到今天為止萌踱,flask-socketio官方文檔,除了api限书,其它都已經(jīng)翻譯完畢虫蝶,感謝各位的賞臉。_
11.訪問flask上下文全局變量
SocketIO活動(dòng)處理不同于路由處理倦西,在于它引入了許多容易混淆的東西能真,圍繞著SocketIO什么可以做,什么不可以做扰柠。最主要的區(qū)別就是SocketIO活動(dòng)發(fā)生在單個(gè)長(zhǎng)期運(yùn)行在上下文的請(qǐng)求之中粉铐。
盡管有所不同,F(xiàn)lask-SocketIO將環(huán)境改造成類似于常規(guī)HTTP請(qǐng)求卤档,使SocketIO活動(dòng)處理更加輕松蝙泼。接下來的列表描述了什么將會(huì)生效,什么不會(huì)劝枣。
在活動(dòng)處理函數(shù)之前推送應(yīng)用的上下文使得
current_app
和g
可以在處理函數(shù)中可用汤踏。這個(gè)請(qǐng)求的上下文同樣在回調(diào)處理函數(shù)前被啟用织鲸,也使
request
和session
可用。但是注意到WebSocket活動(dòng)與之并沒有獨(dú)立的聯(lián)系溪胶,因此為連接期間分派的所有事件推送啟動(dòng)連接的請(qǐng)求上下文搂擦。request
上下文全局變量隨一個(gè)sid成員增加,這個(gè)成員是為了給連接一個(gè)獨(dú)特的會(huì)話編號(hào)(session ID)哗脖。這個(gè)值在客戶端剛剛添加的時(shí)候瀑踢,就被最初的房間使用了。request上下文全局變量由包含了當(dāng)前處理函數(shù)的命名空間和活動(dòng)參數(shù)的
argument
和event
來增加才避。這個(gè)活動(dòng)成員是一個(gè)包含了message
和args
鍵值的字典橱夭。session
上下文全局變量表現(xiàn)得和通常的請(qǐng)求不一樣。在連接開始建立的時(shí)候桑逝,就會(huì)復(fù)制一份用戶的會(huì)話在這個(gè)連接上下文中給處理器調(diào)用棘劣。如果SocketIO處理器修改了這個(gè)會(huì)話,這個(gè)修改過的會(huì)話就會(huì)為未來的SocketIO處理器保留楞遏,但是正常的HTTP路由處理器不會(huì)察覺這些改變呈础。有效率的是,當(dāng)SocketIO處理器改變這個(gè)會(huì)話的時(shí)候橱健,會(huì)話就會(huì)為這些處理器創(chuàng)建一個(gè)“分支”(fork)而钞。這個(gè)限制的技術(shù)原因是用戶的會(huì)話cookie必須要發(fā)送到客戶端,這需要HTTP請(qǐng)求和應(yīng)答而不是SocketIO連接拘荡。在使用服務(wù)端的會(huì)話時(shí)臼节,比如那些由Flask-Session或者Flask-KVSession擴(kuò)展提供的會(huì)話,在HTTP處理器中的會(huì)話改變也可以在SocketIO處理器中可見珊皿,只要這個(gè)會(huì)話不是在SocketIO處理器中修改的网缝。before_request
和after_request
鉤子不會(huì)調(diào)用SocketIO活動(dòng)處理器。SocketIO處理器可以使用自定義的裝飾器蟋定,但是大多數(shù)Flask裝飾器并不適于SocketIO處理器粉臊,考慮到SocketIO連接中沒有Response對(duì)象這一概念。
12.身份認(rèn)證
應(yīng)用的共同需要就是驗(yàn)證他們用戶的身份驶兜。自從SocketIO沒有使用HTTP請(qǐng)求和應(yīng)答扼仲,傳統(tǒng)的基于網(wǎng)頁表單和HTTP請(qǐng)求的機(jī)制不能用于SocketIO連接。如果需要的話抄淑,應(yīng)用可以實(shí)施自定義的登陸表單屠凶,當(dāng)用戶按下提交按鈕時(shí),它利用一個(gè)SocketIO消息將證書發(fā)送到服務(wù)器肆资。
然而矗愧,在大多數(shù)情況下,在SocketIO連接建立之前使用傳統(tǒng)的身份驗(yàn)證方式會(huì)更加方便郑原,用戶的身份信息可以被記錄下來作為用戶會(huì)話或者cookie唉韭,之后在SocketIO連接建立起來的時(shí)候夜涕,這些信息也可以被SocketIO活動(dòng)處理器得到。
13.使用Flask-SocketIO的Flask-Login模塊
Flask-SocketIO可以獲得由Flask-Login維護(hù)的登陸信息属愤。在一個(gè)正常的Flask-Login身份認(rèn)證被使用的時(shí)候钠乏,login_user()函數(shù)將會(huì)被調(diào)用去記錄用戶會(huì)話中的用戶,任何SocketIO連接都可以得到current_user
上下文變量:
@socketio.on('connect')
def connect_handler():
if current_user.is_authenticated:
emit('my response',
{'message':'{0} has joined'.format(current_user.name)},
broadcast=True
)
else:
return False # not allowed here
注意到login_required裝飾器不能和SocketIO活動(dòng)處理器一起使用春塌,但是一個(gè)自定義的關(guān)閉連接無身份認(rèn)證的裝飾器可以按下面的方式創(chuàng)建:
import functools
from flask import request
from flask_login import current_user
from flask_socketio import disconnect
def authenticated_only(f):
@functools.wraps(f):
def wraped(*args, **kwargs):
if not current_user.is_authenticated:
disconnect()
else:
return f(*args, **kwargs)
return wraped
@socketio.on('my event')
@authenticated_only
def handle_my_custom_event(data)
emit('my response',
{'message': '{0} has joined'.format(current_user.name)},
broadcast=True
)
14.部署
我們有多種部署Flask-SocketIO服務(wù)器的選擇,從最簡(jiǎn)單到瘋狂地復(fù)雜簇捍。在這一章節(jié)里只壳,我們將會(huì)介紹最普遍的選擇映皆。
嵌入式服務(wù)器
最簡(jiǎn)單的策略是安裝eventlet或者gevent骑冗,并且就像前面章節(jié)的例子中引用socketio.run(app)的方式來啟動(dòng)網(wǎng)絡(luò)服務(wù)器歌憨。這個(gè)將會(huì)在eventlet或者gevent網(wǎng)絡(luò)服務(wù)器中啟動(dòng)這個(gè)應(yīng)用悯许,被嵌入的網(wǎng)絡(luò)服務(wù)器是哪一個(gè)取決于是安裝的是哪一個(gè)捂齐。
注意到socketio.run(app)運(yùn)行在eventlet或gevent已安裝上的生產(chǎn)服務(wù)器中热幔。如果它們中沒有一個(gè)被安裝玄帕,那么這個(gè)應(yīng)用運(yùn)行在Flask開發(fā)服務(wù)器中晨缴,這并不適于生產(chǎn)環(huán)境的使用驹愚。
不幸的是远搪,這個(gè)選擇并不能在帶有uWSGI的gevent服務(wù)器上使用,你可以在下面獲取更多有關(guān)這個(gè)選項(xiàng)的信息逢捺。
Gunicorn網(wǎng)絡(luò)服務(wù)器
作為Socketio.run(app)替代方法的就是使用gunicorn作為網(wǎng)絡(luò)服務(wù)器谁鳍,工作在eventlet或gevent下。這個(gè)選擇下劫瞳,除了gunicorn要安裝倘潜,eventlet或者gevent也是不可缺少的。這個(gè)條命令將會(huì)啟動(dòng)這個(gè)基于gunicorn的eventlet服務(wù)器:
gunnicorn --worker--class eventlet -w 1 module:app
如果你更傾向于使用gevent志于,啟動(dòng)服務(wù)器的命令如下:
gunicorn -k gevent -w 1 module:app
當(dāng)使用gunicorn作為gevent的工作站并且websocket支持也被提供的時(shí)候涮因,上述命令就必須被改成選擇一個(gè)自定義的gevent網(wǎng)絡(luò)服務(wù)器來支持websocket協(xié)議。修改后的命令如下:
gunicorn -k geventwebsocket.gunicorn.worker.GeventWebSocketWorker -w 1 module:app
在上述這些命令中伺绽,module是python模塊或者是定義了應(yīng)用實(shí)例的包养泡,此外,app是應(yīng)用實(shí)例本身奈应。
Gunicorn 18.0版本是被推薦和Flask-SocketIO搭配的版本瓤荔。19.x版本已知在帶有WebSocket的一些特定部署場(chǎng)景下存在不兼容的情況。
gunicorn由于使用了有限的負(fù)載均衡算法钥组,不可能在使用這種網(wǎng)絡(luò)服務(wù)器時(shí)調(diào)用兩個(gè)以上工作進(jìn)程因?yàn)檫@個(gè)原因输硝,上面的所有例子中都包含了-w 1
的可選參數(shù)。
15.uWSGI網(wǎng)絡(luò)服務(wù)器
當(dāng)使用uWSGI網(wǎng)絡(luò)服務(wù)器搭配geventd的時(shí)候程梦,Socket.IO服務(wù)器的時(shí)候点把,可以利用uWSGI原生的WebSocket支持橘荠。
一個(gè)配置和運(yùn)用uWSGI服務(wù)器完整的解釋超出了本文的論述范圍。uWSGI服務(wù)器確實(shí)是一個(gè)比較復(fù)雜的郎逃,它提供了大量而又詳盡的設(shè)置選項(xiàng)哥童。它必須使用Websocket和SSL編譯才能支持WebSocket傳輸。作為介紹褒翰,下面的命令啟動(dòng)了一個(gè)uWSGI服務(wù)器作為范例贮懈,這個(gè)應(yīng)用app.py運(yùn)行在端口5000:
uwsgi --http :5000 --gevent 1000 --http-websockets --master --wsgi-file app.py --callable app
16.使用nginx作為反向代理服務(wù)器
使用nginx作為前端的反向代理將請(qǐng)求傳遞給應(yīng)用是可行的。然而优训,只有nginx 1.4版本以上才支持WebSocket協(xié)議朵你。下面是nginx代理HTTP和WebSocket請(qǐng)求的一個(gè)最基本的配置:
server {
listen 80;
server_name _;
location / {
include proxy_params;
proxy_pass http://127.0.0.1:5000;
}
location /socket.io {
include proxy_params;
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass http://127.0.0.1:5000/socket.io;
}
}
下面的例子增加了對(duì)負(fù)載平衡多個(gè)服務(wù)器的支持:
upstream socketio_nodes {
ip_hash;
server 127.0.0.1:5000;
server 127.0.0.1:5001;
server 127.0.0.1:5002;
# to scale the app, just add more nodes here!
}
server {
listen 80;
server_name _;
location / {
include proxy_params;
proxy_pass http://127.0.0.1:5000;
}
location /socket.io {
include proxy_params;
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_pass http://socketio_nodes/socket.io;
}
}
雖然上面的例子可以作為最初的配置工作,要知道生產(chǎn)環(huán)境安裝的nginx需要一個(gè)完整的配置揣非,包括部署的其它方面抡医,例如服務(wù)于靜態(tài)文件的assert和SSL支持。
17.使用多個(gè)工作站
Flask-SocketIO從2.0版本起帶有負(fù)載均衡器支持多個(gè)工作站早敬。部署多個(gè)工作站給了使用Flask-SocketIO的應(yīng)用程序有能力在多進(jìn)程和多主機(jī)之間傳播客戶端鏈接忌傻,這種方式的擴(kuò)展支持極大規(guī)模的并發(fā)客戶端。
使用多個(gè)Flask-SocketIO工作站需要兩個(gè)依賴:
- 負(fù)載均衡器必須要配置成總是將所有的HTTP請(qǐng)求從一個(gè)給定的客戶端轉(zhuǎn)發(fā)到同樣的工作站中搞监。這有時(shí)會(huì)作為"sticky session"被提及水孩。對(duì)于nginx,使用這個(gè)ip_bash指示來達(dá)到上述要求琐驴。Gunicorn不能用于多工作站荷愕,因?yàn)樗呢?fù)載均衡算法并不支持粘性會(huì)話(sticky session)。
- 一旦每個(gè)服務(wù)器只擁有一個(gè)客戶端連接棍矛,在Redis安疗、RabbitMQ等例子中,消息隊(duì)列將會(huì)被使用够委,來協(xié)調(diào)復(fù)雜的操作荐类,比如:廣播和房間。
當(dāng)使用消息隊(duì)列的時(shí)候茁帽,有許多額外的依賴包需要被安裝:
- 對(duì)于Redis玉罐,redis包必須被安裝(pip install redis)
- 對(duì)于RabbitMQ,kombu包必須要被安裝(pip install kombu)
- 對(duì)于其它Kombu支持的消息隊(duì)列潘拨,Kombu documentation里可以找到需要的依賴
- 如果使用了eventlet或者gevent吊输,那么通常需要使用猴子(Monkey)修補(bǔ)Python標(biāo)準(zhǔn)庫來強(qiáng)制消息隊(duì)列包使用協(xié)同友好的函數(shù)和類。
為了啟動(dòng)多個(gè)Flask-SocketIO服務(wù)器铁追,你必須首先確保消息隊(duì)列服務(wù)正在運(yùn)行季蚂。為了開啟一個(gè)Socket.IO服務(wù)器,使他連接到一個(gè)消息隊(duì)列,扭屁,需要添加參數(shù)message_queue到構(gòu)造函數(shù)SockIO:
socketio=SocketIO(app,message_queue='redis://')
參數(shù)message_queue的值就是隊(duì)列服務(wù)所使用的連接URL算谈。對(duì)于一個(gè)運(yùn)行在同一個(gè)作為服務(wù)器的主機(jī)中的Redis隊(duì)列來說,可以使用'redis://'這樣的URL料滥。同樣然眼,對(duì)于一個(gè)默認(rèn)的RabbitMQ隊(duì)列可以使用'amqp://'開頭的URL。Kombu包有一個(gè)文檔章節(jié)闡述了對(duì)于所有支持隊(duì)列的URL格式葵腹。
18.外部進(jìn)程消息
對(duì)于許多類型的應(yīng)用高每,從非服務(wù)端創(chuàng)建會(huì)話活動(dòng)很有必要,例如一個(gè)Celery工作站践宴。如果SocketIO服務(wù)器并沒有按照前面章節(jié)那樣配置監(jiān)聽隊(duì)列鲸匿,那么所有其它的進(jìn)程可以像服務(wù)器那樣創(chuàng)建它自己的SocketIO實(shí)例來創(chuàng)建消息活動(dòng)。
例如浴井,一個(gè)運(yùn)行在eventlet網(wǎng)絡(luò)服務(wù)器上的應(yīng)用,使用了Redis消息隊(duì)列霉撵,下面的Python腳本將向所有的客戶端廣播一個(gè)消息活動(dòng):
socketio=SocketIO(message_queue='redis://')
socketio.emit('my event', {'data': 'foo'}, namespace='/test')
當(dāng)使用這種方法引用SocketIO實(shí)例磺浙,F(xiàn)lask應(yīng)用實(shí)例將不會(huì)傳遞到構(gòu)造函數(shù)
當(dāng)SocketIO通過消息隊(duì)列使用參數(shù)channel來選擇一個(gè)具體channel的對(duì)話。當(dāng)很多獨(dú)立的SocketIO服務(wù)公用一個(gè)隊(duì)列的時(shí)候徒坡,使用一個(gè)自定義的channel名稱將是很有必要的撕氧。
Flask-SocketIO并沒有在使用eventlet或者gevent時(shí)應(yīng)用猴子(monkey)來修補(bǔ)。但是當(dāng)使用消息隊(duì)列的時(shí)候喇完,如果Python標(biāo)準(zhǔn)庫沒有使用猴子來修補(bǔ)伦泥,那么消息隊(duì)列服務(wù)的Python包很可能會(huì)掛起。
很重要的一點(diǎn)是:外部進(jìn)程想連接到SocketIO服務(wù)器并不需要像主服務(wù)器那樣使用eventlet或者gevent锦溪。使一個(gè)服務(wù)器使用了協(xié)同框架不脯,外部進(jìn)程不是一個(gè)阻力。例如刻诊,Celery工作站并不需要配置使用eventlet或者gevent防楷,是因?yàn)橹鞣?wù)器已經(jīng)有了。但是则涯,如果你的外部進(jìn)程因?yàn)槟撤N原因
使用了協(xié)同框架复局,那么monkey修復(fù)就很可能是需要的,那么消息隊(duì)列就可以獲得協(xié)同友好的函數(shù)和類粟判。
19.從Flask-SocketIO 0.x 升級(jí)到 1.x 和 2.x 版本
老版本的Flask-SocketIO有完全不同的一系列依賴包亿昏。老版本依賴gevent-socketio和gevent-websocket,這些包 1.0 版本都不需要了档礁。
盡管依賴的改變角钩,但是 1.0 版本卻沒有太多重要的改變。下面是一個(gè)實(shí)際改變的詳細(xì)的清單:
- 1.0 版本放棄支持Python 2.6,增加了對(duì)Python 3.3, Python 3.4 和 pypy 的支持彤断。
- 0.x 版本需要老版本的Socket.IO javascript客戶端野舶。從 1.0 版本開始,支持新發(fā)布的Socket.IO和Engin.IO宰衙。1.0版本以前的Socket.IO將不再被支持平道。Swift和C++官方的Socket.IO客戶端也被支持。
- 0.x 版本依賴gevent供炼,gevent-socketio和gevent-websocket.1.0 版本以后將不再使用一屋。在Flask開發(fā)的網(wǎng)絡(luò)服務(wù)器中,gevent是三種后端網(wǎng)絡(luò)服務(wù)器選擇之一袋哼,另外兩個(gè)是eventlet和其它常規(guī)多線程WSGI服務(wù)器冀墨。
- Socket.IO服務(wù)器選項(xiàng)在 1.0 版本中也有所改變。它們可以由SocketIO構(gòu)造函數(shù)來提供涛贯,或者由
run()
調(diào)用诽嘉。這些選項(xiàng)在使用前在這兩者中被合并。 - 0.x 版本暴露了gevent-socketio在連接中作為
request.namespace
弟翘。在 1.0 版本中它不再被使用虫腋。這個(gè)請(qǐng)求對(duì)象定義了request.namespace
作為待處理的命令空間。并且增加了request.aid
稀余,為客戶端連接定義了一個(gè)獨(dú)有的會(huì)話ID悦冀,request.event
包含了活動(dòng)名稱和參數(shù)。 - 為了獲得房間列表睛琳,0.x版本需要應(yīng)用使用私有g(shù)event-socketio結(jié)構(gòu)盒蟆,包含
request.namespace.rooms
表達(dá)式。這是在 1.0 版本中將不再出現(xiàn)师骗,因?yàn)樗艘粋€(gè)合適的room()
函數(shù)历等。 - 這個(gè)推薦的“把戲(trick)”發(fā)送消息到一個(gè)獨(dú)立的客戶端將消息分發(fā)到每個(gè)客戶端所在的獨(dú)立的房間內(nèi),這個(gè)地址消息對(duì)應(yīng)著目的房間(desired room)辟癌。這個(gè)特性在 1.0 版本中被正式化了募闲,當(dāng)客戶端連接到服務(wù)器時(shí),它會(huì)立即自動(dòng)地被分配到一個(gè)特定的房間內(nèi)愿待。
- 全局命名空間的
connect
活動(dòng)在 1.0 版本之前并沒有被觸發(fā)浩螺。這bug已經(jīng)被修復(fù)了并且按照預(yù)期觸發(fā)。 - 在 1.0 版本增加了對(duì)客戶端的回調(diào)函數(shù)的支持仍侥。
為了升級(jí)到新的Flask-SocketIO版本要出,你需要升級(jí)你的Socket.IO客戶端到兼容Socket.IO 1.0 協(xié)議。對(duì)于Javascript客戶端农渊,1.3.x和1.4.x版本經(jīng)過充分地測(cè)試患蹂,發(fā)現(xiàn)是兼容的或颊。
在服務(wù)端,有一些要點(diǎn)是要被考慮到的:
- 如果你想繼續(xù)使用gevent传于,那么gevent-socketio需要從你的虛擬環(huán)境中卸載囱挑,因?yàn)檫@個(gè)包將不再需要并且可能會(huì)與它的替代——python-socketio相沖突。
- 如果你想輕微地提高性能和穩(wěn)定性沼溜,那么推薦你轉(zhuǎn)而使用eventlet平挑。為了做到這一點(diǎn),需要卸載gevent系草、gevent-socketio和gevent-websocket通熄,然后安裝eventlet。
- 如果你的應(yīng)用使用了猴子修復(fù)了并轉(zhuǎn)向了eventlet找都,需要調(diào)用
eventlet.monkey_patch()
來代替gevent中的monkey.patch_all()
唇辨。此外,任何對(duì)gevent的調(diào)用必須被同等條件下的對(duì)eventlet調(diào)用替代能耻。 - 任何使用
request.namespace
需要被直接調(diào)用Flask-SocketIO函數(shù)替代赏枚。例如,request.namespace.rooms
要用rooms()
函數(shù)替換晓猛。 - 任何使用內(nèi)置的gevent-socketio的對(duì)象都必須被去除饿幅,當(dāng)這個(gè)包不再是所需的依賴的時(shí)候。