requests模塊源碼閱讀總結

Requests is an elegant and simple HTTP library for Python, built for human beings.

Python requests是最常用的Python第三方庫之一,可用於發送HTTP請求,其採用了直觀的API設計風格,使用起來非常簡單方便。

Requests庫是出自於大神 Kenneth Reitz 之手,我之前看過他寫的《Python編程之美》,這本書可以讓我們編寫的代碼更Pythonic,工程結構更加優美。Requests庫的源碼地址在:Github psf/requests,將其源碼下載到本地之後,打開工程我們會發現其工程結構和代碼規範都非常的優美,而且代碼的核心邏輯也非常簡潔,特別適合剛開始嘗試閱讀源碼的同學學習。今天這篇文章就對Python requests庫進行一個簡單的源碼分析。

1. 簡單實例


首先,我們還是先用requests庫編寫一個簡單的入門實例,然後再從實例作爲入口深入分析其源碼。

>>> import requests
>>> url = 'https://api.github.com/repos/psf/requests'
>>> r = requests.get(url)
>>> r.ok
True
>>> r.json()['description']
'A simple, yet elegant HTTP library.'
>>>

Requests庫將常用的HTTP操作在api層進行了封裝,我們可以非常方便地發起get/post/put/delete/head等操作,如果我們的應用場景比較複雜,也可以直接使用其內部對象進行定製化開發。

2. 核心流程


下圖呈現了我們上面實例中get請求的核心時序流程,這裏將一些非關鍵的步驟給忽略掉了,有興趣的話可以自己去細讀源碼。接下來我們逐層解析時序圖中涉及到的對象和流程。
在這裏插入圖片描述
首先是api層,requests庫的api模塊中封裝了 get, post, options, head, put, delete等方法,我們可以直接調用這幾個方法發起HTTP請求,這些方法中都調用了request()方法,request方法的代碼如下,這裏實例化了一個Session對象,然後調用了Session對象的request方法發起請求。

def request(method, url, **kwargs):
    with sessions.Session() as session:
        return session.request(method=method, url=url, **kwargs)

3. Session


Session對象可用於保存請求的狀態,比如證書、cookies、proxy等信息,可實現在多個請求之間保持長連接。如果我們是直接使用的api層的方法發起的請求,那麼在請求結束之後,所有的狀態都會被清理掉。如果我們需要頻繁向服務端發起請求,那麼使用Session實現長連接可以大大提升處理性能。

Session類中維護了多個HTTPAdpater對象,分別用於處理不同scheme的請求,代碼如下,我們也可以通過Session.mount()方法設置我們自定義的HTTPAdapter,比如根據要求重新設置重試次數等等。

class Session(SessionRedirectMixin):
    def __init__(self):
        ......
        self.adapters = OrderedDict()
        self.mount('https://', HTTPAdapter())
        self.mount('http://', HTTPAdapter())

Session類繼承自SessionRedirectMixin類,SessionRedirectMixin類中實現了用於進行HTTP重定向的能力,這塊就不細看了。

我們來看Session對象的request()方法,該方法主要用於發送請求,方法中首先調用了prepare_request()方法將一些請求數據提前封裝到了一個PreparedRequest對象中,然後在後面請求的過程中都是使用的該對象。PreparedRequest對象中將請求的method, url, header, cookie, body等數據進行了預處理。

class PreparedRequest(...):
    ......
    def prepare(self,
            method=None, url=None, headers=None, files=None, data=None,
            params=None, auth=None, cookies=None, hooks=None, json=None):
        self.prepare_method(method)
        self.prepare_url(url, params)
        self.prepare_headers(headers)
        self.prepare_cookies(cookies)
        self.prepare_body(data, files, json)
        self.prepare_auth(auth, url)
        self.prepare_hooks(hooks)

回到request方法中,下一步會將該request對象和一些請求配置信息一起通過調用Session.send()方法將請求發送出去。send方法的邏輯比較簡單,就是先獲取對應的HTTPAdapter,然後調用adapter的send方法發起請求,最後收到應答之後再判斷是否要進行重定向。方法中包含了很多非關鍵代碼,這裏就不貼源碼了。

4. HTTPAdapter


接下來看HTTPAdapter類,之前講了Session對象主要是用來保存請求的狀態的,其實HTTPAdapter纔是實際用於發送請求的組件。

我們看HTTPAdapter類的構建方法可以知道,Adapter中主要維護了三個對象,max_retries用於請求重試,proxy_manager用於維護與proxy的連接,poolmanager屬性維護了一個連接池PoolManager對象。我們在創建HTTPAdapter的時候可以指定連接池的大小和請求最大重試次數。

這裏之所以使用連接池,是爲了對連接到相同服務端的請求的連接進行復用,我們知道在一次請求過程中,TCP連接的建立和斷開是非常耗時的,如果能夠把建立連接這一步省掉那將會大大提升請求性能。連接池的細節我們在下一小節再詳細解析。

def send(self, request, ...):
    conn = self.get_connection(request.url, proxies)
    self.cert_verify(conn, request.url, verify, cert)
    resp = conn.urlopen(...)
    return self.build_response(request, resp)

接下來我們來看HTTPAdapter類的send方法,該方法的代碼比較長,清除掉其他非核心代碼,主要包括以下四步,代碼如上。

  1. 首先是調用get_connection()方法從proxy_manager或者poolmanager中獲取到連接,這裏代碼中用的雖然是connection變量名,但是實際上這裏獲取的是一個連接池HTTPConnectionPool對象,具體使用的是哪個連接發送請求其實是在連接池HTTPConnectionPool中去自動選擇的。這裏如果有使用proxy,那麼這裏就是獲取的連接到proxy的一個連接池。
  2. 然後下一步就是調用cert_verify()方法將TLS證書設置到連接中去,如果使用HTTPS的話。
  3. 接下來就是調用連接池HTTPConnectionPool的urlopen()方法,獲取一個連接,發送請求,獲取響應,這裏獲取到的響應還是urllib3庫中的原生response對象。
  4. 最後一步是調用build_response()方法將urllib3庫中的原生response對象封裝爲requests庫中的Response對象。

5. PoolManager


PoolManager對象是urllib3庫中的成員,requests庫直接在HTTPAdapater類中封裝了urllib3庫的組件實現了連接池。我們這裏對PoolManager對象進行一個簡單的解析。

PoolManager中維護了若干個連接池HTTPConnectionPool或者HTTPSConnectionPool,每個連接池又維護了若干條Connection,這裏默認的大小是10個連接池,每個連接池10條連接,但是我們可以在創建PoolManager的時候自行指定。

PoolManager中針對具有scheme、host、port三個屬性相同的請求使用同一個連接池,每個連接池維護了一個連接隊列,當獲取連接時會優先從隊列中獲取一條現成的連接,如果沒有現成的則新創建一條連接,連接使用完成之後會再次添加回隊列中,以便後續可以繼續使用。

6. 優秀工程實踐


其實requests庫除了代碼非常優美值得我們學習之外,其使用到的工程實踐也是值得我們在自己的項目中學習借鑑的。

Requests項目的主目錄下除了requests目錄用於放置源碼外,還包含了docs目錄存放文檔的網頁源碼,一個項目除了源碼,清晰準確的文檔也是非常重要的。在tests目錄下存放的是在編碼過程中同步編寫的測試用例,另外主目錄下還有CI腳本,每次往項目中集成新代碼時都會執行CI,自動運行測試用例,保證新合入代碼未破壞之前的功能。另外主目錄下還有HISTORY文件,用於詳細記錄每個版本的變更內容,方便版本發佈,另外還有項目依賴包文件、README等。
在這裏插入圖片描述
Requests庫的代碼組織結構也是值得學習的,模塊根據功能組織,清晰明確。另外__version__模塊存放了項目的版本相關信息,在__init__中將api層的接口方法以及一些核心模型直接暴露出去,這樣對於使用者使用起來會更加的方便。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章