python requests库流程简析

今天重新梳理网络编程的时候,想到对于部分应用,他们的数据流是按照http协议,中间经过其他协议层,最后通过底层的物理层到达服务器的,这使我想到了Requests库。Requests库就像我们常用的浏览器,搞清楚了Requests的实现,就基本搞清楚了网络编程客户端方向的数据处理流程,以及经常提到的网络协议在编程过程中的具体体现和代表。

现在就以requests库为例,TCP/IP协议和socket,梳理一下数据流从应用层经过哪些函数,最后到达链路层,并最后通过链路层到达服务器的。
代码如下:

import requests
res = request.get("www.baidu.com" )

上面简单的两句代码就完成了我们通过浏览器访问www.baidu.com的功能。既然requests库的重要功能之一就是作为HTTP客户端,那么它传输的数据应该是一个按照HTTP协议组织的数据,这个数据如何生成,其实就是一个Request数据结构。
request.get其实就是调用api模块中的get方法,get方法是调用api模块中的request方法,而request方法调用的是session模块中Session对象中的request方法,而request方法中会根据request.get方法传进来的参数生成一个Request对象,然后将Request对象作为参数并调用Session对象中的prepare_request函数进一步处理生成请求需要的request对象,这时候的request对象可以理解为一个基础的数据存储,后面会根据具体的请求协议再次封装。

对于上面的例子,下一步就是选用合适的适配器,依据request封装的请求数据并按照对应的访问协议,将request对象数据再次封装成相应协议的数据格式,本例中就是http协议。在requests库中的体现如下。

在Session完成基本request封装之后,就开始调用自身的send函数开始请求,请求的真正操作放是依据请求协议调用对应的适配器,请求协议体现在哪儿呢?其实就是我们在使用request.get("www.baidu.com" )时指定了,因此,这里就是http协议,因此Session中获取的适配器就是HTTPAdapter类,所以Session对象中的send函数中调用的adapter.send()其实就是HTTPAdapter的send函数,HTTPAdapter.send函数中就已经开始真正向服务器发起连接,然后提交请求数据了。

HTTPAdapter.send函数提交请求,提交之前必须连接服务器,因此调用了urllib3的PoolManager,之所以采用PoolManager,其实跟常用的线程池的思想一样,减少socket的创建连接,提高socket的复用。urllib3库底层其实是调用了socket库进行连接,我们知道socket只是tcp/ip协议的实现,所以,到这里,数据已经从应用层开始流到了网络层。

前面说道HTTPAdapter.send会先去调用HTTPAdapter.get_connection从连接池获取连接,这里的连接池就是urllib3的PoolManager。因此HTTPAdapter.get_connection通过调用PoolManager.connection_from_url函数从requests连接池中获取对应协议的连接池对象。这里的连接池对象其实就是PoolManager调用了urllib3中的connectionpool模块中的HTTPConnectionPool创建的对象,所以最后后面的conn.urlopen其实就是HTTPConnectionPool类中的urlopen。这个连接池中的连接其实是根据ConnectionCls(也就是连接类型)去建立连接的,这里是HTTPConnection对象,请求完成后将HTTPConnection对象放入pool中以便下次复用。

查看requests中的urllib3的connection模块,发现最后中连接就是调用socket库建立socket连接。对于网络请求,我们通常提交的都是域名,而socket需要的是IP,而且我们也知道,浏览器在请求服务器的时候也要经过dns查询,获取到服务器的IP,那么对于requests库,哪一步的哪个函数做了这部分的事情呢?答案是socket库中的getaddrinfo函数。
看看getaddrinfo函数说明

socket.getaddrinfo(host,  port, family=0, socktype=0, proto=0, flags=0)    
#根据给定的参数host/port,相应的转换成一个包含用于创建socket对象的五元组,    
#参数host为域名,以字符串形式给出代表一个IPV4/IPV6地址或者None.    
#参数port如果字符串形式就代表一个服务名,比如“http”"ftp""email"等,或者为数字,或者为None    
#参数family为地主族,可以为AF_INET  ,AF_INET6 ,AF_UNIX.    
#参数socketype可以为SOCK_STREAM(TCP)或者SOCK_DGRAM(UDP)    
#参数proto通常为0可以直接忽略    
#参数flags为AI_*的组合,比如AI_NUMERICHOST,它会影响函数的返回值    
#附注:给参数host,port传递None时建立在C基础,通过传递NULL。    
#该函数返回一个五元组(family, socktype, proto, canonname, sockaddr),同时第五个参数sockaddr也是一个二元组(address, port)  

到这里,也就是说HTTPAdapter.get_connection从requests的连接池中取出对应的连接池对象,这里是HTTPConnectionPool对象,然后这个HTTPConnectionPool连接池对象里面有一个pool对象,其实是一个deque,保存着前面请求时使用的socket连接对象。

在requests的代码调用中,HTTPAdapter.send函数里面调用的conn.urlopen()访问服务器其实就是HTTPConnectionPool类的urlopen,因此,接下来关注HTTPConnectionPool类urlopen即可。(简单的总结下来,adapter.send函数先根据协议从连接池中获取对应的连接池对象,再通过连接池对象的urlopen去访问服务器,而连接池对象中有对应的socket连接池。)

而通过分析requests库中urllib3的connectionpool模块中的HTTPConnectionPool类的urlopen对象发现,这个urlopen函数调用了HTTPConnectionPool类的_make_request函数,这个函数会调用HTTPConnectionPool对象连接池中的HTTPConnection对象的request函数,而HTTPConnection对象其实就是标准的httplib库中的HTTPConnection类,这个从requests的urllib3/connection模块的开头的import语句也能得到验证(from httplib import HTTPConnection as _HTTPConnection)

总结:
requests.get的内部调用流程:
requests.get -> request() -> Session.request -> Session.send ->adapter.send -> HTTPConnectionPool -> HTTPConnection
其实就是requests库是封装了urllib3库,urllib3自己封装了连接池,并调用httplib库(这个库的关键就是HTTPConnection,这个类包含了底层的socket连接和request请求方法),httplib库封装了底层的socket库。 dns的解析其实就是socket库中的getaddrinfo函数。

socket:最基础的socket编程库,是TCP/IP协议最直接的实现,实现端对端的网络数据传输
httplib:基于socket库,是最基础最底层的http库,主要是将数据按照http协议组织,然后创建socket连接,将封装的数据发往服务端。
urllib:基于httplib库,主要对url的解析和编码做进一步的处理,比如代理的处理,url编码函数urlencode,同时增加retrival函数实现下载,并缓存,如果在同一个进程中多次请求相同的url,会直接返回缓存,缓存只来源于retrieval函数,urlopen不缓存。
urllib3:基于httplib库,他比urllib更高级的地方在于用PoolManager实现了socket连接复用和线程安全,提高了了效率
requests:基于urllib3,但是比urllib3更高级的是实现了session对象,用session对象保存一些数据状态,进一步提高效率

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容