• 【Python】Webpy 源码学习


    那么webpy是什么呢? 阅读它的源码我们又能学到什么呢? 

    简单说webpy就是一个开源的web应用框架(官方首页:http://webpy.org/) 

    它的源代码非常整洁精干,学习它一方面可以让我们快速了解python语法(遇到看不懂的语法就去google),另一方面可以学习到python高级特性的使用(譬如反射,装饰器),而且在webpy中还内置了一个简单HTTP服务器(文档建议该服务器仅用于开发环境,生产环境应使用apache之类的),对于想简单了解下HTTP服务器实现的朋友来说,这个是再好不过的例子了(并且在这个服务器代码中,还可以学习到线程池,消息队列等技术),除此之外webpy还包括模板渲染引擎,DB框架等等,这里面的每一个部分都可以单独拿出来学习. 

    在JavaWeb开发中有Servlet规范,那么Python Web开发中有规范吗? 
    答案就是:WSGI,它定义了服务器如何与你的webapp交互 

    关于WSGI规范,可以参看下面这个链接: 
    http://ivory.idyll.org/articles/wsgi-intro/what-is-wsgi.html 

    现在我们利用webpy内置的WSGIServer,按照WSGI规范,写一个简单的webapp,eg: 

    Python代码  收藏代码
    1. #/usr/bin/python  
    2. import web.wsgiserver  
    3.   
    4. def my_wsgi_app(env, start_response):  
    5.     status = '200 OK'                                                                                                                           
    6.     response_headers = [('Content-type','text/plain')]  
    7.     start_response(status, response_headers)  
    8.     return ['Hello world!']  
    9.   
    10. server = web.wsgiserver.CherryPyWSGIServer(("127.0.0.1", 8080), my_wsgi_app);  
    11. server.start()  


    执行代码: 

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



    接下来我们看下代码,ps: 为了较清晰的梳理主干流程,我只列出核心代码段 

    Python代码  收藏代码
    1. # Webpy内置的WSGIServer  
    2. class CherryPyWSGIServer(HTTPServer):  
    3.   
    4.     def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,  
    5.                  max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):  
    6.         # 线程池(用来处理外部请求,稍后详述)  
    7.         self.requests = ThreadPool(self, min=numthreads or 1, max=max)  
    8.         # 响应外部请求的webapp  
    9.         self.wsgi_app = wsgi_app  
    10.         # wsgi网关(http_request ->wsgi_gateway ->webpy/webapp)  
    11.         self.gateway = WSGIGateway_10  
    12.         # wsgi_server监听地址  
    13.         self.bind_addr = bind_addr  
    14.     # ...  
    15.   
    16. class HTTPServer(object):  
    17.     # 启动一个网络服务器  
    18.     # 如果你阅读过<<Unix网络编程>>,那么对于后面这些代码将会再熟悉不过,唯一的区别一个是c,  
    19.     #一个是python  
    20.     def start(self):  
    21.   
    22.         # 如果bind_addr是一个字符串(文件名),那么采用unix domain协议  
    23.         if isinstance(self.bind_addr, basestring):  
    24.             try: os.unlink(self.bind_addr)  
    25.             except: pass  
    26.             info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]  
    27.         else:  
    28.             # 否则采用TCP/IP协议  
    29.             host, port = self.bind_addr  
    30.             try:  
    31.                 info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,   
    32.                                             socket.SOCK_STREAM, 0, socket.AI_PASSIVE)  
    33.             except socket.gaierror:  
    34.                 # ...  
    35.           
    36.         # 循环测试 getaddrinfo函数返回值,直到有一个bind成功或是遍历完所有结果集  
    37.         for res in info:  
    38.             af, socktype, proto, canonname, sa = res  
    39.             try:  
    40.                 self.bind(af, socktype, proto)  
    41.             except socket.error:  
    42.                 if self.socket:  
    43.                     self.socket.close()  
    44.                 self.socket = None  
    45.                 continue  
    46.             break  
    47.         if not self.socket:  
    48.             raise socket.error(msg)  
    49.           
    50.         # 此时socket 进入listening状态(可以用netstat命令查看)  
    51.         self.socket.listen(self.request_queue_size)  
    52.           
    53.         # 启动线程池(这个线程池做些什么呢? 稍后会说)  
    54.         self.requests.start()  
    55.           
    56.         self.ready = True  
    57.         while self.ready:  
    58.             # HTTPSever核心函数,用来接受外部请求(request)  
    59.             # 然后封装成一个HTTPConnection对象放入线程池中的消息队列里,  
    60.             # 接着线程会从消息队列中取出该对象并处理  
    61.             self.tick()  
    62.               
    63.     def bind(self, family, type, proto=0):  
    64.         # 创建socket  
    65.         self.socket = socket.socket(family, type, proto)  
    66.         # 设置socket选项(允许在TIME_WAIT状态下,bind相同的地址)  
    67.         self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  
    68.         # socket bind  
    69.         self.socket.bind(self.bind_addr)  
    70.       
    71.     # HTTPSever核心函数  
    72.     def tick(self):  
    73.         try:  
    74.             # 接受一个TCP连接  
    75.             s, addr = self.socket.accept()  
    76.   
    77.             # 把外部连接封装成一个HTTPConnection对象  
    78.             makefile = CP_fileobject  
    79.             conn = self.ConnectionClass(self, s, makefile)  
    80.             # 然后把该对象放入线程池中的消息队列里  
    81.             self.requests.put(conn)  
    82.         except :  
    83.             # ...  


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

    Python代码  收藏代码
    1. class ThreadPool(object):  
    2.       
    3.     def __init__(self, server, min=10, max=-1):  
    4.         # server实例  
    5.         self.server = server  
    6.         # 线程池中线程数配置(最小值,最大值)  
    7.         self.min = min  
    8.         self.max = max  
    9.         # 线程池中的线程实例集合(list)  
    10.         self._threads = []  
    11.         # 消息队列(Queue是一个线程安全队列)  
    12.         self._queue = Queue.Queue()  
    13.         # 编程技巧,用来简化代码,等价于:  
    14.         # def get(self)  
    15.         #    return self._queue.get()  
    16.         self.get = self._queue.get  
    17.       
    18.     # 启动线程池  
    19.     def start(self):  
    20.         # 创建min个WorkThread并启动  
    21.         for i in range(self.min):  
    22.             self._threads.append(WorkerThread(self.server))  
    23.         for worker in self._threads:  
    24.             worker.start()  
    25.       
    26.     # 把obj(通常是一个HTTPConnection对象)放入消息队列  
    27.     def put(self, obj):  
    28.         self._queue.put(obj)  
    29.   
    30.     # 在不超过允许创建线程的最大数下,增加amount个线程  
    31.     def grow(self, amount):  
    32.         for i in range(amount):  
    33.             if self.max > and len(self._threads) >= self.max:  
    34.                 break  
    35.             worker = WorkerThread(self.server)  
    36.             self._threads.append(worker)  
    37.             worker.start()  
    38.       
    39.     # kill掉amount个线程  
    40.     def shrink(self, amount):  
    41.         # 1.kill掉已经不在运行的线程  
    42.         for t in self._threads:  
    43.             if not t.isAlive():  
    44.                 self._threads.remove(t)  
    45.                 amount -= 1  
    46.   
    47.         # 2.如果已经kill掉线程数小于amount,则在消息队列中放入线程退出标记对象_SHUTDOWNREQUEST  
    48.         # 当线程从消息队列中取到的不是一个HTTPConnection对象,而是一个_SHUTDOWNREQUEST,则退出运行  
    49.         if amount > 0:  
    50.             for i in range(min(amount, len(self._threads) - self.min)):  
    51.                 self._queue.put(_SHUTDOWNREQUEST)  
    52.   
    53. # 工作线程WorkThread  
    54. class WorkerThread(threading.Thread):  
    55.   
    56.     def __init__(self, server):  
    57.         self.ready = False  
    58.         self.server = server  
    59.         # ...  
    60.         threading.Thread.__init__(self)  
    61.       
    62.     def run(self):  
    63.          # 线程被调度运行,ready状态位设置为True  
    64.         self.ready = True  
    65.         while True:  
    66.             # 尝试从消息队列中获取一个obj  
    67.             conn = self.server.requests.get()  
    68.   
    69.             # 如果这个obj是一个“退出标记”对象,线程则退出运行  
    70.             if conn is _SHUTDOWNREQUEST:  
    71.                 return  
    72.             # 否则该obj是一个HTTPConnection对象,那么线程则处理该请求  
    73.             self.conn = conn  
    74.   
    75.             try:  
    76.                 # 处理HTTPConnection  
    77.                 conn.communicate()  
    78.             finally:  
    79.                 conn.close()  


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

    Python代码  收藏代码
    1. class HTTPConnection(object):  
    2.      
    3.     RequestHandlerClass = HTTPRequest  
    4.       
    5.     def __init__(self, server, sock, makefile=CP_fileobject):  
    6.         self.server = server  
    7.         self.socket = sock  
    8.         # 把socket对象包装成类File对象,使得对socket读写就像对File对象读写一样简单  
    9.         self.rfile = makefile(sock, "rb", self.rbufsize)  
    10.         self.wfile = makefile(sock, "wb", self.wbufsize)  
    11.       
    12.     def communicate(self):  
    13.         # 把HTTPConnection对象包装成一个HTTPRequest对象  
    14.         req = self.RequestHandlerClass(self.server, self)  
    15.         # 解析HTTP请求  
    16.         req.parse_request()  
    17.         # 响应HTTP请求  
    18.         req.respond()  

         
    在我们具体看HTTPRequest.parse_request如何解析HTTP请求之前,我们先了解下HTTP协议. HTTP协议是一个文本行的协议,它通常由以下部分组成: 

    引用
    请求行(请求方法 URI路径  HTTP协议版本) 
    请求头(譬如:User-Agent,Host等等) 
    空行 
    可选的数据实体


     


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

    Python代码  收藏代码
    1. def parse_request(self):  
    2.       self.rfile = SizeCheckWrapper(self.conn.rfile,  
    3.                                     self.server.max_request_header_size)  
    4.       # 读取请求行  
    5.       self.read_request_line()  
    6.       # 读取请求头  
    7.       success = self.read_request_headers()  
    8.   
    9.   # ----------------------------------------------------------------  
    10.   def read_request_line(self):  
    11.       # 从socket中读取一行数据  
    12.       request_line = self.rfile.readline()  
    13.         
    14.       # 按照HTTP协议规范,把request_line分割成请求方法(method),uri路径(uri),HTTP协议版本(req_protocol)  
    15.       method, uri, req_protocol = request_line.strip().split(" ", 2)  
    16.       self.uri = uri  
    17.       self.method = method  
    18.         
    19.       scheme, authority, path = self.parse_request_uri(uri)  
    20.       # 获取uri请求参数  
    21.       qs = ''  
    22.       if '?' in path:  
    23.           path, qs = path.split('?', 1)  
    24.       self.path = path  
    25.   
    26.   # ----------------------------------------------------------------  
    27.   def read_request_headers(self):  
    28.       # 读取请求头,inheaders是一个dict  
    29.       read_headers(self.rfile, self.inheaders)  
    30.   
    31.   # ----------------------------------------------------------------  
    32.   def read_headers(rfile, hdict=None):  
    33.       if hdict is None:  
    34.           hdict = {}  
    35.         
    36.       while True:  
    37.           line = rfile.readline()  
    38.           # 把line按照":"分割成k, v,譬如 Host:baidu.com就被分割成Host和baidu.com两部分  
    39.           k, v = line.split(":", 1)  
    40.           # 格式化分割后的     
    41.           k = k.strip().title()  
    42.           v = v.strip()  
    43.           hname = k  
    44.             
    45.           # HTTP协议中的有些请求头允许重复(譬如Accept等等),那么webpy就会把这些相同头的value用","连接起来  
    46.           if k in comma_separated_headers:  
    47.               existing = hdict.get(hname)  
    48.               if existing:  
    49.                   v = ", ".join((existing, v))  
    50.           # 把请求头k, v存入hdict  
    51.           hdict[hname] = v  
    52.         
    53.       return hdict  


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

    Python代码  收藏代码
    1. def respond(self):  
    2.         # 把请求交给gateway响应  
    3.         self.server.gateway(self).respond()  


    在继续往下看代码之前,我们先简单思考下,为什么要有这个gateway,为什么这里不把请求直接交给webapp处理? 
    我自己觉得还是出于分层和代码复用性考虑。因为可能存在,或者需要支持很多web规范,目前我们使用的是wsgi规范,明天可能出来个ysgi,大后天可能还来个zsgi,如果按照当前的设计,我们只需要替换HTTPServer的gateway属性,而不用修改其他代码(类似JAVA概念中的DAO层),下面我们就来看看这个gateway的具体实现(回到本文最初,我们在Server中注册的gateway是WSGIGateway_10): 

    WSGI网关 

    Python代码  收藏代码
    1. class WSGIGateway(Gateway):  
    2.     def __init__(self, req):  
    3.         self.req = req  # HTTPRequest对象  
    4.         self.env = self.get_environ()  
    5.       
    6.     # 获取wsgi的环境变量(留给子类实现)  
    7.     def get_environ(self):  
    8.         raise NotImplemented  
    9.       
    10.     def respond(self):  
    11.         # -----------------------------------  
    12.         # 按照 WSGI 规范调用我们得 webapp/webpy  
    13.         # -----------------------------------  
    14.         response = self.req.server.wsgi_app(self.env, self.start_response)  
    15.   
    16.         # 把处理结果写回给客户端  
    17.         for chunk in response:  
    18.             self.write(chunk)  
    19.       
    20.     def start_response(self, status, headers, exc_info = None):  
    21.         self.req.status = status  
    22.         self.req.outheaders.extend(headers)  
    23.           
    24.         return self.write  
    25.       
    26.     def write(self, chunk):  
    27.         # 写http响应头  
    28.         self.req.send_headers()  
    29.         # 写http响应体  
    30.         self.req.write(chunk)  


    WSGIGateway_10继承WSGIGateway类,并实现get_environ方法 

    Python代码  收藏代码
      1. class WSGIGateway_10(WSGIGateway):  
      2.       
      3.     def get_environ(self):  
      4.         # build WSGI环境变量(req中的这些属性,都是通过HTTPRequest.prase_request解析HTTP请求获得的)  
      5.         req = self.req  
      6.         env = {  
      7.             'ACTUAL_SERVER_PROTOCOL': req.server.protocol,  
      8.             'PATH_INFO': req.path,  
      9.             'QUERY_STRING': req.qs,  
      10.             'REMOTE_ADDR': req.conn.remote_addr or '',  
      11.             'REMOTE_PORT': str(req.conn.remote_port or ''),  
      12.             'REQUEST_METHOD': req.method,  
      13.             'REQUEST_URI': req.uri,  
      14.             'SCRIPT_NAME': '',  
      15.             'SERVER_NAME': req.server.server_name,  
      16.             'SERVER_PROTOCOL': req.request_protocol,  
      17.             'SERVER_SOFTWARE': req.server.software,  
      18.             'wsgi.errors': sys.stderr,  
      19.             'wsgi.input': req.rfile,  
      20.             'wsgi.multiprocess': False,  
      21.             'wsgi.multithread': True,  
      22.             'wsgi.run_once': False,  
      23.             'wsgi.url_scheme': req.scheme,  
      24.             'wsgi.version': (1, 0),  
      25.             }  
      26.         # ...  
      27.   
      28.         # 请求头  
      29.         for k, v in req.inheaders.iteritems():  
      30.             env["HTTP_" + k.upper().replace("-", "_")] = v  
      31.           
      32.         # ...  
      33.         return env  
  • 相关阅读:
    数据库的创建,数据的增删改查
    Ubuntu系统下查看显卡相关信息
    分布式文件系统测试方法与测试工具
    分布式存储产品的测试实践及心得
    sql注入
    web测试项目总结
    Ubuntu系统下使用Jenkins进行项目的自动构建还是项目回滚方法
    Ubuntu系统下Jenkins的git构建基本方法
    Ubuntu系统下在github中新增库的方法
    ADO.NET复习总结(2)--连接池
  • 原文地址:https://www.cnblogs.com/xiaoleiel/p/8301453.html
Copyright © 2020-2023  润新知