Django中服务器结构演变
起步
根据前面的分析实在是有太多太多 Handler ,绕来绕去,今天就从头整理,将一个最基础的服务器慢慢改成类似 django 的服务器结构。
从 simple_server 说起
根据 django 运行的服务器 django.core.servers.basehttp 的 run 函数,我们也利用 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.request 中 HttpRequest ,作为模仿,简单的展示这个类:
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
访问浏览器可以看到请求:

总结
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()

浙公网安备 33010602011771号