我的博客

django 1.9 中的 request.META['wsgi.input'] 类型

目录
  1. django 1.9
    1. run 函数
    2. WSGIServer 对请求的处理过程
      1. BaseServer
      2. ThreadingMixIn
      3. BaseServer
    3. WSGIRequestHandler 对请求的处理过程
      1. django.core.servers.basehttp.WSGIRequestHandler

关于 django 中 view 的参数 request.MEAT[‘wsgi.input’] 的类型问题

我发现这个对象的类型在 django 不同版本中是不同的。我希望探究一下这个问题。

首先又两个关键:

  1. request.MEAT[‘wsgi.input’] 对象在哪里构建
  2. request 对象在哪里构建(request 对象的类型是 WSGIRequest)

django 1.9

版本 VERSION = (1, 9, 0, 'final', 0)

执行了 py manage.py runserver 以后,我们从 django.core.management.commands.runserver 中的 Command 类开始看。它的 inner_run 方法中,最终启动了开发服务器

1
2
run(self.addr, int(self.port), handler,
ipv6=self.use_ipv6, threading=threading)

self.addr 和 self.port 是要绑定的地址和端口号,handler 是 django.contrib.staticfiles.handlers.StaticFilesHandler 对象。ipv6=self.use_ipv6 是 False。threading=threading 是 True。

run 函数

实现在 django.core.servers.basehttp 中。

这里创建了一个 WSGIServer 对象。构造函数传入了 WSGIRequestHandler(这个也在同一个文件中定义)。 先调用它的 set_app 方法设置其 application 为上文的 handler 参数,也就是那个 StaticFilesHandler 。

最后调用 serve_forever 进入循环。

WSGIServer 对请求的处理过程

先看继承关系

graph BT;
    WSGIServer --> simple_server.WSGIServer;
    WSGIServer --> socketserver.ThreadingMixIn;
    simple_server.WSGIServer --> HTTPServer;
    HTTPServer --> socketserver.TCPServer;
    socketserver.TCPServer --> BaseServer;

BaseServer

serve_forever 在 BaseServer 里。这里有一个接受请求的 while 循环。循环中调用 self._handle_request_noblock(),它又调 self.process_request(request, client_address),这里传入的 request 是 socket 类型。process_request 在 ThreadingMixIn 中。

ThreadingMixIn

process_request 则以 self.process_request_thread 为 target 启动了新线程。新线程调用了 self.finish_request(request, client_address)。finish_request 又回到 BaseServer。

BaseServer

这里直接创建了 self.RequestHandlerClass 来处理请求。可以看到这个类的类型是 django.core.servers.basehttp.WSGIRequestHandler。构造参数是 (request, client_address, self)。当然 request 仍然是那个 socket。


WSGIRequestHandler 对请求的处理过程

先看继承关系

graph BT;
    WSGIRequestHandler --> simple_server.WSGIRequestHandler;
    simple_server.WSGIRequestHandler --> BaseHTTPRequestHandler
    BaseHTTPRequestHandler --> socketserver.StreamRequestHandler
    socketserver.StreamRequestHandler --> BaseRequestHandler

django.core.servers.basehttp.WSGIRequestHandler

1
2
3
4
5
class WSGIRequestHandler(simple_server.WSGIRequestHandler, object):

def __init__(self, *args, **kwargs):
self.style = color_style()
super(WSGIRequestHandler, self).__init__(*args, **kwargs)

这里只添加了一个 style 属性,再看父类的构造函数。WSGIRequestHandler 无显式构造函数,BaseHTTPRequestHandler 无显式构造函数,StreamRequestHandler 也没有。最后到了 BaseRequestHandler。

1
2
3
4
5
6
7
8
9
10
class BaseRequestHandler:
def __init__(self, request, client_address, server):
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish()

这里设置了 self.request,由上文我们直到这是客户端的 socket。调用了 self.setup()。这个 setup 是 StreamRequestHandler.setup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class StreamRequestHandler(BaseRequestHandler):
rbufsize = -1
wbufsize = 0
# A timeout to apply to the request socket, if not None.
timeout = None
# Disable nagle algorithm for this socket, if True.
# Use only when wbufsize != 0, to avoid small packets.
disable_nagle_algorithm = False

def setup(self):
self.connection = self.request
if self.timeout is not None:
self.connection.settimeout(self.timeout)
if self.disable_nagle_algorithm:
self.connection.setsockopt(socket.IPPROTO_TCP,
socket.TCP_NODELAY, True)
self.rfile = self.connection.makefile('rb', self.rbufsize)
if self.wbufsize == 0:
self.wfile = _SocketWriter(self.connection)
else:
self.wfile = self.connection.makefile('wb', self.wbufsize)

这里设置了 self.rfile 为 self.connection.makefile(‘rb’, self.rbufsize),而这里的 connection 实际上就是 request 也就是客户端的套接字。那么 rfile 的类型实际上是 _io.BufferedReader。

这里需要注意 rfile,下文还会出现,而且实际上它就是我们要找的 wsgi.input。


然后 BaseRequestHandler 还调用了 self.handle() 这个是 simple_server.WSGIRequestHandler.handle 是对请求处理的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class WSGIRequestHandler(BaseHTTPRequestHandler):
def handle(self):
"""Handle a single HTTP request"""
self.raw_requestline = self.rfile.readline(65537)
if len(self.raw_requestline) > 65536:
self.requestline = ''
self.request_version = ''
self.command = ''
self.send_error(414)
return
if not self.parse_request(): # An error code has been sent, just exit
return
handler = ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
)
handler.request_handler = self # backpointer for logging
handler.run(self.server.get_app())

这里构造了 simple_server.ServerHandler

看一下继承关系

graph BT
    ServerHandler --> simple_server.ServerHandler
    simple_server.ServerHandler --> wsgiref.handlers.SimpleHandler
    wsgiref.handlers.SimpleHandler --> BaseHandler

ServerHandler 无构造函数。看父类 SimpleHandler

1
2
3
4
5
6
7
8
9
10
class SimpleHandler(BaseHandler):
def __init__(self,stdin,stdout,stderr,environ,
multithread=True, multiprocess=False
):
self.stdin = stdin
self.stdout = stdout
self.stderr = stderr
self.base_env = environ
self.wsgi_multithread = multithread
self.wsgi_multiprocess = multiprocess

注意 stdin 设置为 第一个参数,就是上文的 rfile

看完了 handler 的构造函数再看,解下来调用的 handler.run(),亦即 ServerHandler.run()。ServerHandler 和 SimpleHandler 都没有 run,所以看 BaseHandler。

1
2
3
4
5
6
7
8
9
10
11
12
def run(self, application):
try:
self.setup_environ()
self.result = application(self.environ, self.start_response)
self.finish_response()
except:
try:
self.handle_error()
except:
# If we get an error handling an error, just give up already!
self.close()
raise # ...and let the actual server figure it out.

run 里调用了 self.setup_environ()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def setup_environ(self):
"""Set up the environment for one request"""

env = self.environ = self.os_environ.copy()
self.add_cgi_vars()
env['wsgi.input'] = self.get_stdin()
env['wsgi.errors'] = self.get_stderr()
env['wsgi.version'] = self.wsgi_version
env['wsgi.run_once'] = self.wsgi_run_once
env['wsgi.url_scheme'] = self.get_scheme()
env['wsgi.multithread'] = self.wsgi_multithread
env['wsgi.multiprocess'] = self.wsgi_multiprocess
if self.wsgi_file_wrapper is not None:
env['wsgi.file_wrapper'] = self.wsgi_file_wrapper
if self.origin_server and self.server_software:
env.setdefault('SERVER_SOFTWARE',self.server_software)

这里终于出现了 ‘wsgi.input’,这里看到了把 self.environ[‘wsgi.input’] 设成了 self.get_stdin(),实际上就是 self.stdin,实际上就是 上文的 rfile,_io.BufferedReader 类型。


下一步是调用了 run 的参数 application。application 是 django.contrib.staticfiles.handlers.StaticFilesHandler 对象。调用对象就是调用其 __call__ 方法。

1
2
3
4
5
6
7
8
9
10
class StaticFilesHandler(WSGIHandler):
def __init__(self, application):
self.application = application
self.base_url = urlparse(self.get_base_url())
super(StaticFilesHandler, self).__init__()

def __call__(self, environ, start_response):
if not self._should_handle(get_path_info(environ)):
return self.application(environ, start_response)
return super(StaticFilesHandler, self).__call__(environ, start_response)

这里是执行了第四行的 self.application,这个是构造函数传入的。类型是 django.core.handlers.wsgi.WSGIHandler 对象,其实这也是它的父类的一个对象。所以要再看 WSGIHandler 的 __call__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_middleware()

def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
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)
return response

终于看到了 WSGIRequest,request 对象正是在这里构建的!且构建的时候只传入一个参数,self.request_class(environ),就是 environ。

最后来到 WSGIRequest 的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class WSGIRequest(HttpRequest):
def __init__(self, environ):
script_name = get_script_name(environ)
# If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
# trailing slash), operate as if '/' was requested.
path_info = get_path_info(environ) or '/'
self.environ = environ
self.path_info = path_info
# be careful to only replace the first slash in the path because of
# http://test/something and http://test//something being different as
# stated in https://www.ietf.org/rfc/rfc2396.txt
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()
self.content_type, self.content_params = cgi.parse_header(environ.get('CONTENT_TYPE', ''))
if 'charset' in self.content_params:
try:
codecs.lookup(self.content_params['charset'])
except LookupError:
pass
else:
self.encoding = self.content_params['charset']
try:
content_length = int(environ.get('CONTENT_LENGTH'))
except (ValueError, TypeError):
content_length = 0
self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
self._read_started = False
self.resolver_match = None

再次看到关键一句,self.META = environ。

终于得出结论,

  1. django 1.9 的 request.MEAT[‘wsgi.input’] 类型是 _io.BufferedReader。

  2. request 对象在 django.core.handlers.wsgi.WSGIHandler 的构造函数中构造。

  3. request.MEAT[‘wsgi.input’] 对象在 socketserver.StreamRequestHandler 中构造。

评论无需登录,可以匿名,欢迎评论!