Django 源码阅读(五): 服务器结构始末

Python 2017-11-28

起步

根据前面的分析实在是有太多太多 Handler ,绕来绕去,今天就从头整理,将一个最基础的服务器慢慢改成类似 django 的服务器结构。

从 simple_server 说起

根据 django 运行的服务器 django.core.servers.basehttprun 函数,我们也利用 simpler_server 模块起一个很简单的服务:

from wsgiref.simple_server import WSGIServer, WSGIRequestHandler

def demo_app(environ,start_response):
    """
    示例的 app
    """
    stdout = "Hello world!"
    h = sorted(environ.items())
    for k,v in h:
        stdout += k + '=' + repr(v) + "\r\n"
    print(start_response)
    start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')])
    return [stdout.encode("utf-8")]

server = WSGIServer(('', 8000), WSGIRequestHandler)
server.set_app(demo_app)
server.serve_forever()

我们会将这个程序改造成与django服务器结构差不多的形式,便于理解其各个 Handler 之间的关系。运行这个程序,访问 http://localhost:8000 可以看到打印了 Hello World!environ 字典中各个项。结构上,我们可以将字符串通过 [stdout.encode("utf-8")] bytes对象组成的数组作为 response 返回即可, 终端也可以看到输出:

<bound method BaseHandler.start_response of <wsgiref.simple_server.ServerHandler object at 0x00000000036290F0>>
127.0.0.1 - - [28/Nov/2017 14:22:45] "GET /app/login/ HTTP/1.1" 200 7281

最外层的 StaticFilesHandler

StaticFilesHandler 是服务器结构第一个接手的对象。所有请求都是先通过这个对象接手再由其他对象处理的。这个对象处理请求也比较简单,就是判断一下请求是否是静态文件,是的话自己处理,不是的话,交个其他 handler 处理。我们模拟一下这个类:

class StaticFilesHandler:
    """
    处理静态文件的句柄
    """
    def __init__(self, application):
        self.application = application
    def __call__(self, environ, start_response):
        if True: # 假设所有请求都不是静态文件
            return self.application(environ, start_response)

启动程序:

server = WSGIServer(('', 8000), WSGIRequestHandler)
static_handler = StaticFilesHandler(demo_app)
server.set_app(static_handler)
server.serve_forever()

这部分比较好理解,就是简单套了一个对象做代理。最终调用还是 demo_app 函数。

封装请求的 request

在封装请求之前,要先定义一下 request 的样子,这部分代码在 django.http.requestHttpRequest ,作为模仿,简单的展示这个类:

class HttpRequest(object):
    """
    定义request所需的属性
    """
    def __init__(self):
        self.GET = {}
        self.POST = {}
        self.COOKIES = {}
        self.META = {}
        self.FILES = {}

        self.path = ''
        self.path_info = ''
        self.method = None

这个类是定义其属性,将其派生个子类,作用是将 environ 转换成对应的属性:

class WSGIRequest(HttpRequest):
    """
    将环境变量转到 request 属性
    """
    def __init__(self, environ):
        script_name = environ["SCRIPT_NAME"]
        path_info = environ["PATH_INFO"]
        if not path_info:
            path_info = '/'
        self.environ = environ
        self.path_info = path_info

        self.path = '%s/%s' % (script_name.rstrip('/'), path_info.replace('/', '', 1))
        self.META = environ
        self.META['PATH_INFO'] = path_info
        self.META['SCRIPT_NAME'] = script_name
        self.method = environ['REQUEST_METHOD'].upper()

定义应用 application

WSGIServer 利用 set_app(app) 将应用作为回调的,根据上面通过 StaticFilesHandler 代理,但最后执行的是 demo_app ,现在需要将其改造一下,用上我们的 WSGIRequest

def get_response(request):
    print("path = %s" % request.path)
    print("method = %s" % request.method)
    return [str(request).encode("utf-8")]

class WSGIHandler(object):
    request_class = WSGIRequest
    def __call__(self, environ, start_response):
        request = self.request_class(environ)
        response = get_response(request)
        start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
        return response

这样,在 get_response 中,就是 request 对象了。定义了 __call__ 函数用于 StaticFilesHandler 对实例进行调用 self.application(environ, start_response) 。而在这个 __call__ 函数中,根据 environ 环境变量创建一个 WSGIRequest 的对象, get_response 函数中的 request 就是通常使用的 django 中 views 里面的 request

将启动应用的代码稍作修改:

application = WSGIHandler()
static_handler = StaticFilesHandler(application)

server = WSGIServer(('', 8000), WSGIRequestHandler)
server.set_app(static_handler)
server.serve_forever()

封装响应的 response

现在还差对响应进行封装了,这个封装其实也比较简单,最后返回的是一个 [bytes()] 形式的既可。

class HttpResponse(object):
    """
    一个简单封装的response类
    """
    def __init__(self, content="", status=200,
                charset="utf-8", content_type="text/plain; charset=utf-8",
                *args, **kwargs):
        self._headers = {'Content-Type':content_type}
        self._charset = charset
        self.content(content)
        self.status_code = status

    def serialize_headers(self): # 序列化 headers
        def to_bytes(val, encoding):
            return val if isinstance(val, bytes) else val.encode(encoding)
        headers = [
            (b': '.join([to_bytes(key, 'ascii'), to_bytes(value, 'latin-1')]))
            for key, value in self._headers.values()
        ]
        return b'\r\n'.join(headers)

    def content(self, value): # 将str转为bytes
        content = bytes(value.encode(self._charset))
        self._container = [content]
    def __iter__(self):       # 将对象视为可迭代
        return iter(self._container)

这样,我们的 get_response 就可以很自由的操作响应对象了:

def get_response(request):
    print("path = %s" % request.path)
    print("method = %s" % request.method)
    res = HttpResponse("hello world")
    res.status_code = 400
    res._headers["hahahah"] = "xxxxxx"
    return res

访问浏览器可以看到请求:

20171128163212.png

总结

django的服务器结构大致就是这样了,更多的 reponse 响应类基本都是按照这个属性进行延伸和拓展。这篇文章主要讲述的是各个 handler 之间如何协作,请求的转接过程和最后变成 request 对象以及对 response 封装整理。

附录

测试源码:

from wsgiref.simple_server import WSGIServer, WSGIRequestHandler
def demo_app(environ,start_response):
    """
    示例的 app
    """
    stdout = "Hello world!"
    h = sorted(environ.items())
    for k,v in h:
        stdout += k + '=' + repr(v) + "\r\n"
    print(start_response)
    start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')])
    return [stdout.encode("utf-8")]

class StaticFilesHandler:
    """
    处理静态文件的句柄
    """
    def __init__(self, application):
        self.application = application
    def __call__(self, environ, start_response):
        if True: # 假设所有请求都不是静态文件
            return self.application(environ, start_response)

class HttpRequest(object):
    """
    定义request所需的属性
    """
    def __init__(self):
        self.GET = {}
        self.POST = {}
        self.COOKIES = {}
        self.META = {}
        self.FILES = {}

        self.path = ''
        self.path_info = ''
        self.method = None

class WSGIRequest(HttpRequest):
    """
    将环境变量转到 request 属性
    """
    def __init__(self, environ):
        super(WSGIRequest, self).__init__()
        script_name = environ["SCRIPT_NAME"]
        path_info = environ["PATH_INFO"]
        if not path_info:
            path_info = '/'
        self.environ = environ
        self.path_info = path_info

        self.path = '%s/%s' % (script_name.rstrip('/'), path_info.replace('/', '', 1))
        self.META = environ
        self.META['PATH_INFO'] = path_info
        self.META['SCRIPT_NAME'] = script_name
        self.method = environ['REQUEST_METHOD'].upper()

class HttpResponse(object):
    """
    一个简单封装的response类
    """
    def __init__(self, content="", status=200,
                charset="utf-8", content_type="text/plain; charset=utf-8",
                *args, **kwargs):
        self._headers = {'Content-Type':content_type}
        self._charset = charset
        self.content(content)
        self.status_code = status

    def serialize_headers(self):
        def to_bytes(val, encoding):
            return val if isinstance(val, bytes) else val.encode(encoding)
        headers = [
            (b': '.join([to_bytes(key, 'ascii'), to_bytes(value, 'latin-1')]))
            for key, value in self._headers.values()
        ]
        return b'\r\n'.join(headers)

    def content(self, value): # 将str转换为 [bytes()] 形式
        content = bytes(value.encode(self._charset))
        self._container = [content]

    def __iter__(self): # 可迭代对象
        return iter(self._container)

def get_response(request):
    """
    示例
    :param request:
    :return:
    """
    print("path = %s" % request.path)
    print("method = %s" % request.method)
    res = HttpResponse("hello world")
    res.status_code = 400
    res._headers["hahahah"] = "xxxxxx"
    return res

class WSGIHandler(object):
    """
    WSGI句柄, 起中心调控作用
    """
    request_class = WSGIRequest
    def __call__(self, environ, start_response):
        request = self.request_class(environ)
        response = get_response(request)
        status = '%d %s' % (response.status_code, "OK")
        response_headers = [(str(k), str(v)) for k, v in response._headers.items()]
        start_response(str(status), response_headers)
        return response

server = WSGIServer(('', 8000), WSGIRequestHandler)
application = WSGIHandler()
static_handler = StaticFilesHandler(application)
server.set_app(static_handler)
server.serve_forever()

本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

赏个馒头吧