首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > perl python >

webpy 源码学习(1)

2013-08-13 
webpy 源码学习(一)在具体看WSGIServer代码之前,我们先看一幅图,这幅图概述了WSGIServer内部执行流程:接下

webpy 源码学习(一)

在具体看WSGIServer代码之前,我们先看一幅图,这幅图概述了WSGIServer内部执行流程:

webpy 源码学习(1)

接下来我们看下代码,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 :
            # ...


之前我们说过HTTPServer中的request属性是一个线程池(这个线程池内部关联着一个消息队列),现在我们看看作者是如何实现一个线程池的:
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()


刚才我们看到,WorkThread从消息队列中获取一个HTTPConnection对象,然后调用它的communicate方法,那这个communicate方法究竟做了些什么呢?



而HTTPRequest.parse_request方法就是把socket中的字节流,按照HTTP协议规范解析,并且从中提取信息(最终封装成一个env传递给webapp):
  
  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


至此我们就分析完了HTTPRequest.parse_request方法如何解析HTTP请求,下面我们就接着看看HTTPRequest.respond如何响应请求:
    

[解决办法]
建议楼主开个博客去

热点排行