欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Django】Django如何实现WSGI协议

程序员文章站 2022-06-06 17:04:30
...

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