更新時間:2018年07月26日 09:51:36 做者:Dear、 我要評論python
Flasknginx
Flask是什麼?web
Flask是一個使用 Python 編寫的輕量級 Web 應用框架, 讓咱們可使用Python語言快速搭建Web服務, Flask也被稱爲 "microframework" ,由於它使用簡單的核心, 用 extension 增長其餘功能數據庫
爲何選擇Flask?flask
咱們先來看看python如今比較流行的web框架服務器
Flask: 輕, 組件間鬆耦合, 自由、靈活,可擴展性強,第三方庫的選擇面廣的同時也增長了組件間兼容問題session
Django: Django至關於一個全家桶, 幾乎包括了全部web開發用到的模塊(session管理、CSRF防僞造請求、Form表單處理、ORM數據庫對象化、模板語言), 可是相對應的會形成一個緊耦合的狀況, 對第三方插件不太友好app
Tornado: 底層經過eventloop來實現異步處理請求, 處理效率高, 學習難度大, 處理稍有不慎很容易阻塞主進程致使不能正常提供服務, 新版本也支持asyncio框架
Sanic: 一個類Flask框架, 可是底層使用uvloop進行異步處理, 可使用同步的方式編寫異步代碼, 並且運行效率十分高效.異步
WSGI
先來看看維基百科對WSGI的定義
Web服務器網關接口(Python Web Server Gateway Interface,縮寫爲WSGI)是爲Python語言定義的Web服務器和Web應用程序或框架之間的一種簡單而通用的接口.
何爲網關, 即從客戶端發出的每一個請求(數據包)第一個到達的地方, 而後再根據路由進行轉發處理. 而對於服務端發送過來的消息, 老是先經過網關層, 而後再轉發至客戶端
那麼可想而知, WSGI實際上是做爲一個網關接口, 來接受Server傳遞過來的信息, 而後經過這個接口調用後臺app裏的view function進行響應.
先看一段有趣的對話:
Nginx:Hey, WSGI, 我剛收到了一個請求,我須要你做些準備, 而後由Flask來處理這個請求.
WSGI:OK, Nginx. 我會設置好環境變量, 而後將這個請求傳遞給Flask處理.
Flask:Thanks. WSGI給我一些時間,我將會把請求的響應返回給你.
WSGI:Alright, 那我等你.
Flask:Okay, 我完成了, 這裏是請求的響應結果, 請求把結果傳遞給Nginx.
WSGI:Good job! Nginx, 這裏是響應結果, 已經按照要求給你傳遞回來了.
Nginx:Cool, 我收到了, 我把響應結果返回給客戶端.你們合做愉快~
對話裏面能夠清晰瞭解到WSGI、nginx、Flask三者的關係
下面來看看Flask中的wsgi接口(注意:每一個進入Flask的請求都會調用Flask.__call__)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
# 摘自Flask源碼 app.py
class
Flask(_PackageBoundObject):
# 中間省略
def
__call__(
self
, environ, start_response):
return
self
.wsgi_app(environ, start_response)
def
wsgi_app(
self
, environ, start_response):
# environ: 一個包含所有HTTP請求信息的字典, 由WSGI Server解包HTTP請求生成
# start_response: WSGI Server提供的函數, 調用能夠發送響應的狀態碼和HTTP報文頭,
# 函數在返回前必須調用一次.
:param environ: A WSGI environment.
:param start_response: A
callable
accepting a status code,
a
list
of headers,
and
an optional exception context to
start the response.
# 建立上下文
ctx
=
self
.request_context(environ)
error
=
None
try
:
try
:
# 把上下文壓棧
ctx.push()
# 分發請求
response
=
self
.full_dispatch_request()
except
Exception as e:
error
=
e
response
=
self
.handle_exception(e)
except
:
error
=
sys.exc_info()[
1
]
raise
# 返回結果
return
response(environ, start_response)
finally
:
if
self
.should_ignore_error(error):
error
=
None
# 上下文出棧
ctx.auto_pop(error)
|
wsgi_app中定義的就是Flask處理一個請求的基本流程,
1.建立上下文
2.把上下文入棧
3.分發請求
4.上下文出棧
5.返回結果
其中response = self.full_dispatch_request()請求分發的過程咱們須要關注一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
# 摘自Flask源碼 app.py
class
Flask(_PackageBoundObject):
# 中間省略
def
full_dispatch_request(
self
):
self
.try_trigger_before_first_request_functions()
try
:
request_started.send(
self
)
rv
=
self
.preprocess_request()
if
rv
is
None
:
rv
=
self
.dispatch_request()
except
Exception as e:
rv
=
self
.handle_user_exception(e)
return
self
.finalize_request(rv)
def
dispatch_request(
self
):
req
=
_request_ctx_stack.top.request
if
req.routing_exception
is
not
None
:
self
.raise_routing_exception(req)
rule
=
req.url_rule
if
getattr
(rule,
'provide_automatic_options'
,
False
) \
and
req.method
=
=
'OPTIONS'
:
return
self
.make_default_options_response()
return
self
.view_functions[rule.endpoint](
*
*
req.view_args)
def
finalize_request(
self
, rv, from_error_handler
=
False
):
response
=
self
.make_response(rv)
try
:
response
=
self
.process_response(response)
request_finished.send(
self
, response
=
response)
except
Exception:
if
not
from_error_handler:
raise
self
.logger.exception(
'Request finalizing failed with an '
'error while handling an error'
)
return
response
|
咱們能夠看到, 請求分發的操做實際上是由dispatch_request來完成的, 而在請求進行分發的先後咱們能夠看到Flask進行了以下操做:
1.try_trigger_before_first_request_functions, 首次處理請求前的操做,經過@before_first_request定義,能夠進行數據庫鏈接
2.preprocess_request, 每次處理請求前進行的操做, 經過@before_request來定義, 能夠攔截請求
3.process_response, 每次正常處理請求後進行的操做, 經過@after_request來定義, 能夠統計接口訪問成功的數量
4.finalize_request, 把視圖函數的返回值轉換成一個真正的響應對象
以上的這些是Flask提供給咱們使用的鉤子(hook), 能夠根據自身需求來定義,
而hook中還有@teardown_request, 是在每次處理請求後執行(不管是否有異常), 因此它是在上下文出棧的時候被調用
若是同時定義了四種鉤子(hook), 那麼執行順序應該是
graph LR
before_first_request --> before_request
before_request --> after_request
after_request --> teardown_request
在請求函數和鉤子函數之間,通常經過全局變量g實現數據共享
如今的處理流程就變爲:
1.建立上下文
2.上下文入棧
3.執行before_first_request操做(若是是第一次處理請求)
4.執行before_request操做
5.分發請求
6.執行after_request操做
7.執行teardown_request操做
8.上下文出棧
9.返回結果
其中3-7就是須要咱們完成的部分.
如何使用Flask
上面咱們知道, Flask處理請求的步驟, 那麼咱們來試試
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
from
flask
import
Flask
app
=
Flask(__name__)
@app
.before_first_request
def
before_first_request():
print
(
'before_first_request run'
)
@app
.before_request
def
before_request():
print
(
'before_request run'
)
@app
.after_request
def
after_request(param):
print
(
'after_request run'
)
return
param
@app
.teardown_request
def
teardown_request(param):
print
(
'teardown_request run'
)
@app
.route(
'/'
)
def
hello_world():
return
'Hello World!'
if
__name__
=
=
'__main__'
:
app.run()
|
當運行flask進程時, 訪問127.0.0.1:5000, 程序輸出, 正好認證了咱們以前說的執行順序.
before_first_request run
before_request run
after_request run
teardown_request run
127.0.0.1 - - [03/May/2018 18:42:52] "GET / HTTP/1.1" 200 -
路由分發
看了上面的代碼, 咱們可能仍是會有疑問, 爲何咱們的請求就會跑到hello world 函數去處理呢?咱們先來普及幾個知識點:
添加路由的方法:
1.@app.route
2.add_url_rule
咱們先來看看@app.route幹了什麼事情
1
2
3
4
5
6
7
8
9
|
# 摘自Flask源碼 app.py
class
Flask(_PackageBoundObject):
# 中間省略
def
route(
self
, rule,
*
*
options):
def
decorator(f):
endpoint
=
options.pop(
'endpoint'
,
None
)
self
.add_url_rule(rule, endpoint, f,
*
*
options)
return
f
return
decorator
|
咱們能夠看到, route函數是一個裝飾器, 它在執行時會先獲取endpoint, 而後再經過調用add_url_rule來添加路由, 也就是說全部添加路由的操做其實都是經過add_url_rule來完成的. 下面咱們再來看看add_url_rule.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# 摘自Flask源碼 app.py
class
Flask(_PackageBoundObject):
# 中間省略
# 定義view_functions
self
.view_functions
=
{}
# 定義url_map
self
.url_map
=
Map
()
def
add_url_rule(
self
, rule, endpoint
=
None
, view_func
=
None
,
provide_automatic_options
=
None
,
*
*
options):
# 建立rule
rule
=
self
.url_rule_class(rule, methods
=
methods,
*
*
options)
rule.provide_automatic_options
=
provide_automatic_options
# 把rule添加到url_map
self
.url_map.add(rule)
if
view_func
is
not
None
:
old_func
=
self
.view_functions.get(endpoint)
if
old_func
is
not
None
and
old_func !
=
view_func:
raise
AssertionError(
'View function mapping is overwriting an '
'existing endpoint function: %s'
%
endpoint)
# 把view_func 添加到view_functions字典
self
.view_functions[endpoint]
=
view_func
|
能夠看到, 當咱們添加路由時, 會生成一個rule, 並把它存放到url_map裏頭, 而後把view_func與其對應的endpoint存到字典.
當一個請求進入時, Flask會先根據用戶訪問的Url到url_map裏邊根據rule來獲取到endpoint, 而後再利用view_functions獲取endpoint在裏邊所對應的視圖函數
graph LR
url1 -->url_map
url2 -->url_map
url3 -->url_map
urln -->url_map
url_map --> endpoint
endpoint --> view_functions
上下文管理
下面咱們再來看看以前一直忽略的上下文,什麼是上下文呢?
上下文即語境、語意,是一句話中的語境,也就是語言環境. 一句莫名其妙的話出現會讓人不理解什麼意思, 若是有語言環境的說明, 則會更好, 這就是語境對語意的影響. 而對應到程序裏每每就是程序中須要共享的信息,保存着程序運行或交互中須要保持或傳遞的信息.
Flask中有兩種上下文分別爲:應用上下文(AppContext)和請求上下文(RequestContext). 按照上面提到的咱們很容易就聯想到:應用上下文就是保存着應用運行或交互中須要保持或傳遞的信息, 如當前應用的應用名, 當前應用註冊了什麼路由, 又有什麼視圖函數等. 而請求上下文就保存着處理請求過程當中須要保持或傳遞的信息, 如此次請求的url是什麼, 參數又是什麼, 請求的method又是什麼等.
咱們只須要在須要用到這些信息的時候把它從上下文中取出來便可. 而上下文是有生命週期的, 不是全部時候都能獲取到.
上下文生命週期:
那麼上下文是在何時建立的呢?咱們又要如何建立上下文: 剛纔咱們提到, 在wsgi_app處理請求的時候就會先建立上下文, 那個上下文實際上是請求上下文, 那應用上下文呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
# 摘自Flask源碼 ctx.py
class
RequestContext(
object
):
# 中間省略
def
push(
self
):
top
=
_request_ctx_stack.top
if
top
is
not
None
and
top.preserved:
top.pop(top._preserved_exc)
# 獲取應用上下文
app_ctx
=
_app_ctx_stack.top
# 判斷應用上下文是否存在並與當前應用一致
if
app_ctx
is
None
or
app_ctx.app !
=
self
.app:
# 建立應用上下文併入棧
app_ctx
=
self
.app.app_context()
app_ctx.push()
self
._implicit_app_ctx_stack.append(app_ctx)
else
:
self
._implicit_app_ctx_stack.append(
None
)
if
hasattr
(sys,
'exc_clear'
):
sys.exc_clear()
# 把請求上下文入棧
_request_ctx_stack.push(
self
)
|
咱們知道當有請求進入時, Flask會自動幫咱們來建立請求上下文. 而經過上述代碼咱們能夠看到,在建立請求上下文時會有一個判斷操做, 若是應用上下文爲空或與當前應用不匹配, 那麼會從新建立一個應用上下文. 因此說通常狀況下並不須要咱們手動去建立, 固然若是須要, 你也能夠顯式調用app_context與request_context來建立應用上下文與請求上下文.
那麼咱們應該如何使用上下文呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
from
flask
import
Flask, request, g, current_app
app
=
Flask(__name__)
@app
.before_request
def
before_request():
print
'before_request run'
g.name
=
"Tom"
@app
.after_request
def
after_request(response):
print
'after_request run'
print
(g.name)
return
response
@app
.route(
'/'
)
def
index():
print
(request.url)
g.name
=
'Cat'
print
(current_app.name)
if
__name__
=
=
'__main__'
:
app.run()
|
訪問127.0.0.1:5000時程序輸出
before_request run
http://127.0.0.1:5000/
flask_run
after_request run
Cat
127.0.0.1 - - [04/May/2018 18:05:13] "GET / HTTP/1.1" 200 -
代碼裏邊應用到的current_app和g都屬於應用上下文對象, 而request就是請求上下文.
那麼隨之而來的問題是: 這些上下文的做用域是什麼?
線程有個叫作ThreadLocal的類,也就是一般實現線程隔離的類. 而werkzeug本身實現了它的線程隔離類: werkzeug.local.Local. 而LocalStack就是用Local實現的.
這個咱們能夠經過globals.py能夠看到
1
2
3
4
5
6
7
8
9
10
11
|
# 摘自Flask源碼 globals.py
from
functools
import
partial
from
werkzeug.local
import
LocalStack, LocalProxy
_request_ctx_stack
=
LocalStack()
_app_ctx_stack
=
LocalStack()
current_app
=
LocalProxy(_find_app)
request
=
LocalProxy(partial(_lookup_req_object,
'request'
))
session
=
LocalProxy(partial(_lookup_req_object,
'session'
))
g
=
LocalProxy(partial(_lookup_app_object,
'g'
))
|
_lookup_app_object思就是說, 對於不一樣的線程, 它們訪問這兩個對象看到的結果是不同的、徹底隔離的. Flask經過這樣的方式來隔離每一個請求.
以上就是本文的所有內容,但願對你們的學習有所幫助,也但願你們多多支持腳本之家。