日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python flask源码解析_浅谈flask源码之请求过程

發布時間:2023/12/15 python 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python flask源码解析_浅谈flask源码之请求过程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Flask

Flask是什么?

Flask是一個使用 Python 編寫的輕量級 Web 應用框架, 讓我們可以使用Python語言快速搭建Web服務, Flask也被稱為 "microframework" ,因為它使用簡單的核心, 用 extension 增加其他功能

為什么選擇Flask?

我們先來看看python現在比較流行的web框架

Flask

Django

Tornado

Sanic

Flask: 輕, 組件間松耦合, 自由、靈活,可擴展性強,第三方庫的選擇面廣的同時也增加了組件間兼容問題

Django: Django相當于一個全家桶, 幾乎包括了所有web開發用到的模塊(session管理、CSRF防偽造請求、Form表單處理、ORM數據庫對象化、模板語言), 但是相對應的會造成一個緊耦合的情況, 對第三方插件不太友好

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__)

# 摘自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()請求分發的過程我們需要關注一下

# 摘自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處理請求的步驟, 那么我們來試試

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 函數去處理呢?我們先來普及幾個知識點:

url: 客戶端訪問的網址

view_func: 即我們寫的視圖函數

rule: 定義的匹配路由的地址

url_map: 存放著rule與endpoint的映射關系

endpoint: 可以看作為每個view_func的ID

view_functions: 一個字典, 以endpoint為key, view_func 為value

添加路由的方法:

1.@app.route

2.add_url_rule

我們先來看看@app.route干了什么事情

# 摘自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.

# 摘自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又是什么等.

我們只需要在需要用到這些信息的時候把它從上下文中取出來即可. 而上下文是有生命周期的, 不是所有時候都能獲取到.

上下文生命周期:

RequestContext: 生命周期在處理一次請求期間, 請求處理完成后生命周期也就結束了.

AppContext: 生命周期最長, 只要當前應用還在運行, 就一直存在. (應用未運行前并不存在)

那么上下文是在什么時候創建的呢?我們又要如何創建上下文: 剛才我們提到, 在wsgi_app處理請求的時候就會先創建上下文, 那個上下文其實是請求上下文, 那應用上下文呢?

# 摘自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來創建應用上下文與請求上下文.

那么我們應該如何使用上下文呢?

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就是請求上下文.

current_app 表示當前運行程序文件的程序實例

g: 處理請求時用作臨時存儲的對象. 每次請求都會重設這個變量 生命周期同RequestContext

request 代表的是當前的請求

那么隨之而來的問題是: 這些上下文的作用域是什么?

線程有個叫做ThreadLocal的類,也就是通常實現線程隔離的類. 而werkzeug自己實現了它的線程隔離類: werkzeug.local.Local. 而LocalStack就是用Local實現的.

這個我們可以通過globals.py可以看到

# 摘自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通過這樣的方式來隔離每個請求.

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

總結

以上是生活随笔為你收集整理的python flask源码解析_浅谈flask源码之请求过程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。