[toc]python
requests是一個Python的網絡請求庫,和urllib、httplib之流相比起來最大的優勢就是好用,requests官方標榜的就是「咱們的庫是給人用的哦」。linux
此外,requests還支持https驗證而且是線程安全的git
官方文檔github
系統:Linux -Fedora 29 x64
開發工具: vscode
Requests版本: V2.20.0
python版本: 3.6.7web
git下載地址算法
首先git clone源碼到本地,而後使用cloc工具查看文件格式瀏覽器
若是系統沒有cloc工具,能夠進行安裝。
因爲我本地是fedora系統,可使用dnf install cloc命令進行安裝
使用下面命令查看項目關於python文件的統計狀況:安全
[jian@laptop requests]$ cloc --include-lang=Python .
Language | files | blank | comment | code |
---|---|---|---|---|
Python | 35 | 1951 | 1990 | 5937 |
能夠看到總共python文件有35個,代碼量是5937行
服務器
[jian@laptop requests]$ pwd /home/jian/prj/github/requests [jian@laptop requests]$ python Python 3.6.7 (default, Mar 21 2019, 20:23:57) [GCC 8.3.1 20190223 (Red Hat 8.3.1-2)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import requests >>> r = requests.get("http://www.baidu.com") >>> r.status_code 200
打開README.md文件看看requests當前都支持哪些功能cookie
International Domains and URLs #國際化域名和URLS Keep-Alive & Connection Pooling #keep—Alive&鏈接池 Sessions with Cookie Persistence #持久性cookie的會話 Browser-style SSL Verification #瀏覽器式SSL認證 Basic & Digest Authentication #基本/摘要認證 Familiar dict–like Cookies #key/value cookies Automatic Decompression of Content #自動內容解壓縮 Automatic Content Decoding #自動內容解碼 Automatic Connection Pooling #自動鏈接池 Unicode Response Bodies #Unicode響應體 Multi-part File Uploads #文件分塊上傳 SOCKS Proxy Support #HTTP(S)代理支持 Connection Timeouts #鏈接超時 Streaming Downloads #數據流下載 Automatic honoring of .netrc #netrc支持 Chunked HTTP Requests #Chunked請求
看源碼看的是思想,要明白做者的設計思路究竟是什麼。
好比requests,看完了你應當問問本身,cookie爲何要封裝而不是直接用?request爲何要有兩個形態?設計session是爲了解決什麼問題?
只有理解了設計思路,再去看具體的細節實現纔能有收穫,不然你看到的就是滿屏的raise、isinstanceof,這樣去看代碼恐怕是浪費時間了。
so...開始開幹吧
源碼目錄下有一個tests文件夾,這裏面以test開頭的測試文件是專門用於測試requests接口,使用的是pytest方式,pytest我會單獨寫一章節介紹具體的內容
首先選第一個方法分析,找到test_DIGEST_HTTP_200_OK_GET
方法
tests/test_requests.py
def test_DIGEST_HTTP_200_OK_GET(self, httpbin): for authtype in self.digest_auth_algo: auth = HTTPDigestAuth('user', 'pass') url = httpbin('digest-auth', 'auth', 'user', 'pass', authtype, 'never') r = requests.get(url, auth=auth) assert r.status_code == 200 r = requests.get(url) assert r.status_code == 401 print(r.headers['WWW-Authenticate']) s = requests.session() s.auth = HTTPDigestAuth('user', 'pass') r = s.get(url) assert r.status_code == 200
上面這段代碼主要作了下面的工做:
1.傳遞一個httpbin參數,這個httpbin
是什麼東西?2.遍歷不一樣的摘要認證算法,
self.digest_auth_algo
是什麼?3.摘要認證變量auth及url變量設置,
HTTPDigestAuth
是什麼?4.對url發起get請求,200表示請求成功,401表示未經受權。
這個測試是爲了驗證auth的必要性
5.新建了一個會話對象s,同時也設置了auth變量,跟前面不一樣的是這個請求是由會話對象s發起的
首先回答前面說的self.digest_auto_algo的問題:
tests/test_requests.py
class TestRequests: digest_auth_algo = ('MD5', 'SHA-256', 'SHA-512') ....
摘要訪問認證,它是一種協議規定的web服務器用來同網頁瀏覽器進行認證信息協商的方法。
瀏覽器在向服務器發送請求的過程當中須要傳遞認證信息auth
auth通過摘要算法加密造成祕文,最後發送給服務器。
服務器驗證成功後返回「200」告知瀏覽器能夠繼續訪問
若驗證失敗則返回"401"告訴瀏覽器禁止訪問。
當前該摘要算法分別選用了"MD5","SHA-256","SHA-512"。
tests/test_requests.py
from requests.auth import HTTPDigestAuth, _basic_auth_str
看到是導入requests.auth模塊裏面的HTTPDigestAuth方法
好 咱們去查看下這個東西是什麼?
requests/auth.py
class HTTPDigestAuth(AuthBase): """Attaches HTTP Digest Authentication to the given Request object.""" def __init__(self, username, password): self.username = username self.password = password # Keep state in per-thread local storage self._thread_local = threading.local() .... def __call__(self, r): # Initialize per-thread state, if needed self.init_per_thread_state() # If we have a saved nonce, skip the 401 if self._thread_local.last_nonce: r.headers['Authorization'] = self.build_digest_header(r.method, r.url) try: self._thread_local.pos = r.body.tell() except AttributeError: # In the case of HTTPDigestAuth being reused and the body of # the previous request was a file-like object, pos has the # file position of the previous body. Ensure it's set to # None. self._thread_local.pos = None r.register_hook('response', self.handle_401) r.register_hook('response', self.handle_redirect) self._thread_local.num_401_calls = 1 return r ...
HTTPDigestAuth:爲http請求對象提供摘要認證。
實例化對象auth時須要傳入認證所需的username及password。
threading.local()在這裏的做用是保存一個全局變量,可是這個全局變量只能在當前線程才能訪問,每個線程都有單獨的內存空間來保存這個變量,它們在邏輯上是隔離的,其餘線程都沒法訪問。
咱們能夠經過實例演示一下摘要認證:
[jian@laptop requests]$ python Python 3.6.7 (default, Mar 21 2019, 20:23:57) [GCC 8.3.1 20190223 (Red Hat 8.3.1-2)] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import requests >>> from requests.auth import HTTPDigestAuth >>> r = requests.get('http://httpbin.org/digest-auth/auth/user/pass',auth=HTTPDigestAuth ... ('user','pass')) >>> r.status_code 200
終於要解決這個東西了,這個東西是啥呢?
1.設置斷點:
tests/conftest.py
@pytest.fixture def httpbin(httpbin): pytest.set_trace() # 設置斷點 return prepare_url(httpbin)
在tests目錄新建立個文件test_xx.py
tests/test_xx.py
from requests.auth import HTTPDigestAuth import requests import pytest class TestRequests: digest_auth_algo = ('MD5', 'SHA-256', 'SHA-512') def test_DIGEST_HTTP_200_OK_GET(self, httpbin): for authtype in self.digest_auth_algo: auth = HTTPDigestAuth('user', 'pass') url = httpbin('digest-auth', 'auth', 'user', 'pass', authtype, 'never') pytest.set_trace() # 設置斷點 r = requests.get(url, auth=auth) assert r.status_code == 200 r = requests.get(url) assert r.status_code == 401 print(r.headers['WWW-Authenticate']) s = requests.session() s.auth = HTTPDigestAuth('user', 'pass') r = s.get(url) assert r.status_code == 200
而後在終端執行
[jian@laptop requests]$ pwd /home/jian/prj/github/requests [jian@laptop requests]$ pytest tests/test_xx.py xxx @pytest.fixture def httpbin(httpbin): E fixture 'httpbin' not found
怎麼報錯呢? -說httpbin 這個fixture沒有找到
查資料是說缺乏pytest-httpbin
模塊
pip安裝起來:
pip install pytest-httpbin
而後再次執行 pytest tests/test_xx.py
命令
[jian@laptop requests]$ pytest tests/test_xx.py Test session starts (platform: linux, Python 3.6.7, pytest 3.2.1, pytest-sugar 0.9.2) rootdir: /home/jian/prj/github/requests, inifile: pytest.ini plugins: sugar-0.9.2, pep8-1.0.6, mock-1.6.2, httpbin-1.0.0, flakes-2.0.0, env-0.6.2, cov-2.5.1, assume-2.2.0, allure-adaptor-1.7.10, celery-4.4.0 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> PDB set_trace (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> > /home/jian/prj/github/requests/tests/conftest.py(20)httpbin() -> return prepare_url(httpbin) (Pdb) httpbin <pytest_httpbin.serve.Server object at 0x7f5e78e44438> (Pdb) c >>>>>>>>>>>>>>>>>>> PDB set_trace (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>> > /home/jian/prj/github/requests/tests/test_xx.py(18)test_DIGEST_HTTP_200_OK_GET() -> r = requests.get(url, auth=auth) (Pdb) httpbin <function prepare_url.<locals>.inner at 0x7f5e78ea3c80> (Pdb) url 'http://127.0.0.1:44131/digest-auth/auth/user/pass/MD5/never' (Pdb)
在調試窗口PDB set_trace中能夠看到,首先被調用的是的conftest.py中的httpbin()方法
咱們在(pdb)中輸入httpbin變量,結果
返回了<pytest_httpbin.serve.Server object at 0x7f5e78e44438>
而後繼續調用方法test_DIGEST_HTTP_200_OK_GET(),輸入httpbin變量,結果返回了<function prepare_url.<locals>.inner at 0x7f5e78ea3c80>
通過調試後,httpbin的面貌漸漸變得清晰了
test_DIGEST_HTTP_200_OK_GET(self, httpbin)中的httpbin對象爲<function prepare_url.<locals>.inner at 0x7f5e78ea3c80>
也就是源碼中prepare_url(value)方法裏的inner(*suffix)方法。
也就是這個文件:
tests/conftest.py
def prepare_url(value): # Issue #1483: Make sure the URL always has a trailing slash httpbin_url = value.url.rstrip('/') + '/' def inner(*suffix): return urljoin(httpbin_url, '/'.join(suffix)) return inner
這裏使用了函數閉包
,有什麼做用? -保持程序上一次運行後的狀態而後繼續執行
httpbin(httpbin)方法中參數httpbin對象爲<pytest_httpbin.serve.Server object at 0x7f5e78e44438>
pytest_httpbin是pytest的一個插件,那確定跟pytest調用有關係了
而後Server是什麼東東?咱們來查看下它的源碼:
pytest_httpbin/serve.py
class Server(object): """ HTTP server running a WSGI application in its own thread. """ port_envvar = 'HTTPBIN_HTTP_PORT' def __init__(self, host='127.0.0.1', port=0, application=None, **kwargs): self.app = application if self.port_envvar in os.environ: port = int(os.environ[self.port_envvar]) self._server = make_server( host, port, self.app, handler_class=Handler, **kwargs )
原來這是一個本地的WSGI服務器,專門用於pytest進行網絡測試,這樣的好處在於咱們無需鏈接外部網絡環境,在本地就能實現一系列的網絡測試工做。
WSGI全稱是Web Server Gateway Interface,它實際上是一個標準,介於web應用與web服務器之間。
只要咱們遵循WSGI接口標準設計web應用,就無需在乎TCP鏈接,HTTP請求等等底層的實現,全權交由web服務器便可。
上述代碼實現的邏輯已經比較清晰了,httpbin對象被實例化的時候調用__init__(self, host='127.0.0.1',port=0, application=None, **kwargs)。
提到fixture方法httpbin(httpbin)中的參數httpbin是一個Server對象,可是這個對象是在何時建立的?原來這個httpbin也是一個fixture方法,存在於pytest-httpbin插件中。
pytest-httpbin/plugin.py
from __future__ import absolute_import import pytest from httpbin import app as httpbin_app from . import serve, certs @pytest.fixture(scope='session') def httpbin(request): server = serve.Server(application=httpbin_app) server.start() request.addfinalizer(server.stop) return server
這是一個"session"級別的fixture方法,首先實例化Server對象爲server,傳入application參數"httpbin_app",application參數咱們在前面提到過,它指向咱們的web應用程序。
這裏的httpbin_app是pytest-httpbin下app模塊的別稱,該模塊是專門用於http測試而編寫的web應用程序,這裏就不擴展了。
而後server繼續調用start()方法,啓動線程,開啓WSGI服務器,最後返回server。