【Django】Django如何实现WSGI协议
Django如何实现WSGI协议
什么是WSGI?
WSGI全称Web Server Gateway Interface,中文名Web服务器网关接口,
关于WSGI是什么,【WSGI简介】这篇博文非常清晰的进行了解释。
WSGI由Server+Middleware+Applacation三个组件构成,
Server定义了environ和start_response,
其中environ存放了所有和客户端相关的信息,已经一些WSGI协议必须的参数,
start_response在Applacation中调用,接收HTTP响应状态字符串status和HTTP响应头headers,
还有一个可选参数exc_info接收Applacation出错信息;
Middleware对于Server是Applacation,对于Applacation是Middleware,
在中间多数起到预处理environ和start_response的作用;
Applacation入口是一个函数或者是一个类的__call__方法,接收environ和start_response这两个参数,
响应environ中的具体请求,> 并将响应状态码和响应头通过start_response返回Server或中间件,最后返回body信息;
Server最后根据Applacation或中间件返回的响应信息处理返回给客户端。
Django是如何实现WSGI?
严格来说Django框架只是实现了Applacation部分,
Server部分由启动Django服务的组件实现,比如常用的gunicorn以及开发环境常用的’python manage.py runserver’的方式,
本文根据前面几篇关于django启动与访问过程的源码解读的博文,从manage.py启动方式来简述Django关于WSGI的实现过程。
Server端实现过程
WSGIServer
从【Django启动过程(二)】结尾处我们了解到Django给WSGIServer设置了一个applacation对象(WSGIHandler)并调用WSGIServer.serve_forever启动服务,而WSGIServer本质上是一个TCPServer,在socket监听到访问请求后,TCPServer将本次请求的套接字信息request,监听地址client_address以及本次socket连接self,一起交给WSGIRequestHandler进行处理。
WSGIRequestHandler
在WSGIRequestHandler的handler方法中,通过get_environ方法获取到了前面所提到的environ参数,并将socker服务的读取和写入文件跟environ一起传递给了ServerHandler处理,最后用ServerHandler.run方法调用Applacation完成请求内容响应。
- WSGIRequestHandler.run关键代码如下:
self.setup_environ()
self.result = application(self.environ, self.start_response)
# 调用wfile发送响应头和body信息,
# 如果应用程序返回的是封装的文件类型对象,关闭应用程序打开的文件类型对象。
self.finish_response()
- WSGIRequestHandler.get_environ 关键代码如下:
def get_environ(self):
env = self.server.base_environ.copy()
# 请求协议,如“http/1.0”或“http/1.1”
env['SERVER_PROTOCOL'] = self.request_version
# WSGI协议版本号,如“WSGIServer/0.2”
env['SERVER_SOFTWARE'] = self.server_version
# 请求方法,如GET,POST
env['REQUEST_METHOD'] = self.command
if '?' in self.path:
path,query = self.path.split('?',1)
else:
path,query = self.path,''
# 请求地址关于应用根地址的剩余部分
env['PATH_INFO'] = urllib.parse.unquote(path, 'iso-8859-1')
# url关于?后面部分内容,可能为空
env['QUERY_STRING'] = query
# 客户端地址
host = self.address_string()
if host != self.client_address[0]:
env['REMOTE_HOST'] = host
# 客户端地址
env['REMOTE_ADDR'] = self.client_address[0]
# 请求头的'content-type'
if self.headers.get('content-type') is None:
env['CONTENT_TYPE'] = self.headers.get_content_type()
else:
env['CONTENT_TYPE'] = self.headers['content-type']
# 请求头的'content-length',可能为空
length = self.headers.get('content-length')
if length:
env['CONTENT_LENGTH'] = length
# 其他以“HTTP_”开头的变量
for k, v in self.headers.items():
k=k.replace('-','_').upper(); v=v.strip()
if k in env:
continue # skip content length, type,etc.
if 'HTTP_'+k in env:
env['HTTP_'+k] += ','+v # comma-separate multiple headers
else:
env['HTTP_'+k] = v
return env
ServerHandler
在WSGIRequestHandler虽然提供了environ参数,但缺少start_response方法,因此还不是一个完整的Server端;
在ServerHandler的run方法中,首先通过setup_environ方法将WSGI协议必须的一些参数封装进environ,
并且提供了start_response方法,然后将environ和start_response作为参数传递给Applacation的接口,
最后通过finish_response或者handle_error将响应结果返回给服务端,最后反馈给客户端,
可以看出ServerHandler同时实现了Server和Middleware的功能,
由WSGIServer、WSGIRequestHandler和ServerHandler实现了完整的Server功能。
- ServerHandler.setup_environ代码如下:
def setup_environ(self):
"""Set up the environment for one request"""
env = self.environ = self.os_environ.copy()
self.add_cgi_vars()
# ServerHandler(self.rfile, self.wfile, self.get_stderr(), self.get_environ())
# Applacation通过它获取Http request body
env['wsgi.input'] = self.get_stdin() # LimitedStream封装后的self.rfile,
# Applacation错误信息写入
env['wsgi.errors'] = self.get_stderr() # sys.stderr
# wsgi协议版本号
env['wsgi.version'] = self.wsgi_version # (1,0)
# 如果希望应用程序在进程生命周期内只被调用一次时设置为False
env['wsgi.run_once'] = self.wsgi_run_once # False
# # 协议版本http或https
env['wsgi.url_scheme'] = self.get_scheme() # http或https
# 多线程支持,应用程序可能被多个线程调用时设置为True
env['wsgi.multithread'] = self.wsgi_multithread # True
# 多进程支持,应用程序可能被多个进程调用时设置为True
env['wsgi.multiprocess'] = self.wsgi_multiprocess # True
if self.wsgi_file_wrapper is not None:
# 可以将文件类对象包装成迭代器,如果应用程序最后返回的是文件类型对象,在最后会调用包装器进行封装
env['wsgi.file_wrapper'] = self.wsgi_file_wrapper # wsgiref.util.FileWrapper
if self.origin_server and self.server_software:
# 服务端名称
env.setdefault('SERVER_SOFTWARE',self.server_software) # None
- ServerHandler.start_response代码如下:
# 该方法由Applacation调用
def start_response(self, status, headers,exc_info=None):
"""'start_response()' callable as specified by PEP 3333"""
# Applacation异常处理
if exc_info:
try:
if self.headers_sent:
# Re-raise original exception if headers sent
raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
finally:
exc_info = None # avoid dangling circular ref
elif self.headers is not None:
raise AssertionError("Headers already set!")
# 设置响应内容的状态码字符串
self.status = status
# 封装并设置响应头
self.headers = self.headers_class(headers)
# 状态码字符串类型检测
status = self._convert_string_type(status, "Status")
# 状态码字符串长度检测,最少4个字符串,否则报错
assert len(status)>=4,"Status must be at least 4 characters"
#状态码格式检测,必须以3位数字开头,否则报错
assert status[:3].isdigit(), "Status message must begin w/3-digit code"
# 第4个字符串必须为空格,否则报错
assert status[3]==" ", "Status message must have a space after code"
# python启动时以'-O'或者‘-OO’参数启动时debug=True。这个模式下py文件编译为更小的pyo而不是pyc。
if __debug__:
# headersde键值字符类型检测
for name, val in headers:
name = self._convert_string_type(name, "Header name")
val = self._convert_string_type(val, "Header value")
assert not is_hop_by_hop(name),\
f"Hop-by-hop header, '{name}: {val}', not allowed"
return self.write
Applacation端实现过程
前面提到启动的时候设置的Applacation实际是一个WSGIHandler实例化对象,
这个对象接收了environ和start_response这两个参数,并且返回响应头和body。
- django.core.handlers.wsgi.WSGIHandler 核心代码如下:
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 这一部分已经实例化,主要是导入相关模块并利用装饰器闭包原理封装中间件实例化对象。
self.load_middleware()
# 这里就是实际的Applacation部分代码,在前几篇的源码解析中已经做过解析,
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
# 用WSGIRequest封装request
request = self.request_class(environ)
# 调用中间件和视图获得响应response
response = self.get_response(request)
response._handler_class = self.__class__
# 设置状态码字符串
status = '%d %s' % (response.status_code, response.reason_phrase)
# 设置响应头
response_headers = [
*response.items(),
*(('Set-Cookie', c.output(header='')) for c in response.cookies.values()),
]
# 发送响应状态码字符串和响应头
start_response(status, response_headers)
# 如果响应内容是文件对象,用文件包装器封装后返回
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
response = environ['wsgi.file_wrapper'](response.file_to_stream)
# 返回响应body
return response
上一篇: POJ-3061【尺取算法】