[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。