`
DiaoCow
  • 浏览: 244356 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

【Python】Webpy 源码学习(一)

阅读更多
自己是个python新手,之前买了本<<python核心编程>>,但看了一半实在看不下去了(内容过于啰嗦,而且在关键点的地方又浅尝辄止),所以希望通过阅读一些简单的开源项目来快速提高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:
#/usr/bin/python
import web.wsgiserver

def my_wsgi_app(env, start_response):
    status = '200 OK'                                                                                                                         
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello world!']

server = web.wsgiserver.CherryPyWSGIServer(("127.0.0.1", 8080), my_wsgi_app);
server.start()

执行代码:


在具体看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核心函数,用来接受外部请求(request)
            # 然后封装成一个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方法究竟做了些什么呢?
class HTTPConnection(object):
   
    RequestHandlerClass = HTTPRequest
    
    def __init__(self, server, sock, makefile=CP_fileobject):
        self.server = server
        self.socket = sock
        # 把socket对象包装成类File对象,使得对socket读写就像对File对象读写一样简单
        self.rfile = makefile(sock, "rb", self.rbufsize)
        self.wfile = makefile(sock, "wb", self.wbufsize)
    
    def communicate(self):
        # 把HTTPConnection对象包装成一个HTTPRequest对象
        req = self.RequestHandlerClass(self.server, self)
        # 解析HTTP请求
        req.parse_request()
        # 响应HTTP请求
        req.respond()
    
在我们具体看HTTPRequest.parse_request如何解析HTTP请求之前,我们先了解下HTTP协议. HTTP协议是一个文本行的协议,它通常由以下部分组成:
引用
请求行(请求方法 URI路径  HTTP协议版本)
请求头(譬如:User-Agent,Host等等)
空行
可选的数据实体




而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如何响应请求:
   
def respond(self):
        # 把请求交给gateway响应
        self.server.gateway(self).respond()

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

WSGI网关
class WSGIGateway(Gateway):
    def __init__(self, req):
        self.req = req  # HTTPRequest对象
        self.env = self.get_environ()
    
    # 获取wsgi的环境变量(留给子类实现)
    def get_environ(self):
        raise NotImplemented
    
    def respond(self):
        # -----------------------------------
        # 按照 WSGI 规范调用我们得 webapp/webpy
        # -----------------------------------
        response = self.req.server.wsgi_app(self.env, self.start_response)

        # 把处理结果写回给客户端
        for chunk in response:
            self.write(chunk)
    
    def start_response(self, status, headers, exc_info = None):
        self.req.status = status
        self.req.outheaders.extend(headers)
        
        return self.write
    
    def write(self, chunk):
        # 写http响应头
        self.req.send_headers()
        # 写http响应体
        self.req.write(chunk)

WSGIGateway_10继承WSGIGateway类,并实现get_environ方法
class WSGIGateway_10(WSGIGateway):
    
    def get_environ(self):
        # build WSGI环境变量(req中的这些属性,都是通过HTTPRequest.prase_request解析HTTP请求获得的)
        req = self.req
        env = {
            'ACTUAL_SERVER_PROTOCOL': req.server.protocol,
            'PATH_INFO': req.path,
            'QUERY_STRING': req.qs,
            'REMOTE_ADDR': req.conn.remote_addr or '',
            'REMOTE_PORT': str(req.conn.remote_port or ''),
            'REQUEST_METHOD': req.method,
            'REQUEST_URI': req.uri,
            'SCRIPT_NAME': '',
            'SERVER_NAME': req.server.server_name,
            'SERVER_PROTOCOL': req.request_protocol,
            'SERVER_SOFTWARE': req.server.software,
            'wsgi.errors': sys.stderr,
            'wsgi.input': req.rfile,
            'wsgi.multiprocess': False,
            'wsgi.multithread': True,
            'wsgi.run_once': False,
            'wsgi.url_scheme': req.scheme,
            'wsgi.version': (1, 0),
            }
        # ...

        # 请求头
        for k, v in req.inheaders.iteritems():
            env["HTTP_" + k.upper().replace("-", "_")] = v
        
        # ...
        return env

好了,到这里我们已经把整个流程:从HTTPServer接受外部请求,到我们web应用处理这一过程已经大致说完,希望对各位有帮助。
                  
       
       
  • 大小: 71 KB
  • 大小: 11 KB
  • 大小: 28.7 KB
  • 大小: 64.7 KB
分享到:
评论

相关推荐

    python-web.py包

    首先,你需要访问Web.py的官方网站或者通过GitHub等代码托管平台找到web.py的源码压缩包,这里显示的文件名是"webpy-master",这意味着你已经下载了源码的主分支。下载后,将其解压缩到本地的一个目录,例如`C:\temp...

    Python web.py 应用源码

    web.py 是一个轻量级且强大的Python Web框架,它的设计理念是简洁、直观,并且易于上手。这个框架旨在让开发者能够快速地构建Web应用,同时保持代码的清晰性和可维护性。web.py 的核心特性包括URL路由、模板渲染、...

    webpy离线包

    2. **解压文件**:解压缩下载的文件,通常会得到一个名为`webpy`的目录,里面包含了WebPy的源码和其他可能的依赖。 3. **安装WebPy**:进入解压后的目录,通过Python的`setup.py`脚本进行安装。在命令行中运行`...

    PythonWeb开发案例教程_源代码.zip

    "PythonWeb开发案例教程_源代码.zip"这个压缩包包含了整个教程的实践代码,这将帮助你更好地跟随教程的步伐,亲手操作每一个示例,加深对理论知识的理解。 Python Web开发主要依赖于一些强大的框架,如Django、...

    python webpy

    创建一个新的 Python 文件,例如 `test_webpy.py`,并在其中编写以下代码: ```python import web urls = ( '/hello', 'hello', ) class hello: def GET(self): return 'Hello World' if __name__ == ...

    Python web增删查改demo源码

    【Python web增删查改demo源码】是一个基于Python的Web应用程序示例,它利用了强大的Web框架Django来实现基本的数据操作功能,包括增(添加)、删(删除)、查(查询)和改(修改)。这个demo对于初学者来说是一个很...

    web_develop, 《Python Web开发实战》书中源码.zip

    总之,“web_develop, 《Python Web开发实战》书中源码.zip”是一个宝贵的资源,涵盖了Python Web开发的多个方面。通过深入研究这些源码,你将能够更好地理解和应用Python Web开发的技术,提升自己的编程能力。

    webpy 0.36

    WebPy 0.36是Python编程语言中的一个轻量级Web应用框架,它以其简洁、高效和易于学习的特点在开发者社区中广受欢迎。本文将深入探讨WebPy 0.36的核心特性、安装过程、基本使用方法以及相关知识点。 **一、WebPy概述...

    102套python源码实例.zip

    102套python源码实例含Py源代码,提供python语言学习者。目录简述: 根据出生日期判断你的星座 根据出生日期判断生肖 使用Python制作简易数字累加器 商品竞猜价格 根据计算机硬盘、主板、CPU生成注册信息 语音版东北...

    py源码大全.zip

    "py源码大全.zip"这个文件很可能是包含了一系列的Python源代码文件,这些文件可能涵盖各种编程主题,从基础的语法示例到复杂的算法实现,或者是特定应用领域的项目代码。 在Python编程中,源代码通常以".py"为扩展...

    Python编程锦囊源码

    Python编程是一种广泛应用于数据分析、机器学习、Web开发和自动化任务的高级编程语言。"Python编程锦囊源码"很可能是包含了一系列实用的Python代码示例和相关解释的资源集合,旨在帮助初学者和经验丰富的开发者更好...

    webpy blog源代码

    webpy blog源代码 =============== blog.py 主程序模块 model.py 数据模块 templates/ base.html index.html view.html new.html edit.html static/ util.js Javascript工具模块 使用方法: ====...

    python+flask项目示例源码

    这个项目示例源码是一个很好的学习起点,它涵盖了Flask基础和Web开发的基本流程。通过阅读和实践,你可以深入了解如何利用Python和Flask构建Web应用,包括路由设计、视图函数编写、模板渲染以及与用户的交互。同时,...

    webpy简介及其在OpenShift上的部署(源码)

    3. **上传代码**:将你的WebPy源码上传到OpenShift应用的git仓库。这通常通过`git clone`获取应用的远程仓库地址,然后将WebPy项目文件添加并提交。 4. **配置环境**:根据你的WebPy应用需求,可能需要在OpenShift...

    一个纯粹的Python库存管理系统源码.zip

    通过学习和分析这个库存管理系统的源码,我们可以提升Python编程技能,了解实际项目开发流程,并可能接触到其他相关技术,如数据库设计、前端界面开发、软件工程实践等。这是一次宝贵的实践机会,有助于我们在IT领域...

    webpy-0.36.tar.gz

    **webpy-0.36.tar.gz** 是一个包含Python Web框架web.py的源代码压缩包,版本为0.36。web.py以其简洁而强大的特性,在Python Web开发领域受到许多开发者的青睐。这个框架旨在简化Web应用的构建,同时提供足够的灵活...

    Python绝技带源码

    《Python绝技带源码》是一本专注于Python编程高级技巧的资源集合,旨在帮助开发者提升在Python编程中的技能水平。该资源包包含了丰富的代码示例,涵盖了Python编程的多个核心领域,如数据处理、网络编程、并发编程、...

    Python+SQLite的WEB采集器源码

    【Python+SQLite的WEB采集器源码】是一个利用Python编程语言构建的自动化网络数据采集工具。这个工具的主要功能是定期从指定的网站抓取信息,并将抓取到的数据存储到SQLite数据库中。SQLite是一个轻量级的关系型...

    Python Web 框架,第 1 部分 使用 Django 和 Python 开发 Web 站点.doc

    通过学习本文档,我们了解到Django是一个功能强大且易于使用的Python Web框架,它不仅提供了丰富的内置功能,还拥有活跃的社区支持。掌握Django框架的使用,对于Python开发者来说,意味着能够在较短时间内构建出高...

    python核心编程源码, python练习的数十个小例子

    "Python核心编程源码,python练习的数十个小例子"这个资源集合是针对Python学习者的一个宝贵资料,无论你是初学者还是经验丰富的开发者,都可以通过这些实例来巩固和提升你的Python技能。 深入Python书上的源码.rar...

Global site tag (gtag.js) - Google Analytics