webpy 源码学习(一)
在具体看WSGIServer代码之前,我们先看一幅图,这幅图概述了WSGIServer内部执行流程:
接下来我们看下代码,ps: 为了较清晰的梳理主干流程,我只列出核心代码段
# Webpy内置的WSGIServer
class CherryPyWSGIServer(HTTPServer):
def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
# 线程池(用来处理外部请求,稍后详述)
self.requests = ThreadPool(self, min=numthreads or 1, max=max)
# 响应外部请求的webapp
self.wsgi_app = wsgi_app
# wsgi网关(http_request ->wsgi_gateway ->webpy/webapp)
self.gateway = WSGIGateway_10
# wsgi_server监听地址
self.bind_addr = bind_addr
# ...
class HTTPServer(object):
# 启动一个网络服务器
# 如果你阅读过<<Unix网络编程>>,那么对于后面这些代码将会再熟悉不过,唯一的区别一个是c,
#一个是python
def start(self):
# 如果bind_addr是一个字符串(文件名),那么采用unix domain协议
if isinstance(self.bind_addr, basestring):
try: os.unlink(self.bind_addr)
except: pass
info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
else:
# 否则采用TCP/IP协议
host, port = self.bind_addr
try:
info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
except socket.gaierror:
# ...
# 循环测试 getaddrinfo函数返回值,直到有一个bind成功或是遍历完所有结果集
for res in info:
af, socktype, proto, canonname, sa = res
try:
self.bind(af, socktype, proto)
except socket.error:
if self.socket:
self.socket.close()
self.socket = None
continue
break
if not self.socket:
raise socket.error(msg)
# 此时socket 进入listening状态(可以用netstat命令查看)
self.socket.listen(self.request_queue_size)
# 启动线程池(这个线程池做些什么呢? 稍后会说)
self.requests.start()
self.ready = True
while self.ready:
# HTTPSever核心函数,用来接受外部请求(crequest)
# 然后封装成一个HTTPConnection对象放入线程池中的消息队列里,
# 接着线程会从消息队列中取出该对象并处理
self.tick()
def bind(self, family, type, proto=0):
# 创建socket
self.socket = socket.socket(family, type, proto)
# 设置socket选项(允许在TIME_WAIT状态下,bind相同的地址)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# socket bind
self.socket.bind(self.bind_addr)
# HTTPSever核心函数
def tick(self):
try:
# 接受一个TCP连接
s, addr = self.socket.accept()
# 把外部连接封装成一个HTTPConnection对象
makefile = CP_fileobject
conn = self.ConnectionClass(self, s, makefile)
# 然后把该对象放入线程池中的消息队列里
self.requests.put(conn)
except :
# ...
class ThreadPool(object):
def __init__(self, server, min=10, max=-1):
# server实例
self.server = server
# 线程池中线程数配置(最小值,最大值)
self.min = min
self.max = max
# 线程池中的线程实例集合(list)
self._threads = []
# 消息队列(Queue是一个线程安全队列)
self._queue = Queue.Queue()
# 编程技巧,用来简化代码,等价于:
# def get(self)
# return self._queue.get()
self.get = self._queue.get
# 启动线程池
def start(self):
# 创建min个WorkThread并启动
for i in range(self.min):
self._threads.append(WorkerThread(self.server))
for worker in self._threads:
worker.start()
# 把obj(通常是一个HTTPConnection对象)放入消息队列
def put(self, obj):
self._queue.put(obj)
# 在不超过允许创建线程的最大数下,增加amount个线程
def grow(self, amount):
for i in range(amount):
if self.max > 0 and len(self._threads) >= self.max:
break
worker = WorkerThread(self.server)
self._threads.append(worker)
worker.start()
# kill掉amount个线程
def shrink(self, amount):
# 1.kill掉空闲线程
for t in self._threads:
if not t.isAlive():
self._threads.remove(t)
amount -= 1
# 2.如果已经kill掉线程数小于amount,则在消息队列中放入线程退出标记对象_SHUTDOWNREQUEST
# 当线程从消息队列中取到的不是一个HTTPConnection对象,而是一个_SHUTDOWNREQUEST,则退出运行
if amount > 0:
for i in range(min(amount, len(self._threads) - self.min)):
self._queue.put(_SHUTDOWNREQUEST)
# 工作线程WorkThread
class WorkerThread(threading.Thread):
def __init__(self, server):
self.ready = False
self.server = server
# ...
threading.Thread.__init__(self)
def run(self):
# 线程被调度运行,ready状态位设置为True
self.ready = True
while True:
# 尝试从消息队列中获取一个obj
conn = self.server.requests.get()
# 如果这个obj是一个“退出标记”对象,线程则退出运行
if conn is _SHUTDOWNREQUEST:
return
# 否则该obj是一个HTTPConnection对象,那么线程则处理该请求
self.conn = conn
try:
# 处理HTTPConnection
conn.communicate()
finally:
conn.close()
def parse_request(self):
self.rfile = SizeCheckWrapper(self.conn.rfile,
self.server.max_request_header_size)
# 读取请求行
self.read_request_line()
# 读取请求头
success = self.read_request_headers()
# ----------------------------
def read_request_line(self):
# 从socket中读取一行数据
request_line = self.rfile.readline()
# 按照HTTP协议规范,把request_line分割成请求方法(method),uri路径(uri),HTTP协议版本(req_protocol)
method, uri, req_protocol = request_line.strip().split(" ", 2)
self.uri = uri
self.method = method
scheme, authority, path = self.parse_request_uri(uri)
# 获取uri请求参数
qs = ''
if '?' in path:
path, qs = path.split('?', 1)
self.path = path
# ----------------------------
def read_request_headers(self):
# 读取请求头,inheaders是一个dict
read_headers(self.rfile, self.inheaders)
# ----------------------------
def read_headers(rfile, hdict=None):
if hdict is None:
hdict = {}
while True:
line = rfile.readline()
# 把line按照":"分割成k, v,譬如 Host:baidu.com就被分割成Host和baidu.com两部分
k, v = line.split(":", 1)
# 格式化分割后的
k = k.strip().title()
v = v.strip()
hname = k
# HTTP协议中的有些请求头允许重复(譬如Accept等等),那么webpy就会把这些相同头的value用","连接起来
if k in comma_separated_headers:
existing = hdict.get(hname)
if existing:
v = ", ".join((existing, v))
# 把请求头k, v存入hdict
hdict[hname] = v
return hdict