欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談flask源碼之請求過程

 更新時間:2018年07月26日 09:51:35   作者:Dear、  
這篇文章主要介紹了淺談flask源碼之請求過程,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

Flask

Flask是什么?

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

為什么選擇Flask?

我們先來看看python現(xiàn)在比較流行的web框架

  • Flask
  • Django
  • Tornado
  • Sanic

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

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

Tornado: 底層通過eventloop來實現(xiàn)異步處理請求, 處理效率高, 學(xué)習(xí)難度大, 處理稍有不慎很容易阻塞主進(jìn)程導(dǎo)致不能正常提供服務(wù), 新版本也支持asyncio

Sanic: 一個類Flask框架, 但是底層使用uvloop進(jìn)行異步處理, 可以使用同步的方式編寫異步代碼, 而且運(yùn)行效率十分高效.

WSGI

先來看看維基百科對WSGI的定義

Web服務(wù)器網(wǎng)關(guān)接口(Python Web Server Gateway Interface,縮寫為WSGI)是為Python語言定義的Web服務(wù)器和Web應(yīng)用程序或框架之間的一種簡單而通用的接口.

何為網(wǎng)關(guān), 即從客戶端發(fā)出的每個請求(數(shù)據(jù)包)第一個到達(dá)的地方, 然后再根據(jù)路由進(jìn)行轉(zhuǎn)發(fā)處理. 而對于服務(wù)端發(fā)送過來的消息, 總是先通過網(wǎng)關(guān)層, 然后再轉(zhuǎn)發(fā)至客戶端

那么可想而知, WSGI其實是作為一個網(wǎng)關(guān)接口, 來接受Server傳遞過來的信息, 然后通過這個接口調(diào)用后臺app里的view function進(jìn)行響應(yīng).

先看一段有趣的對話:

Nginx:Hey, WSGI, 我剛收到了一個請求,我需要你作些準(zhǔn)備, 然后由Flask來處理這個請求.
WSGI:OK, Nginx. 我會設(shè)置好環(huán)境變量, 然后將這個請求傳遞給Flask處理.
Flask:Thanks. WSGI給我一些時間,我將會把請求的響應(yīng)返回給你.
WSGI:Alright, 那我等你.
Flask:Okay, 我完成了, 這里是請求的響應(yīng)結(jié)果, 請求把結(jié)果傳遞給Nginx.
WSGI:Good job! Nginx, 這里是響應(yīng)結(jié)果, 已經(jīng)按照要求給你傳遞回來了.
Nginx:Cool, 我收到了, 我把響應(yīng)結(jié)果返回給客戶端.大家合作愉快~

對話里面可以清晰了解到WSGI、nginx、Flask三者的關(guān)系

下面來看看Flask中的wsgi接口(注意:每個進(jìn)入Flask的請求都會調(diào)用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提供的函數(shù), 調(diào)用可以發(fā)送響應(yīng)的狀態(tài)碼和HTTP報文頭,
    # 函數(shù)在返回前必須調(diào)用一次.
    :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.
    # 創(chuàng)建上下文
    ctx = self.request_context(environ)
    error = None
    try:
      try:
        # 把上下文壓棧
        ctx.push()
        # 分發(fā)請求
        response = self.full_dispatch_request()
      except Exception as e:
        error = e
        response = self.handle_exception(e)
      except:
        error = sys.exc_info()[1]
        raise
      # 返回結(jié)果
      return response(environ, start_response)
    finally:
      if self.should_ignore_error(error):
        error = None
        # 上下文出棧
        ctx.auto_pop(error)

wsgi_app中定義的就是Flask處理一個請求的基本流程,
1.創(chuàng)建上下文
2.把上下文入棧
3.分發(fā)請求
4.上下文出棧
5.返回結(jié)果

其中response = self.full_dispatch_request()請求分發(fā)的過程我們需要關(guān)注一下

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

我們可以看到, 請求分發(fā)的操作其實是由dispatch_request來完成的, 而在請求進(jìn)行分發(fā)的前后我們可以看到Flask進(jìn)行了如下操作:
1.try_trigger_before_first_request_functions, 首次處理請求前的操作,通過@before_first_request定義,可以進(jìn)行數(shù)據(jù)庫連接
2.preprocess_request, 每次處理請求前進(jìn)行的操作, 通過@before_request來定義, 可以攔截請求
3.process_response, 每次正常處理請求后進(jìn)行的操作, 通過@after_request來定義, 可以統(tǒng)計接口訪問成功的數(shù)量
4.finalize_request, 把視圖函數(shù)的返回值轉(zhuǎn)換成一個真正的響應(yīng)對象

以上的這些是Flask提供給我們使用的鉤子(hook), 可以根據(jù)自身需求來定義,
而hook中還有@teardown_request, 是在每次處理請求后執(zhí)行(無論是否有異常), 所以它是在上下文出棧的時候被調(diào)用

如果同時定義了四種鉤子(hook), 那么執(zhí)行順序應(yīng)該是

graph LR
before_first_request --> before_request
before_request --> after_request
after_request --> teardown_request

在請求函數(shù)和鉤子函數(shù)之間,一般通過全局變量g實現(xiàn)數(shù)據(jù)共享

現(xiàn)在的處理流程就變?yōu)?

1.創(chuàng)建上下文
2.上下文入棧
3.執(zhí)行before_first_request操作(如果是第一次處理請求)
4.執(zhí)行before_request操作
5.分發(fā)請求
6.執(zhí)行after_request操作
7.執(zhí)行teardown_request操作
8.上下文出棧
9.返回結(jié)果

其中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()

當(dāng)運(yùn)行flask進(jìn)程時, 訪問127.0.0.1:5000, 程序輸出, 正好認(rèn)證了我們之前說的執(zhí)行順序.

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 -

路由分發(fā)

看了上面的代碼, 我們可能還是會有疑問, 為什么我們的請求就會跑到hello world 函數(shù)去處理呢?我們先來普及幾個知識點(diǎn):

  • url: 客戶端訪問的網(wǎng)址
  • view_func: 即我們寫的視圖函數(shù)
  • rule: 定義的匹配路由的地址
  • url_map: 存放著rule與endpoint的映射關(guān)系
  • 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函數(shù)是一個裝飾器, 它在執(zhí)行時會先獲取endpoint, 然后再通過調(diào)用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):
    # 創(chuàng)建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

可以看到, 當(dāng)我們添加路由時, 會生成一個rule, 并把它存放到url_map里頭, 然后把view_func與其對應(yīng)的endpoint存到字典.

當(dāng)一個請求進(jìn)入時, Flask會先根據(jù)用戶訪問的Url到url_map里邊根據(jù)rule來獲取到endpoint, 然后再利用view_functions獲取endpoint在里邊所對應(yīng)的視圖函數(shù)

graph LR
url1 -->url_map
url2 -->url_map
url3 -->url_map
urln -->url_map
url_map --> endpoint
endpoint --> view_functions

上下文管理

下面我們再來看看之前一直忽略的上下文,什么是上下文呢?

上下文即語境、語意,是一句話中的語境,也就是語言環(huán)境. 一句莫名其妙的話出現(xiàn)會讓人不理解什么意思, 如果有語言環(huán)境的說明, 則會更好, 這就是語境對語意的影響. 而對應(yīng)到程序里往往就是程序中需要共享的信息,保存著程序運(yùn)行或交互中需要保持或傳遞的信息.

Flask中有兩種上下文分別為:應(yīng)用上下文(AppContext)和請求上下文(RequestContext). 按照上面提到的我們很容易就聯(lián)想到:應(yīng)用上下文就是保存著應(yīng)用運(yùn)行或交互中需要保持或傳遞的信息, 如當(dāng)前應(yīng)用的應(yīng)用名, 當(dāng)前應(yīng)用注冊了什么路由, 又有什么視圖函數(shù)等. 而請求上下文就保存著處理請求過程中需要保持或傳遞的信息, 如這次請求的url是什么, 參數(shù)又是什么, 請求的method又是什么等.

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

上下文生命周期:

  • RequestContext: 生命周期在處理一次請求期間, 請求處理完成后生命周期也就結(jié)束了.
  • AppContext: 生命周期最長, 只要當(dāng)前應(yīng)用還在運(yùn)行, 就一直存在. (應(yīng)用未運(yùn)行前并不存在)

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

# 摘自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)
    # 獲取應(yīng)用上下文
    app_ctx = _app_ctx_stack.top
    # 判斷應(yīng)用上下文是否存在并與當(dāng)前應(yīng)用一致
    if app_ctx is None or app_ctx.app != self.app:
      # 創(chuàng)建應(yīng)用上下文并入棧
      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)

我們知道當(dāng)有請求進(jìn)入時, Flask會自動幫我們來創(chuàng)建請求上下文. 而通過上述代碼我們可以看到,在創(chuàng)建請求上下文時會有一個判斷操作, 如果應(yīng)用上下文為空或與當(dāng)前應(yīng)用不匹配, 那么會重新創(chuàng)建一個應(yīng)用上下文. 所以說一般情況下并不需要我們手動去創(chuàng)建, 當(dāng)然如果需要, 你也可以顯式調(diào)用app_context與request_context來創(chuàng)建應(yīng)用上下文與請求上下文.

那么我們應(yīng)該如何使用上下文呢?

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 -

代碼里邊應(yīng)用到的current_app和g都屬于應(yīng)用上下文對象, 而request就是請求上下文.

  • current_app 表示當(dāng)前運(yùn)行程序文件的程序?qū)嵗?/li>
  • g: 處理請求時用作臨時存儲的對象. 每次請求都會重設(shè)這個變量 生命周期同RequestContext
  • request 代表的是當(dāng)前的請求

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

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

這個我們可以通過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思就是說, 對于不同的線程, 它們訪問這兩個對象看到的結(jié)果是不一樣的、完全隔離的. Flask通過這樣的方式來隔離每個請求.

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • PyCharm無法登陸Codeium的解決方法

    PyCharm無法登陸Codeium的解決方法

    Codeium插件可以自動建議、解釋代碼、生成注釋和根據(jù)注釋生成代碼等,本文主要介紹了PyCharm無法登陸Codeium的解決方法,感興趣的可以了解一下
    2023-11-11
  • 通過Python編程將CSV文件導(dǎo)出為PDF文件的方法

    通過Python編程將CSV文件導(dǎo)出為PDF文件的方法

    CSV文件通常用于存儲大量的數(shù)據(jù),而PDF文件則是一種通用的文檔格式,便于與他人共享和打印,將CSV文件轉(zhuǎn)換成PDF文件可以幫助我們更好地管理和展示數(shù)據(jù),本文將介紹如何通過Python編程將CSV文件導(dǎo)出為PDF文件,需要的朋友可以參考下
    2024-06-06
  • python如何編寫類似nmap的掃描工具

    python如何編寫類似nmap的掃描工具

    這篇文章主要介紹了python如何編寫類似nmap的掃描工具,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-11-11
  • 使用Python腳本將文字轉(zhuǎn)換為圖片的實例分享

    使用Python腳本將文字轉(zhuǎn)換為圖片的實例分享

    這篇文章主要介紹了使用Python腳本將文字轉(zhuǎn)換為圖片的實例分享,主要用到了PIL庫,需要的朋友可以參考下
    2015-08-08
  • TensorFlow 實戰(zhàn)之實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)的實例講解

    TensorFlow 實戰(zhàn)之實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)的實例講解

    下面小編就為大家分享一篇TensorFlow 實戰(zhàn)之實現(xiàn)卷積神經(jīng)網(wǎng)絡(luò)的實例講解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-02-02
  • Python中字典創(chuàng)建、遍歷、添加等實用操作技巧合集

    Python中字典創(chuàng)建、遍歷、添加等實用操作技巧合集

    這篇文章主要介紹了Python中字典創(chuàng)建、遍歷、添加等實用操作技巧合集,本文講解了字典中常見方法列表、創(chuàng)建字典的五種方法、字典中鍵值遍歷方法等內(nèi)容,需要的朋友可以參考下
    2015-06-06
  • python安裝讀取grib庫總結(jié)(推薦)

    python安裝讀取grib庫總結(jié)(推薦)

    這篇文章主要介紹了python安裝讀取grib庫總結(jié),本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-06-06
  • 使用Django2快速開發(fā)Web項目的詳細(xì)步驟

    使用Django2快速開發(fā)Web項目的詳細(xì)步驟

    這篇文章主要介紹了使用Django2快速開發(fā)Web項目的詳細(xì)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-01-01
  • python字典鍵值對的添加和遍歷方法

    python字典鍵值對的添加和遍歷方法

    下面小編就為大家?guī)硪黄猵ython字典鍵值對的添加和遍歷方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-09-09
  • Python人工智能語音合成實現(xiàn)案例詳解

    Python人工智能語音合成實現(xiàn)案例詳解

    這篇文章主要為大家介紹了Python人工智能語音合成實現(xiàn)案例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03

最新評論