一. 背景
最近项目中有个需求:策划表数据修改完成后,向服务器发送一个https请求,通知服务器从svn服务器拉去代码,并且重启服务器。对于服务端来说,需要提供一个url,当有主机发出https请求,通过认证后,完成后续的工作。对于客户端来说,也非常简单:在使用倒表工具完成倒表后,将数据commit到svn服务器,然后发送一个url请求即可。
二. urllib2库使用
urllib2是python的一个获取URL的组件。它以 urllib2.urlopen接口的形式提供了一个非常简单的接口。具体的使用方法可以参考 extensible library for opening URLs 的接口说明。一般的使用:
<代码段1>
import urllib2
response = 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)
<代码段5>
def build_opener(*handlers):
...
opener = OpenerDirector()
...
return 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 = fullurl
if 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总结。