Django 源码小剖: 初探 WSGI

python 做为一种脚本语言, 已经逐渐大量用于 web 后台开发中, 而基于 python 的 web 应用程序框架也愈来愈多, Bottle, Django, Flask 等等.python

在一个 HTTP 请求到达服务器时, 服务器接收并调用 web 应用程序解析请求, 产生响应数据并返回给服务器. 这里涉及了两个方面的东西: 服务器(server)和应用程序(application). 势必要有一个合约要求服务器和应用程序都去遵照, 如此按照此合约开发的不管是服务器仍是应用程序都会具备较大的广泛性. 而这就好像在计算机通讯的早期, 各大公司都有属于本身的通讯协议, 如此只会让市场杂乱无章, 宁愿只要一种通讯协议.nginx

而针对 python 的合约是 WSGI(Python Web Server Gateway Interface). 具体的规定见 PEP 333.git

实习的时候一直使用 Django, 下面是结合 Django 学习 WSGI 的笔记.github

application/应用程序

在应用程序一方面, 必须提供下面的方法:web

def simple_app(environ, start_response):
    """多是最简单的处理了"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n'] # 返回结果必须可迭代

除了方法之外, 还能够用实现了 __call__ 的类实现.apache

它会被服务器调用, 在这里 environ 是一个字典, 包含了环境变量, REQUEST_METHOD,SCRIPT_NAME,QUERY_STRING 等; start_response 是一个回调函数, 会在 simple_app 中被调用, 主要用来开始响应 HTTP. start_response 原型大概是这样:浏览器

def start_response(status, response_headers, exc_info=None):
    ...
    return write # 返回这 write 函数 只是为了兼容以前的 web 框架, 新的框架根本用不到.

参数有 status 即状态码; response_headers HTTP 头, 能够修改; exc_info 是与错误相关的信息, 在产生相应数据过程当中可能发生错误, 这时须要更新 HTTP 头部, 经过再次调用 start_response 能够实现. 所以更为详尽的实现写法多是这种:服务器

def start_response(status, response_headers, exc_info=None):
    if exc_info:
         try:
             # do stuff w/exc_info here
         finally:
             exc_info = None    # Avoid circular ref.
    return write

Server/服务器

在服务器方面, 能够想象最简单的工做就是调用 simple_app(), 而后向客户端发送数据:app

result = simple_app(environ, start_response) #名字不必定为 simple_app
try:
    for data in result:
        if data:    # don't send headers until body appears
            write(data)
    if not headers_sent:
        write('')   # send headers now if body was empty
finally:
    if hasattr(result, 'close'):
        result.close()

注意 WSGI 并无事无巨细规定 web 应用程序和服务器内部的工做方式, 只是是规定了它们之间链接的标准.框架

python wsgiref 模块

下面看看 Django 是如何实现 WSGI 的. Django 其内部已经自带了一个方便本地测试的小服务器, 因此在刚开始学习 Django 的时候并不需搭建 apache 或者 nginx 服务器. Django 自带的服务器基于 python wsgiref 模块实现, 它自带的测试代码:

# demo_app() 是 application
def demo_app(environ,start_response):
    from StringIO import StringIO
    stdout = StringIO()
    print >>stdout, "Hello world!"
    print >>stdout
    h = environ.items(); h.sort()
    for k,v in h:
        print >>stdout, k,'=', repr(v)
    start_response("200 OK", [('Content-Type','text/plain')])
    return [stdout.getvalue()]

def make_server(
    host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
):
    """Create a new WSGI server listening on `host` and `port` for `app`"""
    server = server_class((host, port), handler_class)
    server.set_app(app)
    return server

if __name__ == '__main__':
    httpd = make_server('', 8000, demo_app)
    sa = httpd.socket.getsockname()
    print "Serving HTTP on", sa[0], "port", sa[1], "..."
    import webbrowser
    webbrowser.open('http://localhost:8000/xyz?abc')
    httpd.handle_request()  # serve one request, then exit

python 的库有好多的工具, 这时可能由于须要的缘由, 会生出好多的父类, 为了讲明, 根据 wsgiref 模块和它自带的测试用例得出下面的 UML 图(注意, 这只是 wsgiref, 没有涉及 Django):

python-wsgiref

我读完这些的时候已经晕了, 确实是里边的继承关系有些复杂. 所以, 简要的归纳了测试代码的执行关系:

  • make_server() 中 WSGIServer 类已经做为服务器类, 负责接收请求, 调用 application 的处理, 返回相应;
  • WSGIRequestHandler 做为请求处理类, 并已经配置在 WSGIServer 中;
  • 接着还设置了 WSGIServer.application 属性(set_app(app));
  • 返回 server 实例.
  • 接着打开浏览器, 即发起请求. 服务器实例 WSGIServer httpd 调用自身 handle_request() 函数处理请求. handle_request() 的工做流程以下:请求-->WSGIServer 收到-->调用 WSGIServer.handle_request()-->调用 _handle_request_noblock()-->调用 process_request()-->调用 finish_request()-->finish_request() 中实例化 WSGIRequestHandler-->实例化过程当中会调用 handle()-->handle() 中实例化 ServerHandler-->调用 ServerHandler.run()-->run() 调用 application() 这才是真正的逻辑.-->run() 中在调用 ServerHandler.finish_response() 返回数据-->回到 process_request() 中调用 WSGIServer.shutdown_request() 关闭请求(其实什么也没作)

ps: 明明 application 是 WSGIServer 的属性, 为何会在 ServerHandler 中调用? 由于在实例化 WSGIRequestHandler 的时候 WSGIServer 把本身搭进去了, 因此在 WSGIRequestHandler 中实例化 ServerHandler 时候能够经过 WSGIRequestHandler.server.get_app() 获得真正的 application.

总结

从上面能够获得, 启动服务器的时候, 不管以什么方式都要给它传递一个 application(), 是一个函数也好, 一个实现了 __call__ 的类也好; 当请求到达服务器的时候, 服务器自会调用 application(), 从而获得相应数据. 至于, 对请求的数据如何相应, application() 中能够细化.

确实, 其中的调用链太过长, 这期间尚未加入 HTTP 头的分析(提取 Cookie等). 若是只为响应一个 "helloworld", 在 WSGIServer.finish_request() 中直接相应数据就行了, WSGIRequestHandler 和 ServerHandler 类能够直接省去, 而只须要你提供一个 application()! 但事实上, 并不仅是相应 "helloworld" 那样简单...

关于 Django 中的 WSGI 如何, 下一节再说. Django 源码剖析从这里开始! 我已经在 github 备份了 Django 源码的注释: Decode-Django, 有兴趣的童鞋 fork 吧. 本文结合 python wsgiref, BaseHTTPServer.py, SocketServer.py 模块源码看更好. 

捣乱  2013-9-4

http://daoluan.net

相关文章
相关标签/搜索