├── README.md
└── doc
├── Requests_v0.2.0.md
├── Requests_v0.3.0.md
├── Requests_v0.4.0.md
├── Requests_v0.5.0.md
├── Requests_v0.6.0.md
├── Requests_v0.7.0.md
├── Requests_v0.8.0.md
└── img
├── author.png
└── five2four.png
/README.md:
--------------------------------------------------------------------------------
1 | # read_requests
2 | > python requests 源码阅读,学习更pythonic 的python代码写法。
3 |
4 |
5 |
6 |
7 |
8 | ## 拆轮子的目录
9 |
10 |
11 | [Requests v0.2.0 Birth!](./doc/Requests_v0.2.0.md) 2016-03-14
12 |
13 | [Requests v0.3.0 Be frinendly](./doc/Requests_v0.3.0.md) 2016-03-16
14 |
15 | [Requests v0.4.0 Amazing tour ](./doc/Requests_v0.4.0.md) 2016-03-17
16 |
17 | [Requests v0.5.0 Context Manager ](./doc/Requests_v0.5.0.md) 2016-03-18
18 |
19 | [Reuqests v0.6.0 Captain Hook! ](./doc/Requests_v0.6.0.md) 2016-03-19
20 |
21 | [Reuqests v0.7.0 awesome gevent](./doc/Requests_v0.7.0.md) 2016-03-22
22 |
23 | [Reuqests v0.8.0 ](./doc/Requests_v0.8.0.md) 2016-03-??
24 |
25 | ### 目的
26 |
27 | 我的python 学习遇到了瓶颈。
28 |
29 | 是的,我能按照需求写完公司需要的API,它能正常工作,有详尽的接口调用测试(用swagger),有还算不错的测试(一直强迫自己TDD),有一些文档和注释。但是也仅仅是正常工作而已。
30 |
31 | 我也经常在自己的能力范围之类对代码进行优化(Dont't Repeat yourself),但是我也知道和高手的段位差太多。
32 |
33 | 之前我让自己把触角伸的更长一点,去看看Java, Spring、除了 Flask之外别的 web 框架,例如Django。但是因为没法在工作中实践这些,水平也仅限于各种官网的 demo 水平。
34 |
35 | 原来那些剑客是怎么提高自己的呢?
36 |
37 | 原来看过一本日漫,叫《浪客行》,作者是井上雄彦——即灌篮高手的作者,已经连载15年了,现在还在画。
38 |
39 | 男主角是个剑客,挑战各地的高手,以提高自己。有时候看到高手,高山仰止,也是硬着头皮硬上。
40 |
41 | 于是该怎么做,居然豁然开朗起来。
42 |
43 | 得益于 git 的存在,我能看到现在剑法如鬼神的高手是怎么成长起来了,他们的努力,尝试,谦虚,态度。统统在版本、tag、issue、pr中展现无遗。
44 |
45 | 所以正如你现在所见的,我从 python 中著名的轮子 requests 开始拆起,把每个版本都去看看,思考,写,运行。
46 |
47 | 我会尽可能加速这个项目,我会让自己保持对它的激情。
48 |
49 | Kenneth Reitz ,请多指教。
50 |
51 |
52 | ### 使用
53 |
54 | 是的,这个系列是在拆轮子。但是也是面向所有跟我一样,对高手充满憧憬的 python开发者。
55 |
56 | 我尽可能使里面的内容不过于枯燥,嬉笑怒骂,皆是文章。
57 |
58 | 如果你开始阅读,请配合 [Reuqests源码](https://github.com/kennethreitz/requests) 和git图形界面使用(因为可能需要反复的比较各种版本的微小改动,需要反复的 checkout 和 git diff)。
59 |
60 |
61 | ### 贡献
62 |
63 | 如果你对文章的内容有建议和观点,欢迎pr给我,我尽可能的merge进来,把你添加到贡献名单中。
64 |
65 | 但是我不希望对我还没有完成的版本进行 pr,这样就失去了我的初衷。
66 |
67 |
68 | 最后,功不唐捐。
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/doc/Requests_v0.2.0.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### 0X00
4 |
5 | 是的,我开始拆轮子了,我一拆轮子,连我自己都害怕。
6 |
7 | 从 requests 开始拆,一是因为这轮子被各种人贴上pythonic 的标签;二是这轮子足够好拆;三是这轮子的作者跟我一样是个靠颜值吃饭的。不信?
8 |
9 | 上图。
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | ### 0X01
23 |
24 | 这篇文章拆的是v 0.2.0版本。
25 |
26 | 根据 HISTORY.rst ,requests 是从 0.0.1 版本开始的,但是 github 的版本库里面找不到 v0.0.1版本的了。所以从 v0.2.0 开始。
27 |
28 | 下面是版本信息。
29 |
30 | ```
31 | History
32 | -------
33 |
34 | 0.2.0 (2011-02-14)
35 | ++++++++++++++++++
36 |
37 | * Birth!
38 |
39 |
40 | 0.0.1 (2011-02-13)
41 | ++++++++++++++++++
42 |
43 | * Frustration
44 | * Conception
45 |
46 | ```
47 |
48 | 2011-02-14, requests v0.2.0正式发布,看日期,情人节呐,这个轮子肯定是送给自己情人节礼物:)
49 |
50 | 话说我今年送给自己什么情人节礼物来着.... (脑补思索)
51 |
52 | 可以看到0.01 版本中用到了 frustration ,结果第二天就发了新版本:)
53 |
54 | ### 0X03
55 |
56 | 下面是 README.rst 信息
57 |
58 | ```
59 |
60 | Requests: The Simple (e.g. usable) HTTP Module
61 | ==============================================
62 |
63 | Most existing Python modules for dealing HTTP requests are insane. I have to look up *everything* that I want to do. Most of my worst Python experiences are a result of the various built-in HTTP libraries (yes, even worse than Logging).
64 |
65 | But this one's different. This one's going to be awesome. And simple.
66 |
67 | Really simple.
68 |
69 | ```
70 |
71 | 这是readme的第一部分信息。我用我专业的英文六级水平翻译一下。
72 |
73 | Requests: 简单好用的HTTP 模块
74 |
75 | 大部分存在的处理HTTP请求的模块太傻逼了,我找了整个互联网,然后我准备自己干这个了。 由于我 python 的经验不够,这个库的大部分都是基于python 标准库HTTP 模块的(是的,比Logging 还搓)。
76 |
77 | 但是!这个库跟其他那些傻逼是不一样的!这个库会变的碉堡了!嗯,还有简单。
78 |
79 | 真的超级简单 :)
80 |
81 |
82 | 哈哈哈,看了这个README 我就笑了,我喜欢狂妄的年轻人。
83 |
84 | ```
85 | Features
86 | --------
87 |
88 | - Extremely simple GET, HEAD, POST, PUT, DELETE Requests
89 | + Simple HTTP Header Request Attachment
90 | + Simple Data/Params Request Attachment
91 | - Simple Basic HTTP Authentication
92 | + Simple URL + HTTP Auth Registry
93 |
94 | ```
95 |
96 | 能GET, HEAD, POST, PUT, DELETE, 能添加请求头,能添加参数和数据,能进行简单的HTTP 验证。
97 |
98 | 嗯,都是基本功能。
99 |
100 | ```
101 |
102 | Usage
103 | -----
104 |
105 | It couldn't be simpler. ::
106 |
107 | >>> import requests
108 | >>> r = requests.get('http://google.com')
109 |
110 |
111 | HTTPS? Basic Authentication? ::
112 |
113 | >>> r = requests.get('https://convore.com/api/account/verify.json')
114 | >>> r.status_code
115 | 401
116 |
117 |
118 | Uh oh, we're not authorized! Let's add authentication. ::
119 |
120 | >>> conv_auth = requests.AuthObject('requeststest', 'requeststest')
121 | >>> r = requests.get('https://convore.com/api/account/verify.json', auth=conv_auth)
122 |
123 | >>> r.status_code
124 | 200
125 |
126 | >>> r.headers['content-type']
127 | 'application/json'
128 |
129 | >>> r.content
130 | '{"username": "requeststest", "url": "/users/requeststest/", "id": "9408", "img": "censored-long-url"}'
131 |
132 | ```
133 |
134 | 嗯。跟现在的用法都是一样简洁 :)
135 |
136 | API 部分,安装部分,就直接跳过吧。
137 |
138 |
139 | ### 0X04
140 |
141 | requests 代码之前,先从测试代码开始吧。
142 |
143 | ```
144 | class RequestsTestSuite(unittest.TestCase):
145 | """Requests test cases."""
146 |
147 | def setUp(self):
148 | pass
149 |
150 | def tearDown(self):
151 | """Teardown."""
152 | pass
153 |
154 | def test_invalid_url(self):
155 | self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
156 |
157 | def test_HTTP_200_OK_GET(self):
158 | r = requests.get('http://google.com')
159 | self.assertEqual(r.status_code, 200)
160 |
161 | def test_HTTPS_200_OK_GET(self):
162 | r = requests.get('https://google.com')
163 | self.assertEqual(r.status_code, 200)
164 |
165 | def test_HTTP_200_OK_HEAD(self):
166 | r = requests.head('http://google.com')
167 | self.assertEqual(r.status_code, 200)
168 |
169 | def test_HTTPS_200_OK_HEAD(self):
170 | r = requests.head('https://google.com')
171 | self.assertEqual(r.status_code, 200)
172 |
173 | def test_AUTH_HTTPS_200_OK_GET(self):
174 | auth = requests.AuthObject('requeststest', 'requeststest')
175 | url = 'https://convore.com/api/account/verify.json'
176 | r = requests.get(url, auth=auth)
177 |
178 | self.assertEqual(r.status_code, 200)
179 |
180 | ```
181 |
182 | 凭我多年(半年)TDD的经验,这份测试肯定是代码写完之后补的。并且写完代码之后很开心,写测试的时候满脑子想着快点丢到github 上面去哈哈哈哈,只测了get 和 head ,当然也可能他找不到 url去测post :)
183 |
184 | self.assertRaises 这个用法我没用过,去翻了一个标准库文档,文档里面有这样一个例子,还是很好玩的:
185 |
186 | ```
187 | def test_split(self):
188 | # check that s.split fails when the separator is not a string
189 | with self.assertRaises(TypeError):
190 | s.split(2)
191 |
192 | ```
193 | 函数定义:
194 | assertRaises(exc, fun, *args, **kwds)
195 |
196 |
197 | ### 0x05
198 |
199 | 终于开始撸代码了,requests 包只包含一个__init__.py 和 core.py, core.py 405行。
200 |
201 | 当使用 requests.get('www.baidu.com')时,直接到requests 包定义的相应函数,以 get 举例。
202 |
203 | ```
204 | def get(url, params={}, headers={}, auth=None):
205 | """Sends a GET request. Returns :class:`Response` object.
206 |
207 | :param url: URL for the new :class:`Request` object.
208 | :param params: (optional) Dictionary of GET Parameters to send with the :class:`Request`.
209 | :param headers: (optional) Dictionary of HTTP Headers to sent with the :class:`Request`.
210 | :param auth: (optional) AuthObject to enable Basic HTTP Auth.
211 | """
212 |
213 | r = Request()
214 |
215 | r.method = 'GET'
216 | r.url = url
217 | r.params = params
218 | r.headers = headers
219 | r.auth = _detect_auth(url, auth)
220 |
221 | r.send()
222 |
223 | return r.response
224 |
225 | ```
226 | 先会实例化一个 Request, Request 类定义在47行。
227 |
228 | ```
229 | class Request(object):
230 | """The :class:`Request` object. It carries out all functionality of
231 | Requests. Recommended interface is with the Requests functions.
232 |
233 | """
234 |
235 | _METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')
236 |
237 | def __init__(self):
238 | self.url = None
239 | self.headers = dict()
240 | self.method = None
241 | self.params = {}
242 | self.data = {}
243 | self.response = Response()
244 | self.auth = None
245 | self.sent = False
246 |
247 | def __repr__(self):
248 | try:
249 | repr = '' % (self.method)
250 | except:
251 | repr = ''
252 | return repr
253 |
254 | ```
255 |
256 | 接下来初始化这个类的各个属性。 r.method = 'GET' , r.url = url ....
257 |
258 | 在 `__setattr__` 对method做了限制。
259 |
260 | ```
261 | def __setattr__(self, name, value):
262 | if (name == 'method') and (value):
263 | if not value in self._METHODS:
264 | raise InvalidMethod()
265 |
266 | object.__setattr__(self, name, value)
267 | ```
268 |
269 | 这也是一个好玩的用法,我比较水啦,如果是我,我会直接在类初始化的时候搞定这些...
270 | 就像下面这样。
271 |
272 | ```
273 | class Request(object):
274 | """The :class:`Request` object. writtern by Lionel Wang
275 | """
276 |
277 | _METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')
278 |
279 | def __init__(self, url, method, data, params):
280 | self.url = url
281 | self.headers = dict()
282 | self.method = method
283 | self.params = params
284 | self.data = data
285 | self.response = Response()
286 | self.auth = None
287 | self.sent = False
288 |
289 | if self.method not in _METHODS:
290 | raise InvalidMethod
291 | ```
292 |
293 | 有没有高人跟我说下这样的好处?
294 |
295 | ```
296 | r,auth = _detect_auth(url, auth)
297 | ```
298 |
299 | 权限认证,如果需要的话 :)
300 |
301 | 所以重要的逻辑都在 r.send() 里面了,继续~
302 |
303 | 在Requests.send() 里面,对 method 的不同,做了不一样的处理,我们只看get。
304 |
305 | ```
306 | def send(self, anyway=False):
307 | """Sends the request. Returns True of successfull, false if not.
308 | If there was an HTTPError during transmission,
309 | self.response.status_code will contain the HTTPError code.
310 |
311 | Once a request is successfully sent, `sent` will equal True.
312 |
313 | :param anyway: If True, request will be sent, even if it has
314 | already been sent.
315 | """
316 | self._checks()
317 |
318 | success = False
319 |
320 | if self.method in ('GET', 'HEAD', 'DELETE'):
321 | if (not self.sent) or anyway:
322 |
323 | # url encode GET params if it's a dict
324 | if isinstance(self.params, dict):
325 | params = urllib.urlencode(self.params)
326 | else:
327 |
328 | params = self.params
329 |
330 | req = _Request(("%s?%s" % (self.url, params)), method=self.method)
331 |
332 | if self.headers:
333 | req.headers = self.headers
334 |
335 | opener = self._get_opener()
336 |
337 | try:
338 | resp = opener(req)
339 | self.response.status_code = resp.code
340 | self.response.headers = resp.info().dict
341 | if self.method.lower() == 'get':
342 | self.response.content = resp.read()
343 |
344 | success = True
345 | except urllib2.HTTPError, why:
346 | self.response.status_code = why.code
347 |
348 | self.sent = True if success else False
349 |
350 | return success
351 | ```
352 | self._checks() 是封装了url是否非None 的函数。pass
353 |
354 | 我估摸着 `anyway` 是作者自己用来测试的 :), not self.send 总是会通过if的。
355 |
356 | 正如作者所说,这个版本封装了urllib, urllib2 的方法,像我这种被reqeusts 宠坏了的人,为了拆他,滚去翻urllib2的文档了 (; ̄ェ ̄)
357 |
358 | ```
359 | if isinstance(self.params, dict):
360 | params = urllib.urlencode(self.params)
361 | else:
362 |
363 | params = self.params
364 |
365 | req = _Request(("%s?%s" % (self.url, params)), method=self.method)
366 | ```
367 |
368 | 如果传过来parms 形如
369 |
370 | ```
371 | parms ={'age':23, 'name':wsp}, url='www.baidu.com'
372 |
373 |
374 | ```
375 |
376 | 将他转换成 www.baidu.com?age=23&name=wsp
377 |
378 | urllib.urlencode(query, [,doseq])
379 |
380 | 定义为 Convert a mapping object or a sequence of two-element tuples to a “percent-encoded” string,
381 | 将字典或者两个元素的元组,转换成有%的字符串。
382 |
383 |
384 | _Request 是继承了 urllib2.Requsts 的类。
385 | 代码如下
386 |
387 | ```
388 | class _Request(urllib2.Request):
389 | """Hidden wrapper around the urllib2.Request object. Allows for manual
390 | setting of HTTP methods.
391 | """
392 |
393 | def __init__(self, url,
394 | data=None, headers={}, origin_req_host=None,
395 | unverifiable=False, method=None):
396 | urllib2.Request.__init__( self, url, data, headers, origin_req_host,
397 | unverifiable)
398 | self.method = method
399 |
400 | def get_method(self):
401 | if self.method:
402 | return self.method
403 |
404 | return urllib2.Request.get_method(self)
405 | ```
406 |
407 | `self._get_opener()`, 如果需要验证,根据 urllib提供的函数,进行请求的验证,如果不需要,则直接返回 urllib.urlopen 。
408 |
409 | 接着 `resp = opener(req)` , 等于 `resp = urllib2.urlopen(urllib.Reuqest())`了。即是调用标准库发送请求的真正的方法了。
410 |
411 | 最后组装返回的类,没什么好说的了
412 |
413 | ```
414 | self.response.status_code = resp.code
415 | self.response.headers = resp.info().dict
416 | self.response.content = resp.read()
417 | ```
418 |
419 | 不过try except 写的很好玩。
420 |
421 | ```
422 | except urllib2.HTTPError, why:
423 | self.response.status_code = why.code
424 | ```
425 |
426 | 诚哥说pep8已经不推荐写逗号了,用as, 至少我的ide 会报一个warning, 我想说的是把 `ex` 写出 `why`,
427 | 这样写好像代码可读性又高了一丢丢哈哈哈,我以后也写成why.
428 |
429 | 嗯。到了这里 requests v0.2.0 算是看完了~~
430 |
431 |
432 | ### 0X06 后记
433 |
434 |
435 | 1. 我喵了一眼现在的版本,已经不是基于 urllib 了,肯定会有意思的多;
436 | 2. 这个时候的作者还是很逗比的嘛~ 很多地方都可以抽象的更好一点,但是他直接 copy himself 了,所以我说啊,年轻人,不用怂,有什么想法,赶紧写,出名要趁早,有什么地方有问题,可以给我提issue ,给我看看嘛哈哈哈;
437 | 3. requests 的这个系列我会继续写啊,我对这个小鲜肉变成大叔的历程很感兴趣啊;
438 | 4. 当然啦,每个版本我都会checkout 去看看,但是只会写改动比较大的版本了;
439 | 5. 都看到这里,肯定是真爱啦,赶紧关注我公众号啦混蛋 (((o(*゚▽゚*)o)))
440 |
--------------------------------------------------------------------------------
/doc/Requests_v0.3.0.md:
--------------------------------------------------------------------------------
1 | ## Reuqests 源码阅读 v0.3.0
2 |
3 |
4 | ### 0x00
5 | 2011.2.14 是礼拜一。
6 |
7 | 这个系列叫源码阅读好了.... 源码剖析这名字太专业,而且万一我说错了呢,本来我还想叫源码扯淡呢...
8 |
9 | 我喵了一眼tag,觉得每个大版本更新一次好了,这个大版本到下一个版本中迭代的东西,尽量都扒出来说下。即使这样,我也得写个20+篇...
10 |
11 | 话说我第一篇的反响很好啊!(并不是), 所以我当然会接着写下去了。
12 |
13 | 嗯,这次是 requests v0.3.0 版本。
14 |
15 | ### 0x01
16 |
17 | 照例先从 HISTORY.rst 开始咯。
18 |
19 | ```
20 | 0.3.0 (2011-02-25)
21 | ++++++++++++++++++
22 |
23 | * Automatic Authentication API Change
24 | * Smarter Query URL Parameterization
25 | * Allow file uploads and POST data together
26 | * New Authentication Manager System
27 | - Simpler Basic HTTP System
28 | - Supports all build-in urllib2 Auths
29 | - Allows for custom Auth Handlers
30 | ```
31 |
32 | 才过11天,就跳大版本了...
33 |
34 | 而且...
35 |
36 | ```
37 | 0.2.2 (2011-02-14)
38 | ++++++++++++++++++
39 |
40 | * Still handles request in the event of an HTTPError. (Issue #2)
41 | * Eventlet and Gevent Monkeypatch support.
42 | * Cookie Support (Issue #1)
43 |
44 |
45 | 0.2.1 (2011-02-14)
46 | ++++++++++++++++++
47 |
48 | * Added file attribute to POST and PUT requests for multipart-encode file uploads.
49 | * Added Request.url attribute for context and redirects
50 |
51 | ```
52 |
53 | 情人节那天更是连更了两个版本... 默默搜了下2011.2.14那天是礼拜一。
54 |
55 | 颤抖吧屌丝..
56 |
57 | 扯句题外话,最近能看到的混的风生水起的年轻人都是高产似母猪,bilibili 上面的王老菊更游戏实况,更是变态到日更...一集还两个小时。
58 |
59 | 果然大力出奇迹。
60 |
61 | 好的,回归正题,看下跳大版本之间都做了哪些改动。
62 |
63 | ```
64 | 0.2.1 (2011-02-14)
65 | ++++++++++++++++++
66 |
67 | * Added file attribute to POST and PUT requests for multipart-encode file uploads.
68 | * Added Request.url attribute for context and redirects
69 |
70 | ```
71 |
72 | 1. 加了对post 方法文件的支持。嗯,这个要研究下。
73 | 2. 加了request.url 的暴露.. 这个应该很简单吧。
74 |
75 | ```
76 | 0.2.2 (2011-02-14)
77 | ++++++++++++++++++
78 |
79 | * Still handles request in the event of an HTTPError. (Issue #2)
80 | * Eventlet and Gevent Monkeypatch support.
81 | * Cookie Support (Issue #1)
82 | ```
83 |
84 | 1. 当不是200时,依然有返回内容(原来错误状态码时,返回的内容都是None)。
85 | 2. 异步的monkeypatch支持,又是我不熟的东西。
86 | 3. cookie 支持。
87 |
88 | 题外话,push上去一天就有两个issue? 这么凶?我去扒一下...
89 |
90 | 好的,我回来了,的确不是自己提的,一个法国人,一个意大利人。果然当时大家都很需要这玩意。
91 | 所以说,要顺势而动 :)
92 |
93 |
94 | ```
95 | 0.2.3 (2011-02-15)
96 | ++++++++++++++++++
97 |
98 | * New HTTPHandling Methods
99 | - Reponse.__nonzero__ (false if bad HTTP Status)
100 | - Response.ok (True if expected HTTP Status)
101 | - Response.error (Logged HTTPError if bad HTTP Status)
102 | - Reponse.raise_for_status() (Raises stored HTTPError)
103 |
104 | ```
105 |
106 | 1. 新的HTTPHandling Methods,什么意思.. 猜测是对返回的Response类做了友好的处理。
107 |
108 | ```
109 | 0.2.4 (2011-02-19)
110 | ++++++++++++++++++
111 |
112 | * Python 2.5 Support
113 | * PyPy-c v1.4 Support
114 | * Auto-Authentication tests
115 | * Improved Request object constructor
116 |
117 | ```
118 |
119 | 1. python2.5 支持,i don't care..
120 | 2. pypy1.4 支持, i don't care..
121 | 3. 自动认证测试。
122 | 4. 改善了 Request 的类结构。
123 |
124 | ```
125 | 0.3.0 (2011-02-25)
126 | ++++++++++++++++++
127 |
128 | * Automatic Authentication API Change
129 | * Smarter Query URL Parameterization
130 | * Allow file uploads and POST data together
131 | * New Authentication Manager System
132 | - Simpler Basic HTTP System
133 | - Supports all build-in urllib2 Auths
134 | - Allows for custom Auth Handlers
135 | ```
136 |
137 | 1. 自动认证的API做了调整;
138 | 2. 更智能的查询参数设置;
139 | 3. 允许post数据的同时进行文件上传;
140 | 4. 新的认证管理系统 balabala...
141 |
142 | 果然大版本改动的东西多一点呢...
143 | ┬─┬ ノ( ゜-゜ノ)
144 |
145 | 喵了一眼README.rst, 没有什么特别的改动。
146 | 好的我要享用源码了 ψ(`∇´)ψ
147 |
148 | ### 0X02
149 |
150 | ### v0.2.1
151 |
152 | ** 对post文件的支持。**
153 |
154 |
155 | 测试终于加了关于post的测试。
156 |
157 | ```
158 | def test_POSTBIN_GET_POST_FILES(self):
159 |
160 | bin = requests.post('http://www.postbin.org/')
161 | self.assertEqual(bin.status_code, 200)
162 |
163 | post = requests.post(bin.url, data={'some': 'data'})
164 | self.assertEqual(post.status_code, 201)
165 |
166 | post2 = requests.post(bin.url, files={'some': StringIO('data')})
167 | self.assertEqual(post2.status_code, 201)
168 |
169 | ```
170 |
171 | postbin是什么鬼..我去扒一下..
172 |
173 | 好了,我回来了,真是什么鬼都有,postbin就是给你随便post的一个网站。我访问会直接跳转到 http://requestb.in/
174 |
175 | 果然之前没有post的测试是没有地方post啊哈哈哈哈...
176 |
177 | 用ipython 测试了下
178 |
179 | ```
180 | In [22]: import requests, time
181 |
182 | In [23]: r = requests.post('http://requestb.in/xiwxabxi', data={"ts":time.time()})
183 |
184 | In [24]: print r.status_code
185 | 200
186 |
187 | In [25]: print r.content
188 | ok
189 | ```
190 |
191 | 可以看到,现在post过去,返回的状态码是200,而不是201(Created)了。
192 |
193 | StringIO 是一个可以把 String 强制转换成 类似于 FILE 类的东西。标准库里面这么描述。
194 |
195 | ```
196 | StringIO — Read and write strings as files
197 | ```
198 |
199 | 也就是说,在post和put方法里面加入files 参数来支持,可以是StirngIO类型,也可以是File类型(maybe),上源码细节。
200 |
201 | ```
202 | elif self.method == 'POST':
203 | if (not self.sent) or anyway:
204 |
205 | if self.files:
206 | register_openers()
207 | datagen, headers = multipart_encode(self.files)
208 | req = _Request(self.url, data=datagen, headers=headers, method='POST')
209 |
210 | if self.headers:
211 | req.headers.update(self.headers)
212 |
213 | else:
214 | req = _Request(self.url, method='POST')
215 | req.headers = self.headers
216 |
217 | ```
218 | 加了一层 if self.files 的判断,进入后做了两件事。 1. register_opener(), 2. multipart_encode(self.files)
219 |
220 | 这两个函数,又是调用了另一个轮子...这两个代码文件直接被copy到requests/packages中。这个轮子的作者是 Chris AtLee,我去他 github 扒了一下,并不能找到这个仓库。感谢他让我几个小时欲仙欲死 :)
221 |
222 | 我猜可能2011年,urllib2并不支持文件上传,(现在的版本有看到fileHandler)。他觉得已经有了,就把仓库给删了?或者标准库用的就是他的代码哈哈哈~
223 |
224 | 他用Mixin的方法重写 httplib.HTTPConnection 类里面的send方法,类的名字叫 StreamHTTPConnection。然后用自己继承的类改了全局的urllib.opener,扒到这里不想看细节了...
225 |
226 | 好,转战 multipart_encode。当post 一个文件的时候,数据要以MIME 格式分块,客户端生成随机的 boundry来标记一块的开头和结尾,并且需要在 header中加入 Content-Length 来表明文件大小。所以这个函数大致是干这些事情。我把注释写在后面。
227 |
228 | ```
229 | def multipart_encode(params, boundary=None, cb=None):
230 | if boundary is None:
231 | boundary = gen_boundary() # 生成随机字符串作为 boundary
232 | else:
233 | boundary = urllib.quote_plus(boundary)
234 |
235 | headers = get_headers(params, boundary) # 生成Content-Length等头信息
236 | params = MultipartParam.from_params(params)
237 |
238 | return multipart_yielder(params, boundary, cb), headers # 返回迭代器和头信息
239 |
240 | ```
241 |
242 | ** 加了request.url 的暴露 **
243 |
244 | 在Response 类中加入了 url。
245 |
246 | ```
247 | self.response.url = resp.url
248 | ```
249 |
250 | ### v0.2.2
251 |
252 | 1. 当不是200时,依然有返回内容(原来错误状态码时,返回的内容都是None)。#isssue 2
253 | 2. 异步的monkeypatch支持。
254 | 3. cookie 支持。#issue 1
255 |
256 | ** issue2 算是一个小bug, 下面是v0.2.1 和 v0.2.2版本的对比,以get举例。**
257 |
258 | v0.2.1
259 |
260 | ```
261 | try:
262 | opener = self._get_opener()
263 | resp = opener(req)
264 |
265 | self.response.status_code = resp.code
266 | self.response.headers = resp.info().dict
267 | self.response.content = resp.read()
268 | self.response.url = resp.url
269 |
270 | success = True
271 |
272 | except urllib2.HTTPError as why:
273 | self.response.status_code = why.code
274 | ```
275 |
276 | 当 resp = opener(req) 出现问题时(即进入 except 时),只返回了状态码。
277 |
278 |
279 | v0.2.2
280 |
281 | ```
282 | try:
283 | resp = opener(req)
284 | self._build_response(resp)
285 | success = True
286 |
287 | except urllib2.HTTPError as why:
288 | self._build_response(why)
289 | success = False
290 | ```
291 | 修复问题,封装一个返回函数~
292 |
293 | ** monkeypatch支持。**
294 |
295 | 在 core 代码最顶部加了如下部分。
296 |
297 | ```
298 | try:
299 | import eventlet
300 | eventlet.monkey_patch()
301 | except ImportError:
302 | pass
303 |
304 | if not 'eventlet' in locals():
305 | try:
306 | from gevent import monkey
307 | monkey.patch_all()
308 | except ImportError:
309 | pass
310 |
311 | ```
312 |
313 | 此外找不到跟这个有关系的东西。难道只要import就好了?这么黑科技?
314 |
315 | 在知乎上找到了赖勇浩大神回答某个问题时提到的
316 |
317 | > 当一个库的代码是纯 Python 的时候,加上 monkey patch 技法,那么这个库使用的就是 gevent.socket 了,从而不需要任何更改就能够获得 gevent 的同步代码异步执行的“超级牛力”。 -- 赖勇浩
318 |
319 | 问题地址 https://www.zhihu.com/question/29746887
320 |
321 | 666,这个内容还需要仔细研究下,先挖个坑
322 |
323 |
324 | ** cookie 支持 **
325 |
326 | 同样以get 举例
327 |
328 | ```
329 | r.cookiejar = cookies
330 | ```
331 | 加入了cookies 参数
332 |
333 | ```
334 | if self.cookiejar:
335 |
336 | cookie_handler = urllib2.HTTPCookieProcessor(cookiejar)
337 | _handlers.append(cookie_handler)
338 | opener = urllib2.build_opener(*_handlers)
339 | return opener.open
340 |
341 | ```
342 |
343 | 调用urllib2函数,加入cookie_handler
344 | 标准库我就不扒了..
345 |
346 | ### V0.2.3
347 |
348 | ```
349 | 0.2.3 (2011-02-15)
350 | ++++++++++++++++++
351 |
352 | * New HTTPHandling Methods
353 | - Reponse.__nonzero__ (false if bad HTTP Status)
354 | - Response.ok (True if expected HTTP Status)
355 | - Response.error (Logged HTTPError if bad HTTP Status)
356 | - Reponse.raise_for_status() (Raises stored HTTPError)
357 |
358 | ```
359 |
360 | 改进了类 Response 的表现形式。来看一下对新的 Response 类的新加的测试内容,第一个测试。
361 |
362 | ```
363 | def test_nonzero_evaluation(self):
364 | r = requests.get('http://google.com/some-404-url')
365 | self.assertEqual(bool(r), False)
366 |
367 | r = requests.get('http://google.com/')
368 | self.assertEqual(bool(r), True)
369 | ```
370 |
371 | 对返回的 Response 类进行 bool 运算,相关实现代码如下。
372 |
373 | ```
374 | def __nonzero__(self):
375 | """Returns true if status_code is 'OK'."""
376 | return not self.error
377 |
378 | ```
379 |
380 | `__nonzero__(self):` 为python的内置 magic 方法,当对该类调用bool(object) 时调用该方法。
381 |
382 | 当调用opener(resq)的时候,出现异常,会更改self.error 变量。
383 |
384 | ```
385 | try:
386 | resp = opener(req)
387 | self._build_response(resp)
388 | self.response.ok = True
389 |
390 | except urllib2.HTTPError as why:
391 | self._build_response(why)
392 | self.response.error = why
393 |
394 | ```
395 |
396 | 第二个测试。
397 |
398 | ```
399 | def test_request_ok_set(self):
400 | r = requests.get('http://google.com/some-404-url')
401 | self.assertEqual(r.ok, False)
402 |
403 | ```
404 | 跟上面的程序类似,当正确发送的时候,会对response.ok 参数进行改变。
405 |
406 | 第三个测试
407 |
408 | ```
409 | def test_status_raising(self):
410 | r = requests.get('http://google.com/some-404-url')
411 | self.assertRaises(requests.HTTPError, r.raise_for_status)
412 |
413 | r = requests.get('http://google.com/')
414 | self.assertFalse(r.error)
415 | r.raise_for_status()
416 |
417 | ```
418 | 相应代码:
419 |
420 | ```
421 | def raise_for_status(self):
422 | """Raises stored HTTPError if one exists."""
423 | if self.error:
424 | raise self.error
425 | ```
426 |
427 | 这个版本没什么好说的,针对返回的类的细节优化。
428 | 我觉得自己很少会对返回的东西做细节的优化,好像它可用,我就接受了。
429 | 当真的需要把轮子开放给其他开发者时,那种追求卓越就会让你做的更好。
430 |
431 | 或许这就是一流代码狗和三流的区别。
432 |
433 | ### v0.2.4
434 |
435 | ```
436 | 0.2.4 (2011-02-19)
437 | ++++++++++++++++++
438 |
439 | * Python 2.5 Support
440 | * PyPy-c v1.4 Support
441 | * Auto-Authentication tests
442 | * Improved Request object constructor
443 |
444 | ```
445 | 1,2 两点略过。
446 |
447 | ** 3. 自动验证测试 **
448 | 这个版本最主要的改动,就是优化了测试文件。把测试文件改成了继承 TestSite的类(话说这不是常识吗...)
449 |
450 | ```
451 |
452 | class RequestsTestSuite(unittest.TestCase):
453 | """Requests test cases."""
454 |
455 | def setUp(self):
456 | pass
457 |
458 | def tearDown(self):
459 | """Teardown."""
460 | pass
461 |
462 | def test_invalid_url(self):
463 | self.assertRaises(ValueError, requests.get, 'hiwpefhipowhefopw')
464 |
465 | ```
466 |
467 | 补充了权限验证的测试内容,现在这个测试文件算是比较完整的了,get, head, post, 权限,返回类的检测,都有比较完整的测试。
468 |
469 | ** 4. 优化 Request 类结构 **
470 |
471 | 哈哈哈,我上一遍质疑他的东西,果然在五天后就改过来了~
472 |
473 | ```
474 | class Request(object):
475 | """The :class:`Request` object. It carries out all functionality of
476 | Requests. Recommended interface is with the Requests functions.
477 | """
478 |
479 | _METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')
480 |
481 | def __init__(self, url=None, headers=dict(), files=None, method=None,
482 | params=dict(), data=dict(), auth=None, cookiejar=None):
483 | self.url = url
484 | self.headers = headers
485 | self.files = files
486 | self.method = method
487 | self.params = params
488 | self.data = data
489 | self.response = Response()
490 |
491 | self.auth = auth
492 | self.cookiejar = cookiejar
493 | self.sent = False
494 |
495 | ```
496 |
497 | 果然写成那样,是因为太年轻呐 (≧∇≦)
498 |
499 | 所以我还是说,大家都是这么成长起来的,Don't pray for something, fight.
500 |
501 | ### V0.3.0
502 |
503 | ```
504 | 0.3.0 (2011-02-25)
505 | ++++++++++++++++++
506 |
507 | * Automatic Authentication API Change
508 | * Smarter Query URL Parameterization
509 | * Allow file uploads and POST data together
510 | * New Authentication Manager System
511 | - Simpler Basic HTTP System
512 | - Supports all build-in urllib2 Auths
513 | - Allows for custom Auth Handlers
514 | ```
515 |
516 | 终于到了v0.3.0了, 写这个真是太耗费体力了。可是一想,我又没有天赋造轮子,万一我写的这个给某些小同学看到,那些小同学很激动,一不小心搞个大新闻,我岂不是也很自豪 :D
517 |
518 | ** 1. 自动验证API的改变 **
519 |
520 | 照例先看测试
521 |
522 | 之前
523 |
524 | ```
525 | def test_AUTH_HTTPS_200_OK_GET(self):
526 | auth = requests.AuthObject('requeststest', 'requeststest')
527 | url = 'https://convore.com/api/account/verify.json'
528 | r = requests.get(url, auth=auth)
529 |
530 | self.assertEqual(r.status_code, 200)
531 |
532 |
533 | requests.add_autoauth(url, auth)
534 |
535 | r = requests.get(url)
536 | self.assertEqual(r.status_code, 200)
537 |
538 | # reset auto authentication
539 | requests.AUTOAUTHS = []
540 | ```
541 |
542 | 现在
543 |
544 | ```
545 | def test_AUTH_HTTPS_200_OK_GET(self):
546 | auth = ('requeststest', 'requeststest')
547 | url = 'https://convore.com/api/account/verify.json'
548 | r = requests.get(url, auth=auth)
549 |
550 | self.assertEqual(r.status_code, 200)
551 |
552 | r = requests.get(url)
553 | self.assertEqual(r.status_code, 200)
554 |
555 | # reset auto authentication
556 | requests.auth_manager.empty()
557 | ```
558 |
559 | 可以看到,
560 |
561 | 1. 原来需要实例化一个AuthObject对象, 现在只需要一个元组作为参数就可以,使用角度来说方便了很多;
562 | 2. 原来需要手动执行 add_autoauth(), 现在第一次auth=auth后,会自动保存;
563 |
564 | 这样使用减少了很多使用的困难,不用import 各种东西,除了 requests.get
565 |
566 | 关于实现细节,我放到第四点去说。
567 |
568 |
569 | ** 2. 更智能的查询参数 **
570 |
571 | 关于这一点更新,体现在这个测试中
572 |
573 | ```
574 | def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self):
575 | heads = {'User-agent': 'Mozilla/5.0'}
576 | r = requests.get('http://google.com/search?test=true', params={'q': 'test'}, headers=heads)
577 | self.assertEqual(r.status_code, 200)
578 | ```
579 |
580 | 代码实现是,在Reuqest.send() 函数中,对url 进行拼接和处理
581 |
582 | send中
583 |
584 | ```
585 | if self.method in ('GET', 'HEAD', 'DELETE'):
586 | req = _Request(self._build_url(self.url, self._enc_data), method=self.method)
587 | ```
588 | self._build_url()
589 |
590 | ```
591 | @staticmethod
592 | def _build_url(url, data):
593 | """Build URLs."""
594 |
595 | if urlparse(url).query:
596 | return '%s&%s' % (url, data)
597 | else:
598 | if data:
599 | return '%s?%s' % (url, data)
600 | else:
601 | return url
602 | ```
603 |
604 | 调用了标准库 urlparse ,逻辑大概为,如果url 里面有参数,把data的key,value 以&加到后面,否则,以?加到后面。
605 | 很简单的逻辑。
606 |
607 | ** 3. 允许文件上传和post数据同时进行 **
608 |
609 | 这一点更新,跟上一点差不多。代码如下。
610 |
611 | ```
612 | if self.files:
613 | register_openers()
614 | if self.data:
615 | self.files.update(self.data)
616 | ```
617 |
618 | 挺无聊的,就是把data 加到file 里面233
619 |
620 | ** 4. 新的认证管理系统 **
621 | 我能看到的新的认证系统的功能包括,
622 | 1. 可以接受形如元组的参数;
623 | 2. 第二次对同一个url 发送请求时,无需再次添加验证;
624 |
625 | 所以深挖下实现细节。
626 |
627 | 首先是Request 初始化时,关于Auth 的细节。
628 |
629 | ```
630 | class Reuqest(object):
631 |
632 | def __init__(self):
633 | ...
634 |
635 | if isinstance(auth, (list, tuple)):
636 | auth = AuthObject(*auth)
637 | if not auth:
638 | auth = auth_manager.get_auth(self.url)
639 | self.auth = auth
640 |
641 | ...
642 |
643 | ```
644 |
645 | 也就是说,如果没有提供auth 参数时,会去 auth_manager 中拿取当前url 的认证信息。
646 |
647 | auth_manager 是一个全局的 AuthManager() 对象。
648 |
649 | 并且这个类采用了单例的写法,确保不同文件import requests 时,只有一个 auth_manager。
650 |
651 | 代码如下
652 |
653 | ```
654 | class AuthManager(object):
655 | """Authentication Manager."""
656 |
657 | def __new__(cls):
658 | singleton = cls.__dict__.get('__singleton__')
659 | if singleton is not None:
660 | return singleton
661 |
662 | cls.__singleton__ = singleton = object.__new__(cls)
663 |
664 | return singleton
665 |
666 | ```
667 |
668 | 这种写法好像和在豆瓣的dongweiming 大神的写法很像。
669 |
670 | 地址在这里 http://dongweiming.github.io/python-singleton.html , 例子是做mongo连接的单例。
671 |
672 |
673 | 这个类做了一个认证信息的存储和获取,也就是说,当你第二次请求时,如果没有给auth, 会默认从这个类实例中获取认证信息。
674 |
675 | 基本在这个函数中,可窥一斑。
676 |
677 | ```
678 | def reduce_uri(self, uri, default_port=True):
679 | """Accept authority or URI and extract only the authority and path."""
680 | # note HTTP URLs do not have a userinfo component
681 | parts = urllib2.urlparse.urlsplit(uri)
682 | if parts[1]:
683 | # URI
684 | scheme = parts[0]
685 | authority = parts[1]
686 | path = parts[2] or '/'
687 | else:
688 | # host or host:port
689 | scheme = None
690 | authority = uri
691 | path = '/'
692 | host, port = urllib2.splitport(authority)
693 | if default_port and port is None and scheme is not None:
694 | dport = {"http": 80,
695 | "https": 443,
696 | }.get(scheme)
697 | if dport is not None:
698 | authority = "%s:%d" % (host, dport)
699 | return authority, path
700 | ```
701 |
702 | 将 (域名+端口, 路径) 作为key, 保存 auth 信息,如果请求参数没有 auth,默认都会进来查找一次。
703 |
704 |
705 |
706 | 0X03
707 |
708 | 好的。终于写完了。
709 |
710 | 很早之前,有人就教育我,知乎上答不了的代码不要强答、追不到的妹子不要强追、看不了的代码不要强看。
711 |
712 | 如果那样,和咸鱼有什么区别了。
713 |
714 | 嗯,快来订阅我吧。
715 |
716 | 不介意任何形式的转载,请注明作者就可以了,汪顺平或者汪小小。
717 |
--------------------------------------------------------------------------------
/doc/Requests_v0.4.0.md:
--------------------------------------------------------------------------------
1 | ## Requests v0.4.0
2 |
3 | ### 0x01 照例说几句
4 |
5 | 昨天几个老司机在我朋友圈说,小伙子我看好你噢。
6 |
7 | 鼓励是鞭子,不是兴奋剂。
8 |
9 | 多谢老司机。
10 |
11 | ### 0x02 HISTORY.rst
12 |
13 | ```
14 | 0.4.0 (2011-05-15)
15 | ++++++++++++++++++
16 |
17 |
18 | ```
19 |
20 | 距离上个大版本两个多月的时间,我去github 里面 issue 看了下milestone。有四个issue与v0.4.0 的milestone有关。
21 |
22 | 依照惯例,我们从每个版本的更新的翻译开始。(话说history 现在都不注明 issue了,还得一个一个去查)
23 |
24 | ```
25 | 0.3.1 (2011-04-01)
26 | ++++++++++++++++++
27 |
28 | * Cookie Changes
29 | * Response.read()
30 | * Poster fix
31 | ```
32 |
33 | 1. cookie改变。怀疑是支持的方式有改,原来好像是直接调用了urllib的函数。
34 | 2. Response.read() 给返回的类加了一个方法? ...history 不能这么写啊...这让我怎么猜..
35 | 3. Poster fix, 嗯,轮子的bug咯~
36 |
37 |
38 | ```
39 | 0.3.2 (2011-04-15)
40 | ++++++++++++++++++
41 |
42 | * Automatic Decompression of GZip Encoded Content
43 | * AutoAuth Support for Tupled HTTP Auth
44 | ```
45 |
46 | 1. 自动解压缩 gzip 的内容;
47 | 2. 自动验证加了元组形式的http验证;(话说这个不是在 v0.2.3 已经支持了吗?)
48 |
49 | ```
50 | 0.3.3 (2011-05-12)
51 | ++++++++++++++++++
52 |
53 | * Request timeouts
54 | * Unicode url-encoded data
55 | * Settings context manager and module
56 | ```
57 | 1. 请求 timeout... 我看了下issue,好像是支持自己设置请求 timeout 时间;
58 | 2. 参数utf-8 支持
59 | 3. 支持设置上下文管理
60 |
61 | ```
62 | 0.3.4 (2011-05-14)
63 | ++++++++++++++++++
64 |
65 | * Urllib2 HTTPAuthentication Recursion fix (Basic/Digest)
66 | * Internal Refactor
67 | * Bytes data upload Bugfix
68 | ```
69 |
70 | 1. urllib2 HTTP验证的递归问题修复? 好好看下。
71 | 2. 内部结构重构;
72 | 3. 比特数据流上传的bug修复;
73 |
74 | ```
75 | 0.4.0 (2011-05-15)
76 | ++++++++++++++++++
77 |
78 | * Response.history: list of redirected responses
79 | * Case-Insensitive Header Dictionaries!
80 | * Unicode URLs
81 | ```
82 |
83 | 1. 返回类 Response 加入 history方法;
84 | 2. 支持大小写不敏感的请求头字典信息;
85 | 3. 支持unicode urls, 等下,0.3.3第二点不就已经支持了吗?
86 |
87 |
88 | 话说一定要把 history 写好啊喂...我觉得这篇又会纠结好久了。
89 |
90 | 不管啦,开工吧。
91 |
92 | ### 0X02 源码阅读
93 |
94 | ** v0.3.1 **
95 |
96 | ```
97 | 0.3.1 (2011-04-01)
98 | ++++++++++++++++++
99 |
100 | * Cookie Changes
101 | * Response.read()
102 | * Poster fix
103 | ```
104 |
105 | #### Cookie 改变
106 | ```
107 | def test_cookie_jar(self):
108 | """
109 | .. todo:: This really doesn't test to make sure the cookie is working
110 | """
111 | jar = cookielib.CookieJar()
112 | self.assertFalse(jar)
113 |
114 | requests.get('http://google.com', cookies=jar)
115 | self.assertTrue(jar)
116 |
117 | ```
118 |
119 | 神奇,v0.2.2开始已经有了对cookie的支持,到v0.3.0就没有。这个版本对 cookie的处理又加上去了。
120 |
121 | 直接看对cookie 的处理吧,代码如下
122 |
123 | ```
124 | def _get_opener(self):
125 | """Creates appropriate opener object for urllib2."""
126 |
127 | _handlers = []
128 |
129 | if self.cookiejar is not None:
130 | _handlers.append(urllib2.HTTPCookieProcessor(self.cookiejar))
131 |
132 | ...
133 | if not _handlers:
134 | return urllib2.urlopen
135 |
136 | _handlers.extend(get_handlers())
137 | opener = urllib2.build_opener(*_handlers)
138 | ```
139 |
140 | get_handlers() 来自于之前所说的第三方轮子,加入了 Mixin 的httpstreaminghandler。
141 |
142 | 也就是说,只要带了cookie,或者auth的请求,基本走的都是第三方轮子的opener, 否则返回默认的 urllib2.urlopen()
143 |
144 | 推测之前的cookie支持,没有测试,直接走的是urllib2的默认handlers,结果直接跪了,就默默把它干掉了。
145 |
146 |
147 | #### Response.read()
148 |
149 | 给Response 类加了一个方法,
150 |
151 | 例如
152 |
153 | ```
154 | >>> r = requests.get('www.baidu.com')
155 | >>> r.read()
156 | ....
157 | ```
158 |
159 | 不过我之前用的都是直接 r.content,好像现在的文档也是直接 content, 坐等之后被删。
160 |
161 | #### Poster 修复
162 |
163 | 我并没有看到 v0.3.1版本中有对poster 这两个文件有修改,反倒是在0.3.0 里面有把几行抽象了一个函数。
164 |
165 | 即在第一点里面说到的 get_handlers()方法。它被单独抽象出来。代码如下。
166 |
167 | ```
168 | def get_handlers():
169 | handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler]
170 | if hasattr(httplib, "HTTPS"):
171 | handlers.append(StreamingHTTPSHandler)
172 | return handlers
173 | ```
174 |
175 | 这个版本 history 简直了! 害我去扒各种 pr 和 issue,我发现看 pr 比看 issue 有用。
176 |
177 | ** v0.3.2 **
178 |
179 | ```
180 | 0.3.2 (2011-04-15)
181 | ++++++++++++++++++
182 |
183 | * Automatic Decompression of GZip Encoded Content
184 | * AutoAuth Support for Tupled HTTP Auth
185 | ```
186 |
187 | #### 1. 自动解压缩 gzip
188 |
189 | ```
190 | def test_decompress_gzip(self):
191 |
192 | r = requests.get('http://api.stackoverflow.com/1.1/users/495995/top-answer-tags')
193 | r.content.decode('ascii')
194 | ```
195 |
196 | 这是这个改动的测试,这个链接现在已经不能用了。现在api的版本好像是2.2 ,先看代码。
197 |
198 |
199 | 在函数 _build_response (即根据resp = opener(req) 的返回进行组装,返回 Response 的函数 )中
200 |
201 | ```
202 | def _build_response(self, resp):
203 | """Build internal Response object from given response."""
204 |
205 | ...
206 | if self.response.headers.get('content-encoding', None) == 'gzip':
207 | try:
208 | self.response.content = zlib.decompress(self.response.content, 16+zlib.MAX_WBITS)
209 | except zlib.error:
210 | pass
211 |
212 | ...
213 | ```
214 |
215 | 如果返回头里面 content-encoding 里面有 gzip时,调用标准库函数进行解压缩。
216 |
217 | 在这个版本中发请求是没有自动加 Accept-Encoding: gzip,也就是说如果希望拿到压缩之后的 response, 需要自己加头。
218 |
219 | 刚去测试了下,现在的版本(2.X.X),对www.baidu.com 进行get 请求
220 |
221 | ```
222 | In [11]: resp = requests.get("http://www.baidu.com")
223 |
224 | In [12]: resp.headers
225 | Out[12]: { ... 'content-encoding': 'gzip', ...}
226 | ```
227 |
228 | 返回的压缩的版本的,而现在这个版本(v0.3.2)是没有的。坐等哪个版本加进来。
229 |
230 |
231 | #### 2. 自动验证支持元组
232 |
233 | 上一次的版本已经支持了,如果第一次加了 auth=xxx的参数,会以key,value的形式保存起来,下次直接请求就可以了。
234 | 这次在这个基础上,加入了支持验证信息的插入保存。
235 |
236 | ```
237 | def test_autoauth(self):
238 |
239 | conv_auth = ('requeststest', 'requeststest')
240 | requests.auth_manager.add_auth('convore.com', conv_auth)
241 |
242 | r = requests.get('https://convore.com/api/account/verify.json')
243 | self.assertEquals(r.status_code, 200)
244 |
245 | ```
246 |
247 | 如上所示,auth_manager暴露了 add_auth, 能够直接支持对某个域名地址的验证信息的保存。
248 |
249 | ** v0.3.3 **
250 |
251 | ```
252 | 0.3.3 (2011-05-12)
253 | ++++++++++++++++++
254 |
255 | * Request timeouts
256 | * Unicode url-encoded data
257 | * Settings context manager and module
258 |
259 | ```
260 |
261 | #### 1. 请求 timeout 的控制
262 |
263 | 在请求中加入 timeout 参数。初始Requests 的时候传入。
264 |
265 | 调用了标准库 socket ,代码如下。
266 |
267 | ```
268 | class Request(object):
269 | """The :class:`Request` object. It carries out all functionality of
270 | Requests. Recommended interface is with the Requests functions.
271 | """
272 |
273 | _METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')
274 |
275 | def __init__(self, url=None, headers=dict(), files=None, method=None,
276 | data=dict(), auth=None, cookiejar=None, timeout=None):
277 |
278 | ...
279 |
280 | socket.setdefaulttimeout(timeout)
281 |
282 | ...
283 |
284 | ```
285 |
286 | 但是这个新功能的添加没有相应的测试..估计也是因为不好测。
287 |
288 | #### 2. 支持 url-encoded data
289 |
290 | 测试代码如下。
291 |
292 | ```
293 | def test_unicode_get(self):
294 | requests.get('http://google.com', params={'foo': u'føø'})
295 | requests.get('http://google.com', params={'foo': u'foo'})
296 | requests.get('http://google.com/ø', params={'foo': u'foo'})
297 |
298 | ```
299 |
300 | 正如前面所说到的, 里面的处理逻辑会把url 拼起来。
301 |
302 | ```
303 | class Request(object):
304 | """The :class:`Request` object. It carries out all functionality of
305 | Requests. Recommended interface is with the Requests functions.
306 | """
307 |
308 | _METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE')
309 |
310 | def __init__(self, url=None, headers=dict(), files=None, method=None,
311 | data=dict(), auth=None, cookiejar=None, timeout=None):
312 | ...
313 |
314 | for (k, v) in self.data.iteritems():
315 | self.data[k] = v.encode('utf-8')
316 | ```
317 |
318 | 加入了对utf-8的支持。
319 |
320 | #### 3. 上下文管理配置
321 |
322 | 刚才去扒了一下,这个和第一点,都来自一个叫 jgorset 的同学的pr。实现的功能描述如下。
323 |
324 | ```
325 | I've implemented a settings context manager as described in pull request #25.
326 |
327 | >>> import requests
328 | >>> with requests.settings(timeout=0.5):
329 | ... requests.get('http://example.org') # Times out after 0.5 seconds
330 | ... requests.get('http://example.org', timeout=10) # Times out after 10 seconds
331 | Settings may also be manipulated separately:
332 |
333 | >>> import requests
334 | >>> requests.timeout = 0.5
335 | >>> requests.get('http://example.org') # Times out after 0.5 seconds
336 | ```
337 |
338 | 还是很有意思的,他提到,如果他需要请求很多地址,不同的地址可能需要不同的 timeout 配置,那么他需要写在代码中写各种配置,这不科学。然后他就提了一个 pr...
339 |
340 | 但是我不满意这哥们一点,pr 从来不更新测试...
341 |
342 | 看一下实现细节, 在 requests/__init__.py 中加入 settings类
343 |
344 | ```
345 | import inspect
346 |
347 | class settings:
348 | """Context manager for settings."""
349 |
350 | cache = {}
351 |
352 | def __init__(self, timeout):
353 | self.module = inspect.getmodule(self)
354 |
355 | # Cache settings
356 | self.cache['timeout'] = self.module.timeout
357 |
358 | self.module.timeout = timeout
359 |
360 | def __enter__(self):
361 | print "i'm in __enter__"
362 | print "i'm out __enter__"
363 |
364 | def __exit__(self, type, value, traceback):
365 | # Restore settings
366 | print "i'm in __exit__"
367 | for key in self.cache:
368 | print key
369 | print self.module
370 | print self.cache[key]
371 | setattr(self.module, key, self.cache[key])
372 | print "i'm out __exit__"
373 |
374 | ```
375 |
376 | 代码倒是不难,就是一个用两个 magic 函数实现的上下文管理器,但是实话实说,我花了大概一个小时测试代码,各种 print。
377 |
378 | 上面代码中的print 都是我自己加的,我在 ipythn中运行如下。
379 |
380 | ```
381 | In [1]: import requests
382 |
383 | In [2]: with requests.settings(timeout=0.005):
384 | resp = requests.get('http://www.baidu.com')
385 | print resp.status_code
386 | ...:
387 | i'm in __enter__
388 | i'm out __enter__
389 | 0.005
390 | i'm in __exit__
391 | timeout
392 |
393 | None
394 | i'm out __exit__
395 | ```
396 |
397 | 在模块中加入了一个全局变量,print requets.__dict__['timeout'] 能打印出来。
398 |
399 | 但是不知道在 Request 类中可以直接使用 timeout, 而在外面的get 函数中不行...
400 |
401 | 心累,看这个花了1个小时。
402 |
403 |
404 | ** v0.3.4 **
405 |
406 | ```
407 | 0.3.4 (2011-05-14)
408 | ++++++++++++++++++
409 |
410 | * Urllib2 HTTPAuthentication Recursion fix (Basic/Digest)
411 | * Internal Refactor
412 | * Bytes data upload Bugfix
413 | ```
414 |
415 | #### 1. urllib2 权限认证错误导致一直循环验证 BUG
416 |
417 | bug 链接 http://bugs.python.org/issue8894
418 |
419 | 继承了原来的权限认证的handler 类,重写了两个函数。如下
420 |
421 | ```
422 |
423 | class _HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
424 | # from mercurial
425 |
426 | def __init__(self, *args, **kwargs):
427 | urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
428 | self.retried_req = None
429 |
430 | def reset_retry_count(self):
431 | # Python 2.6.5 will call this on 401 or 407 errors and thus loop
432 | # forever. We disable reset_retry_count completely and reset in
433 | # http_error_auth_reqed instead.
434 | pass
435 |
436 | def http_error_auth_reqed(self, auth_header, host, req, headers):
437 | # Reset the retry counter once for each request.
438 | if req is not self.retried_req:
439 | self.retried_req = req
440 | self.retried = 0
441 | return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
442 | self, auth_header, host, req, headers)
443 |
444 | ```
445 |
446 | 我百度了下 mercurial 的意思,是 幽鬼(dota)
447 |
448 | #### 2. 内部代码重新分开成不同文件
449 |
450 | 之前的形式如下
451 |
452 |
453 | ```
454 | --requests/
455 | |__ packages # 第三方的轮子
456 | |__ __init__.py # 里面有setting类(contentxtmanager)
457 | |__ core.py # 所有代码都在这里面
458 | |__ async.py # monkeypatch
459 | |__ structures.py # 来自于 werkzeug 的多key字典之类的数据结构,没看到使用
460 |
461 | ```
462 |
463 | 现在的结构
464 |
465 | ```
466 | --requests/
467 | |__ packages # 第三方的轮子
468 | |__ __init__.py # 里面有setting类(contentxtmanager)
469 | |__ core.py # 包全局信息,没有实现代码 (即没有类或函数)
470 | |__ async.py # monkeypatch
471 | |__ structures.py # 来自于 werkzeug 的多key字典之类的数据结构,没看到使用
472 | |__ api.py # 暴露的 GET, POST 等各种 method
473 | |__ models.py # 原来 core.py 里面 Request Response 等类
474 | |__ monkeys.py # 对urllib2 的继承的类
475 | |__ patches.py # 什么都没
476 |
477 | ```
478 |
479 | #### 3。 比特流数据上传错误修复
480 |
481 | 看了一下,大概这个测试针对这个问题
482 |
483 | ```
484 | post3 = requests.post(bin.url, data='[{"some": "json"}]')
485 | self.assertEqual(post.status_code, 201)
486 |
487 | ```
488 |
489 | 这个没什么好说的,果然是之前测试不够,代码有 bug..
490 |
491 | ** V0.4.0 **
492 |
493 | ```
494 | 0.4.0 (2011-05-15)
495 | ++++++++++++++++++
496 |
497 | * Response.history: list of redirected responses
498 | * Case-Insensitive Header Dictionaries!
499 | * Unicode URLs
500 | ```
501 | 切到v0.4.0... 竟然test_request.py 文件没有改动,顿时心凉了半截...
502 |
503 | #### 1. 给 Response 类 加入 history 方法
504 |
505 | 这个改进跟重定向(30X)的issue有关,当请求时,希望返回最终的应答而不是重定向response.history 返回经过的 domain。
506 |
507 | 代码如下。
508 |
509 | ```
510 | def _build_response(self, resp):
511 | """Build internal Response object from given response."""
512 |
513 | def build(resp):
514 | ...
515 |
516 | history = []
517 |
518 | r = build(resp)
519 |
520 | if self.redirect:
521 |
522 | while 'location' in r.headers:
523 |
524 | history.append(r)
525 |
526 | url = r.headers['location']
527 |
528 | request = Request(
529 | url, self.headers, self.files, self.method,
530 | self.data, self.auth, self.cookiejar, redirect=False
531 | )
532 | request.send()
533 | r = request.response
534 |
535 | r.history = history
536 |
537 | self.response = r
538 | ```
539 |
540 | 这段代码用到了递归,非常棒的代码。值得好好看看。
541 |
542 | 嗯,再说太多也无法表达对它的喜爱。
543 |
544 | #### 2. HTTP 头信息采用大小写无关字典
545 |
546 | 嗯,之前说到的没用到的 structure.py ,终于出现了。
547 |
548 | CaseInsensitiveDict 继承自 DictMixin,标准库对它的介绍如下
549 |
550 | >class UserDict.DictMixin
551 |
552 | >Mixin defining all dictionary methods for classes that already have a minimum dictionary interface including `__getitem__()`, `__setitem__()`, `__delitem__()`, and keys().
553 |
554 | 这个字典实现的是大小写无关的字典,如下代码所示
555 |
556 | ```
557 | In [31]: from requests.structures import CaseInsensitiveDict
558 |
559 | In [32]: qwe = CaseInsensitiveDict()
560 |
561 | In [33]: qwe['A'] = 5
562 |
563 | In [34]: qwe['b'] = 4
564 |
565 | In [35]: qwe
566 | Out[35]: {'A': 5, 'b': 4}
567 |
568 | In [36]: qwe['a'] = 3
569 |
570 | In [37]: qwe
571 | Out[37]: {'a': 3, 'b': 4}
572 |
573 | In [38]: qwe['a']
574 | Out[38]: 3
575 | ```
576 |
577 | 看下实现。
578 |
579 | ```
580 | def __getitem__(self, key):
581 |
582 | if key.lower() in self:
583 | return self.items()[self._lower_keys().index(key.lower())][1]
584 |
585 |
586 | def __setitem__(self, key, value):
587 | return self.data.__setitem__(key, value)
588 | ```
589 |
590 | 看下一段ipython 信息,就能猜到实现逻辑了。
591 |
592 | ```
593 | In [45]: qwe.__dict__
594 | Out[45]: {'_store': {'a': ('a', 3), 'b': ('b', 4)}}
595 |
596 | In [46]: qwe['A'] = 6
597 |
598 | In [47]: qwe
599 | Out[47]: {'A': 6, 'b': 4}
600 |
601 | In [48]: qwe.__dict__
602 | Out[48]: {'_store': {'a': ('A', 6), 'b': ('b', 4)}}
603 | ```
604 |
605 | 在 get中,每个key,value pair是个元组, [1]即为key的值。
606 |
607 | #### 3. Unicode URLs
608 |
609 | .... 这个不是在上一个版本中搞定了吗,找了一圈每次提交的 commit log,没看到有继续更新。
610 |
611 |
612 |
613 |
614 | ### 0X03 后记
615 |
616 | **1. ** 好啦。0.4.0 版本终于看完了。不想说话了。
617 |
618 | **2. ** 看到一半有件挺开心的事,
619 |
620 |
621 |
622 | 终于从5年前到4年前了。看到3年前,估计得很漫长了。
623 |
624 | **3. ** 好了,我不想说话,晚安。
625 |
626 |
--------------------------------------------------------------------------------
/doc/Requests_v0.5.0.md:
--------------------------------------------------------------------------------
1 | ## Requests v0.5.0 源码阅读
2 |
3 | ### 0X00 前言
4 |
5 | 原来有个姑娘经常跟我说她导师让她做了某个事情,然后跟我抱怨,“你说,这事有什么意义呢?”
6 |
7 | 为了安慰她,我说“其实这个世界所有的事情都是没有意义的,你觉得它有,它就有。”
8 |
9 | 拆轮子是件很耗体力的事情。
10 |
11 | 但是才到 0.5 版本,我就接触到了 优秀的上下文管理配置写法、单例写法、特殊的字典、hacker标准库内容等等刷三观的东西。这些都是写API 是遇不到的,即使遇到,也会尽可能的用一些 trick 去避免这些。
12 |
13 | 所以,以上。
14 |
15 | ### 0X01 目标
16 |
17 | ```
18 | 0.5.0 (2011-06-21)
19 | ++++++++++++++++++
20 |
21 | * PATCH Support
22 | * Support for Proxies
23 | * HTTPBin Test Suite
24 | * Redirect Fixes
25 | * settings.verbose stream writing
26 | * Querystrings for all methods
27 | * URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicity raised
28 | ``r.requests.get('hwe://blah'); r.raise_for_status()``
29 |
30 | ```
31 |
32 | 不得不说,作者还是很拼的。一个月更一个大版本...
33 |
34 | > 哪有什么天才,不过是拿你泡妹子的时间写代码罢了。 ——佚名某码神
35 |
36 | ### 0X02 历史更改
37 |
38 | 版本更新历史。
39 |
40 | ```
41 | 0.4.1 (2011-05-22)
42 | ++++++++++++++++++
43 |
44 | * Improved Redirection Handling
45 | * New 'allow_redirects' param for following non-GET/HEAD Redirects
46 | * Settings module refactoring
47 |
48 | ```
49 |
50 | 1. 加强了重定向处理类的处理逻辑,就是上一章里面说的递归调用(status_code 30X)。
51 | 2. 新的 allow_redirects 参数, 原来的参数名只是为 redirects。
52 | 3. settings 模块的重构,希望这次我能看的明白些。
53 |
54 | ```
55 | 0.5.0 (2011-06-21)
56 | ++++++++++++++++++
57 |
58 | * PATCH Support
59 | * Support for Proxies
60 | * HTTPBin Test Suite
61 | * Redirect Fixes
62 | * settings.verbose stream writing
63 | * Querystrings for all methods
64 | * URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicity raised
65 | ``r.requests.get('hwe://blah'); r.raise_for_status()``
66 |
67 | ```
68 |
69 | 1. patch支持(还记得上一篇中提到了空的 patches.py 文件吗,填坑来了)。
70 | 2. 代理支持。
71 | 3. httpbin 测试集合。
72 | 4. 重定向bug修复,果然这个是个坑,这两个版本一直针对这个反复优化。
73 | 5. settings 相关的改动,翻译无能...
74 | 6. 给所有的方法加入 querystring。
75 | 7. URLErrors 会当作异常报出。
76 |
77 | ### 0X03 源码阅读
78 |
79 | ### v0.4.1
80 |
81 | ```
82 | 0.4.1 (2011-05-22)
83 | ++++++++++++++++++
84 |
85 | * Improved Redirection Handling
86 | * New 'allow_redirects' param for following non-GET/HEAD Redirects
87 | * Settings module refactoring
88 |
89 | ```
90 |
91 | #### 1. 处理 Redirection Handling
92 |
93 | 因为采用递归去处理请求,即如果判断,请求得到的是重定向,则继续请求。
94 |
95 | 所以在urllib 那一层,不希望urllib来处理重定向,跟之前的方法一样,hack了urllib2.HTTPRedirectHandler.http_error_30X 几个函数。
96 |
97 | 代码如下。
98 |
99 | ```
100 | class HTTPRedirectHandler(urllib2.HTTPRedirectHandler):
101 |
102 | def http_error_301(self, req, fp, code, msg, headers):
103 | pass
104 |
105 | http_error_302 = http_error_303 = http_error_307 = http_error_301
106 | ```
107 |
108 | #### 2. 新的允许重定向参数
109 |
110 | 原来的 direct 改成了 allow_redirects 。
111 |
112 | #### 3. Settings 模块重构
113 |
114 | 追着 commit log 看了一下,这个版本关于 settings 的改动来自于把settings 写成了一个单例,并且实例化加到变量中,还对 time_out 参数做了缓存。
115 |
116 | 前前一篇也讲到了对单例模式的应用,用在了权限管理系统中,当你第一次请求,加了auth并且通过,下次请求同样的url 就不需要再带权限了。
117 |
118 | 两者做法大致相同,但是在全局实例化一个对象。
119 |
120 | 但是 settings 用到了上下文管理,还是很有趣的。
121 |
122 |
123 | ### v0.5.0
124 |
125 | ```
126 | 0.5.0 (2011-06-21)
127 | ++++++++++++++++++
128 |
129 | * PATCH Support
130 | * Support for Proxies
131 | * HTTPBin Test Suite
132 | * Redirect Fixes
133 | * settings.verbose stream writing
134 | * Querystrings for all methods
135 | * URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicity raised
136 | ``r.requests.get('hwe://blah'); r.raise_for_status()``
137 |
138 | ```
139 |
140 | #### 1. PATCH 支持
141 |
142 | 好吧,刚 google 回来。patch 是 http method中的一种,我之前不知道。
143 |
144 | https://tools.ietf.org/html/rfc5789
145 |
146 | 大意是 post是新建一个数据(资源),PUT是对资源的替换,而有时候部分替换,所以加了 PATCH。
147 |
148 | 原来写 RESTful API 的时候,我都直接用 PUT 的。
149 |
150 | ```
151 | Several applications extending the Hypertext Transfer Protocol (HTTP)
152 | require a feature to do partial resource modification. The existing
153 | HTTP PUT method only allows a complete replacement of a document.
154 | This proposal adds a new HTTP method, PATCH, to modify an existing
155 | HTTP resource.
156 |
157 | ```
158 | 这是摘要。
159 |
160 |
161 | #### 2. 支持代理
162 |
163 | issue #66
164 |
165 | pull request #54
166 |
167 | 调用了 urllib2 里面的 ProxyHandlers
168 |
169 | ```
170 | if self.proxies:
171 | _handlers.append(urllib2.ProxyHandler(self.proxies))
172 | ```
173 |
174 |
175 | #### 3. httpbin 测试集合
176 |
177 | 即然说到了对测试优化,就贴一下关于post的测试吧。
178 |
179 | ```
180 | def test_POSTBIN_GET_POST_FILES(self):
181 | bin = requests.post('http://www.postbin.org/')
182 | self.assertEqual(bin.status_code, 302)
183 |
184 | post_url = bin.headers['location']
185 | post = requests.post(post_url, data={'some': 'data'})
186 | self.assertEqual(post.status_code, 201)
187 |
188 | post2 = requests.post(post_url, files={'some': open('test_requests.py')})
189 | self.assertEqual(post2.status_code, 201)
190 |
191 | post3 = requests.post(post_url, data='[{"some": "json"}]')
192 | self.assertEqual(post.status_code, 201)
193 | ```
194 | 1. 测试是否返回重定向状态码;
195 | 2. 是否能从 Response 的 headers 拿取 location,并正常post, 返回201;
196 | 3. 文件上传测试;
197 | 4. 字符串格式 data 上传测试;
198 |
199 | ```
200 |
201 | def test_POSTBIN_GET_POST_FILES_WITH_PARAMS(self):
202 | bin = requests.post('http://www.postbin.org/')
203 | self.assertEqual(bin.status_code, 302)
204 |
205 | post_url = bin.headers['location']
206 |
207 | post2 = requests.post(post_url, files={'some': open('test_requests.py')}, data={'some': 'data'})
208 | self.assertEqual(post2.status_code, 201)
209 | ```
210 | 1. post文件的同时,post data,验证是否正常;
211 |
212 | ```
213 | def test_POSTBIN_GET_POST_FILES_WITH_HEADERS(self):
214 | bin = requests.post('http://www.postbin.org/')
215 | self.assertEqual(bin.status_code, 302)
216 |
217 | post_url = bin.headers['location']
218 |
219 | post2 = requests.post(post_url, files={'some': open('test_requests.py')},
220 | headers = {'User-Agent': 'requests-tests'})
221 |
222 | self.assertEqual(post2.status_code, 201)
223 |
224 | ```
225 | 1. post 请求加请求头更改,是否正常;
226 |
227 | #### 4. 重定向 bug 修复
228 |
229 | 重定向时,有时候在 headers['location'] 中没有带域名,只有路径(浏览器会支持重定向到单前域名)。
230 |
231 | 为了修复这个问题,加了几行代码。
232 |
233 | ```
234 | url = r.headers['location']
235 |
236 | # Facilitate for non-RFC2616-compliant 'location' headers
237 | # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
238 | if not urlparse(url).netloc:
239 | parent_url_components = urlparse(self.url)
240 | url = '%s://%s/%s' % (parent_url_components.scheme, parent
241 | ```
242 |
243 | #### 5. settings 的改动, verbose 支持
244 |
245 |
246 |
247 | verbose 支持来自作者自己提的一个 issue(#61)。
248 |
249 | 实现的功能如下
250 |
251 | ```
252 | >>> with requests.settings(verbose=sys.stderr):
253 | ... r = requests.get('http://httpbin.org')
254 | ...
255 | GET http://httpbin.org
256 | ```
257 |
258 | 代码
259 |
260 | ```
261 | # Logging
262 | if settings.verbose:
263 | settings.verbose.write('%s %s %s\n' % (
264 | datetime.now().isoformat(), self.method, self.url
265 | ))
266 | ```
267 |
268 | 突然发现了实现一个上下文之后好好用啊!
269 |
270 | #### 6. 给 method 加入 querystring
271 |
272 | ```
273 | def test_nonurlencoded_post_querystring(self):
274 | r = requests.post(httpbin('post'), params='fooaowpeuf')
275 | self.assertEquals(r.status_code, 200)
276 | self.assertEquals(r.headers['content-type'], 'application/json')
277 | self.assertEquals(r.url, httpbin('post?fooaowpeuf'))
278 | rbody = json.loads(r.content)
279 | self.assertEquals(rbody.get('form'), {}) # No form supplied
280 | self.assertEquals(rbody.get('data'), '')
281 |
282 |
283 | def test_urlencoded_post_query_and_data(self):
284 | r = requests.post(httpbin('post'), params=dict(test='fooaowpeuf'),
285 | data=dict(test2="foobar"))
286 | self.assertEquals(r.status_code, 200)
287 | self.assertEquals(r.headers['content-type'], 'application/json')
288 | self.assertEquals(r.url, httpbin('post?test=fooaowpeuf'))
289 | rbody = json.loads(r.content)
290 | self.assertEquals(rbody.get('form'), dict(test2='foobar'))
291 | self.assertEquals(rbody.get('data'), '')
292 | ```
293 |
294 | 这两个测试跟 history 上这一点有关。
295 |
296 | 但是并没有明白, querystring 和这些断言有什么联系。
297 |
298 | 猜测是能返回经过拼接的url(也就是 querystring)。
299 |
300 | ### 0X04 后记
301 |
302 | 略。
303 |
304 |
305 |
306 |
--------------------------------------------------------------------------------
/doc/Requests_v0.6.0.md:
--------------------------------------------------------------------------------
1 | ## Requests v0.6.0 源码阅读
2 |
3 | ### 0X00 前言
4 |
5 | 前些天跟强哥通电话(对,就是害我入了程序猿的坑的魂淡),他说他回成电读cs博了,导师联系好了,应该问题不大了。
6 |
7 | 他说啊, 实在不想写一点简单的逻辑,维护几行sb代码。准备回学校搞比较火的机器学习,出来去大公司的研究院打杂。
8 |
9 | 深以为然。
10 |
11 | 所以说啊,少年,你如果对现状不满,你就使劲折腾,去寻找自己想要的。
12 |
13 | 如果没能力,那你凭什么对现状不满。
14 |
15 | ### 0X01 目标
16 |
17 | ```
18 | 0.6.0 (2011-08-17)
19 | ++++++++++++++++++
20 |
21 | * New callback hook system
22 | * New persistient sessions object and context manager
23 | * Transparent Dict-cookie handling
24 | * Status code reference object
25 | * Removed Response.cached
26 | * Added Response.request
27 | * All args are kwargs
28 | * Relative redirect support
29 | * HTTPError handling improvements
30 | * Improved https testing
31 | * Bugfixes
32 | ```
33 |
34 | 最近的两个版本的小版本都特别少(v0.4.x, v0.5.x),看起来还是很开心的。
35 |
36 | 但是这 histroy 不能忍 (ex, Bugfixes),谁猜的到这指什么。
37 |
38 | 不过作者的commit log 写的还不错,偶尔会卖萌,配合 issue 和 pr 基本都能确定指什么,个别除外。
39 |
40 | ### 0x02 历史
41 |
42 | ```
43 | 0.5.1 (2011-07-23)
44 | ++++++++++++++++++
45 |
46 | * International Domain Name Support!
47 | * Access headers without fetching entire body (``read()``)
48 | * Use lists as dicts for parameters
49 | * Add Forced Basic Authentication
50 | * Forced Basic is default authentication type
51 | * ``python-requests.org`` default User-Agent header
52 | * CaseInsensitiveDict lower-case caching
53 | * Response.history bugfix
54 | ```
55 |
56 | 1. 域名支持。
57 | 2. 获取头信息无需拿取整个body
58 | 3. 使用列表代替字典做参数
59 | 4. 增加强制基础验证
60 | 5. 增加 User-Agent
61 | 6. 对大小写无关字典的改进
62 | 7. 修复 Respoonse.history 的bug
63 |
64 |
65 | ```
66 | 0.6.0 (2011-08-17)
67 | ++++++++++++++++++
68 |
69 | * New callback hook system
70 | * New persistient sessions object and context manager
71 | * Transparent Dict-cookie handling
72 | * Status code reference object
73 | * Removed Response.cached
74 | * Added Response.request
75 | * All args are kwargs
76 | * Relative redirect support
77 | * HTTPError handling improvements
78 | * Improved https testing
79 | * Bugfixes
80 | ```
81 | 1. 新的回调钩子系统;
82 | 2. 新的上下文管理; ???
83 | 3. 透明的字典形式的cookie处理;
84 | 4. 移除了 Response.cached
85 | 5. 添加了 Response.request
86 | 6. 所有的参数以 kwargs 的形式
87 | 7. 支持了 相对重定向;???
88 | 8. 加强了对 HTTPError 处理
89 | 9. 加强了对 https 的测试
90 | 10. 修bug -_-|||
91 |
92 | ### 0x03 源码阅读
93 |
94 | #### V0.5.1
95 |
96 | ```
97 | 0.5.1 (2011-07-23)
98 | ++++++++++++++++++
99 |
100 | * International Domain Name Support!
101 | * Access headers without fetching entire body (``read()``)
102 | * Use lists as dicts for parameters
103 | * Add Forced Basic Authentication
104 | * Forced Basic is default authentication type
105 | * ``python-requests.org`` default User-Agent header
106 | * CaseInsensitiveDict lower-case caching
107 | * Response.history bugfix
108 | ```
109 |
110 | #### 1. 国际化域名支持
111 |
112 | 国际化域名即 idna,允许非ASCII 的域名。维基链接:https://zh.wikipedia.org/wiki/%E5%9B%BD%E9%99%85%E5%8C%96%E5%9F%9F%E5%90%8D
113 |
114 | 相关代码如下
115 |
116 | ```
117 | parsed_url = list(urlparse(self.url))
118 | parsed_url[1] = parsed_url[1].encode('idna')
119 | self.url = urlunparse(parsed_url)
120 | ```
121 |
122 | 用 urlparse(标准库)将 url 分为元祖,其中索引为1的位置是域名。将他encode成 'idna',再将他拼起来。
123 |
124 | 相关测试如下
125 |
126 | ```
127 | def test_idna(self):
128 | r = requests.get(u'http://➡.ws/httpbin')
129 | self.assertEqual(r.url, HTTPBIN_URL)
130 | ```
131 | 哈哈,这域名够我笑一个月。
132 |
133 | ##### 2. 请求的时候,并不真的拿取整个 body,直到你 read或resp.content的时候
134 |
135 | 问题来自于 issue #86
136 |
137 | 原来的请求返回 resp,直接将 Response.content = resp.read()
138 |
139 | 但是在实际生产中,可能并不需要直接获取body(设想请求一个1G的文件,你就等着它down下来吧~)。
140 |
141 | 作者问,“那你可以直接 head 吗?”
142 |
143 | 提问的人说,“我head 一下, 再判断,再get? 好烦啊,我只想请求一次。 而且有些服务端对 head 的请求支持的不好。”
144 |
145 | 作者就修复了这个问题。其实这个问题的修复,也不是很难,将 Response.read = resp.read,闭包传给了返回的函数。
146 |
147 | 当想直接去 Reponse.content的时候,才去执行 Resposne.read()。
148 |
149 | 因为urllib2.urlopen 返回的也是一个类似文件的对象。
150 |
151 | 上代码。
152 |
153 | ```
154 | def __getattr__(self, name):
155 | """Read and returns the full stream when accessing to :attr: `content`"""
156 | if name == 'content':
157 | if self._content is not None:
158 | return self._content
159 | self._content = self.read()
160 | if self.headers.get('content-encoding', '') == 'gzip':
161 | try:
162 | self._content = zlib.decompress(self._content, 16+zlib.MAX_WBITS)
163 | except zlib.error:
164 | pass
165 | return self._content
166 |
167 | ```
168 |
169 | #### 3. 用列表代替字典做参数
170 |
171 | 问题来自 issue #83
172 |
173 | 之前是不支持多参数的,因为存放的数据参数为字典。现在改为 tuple 的字典
174 |
175 | 按照 issue 里面的例子说明下
176 |
177 | ```
178 | requests.get('http://localhost:7999?ck=1&ck=2&ck=3')
179 | ```
180 |
181 | 之前的处理逻辑,参数会变为
182 |
183 | ```
184 | {'ck': 3}
185 | ```
186 | 现在改为了
187 |
188 | ```
189 | [('ck', ), ('ck', 2), ('ck', 3)]
190 | ```
191 |
192 | 代码就不贴了,算了修了一个 bug 吧。
193 |
194 |
195 | #### 4. 增加强制基础验证
196 |
197 | 问题来自 issue #88
198 |
199 | 问题是有一个用户请求 github 的API,操作自己的私有仓库,带了auth,但是并不能获得数据。
200 |
201 | 作者回到
202 | > You aren't doing anything wrong. The way Basic Auth works is the client requests the url w/o auth, the server challenges it (403), and the client sends the auth. GitHub is sending a not found 404 for security reasons, which isn't standard protocol.
203 |
204 | 翻译一下,你的代码没问题,只是呢,验证的工作方式是客户端先去请求url,如果返回403(Forbidden),则用头信息里面的auth 去验证,但是github为了安全考虑,返回的是404,所以就跪了,当然啦这不是标准的协议定义的。
205 |
206 | 真神奇,今天才知道返回404 还能保证安全,因为403,被人会知道url没有错,这里有东西,但是404别人就不知道了。666
207 |
208 | 代码改动的地方,请求默认改成了 HTTPForcedBasicAuthHandler。这个类也是对 HTTPBasicAuthHandler 的继承,看一下具体的实现逻辑。
209 |
210 | ```
211 | def _http_error_auth_reqed(self, authreq, host, req, headers):
212 |
213 | authreq = headers.get(authreq, None)
214 |
215 | if self.retried > 5:
216 | # retry sending the username:password 5 times before failing.
217 | raise urllib2.HTTPError(req.get_full_url(), 401, "basic auth failed",
218 | headers, None)
219 | else:
220 | self.retried += 1
221 |
222 | if authreq:
223 |
224 | mo = self.rx.search(authreq)
225 |
226 | if mo:
227 | scheme, quote, realm = mo.groups()
228 |
229 | if scheme.lower() == 'basic':
230 | response = self.retry_http_basic_auth(host, req, realm)
231 |
232 | if response and response.code not in (401, 404):
233 | self.retried = 0
234 | return response
235 | else:
236 | response = self.retry_http_basic_auth(host, req, 'Realm')
237 |
238 | if response and response.code not in (401, 404):
239 | self.retried = 0
240 | return response
241 | ```
242 |
243 | 这里面还是挺复杂的,有涉及到 www-authenticate 的东西,就说之前HTTP 1.0版本的验证信息(账号密码)是带在请求头里面的,这会有安全问题。如果请求头里面有 www-authenticate,判断逻辑是有不同的。
244 |
245 | rfc 的文档贴在这里吧。
246 |
247 | https://tools.ietf.org/html/rfc2617
248 |
249 | #### User-Agent
250 |
251 | 用 requests 发送请求的,默认加入 User-Agent头信息:
252 |
253 | ```
254 | settings.base_headers = {'User-Agent': 'python-requests.org'}
255 | ```
256 |
257 | #### 小写字母 get
258 |
259 | 对 CaseInsensitiveDict 的改进。包括允许删除操作, get 如果没有元素,返回None,而不是 IndexError 。
260 |
261 | 没什么好说:)
262 |
263 | #### v0.6.0
264 |
265 | ```
266 | 0.6.0 (2011-08-17)
267 | ++++++++++++++++++
268 |
269 | * New callback hook system
270 | * New persistient sessions object and context manager
271 | * Transparent Dict-cookie handling
272 | * Status code reference object
273 | * Removed Response.cached
274 | * Added Response.request
275 | * All args are kwargs
276 | * Relative redirect support
277 | * HTTPError handling improvements
278 | * Improved https testing
279 | * Bugfixes
280 | ```
281 |
282 | #### 1. 新的回调钩子系统
283 |
284 | 钩子系统还是很有趣的, 可以在请求前,请求后,返回response 之前做一些你想做的事情。
285 |
286 | 看实现
287 |
288 | ```
289 | r = Request(**args)
290 |
291 | # Pre-request hook.
292 | r = dispatch_hook('pre_request', hooks, r)
293 |
294 | # Send the HTTP Request.
295 | r.send()
296 |
297 | # Post-request hook.
298 | r = dispatch_hook('post_request', hooks, r)
299 |
300 | # Response manipulation hook.
301 | r.response = dispatch_hook('response', hooks, r.response)
302 |
303 | return r.response
304 | ```
305 |
306 | 如图,在请求的不同阶段会运行 dispatch_hook 这个函数,来看下这个函数
307 |
308 | ```
309 | def dispatch_hook(key, hooks, hook_data):
310 | """Dipatches a hook dictionary on a given peice of data."""
311 |
312 | hooks = hooks or dict()
313 |
314 | if key in hooks:
315 | try:
316 | return hooks.get(key).__call__(hook_data) or hook_data
317 |
318 | except Exception, why:
319 | warnings.warn(str(why))
320 |
321 | return hook_data
322 | ```
323 |
324 | key 用来标记请求发生的时间,hook 是传进来的字典参数, hook_data 为回调函数名
325 |
326 | 如果key 存在于hook,调用hook[key] 函数。
327 |
328 | 官网文档有个例子说明使用方法。
329 |
330 | http://docs.python-requests.org/zh_CN/latest/user/advanced.html#id8
331 |
332 |
333 | #### 2. 新的 session 参数
334 | ```
335 | def test_session_HTTPS_200_OK_GET(self):
336 |
337 | s = Session()
338 | r = s.get(httpsbin('/'))
339 | self.assertEqual(r.status_code, 200)
340 | ```
341 |
342 | 测试正常可用。
343 |
344 | ```
345 | def test_session_persistent_headers(self):
346 |
347 | heads = {'User-agent': 'Mozilla/5.0'}
348 |
349 | s = Session()
350 | s.headers = heads
351 | # Make 2 requests from Session object, should send header both times
352 | r1 = s.get(httpbin('user-agent'))
353 |
354 | assert heads['User-agent'] in r1.content
355 | r2 = s.get(httpbin('user-agent'))
356 |
357 | assert heads['User-agent'] in r2.content
358 | self.assertEqual(r2.status_code, 200)
359 | ```
360 |
361 | 验证重复请求,User-agent 始终存在
362 |
363 | 看了下写法,然后我就跪着敲这些源码阅读了...
364 |
365 | 简单来说就是装饰器加上下文
366 |
367 | 上代码
368 |
369 | ```
370 | class Session(object):
371 | """A Requests session."""
372 |
373 | __attrs__ = ['headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks']
374 |
375 |
376 | def __init__(self, **kwargs):
377 |
378 | # Set up a CookieJar to be used by default
379 | self.cookies = cookielib.FileCookieJar()
380 |
381 | # Map args from kwargs to instance-local variables
382 | map(lambda k, v: (k in self.__attrs__) and setattr(self, k, v),
383 | kwargs.iterkeys(), kwargs.itervalues())
384 |
385 | # Map and wrap requests.api methods
386 | self._map_api_methods()
387 |
388 | ```
389 |
390 | 第一个注释: 设置 cookies
391 |
392 | 第二个注释: 设置 所有 headers, cookies 等内容
393 |
394 | 第三个注释: 从requests.api中加载所有的 method (get,post等内容),并给他们加上了装饰器
395 |
396 | 接着看下第三个注释的调用函数的代码
397 |
398 | ```
399 | def _map_api_methods(self):
400 | """Reads each available method from requests.api and decorates
401 | them with a wrapper, which inserts any instance-local attributes
402 | (from __attrs__) that have been set, combining them with **kwargs.
403 | """
404 |
405 | def pass_args(func):
406 | def wrapper_func(*args, **kwargs):
407 | inst_attrs = dict((k, v) for k, v in self.__dict__.iteritems()
408 | if k in self.__attrs__)
409 | # Combine instance-local values with kwargs values, with
410 | # priority to values in kwargs
411 | kwargs = dict(inst_attrs.items() + kwargs.items())
412 |
413 | # If a session request has a cookie_dict, inject the
414 | # values into the existing CookieJar instead.
415 | if isinstance(kwargs.get('cookies', None), dict):
416 | kwargs['cookies'] = add_dict_to_cookiejar(
417 | inst_attrs['cookies'], kwargs['cookies']
418 | )
419 |
420 | if kwargs.get('headers', None) and inst_attrs.get('headers', None):
421 | kwargs['headers'].update(inst_attrs['headers'])
422 |
423 | return func(*args, **kwargs)
424 | return wrapper_func
425 |
426 | # Map and decorate each function available in requests.api
427 | map(lambda fn: setattr(self, fn, pass_args(getattr(api, fn))),
428 | api.__all__)
429 |
430 | ```
431 |
432 | map给所有函数加装饰器,装饰器函数 wrapper_func。
433 |
434 | 在请求之前,更新存在的配置(所有 headers, cookies 等内容)
435 |
436 | 囫囵吞枣,见笑了。
437 |
438 | #### 3. 透明的 cookie 处理
439 |
440 | 这应该指response 中可以拿取 cookie 。
441 |
442 | 详情在 issue #116
443 |
444 | #### 4. 移除 Response.cached
445 |
446 | 没找到 issue 跟这有有关,看了一下是作者自己删的,算是小改动。
447 |
448 |
449 | #### 5. 添加 Response.request
450 |
451 | 在返回 Response 中加入 request类
452 |
453 | ```
454 | self.response.request = self
455 | ```
456 |
457 | #### 6. 所有的参数变为 kwargs
458 |
459 | 小改动
460 |
461 | 原来
462 |
463 | ```
464 | def get(url,
465 | params=None, headers=None, cookies=None, auth=None, timeout=None,
466 | proxies=None):
467 | ```
468 |
469 | 现在
470 |
471 | ```
472 | def get(url, **kwargs):
473 |
474 | ```
475 |
476 | #### 7. 相对重定向支持
477 |
478 | ```
479 | def test_relative_redirect_history(self):
480 |
481 | for service in SERVICES:
482 |
483 | r = requests.get(service('relative-redirect', '3'))
484 | self.assertEquals(r.status_code, 200)
485 | self.assertEquals(len(r.history), 3)
486 |
487 | ```
488 |
489 | 依然是上次那个随便post的网站提供的功能。
490 |
491 | ```
492 | In [3]: r = requests.get('http://httpbin.org/relative-redirect/3')
493 |
494 | In [4]: r.status_code
495 | Out[4]: 200
496 |
497 | In [5]: r.history
498 | Out[5]: [, , ]
499 |
500 | ```
501 |
502 | 代码方面也是小改动。
503 |
504 | #### HTTPError 错误处理加强
505 |
506 | 在这个版本中加入了状态码和内容,但是并没有用到 :)
507 |
508 | #### https 的测试
509 |
510 | 测试加入 大量针对 https 没什么好说的,跟之前的 http 差别只是协议。
511 |
512 | #### Bugfixes
513 |
514 | 略。
515 |
516 | ### 0x04 后记
517 |
518 | 其实这个源码阅读很多东西希望能抽出来写一点东西。
519 |
520 | 细致分析下内容的设计,定位番外篇好啦。
521 |
522 | 番外篇肯定比这种流水账受欢迎。
523 |
524 | 嗯。
525 |
526 |
527 |
528 | 如果有问题,欢迎各种形式 的issue 和 pr
529 |
530 | [github v0.6.0](https://github.com/wangshunping/read_requests/blob/master/doc/Requests_v0.6.0.md)
531 |
532 |
533 |
--------------------------------------------------------------------------------
/doc/Requests_v0.7.0.md:
--------------------------------------------------------------------------------
1 | ## Requests v0.7.0 源码阅读
2 |
3 | ### 0X00 前言
4 |
5 | 今天娱乐了好久。
6 |
7 | 当知道自己一天能够做多少事情后,娱乐一天的愧疚感就会越剧烈 。
8 |
9 | ### 0X01 目标
10 |
11 | 今天开始看 Requests 的 v0.7.0 版本,这个版本内容太多了,肯定一天搞不完。
12 |
13 | ```
14 | 0.7.0 (2011-10-22)
15 | ++++++++++++++++++
16 |
17 | * Sessions are now the primary interface.
18 | * Deprecated InvalidMethodException.
19 | * PATCH fix.
20 | * New config system (no more global settings).
21 | ```
22 | 十个月底 0.7.0 版本,平均一个多月迭代一个版本。
23 |
24 | 一个团队保持每个月的版本迭代都不容易,一个人就更别说了。
25 |
26 | 值得一提的是,AUTHORS 里面已经有 36个人了,给所有提过patch 和 suggesstion的人。
27 |
28 | 其中有一个中国人(我猜)。
29 |
30 | ```
31 | - 潘旭 (Xu Pan)
32 | ```
33 |
34 | ### 0x02 历史
35 | ```
36 | 0.6.1 (2011-08-20)
37 | ++++++++++++++++++
38 |
39 | * Enhanced status codes experience ``\o/``
40 | * Set a maximum number of redirects (``settings.max_redirects``)
41 | * Full Unicode URL support
42 | * Support for protocol-less redirects.
43 | * Allow for arbitrary request types.
44 | * Bugfixes
45 | ```
46 |
47 | 1. 加强了状态码的体验;
48 | 2. 加了设置最大重定向次数;
49 | 3. 支持 unicode 编码的 URL
50 | 4. 允许任意的请求类型
51 | 5. 修bug
52 |
53 | ```
54 | 0.6.2 (2011-10-09)
55 | ++++++++++++++++++
56 |
57 | * GET/HEAD obeys allow_redirects=False.
58 |
59 | ```
60 |
61 | 1. GET/HEAD 请求也服从 allow_redirects=False
62 |
63 | ```
64 | 0.6.3 (2011-10-13)
65 | ++++++++++++++++++
66 |
67 | * Beautiful ``requests.async`` module, for making async requests w/ gevent.
68 |
69 | ```
70 | 1.优雅的 async 模块,为了用 gevent 进行异步请求。
71 |
72 | ```
73 | 0.6.4 (2011-10-13)
74 | ++++++++++++++++++
75 |
76 | * Automatic decoding of unicode, based on HTTP Headers.
77 | * New ``decode_unicode`` setting.
78 | * Removal of ``r.read/close`` methods.
79 | * New ``r.faw`` interface for advanced response usage.*
80 | * Automatic expansion of parameterized headers.
81 |
82 | ```
83 |
84 | 1. 自动给 unicode 解码,基于HTTP 请求头。
85 | 2. 新的 decode_unicode 设置。
86 | 3. 移除 response.read 和 response.clode 方法。
87 | 4. 新的 response.raw 接口,用来高级的response使用。
88 | 5. 自动扩展请求头参数。
89 |
90 | ```
91 | 0.6.5 (2011-10-18)
92 | ++++++++++++++++++
93 |
94 | * Offline (fast) test suite.
95 | * Session dictionary argument merging.
96 | ```
97 |
98 | 1. 离线测试集合。
99 | 2. session 字典参数合并。
100 |
101 | ```
102 | 0.6.6 (2011-10-19)
103 | ++++++++++++++++++
104 |
105 | * Session parameter bugfix (params merging).
106 | ```
107 |
108 | 1. Session 参数bug修复(特指参数merge 功能)。
109 |
110 | ```
111 | 0.7.0 (2011-10-22)
112 | ++++++++++++++++++
113 |
114 | * Sessions are now the primary interface.
115 | * Deprecated InvalidMethodException.
116 | * PATCH fix.
117 | * New config system (no more global settings).
118 |
119 | ```
120 |
121 | 1. Session 目前为主要的接口。
122 | 2. 废弃 InvalidMethodException 异常
123 | 3. Patch 修复。
124 | 4. 新的配置系统(不再是全局配置)
125 |
126 | ### 0X03 源码阅读
127 |
128 | ```
129 | 0.6.1 (2011-08-20)
130 | ++++++++++++++++++
131 |
132 | * Enhanced status codes experience ``\o/``
133 | * Set a maximum number of redirects (``settings.max_redirects``)
134 | * Full Unicode URL support
135 | * Support for protocol-less redirects.
136 | * Allow for arbitrary request types.
137 | * Bugfixes
138 | ```
139 |
140 |
141 |
142 | #### 1. 状态码
143 |
144 | 取消代码中的硬编码。ex
145 |
146 | 之前
147 |
148 | ```
149 | REDIRECT_STATI = (301, 302, 303, 307)
150 | ```
151 |
152 | 现在
153 |
154 | ```
155 | REDIRECT_STATI = (codes.moved, codes.found, codes.other, codes.temporary_moved)
156 | ```
157 |
158 | #### 2. 设置最大重定向次数
159 |
160 | 也是取消之前代码的硬编码。ex
161 |
162 | 之前
163 |
164 | ```
165 | if not len(history) < 30:
166 | ```
167 | 现在
168 |
169 | ```
170 | if not len(history) < settings.max_redirects:
171 | ```
172 |
173 | max_redirects 和之前的 timeout 一样,可以由用户自己设置。
174 |
175 | #### 3. 支持 unicode 编码的 URL
176 |
177 | 我记得在之前的版本已经支持了,并没有看到对这个方面的更改。
178 |
179 |
180 | #### 4. 允许任意的请求类型
181 |
182 | 这个 history 应该叫做,不允许更改请求方式
183 |
184 | 这个commit 去掉了以下代码
185 |
186 | ```
187 | _METHODS = ('GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH')
188 |
189 | def __setattr__(self, name, value):
190 | if (name == 'method') and (value):
191 | if not value in self._METHODS:
192 | raise InvalidMethod()
193 |
194 | object.__setattr__(self, name, value)
195 |
196 | ```
197 |
198 | method 在初始化 Request 的时候就确定了,并且不允许之后的更改。
199 |
200 | #### 5. bugfix
201 | 略
202 |
203 | ### v0.6.2
204 |
205 | ```
206 | 0.6.2 (2011-10-09)
207 | ++++++++++++++++++
208 |
209 | * GET/HEAD obeys allow_redirects=False.
210 |
211 | ```
212 |
213 | #### 1. GET/HEAD 请求也可以 allow_redirects=False
214 |
215 | 问题来源于 pr #142
216 |
217 | kennethreitz 解释说,在RFC2616中,allow_redirects 是只针对除了 GET/HEAD的请求的。
218 |
219 | > The allow_redirects parameter is only for PUSH/POST/&c requests, to allow the behavior described in RFC 2616:
220 |
221 | >If the 301 status code is received in response to a request other than GET or HEAD, the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, since this might change the conditions under which the request was issued.
222 |
223 | 所以在之前的代码中,关于是否进行递归重定向,有以下的判断逻辑
224 |
225 | ```
226 | while (
227 | ('location' in r.headers) and
228 | ((self.method in ('GET', 'HEAD')) or
229 | (r.status_code is codes.see_other) or
230 | (self.allow_redirects))
231 | ):
232 | ```
233 |
234 | 即HEAD 和 GET的请求,是默认重定向的。
235 |
236 | 有个人跳出来给了个 pr ,说我要调试,我就不想他重定向了。
237 |
238 | 又有一个人说,
239 |
240 |
241 | >Good though, kennethreitz! Please, apply this pull request, it will make world better ;-)
242 |
243 | 作者就合并了,逻辑改成了
244 |
245 | ```
246 | while (
247 | ('location' in r.headers) and
248 | ((r.status_code is codes.see_other) or (self.allow_redirects))
249 | ):
250 | ```
251 | 有时候看这些 pr 真是热血。
252 |
253 | It will make world better.
254 |
255 | ### v0.6.3
256 |
257 | ```
258 | 0.6.3 (2011-10-13)
259 | ++++++++++++++++++
260 |
261 | * Beautiful ``requests.async`` module, for making async requests w/ gevent.
262 |
263 | ```
264 | #### 1.优雅的 async 模块,为了用 gevent 进行异步请求。
265 |
266 | 没有找到相关测试,但是在 issue 里面找到了作者的说明。
267 |
268 | 先上作者测试的效果
269 |
270 | ```
271 | # -*- coding: utf-8 -*-
272 |
273 | import requests
274 | from requests import async
275 |
276 | urls = [
277 | 'http://www.readability.com',
278 | 'http://tablib.org',
279 | 'http://httpbin.org',
280 | 'http://python-requests.org',
281 | 'http://kennethreitz.com'
282 | ]
283 |
284 | urls = urls + urls
285 |
286 | ## Standard (14.125s)
287 | for url in urls:
288 | r = requests.get(url)
289 | r.content
290 |
291 | ## Standard + Keep-Alive (6.390s)
292 | with requests.session() as s:
293 | for url in urls:
294 | r = requests.get(url)
295 | r.content
296 |
297 | ##Standard + Gevent (1.589s)
298 | >>> rs = [async.get(u) for u in urls]
299 | >>> async.map(rs)
300 | [, , ...]
301 |
302 | ##Gevent + Keep-Alive (1.132s)
303 | >>> rs = [async.get(u) for u in urls]
304 | >>> async.map(rs, keep_alive=True)
305 | [, , ...]
306 | ```
307 |
308 | 直接一个O(1) 一个O(n)。
309 |
310 | Pure awesomesauce !!!!
311 |
312 | 关于 gevent 协程的使用,我丢到番外篇去吧(其实是我没太看懂,怕说错了,玷污这个代码)
313 |
314 | ### v0.6.4
315 |
316 | ```
317 | 0.6.4 (2011-10-13)
318 | ++++++++++++++++++
319 |
320 | * Automatic decoding of unicode, based on HTTP Headers.
321 | * New ``decode_unicode`` setting.
322 | * Removal of ``r.read/close`` methods.
323 | * New ``r.faw`` interface for advanced response usage.*
324 | * Automatic expansion of parameterized headers.
325 |
326 | ```
327 |
328 | #### 1. 自动给 unicode 解码,基于HTTP 请求头。
329 |
330 | 跟第四点有关,返回的内容自动 decode_unicode。
331 |
332 | #### 2. 新的 decode_unicode 设置。
333 |
334 | 请求回来的 body可以自动 decode, 在 settings类中可以设置,默认为True,跟第四点有关。
335 |
336 | #### 3. 移除 response.read 和 response.close 方法。
337 |
338 | 这个 pass了。跟下面那个有关
339 |
340 | #### 4. 新的 response.raw 接口,用来高级的response使用。
341 |
342 | Response.raw 是
343 |
344 | ```
345 | #: File-like object representation of response (for advanced usage).
346 | ```
347 |
348 | 来自于 pr #150
349 |
350 | 实现了,不用将所有的Response 家在到内存中(因为可能很大)。
351 |
352 | #### 5. 自动扩展请求头参数。
353 |
354 | 加入函数 heander_expand(headers):
355 |
356 | 函数 docstring 如下
357 |
358 | ```
359 | """Returns an HTTP Header value string from a dictionary.
360 |
361 | Example expansion::
362 |
363 | {'text/x-dvi': {'q': '.8', 'mxb': '100000', 'mxt': '5.0'}, 'text/x-c': {}}
364 | # Accept: text/x-dvi; q=.8; mxb=100000; mxt=5.0, text/x-c
365 |
366 | (('text/x-dvi', {'q': '.8', 'mxb': '100000', 'mxt': '5.0'}), ('text/x-c', {}))
367 | # Accept: text/x-dvi; q=.8; mxb=100000; mxt=5.0, text/x-c
368 | """
369 | ```
370 |
371 | 一个将字典形式的数据自动转换成请求头的函数。
372 |
373 | ### v0.6.5
374 |
375 | ```
376 | 0.6.5 (2011-10-18)
377 | ++++++++++++++++++
378 |
379 | * Offline (fast) test suite.
380 | * Session dictionary argument merging.
381 | ```
382 |
383 |
384 | #### 1. 离线测试集合。
385 |
386 | 新增加了不需要网络就可以进行get 等测试的函数。
387 |
388 | 实现是用到了作者造的另外一个轮子 envoy 。
389 |
390 |
391 |
392 | #### 2. session 字典参数合并。
393 |
394 | 添加 merge_kwargs 函数,当配置中存在该配置时,会更新成新的配置。
395 |
396 | 这个版本中这个功能有问题,带个小版本有修复,直接丢到下个版本讲好咯。
397 |
398 | ### v0.6.6
399 |
400 | ```
401 | 0.6.6 (2011-10-19)
402 | ++++++++++++++++++
403 |
404 | * Session parameter bugfix (params merging).
405 | ```
406 |
407 | #### 1. Session 参数bug修复(特指参数merge 功能)。
408 |
409 | 上测试
410 |
411 | ```
412 | def test_session_persistent_params(self):
413 |
414 | params = {'a': 'a_test'}
415 |
416 | s = Session()
417 | s.params = params
418 |
419 | # Make 2 requests from Session object, should send header both times
420 | r1 = s.get(httpbin('get'))
421 | assert params['a'] in r1.content
422 |
423 |
424 | params2 = {'b': 'b_test'}
425 |
426 | r2 = s.get(httpbin('get'), params=params2)
427 | assert params['a'] in r2.content
428 | assert params2['b'] in r2.content
429 |
430 |
431 | params3 = {'b': 'b_test', 'a': None, 'c': 'c_test'}
432 |
433 | r3 = s.get(httpbin('get'), params=params3)
434 |
435 | assert not params['a'] in r3.content
436 | assert params3['b'] in r3.content
437 | assert params3['c'] in r3.content
438 | ```
439 | 如上面测试所示。
440 |
441 | 1. 可以给某个测试设置参数,添加到Session中;
442 | 2. 可以更新其参数;
443 | 3. 更新的时候设置为 None,则该设置被删除。
444 |
445 | ### v0.7.0
446 |
447 | 函数的时候逻辑也很简单,更新的跟之前的进行对比 :)
448 |
449 |
450 | ```
451 | 0.7.0 (2011-10-22)
452 | ++++++++++++++++++
453 |
454 | * Sessions are now the primary interface.
455 | * Deprecated InvalidMethodException.
456 | * PATCH fix.
457 | * New config system (no more global settings).
458 |
459 | ```
460 |
461 | #### 1. Session 目前为主要的接口
462 |
463 | 现在所有 method 都去调用 session 提供的接口,直接是两套逻辑(两套很类似的逻辑,除了需要比较params)。
464 |
465 | 现在的 requests.get(xxxx) 代码为:
466 |
467 | ```
468 | def get(url, **kwargs):
469 | kwargs.setdefault('allow_redirects', True)
470 | return request('GET', url, **kwargs)
471 |
472 | def request(method, url,
473 | params=None,
474 | data=None,
475 | headers=None,
476 | cookies=None,
477 | files=None,
478 | auth=None,
479 | timeout=None,
480 | allow_redirects=False,
481 | proxies=None,
482 | hooks=None,
483 | return_response=True,
484 | config=None):
485 |
486 | s = session()
487 | return s.request(
488 | method, url, params, data, headers, cookies, files, auth,
489 | timeout, allow_redirects, proxies, hooks, return_response,
490 | config
491 | )
492 | ```
493 |
494 | 即直接调用 session() 的逻辑进行处理。
495 |
496 | 之前为:
497 |
498 | ```
499 | def request(method, url,
500 | params=None, data=None, headers=None, cookies=None, files=None, auth=None,
501 | timeout=None, allow_redirects=False, proxies=None, hooks=None, return_response=True):
502 |
503 | ....
504 |
505 | # Arguments manipulation hook.
506 | args = dispatch_hook('args', hooks, args)
507 |
508 | r = Request(**args)
509 |
510 | # Pre-request hook.
511 | r = dispatch_hook('pre_request', hooks, r)
512 |
513 | # Don't send if asked nicely.
514 | if not return_response:
515 | return r
516 |
517 | # Send the HTTP Request.
518 | r.send()
519 |
520 | # Post-request hook.
521 | r = dispatch_hook('post_request', hooks, r)
522 |
523 | # Response manipulation hook.
524 | r.response = dispatch_hook('response', hooks, r.response)
525 |
526 | return r.response
527 | ```
528 |
529 | 各种参数的配置,各种钩子。Session 中也有类似的这样的一整套。
530 |
531 | #### 2. 废弃 InvalidMethodException 异常
532 |
533 | 删除这种异常的代码。
534 |
535 | #### 3. Patch 修复
536 |
537 | 因为回滚之类的操作出现的一个bug, issue #160
538 |
539 | #### 4. 新的配置系统(不再是全局配置)
540 |
541 | 之前我巴拉巴拉说半天的上下文形式的配置,这个版本直接干掉了,直接改成了一个默认配置文件。
542 |
543 | 心好累。
544 |
545 | 估计大部分都不太喜欢这样的用法?
546 |
547 | 改成了这种形式的默认配置文件。
548 |
549 | ```
550 |
551 | """
552 | requests.defaults
553 | ~~~~~~~~~~~~~~~~~
554 |
555 | This module provides the Requests configuration defaults.
556 | """
557 |
558 | from . import __version__
559 |
560 | defaults = dict()
561 |
562 |
563 | defaults['base_headers'] = {
564 | 'User-Agent': 'python-requests/%s' % __version__,
565 | 'Accept-Encoding': ', '.join([ 'identity', 'deflate', 'compress', 'gzip' ]),
566 | }
567 |
568 | ....
569 |
570 | ```
571 |
572 | ### 0X04 后记
573 |
574 | 感谢所有真正意义上的 coder。
575 |
576 | 在互联网行业工作一年有余,认识一些真正意义上的coder,他们单纯不做作,目的只是 make the world better。
577 |
578 | 行就是行,不行就是不行,怎样就是怎样,让我倍感美好。
579 |
580 | 最近不是流行一句话叫,少一些套路,多一点真诚。
581 |
582 |
583 |
584 |
--------------------------------------------------------------------------------
/doc/Requests_v0.8.0.md:
--------------------------------------------------------------------------------
1 | ## Requests v0.8.0 源码阅读
2 |
3 | ### 0X00 前言
4 |
5 | ### 0X01 目标
6 |
7 | ### 0x02 历史
8 |
9 | ### 0X03 源码阅读
10 |
11 | ### 0X04 后记
--------------------------------------------------------------------------------
/doc/img/author.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshunping/read_requests/10806252b1037de6d727387d60f29a1b9fb67746/doc/img/author.png
--------------------------------------------------------------------------------
/doc/img/five2four.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wangshunping/read_requests/10806252b1037de6d727387d60f29a1b9fb67746/doc/img/five2four.png
--------------------------------------------------------------------------------