浅谈urllib2中内部调用流程

来源:互联网 发布:英语语言变迁 知乎 编辑:程序博客网 时间:2024/04/20 18:54


一. 背景

最近项目中有个需求:策划表数据修改完成后,向服务器发送一个https请求,通知服务器从svn服务器拉去代码,并且重启服务器。对于服务端来说,需要提供一个url,当有主机发出https请求,通过认证后,完成后续的工作。对于客户端来说,也非常简单:在使用倒表工具完成倒表后,将数据commit到svn服务器,然后发送一个url请求即可。


二. urllib2库使用

urllib2是python的一个获取URL的组件。它以urllib2.urlopen接口的形式提供了一个非常简单的接口。具体的使用方法可以参考 extensible library for opening URLs 的接口说明。一般的使用:

<代码段1>
import urllib2response = urllib2.urlopen('http://python.org/')html = response.read()

从表面看,通过urlopen接口输入一个url字符串即可获得response,其实接口urlopen接口的参数非常丰富。来看一下签名:

<代码段2>
def urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,            cafile=None, capath=None, cadefault=False, context=None):

其中:
data 发给服务器附带的数据,可以通过一定的格式编码;
cafile, capath 是特别为https请求提供的证书;
context 是用来描述SSL选项的 ssl.SSLContext 实例;

返回是一个类似 file 类型的实例,并且附带了三个接口:geturl, info, getcode,具体参考上述链接。

三. 内部调用流程


urlopen是urllib2库中的全局函数,进入函数体后,它会根据其传入的7个参数(少于7个以签名默认值填充)的值,或者采用全局默认值_opener(注意这里有下划线),或者新建一个OpenerDirector实例,来作为该接口内的局部变量 opener(无下划线)<urllib2.OpenerDirector>

<代码段3>
    global _opener    if cafile or capath or cadefault:        ...        opener = build_opener(https_handler)    elif context:        https_handler = HTTPSHandler(context=context)        opener = build_opener(https_handler)    elif _opener is None:        _opener = opener = build_opener()    else:        opener = _opener

并且返回OpenerDirector类中的open方法的返回值:

<代码段4>
    return opener.open(url, data, timeout)

从源码中可以看出opener变量可以由 build_opener 接口生成:

<代码段5>
def build_opener(*handlers):    ...    opener = OpenerDirector()    ...    return opener

细心的读者可能看到了opener也可以由全局变量_opener赋值。如果调用者没有特别的需求,而这个global _opener在urllib2中也是一个默认的OpenDirector全局实例,如果需要新建一个自定义的默认全局_opener,可以通过install_opener(opener)来设置:

<代码段6>:
def install_opener(opener):       global _opener      _opener = opener

OpenerDirector类管理了一些处理器handlers,所有的任务都有handlers处理。每个handlers知道如何通过特定协议打开URLs,或者如何处理URL打开方式。build_opener接口参数handlers ,是要加入到新建的opener的(他是OpenerDirector类实例,调用该类的add_handler(h) ),不过在加入之前要经过筛选:如果handlers里面是default_class的子类或是其中的类,那么这些handlers会被排除(skip),将筛选后的handlers和default_class加入到opener中:

<代码段7>
opener = OpenerDirector()...# 如果handlers里面是default_class的子类或是其中的类,那么这些handlers会被排除(skip)for klass in default_classes:    for check in handlers:        if isclass(check):            if issubclass(check, klass):                skip.add(klass)        elif isinstance(check, klass):            skip.add(klass)for klass in skip:    default_classes.remove(klass)#将筛选后的handlers和default_class加入到opener中:for klass in default_classes:    opener.add_handler(klass())for h in handlers:    if isclass(h):        h = h()    opener.add_handler(h)return opener

讲好了opener是怎么来,再简说一下代码段4中opener.open是怎么回事,他的输入是url, data, timeout,前面介绍过了,返回类型与urlopen接口返回相同:

<代码段8>
def open(self, fullurl, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
接口内首先根据fullurl的类型,生成一个Request,并将data加入到request当中:

<代码段9>
if isinstance(fullurl, basestring):    req = Request(fullurl, data)else:    req = fullurlif data is not None:        req.add_data(data)

所以无论传入的fullurl是str类型,还是Request类型,open接口都能讲fullurl转成Request类型的一个实例。经过request的预处理后,调用了OpenerDirector类的 _open接口,在_open接口内部,就是handler处理的机制了。根据 使用默认的形式获取url,如果默认获取不到,再使用req的获取url的形式,并且调用_call_chain的接口:

<代码段10>
def _call_chain(self, chain, kind, meth_name, *args):
在这个接口中,会从chain取出handlers,对handlers中的每一个handler获取,名为meth_name的函数对象func,并且将*args作为func的参数调用,将不为None的结果返回,即为response:

<代码段11>
handlers = chain.get(kind, ()) for handler in handlers:      func = getattr(handler, meth_name)       result = func(*args)       if result is not None:             return result

四. 结语


文章简要的描述了一下urllib2库中,由urllib2.open(url)发起后的内部调用流程,这里并没有讲述网络中的一些专业知识,例如Request的类型(type),handlers的default_class的种类,以及相关的作用。更多的使用方式和细节请移步urllib2总结。







1 0
原创粉丝点击