django源码分析---runserver

我们在使用Django 的时候,经常会使用一个django自带的本地测试小服务器。

django的自带这个测试小服务器基本都是通过wsgiref这个python的自带标准库实现,对其中模块进行了重写,我们先不在这里讨论,之后我看过wsgiref的源码后会贴出来的 - -。

写在前面

在开始之前,我们需要大致了解一些基础的内容

在一个 HTTP 请求到达服务器时, 服务器接收并调用 web 应用程序解析请求, 产生响应数据并返回给服务器. 这里涉及了两个方面的东西: 服务器(server)和应用程序(application).

关于applicaiton:

1
2
3
4
5
6
def simple_app(environ, start_response):
"""可能是最简单的处理了"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['Hello world!\n'] # 返回结果必须可迭代

也可以是这样,

1
2
3
4
5
6
class Application:
def __init__(self, environ, start_response):
pass
def __iter__(self):
retrun self

还可以是实例的方式

1
2
3
class ApplicationObj:
def __call__(self, environ, start_response):
pass

在这里 :
environ 是一个字典, 包含了环境变量
start_response 是一个回调函数
会在 app中被调用, 主要用来开始响应 HTTP. start_response ,大致是这样的

1
2
3
4
def start_response(status, response_headers, exc_info=None):
...
return write
#返回这 write 函数 只是为了兼容之前的 web 框架, 新的框架根本用不到.

status 即状态码;
response_headers HTTP 头, 可以修改;
exc_info 是与错误相关的信息;

这个start_response主要就是再次封装了status和headers到response这个消息体中去

1
2
3
4
5
6
7
def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
# do stuff w/exc_info here
finally:
exc_info = None # Avoid circular ref.
return write

关于server服务器

在服务器方面, 可以想象最简单的工作就是调用 app()获取其结果的过程

1
result = app(environ, start_response)

在wsgiref中其实就是使用他创建一个simple_server

1
2
3
4
5
6
7
8
def make_server(host, port, app,
server_class=WSGIServer,
handler_class=WSGIRequestHandler):
server = server_class((host, port), handler_class)
server.set_app(app)
#或是在这里之际运行server,server.forever()这种的
return server

这里有一个点不是很懂就是为什么,在传入app之前,需要传递一个自身处理requesthandle的方法,WSGIRequestHandler呢?

python manager.py runserver 0.0.0.0:8000

先找到入口函数

大家都使用过python manager runserver 0.0.0.0:8000来测试我们的服务,但是我们的代码到底是怎么样的呢?

我们先找到runserver在哪里呢

1
django/core/management/commands

在里面发现了很多关于commands的代码,打开每一个都看看,大致发一点规律,基本每个方法里面都有handle这个处理函数,进去看看先

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
def handle(self, *args, **options):
#忽略这里很多效验
...
self.run(**options)
def run(self, **options):
use_reloader = options.get('use_reloader')
if use_reloader:
autoreload.main(self.inner_run, None, options)
else:
#主要是这个inner_run这个函数
self.inner_run(None, **options)
def inner_run(self, *args, **options):
...
#关键的方法出现啦,是不是和我们之前看的呢种很类似呢
handler = self.get_handler(*args, **options)
#这个run方法大致就是wsgiref的中simple_server实现的过程,handle可能就是app啦
run(self.addr, int(self.port), handler,
ipv6=self.use_ipv6, threading=threading)
def get_handler(self, *args, **options):
"""
Returns the default WSGI handler for the runner.
"""
return get_internal_wsgi_application()

让我们来看看handle

1
2
3
4
5
6
7
8
9
10
from django.core.server.basehttp import (get_internal_wsgi_application, run)
from django.core.wsgi import get_wsgi_application
def get_internal_wsgi_application():
from django.conf import settings
app_path = getattr(settings, 'WSGI_APPLICATION’)
if app_path is None:
return get_wsgi_application()

这里get_wsgi_application()最后就返回了一个方法,WSGIHandle这个类
就发现了这个东东

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
33
from django.core.handlers.wsgi import WSGIHandler
class WSGIHandler(base.BaseHandler):
#这是一个线程锁,在多线程情况下防止,资源争抢情况
initLock = Lock()
#wsgi的requesthandle ,WSGIRequest(http.HttpRequest):这个request类
继承了http的中httpRequest这个封装类
request_class = WSGIRequest
def __call__(self, environ, start_response):
#熟悉东西出现了,这个理就是app的入口啦
if self._request_middleware is None:
#首先是加载中间键的相关配置,这里就是呢个步骤,这里先不讨论middleware相关的内容,这块会把配置文件中的middleware加载到内存之中,之后使用
with self.initLock:
try:
# Check that middleware is still uninitialized.
if self._request_middleware is None:
self.load_middleware()
except:
# Unload whatever middleware we got
self._request_middleware = None
raise
#添加环境变量
set_script_prefix(get_script_name(environ))
#这个signals同样是一个非常好的设计,在request调用之前,就会触发者个signals的函数,这个函数比request的中间键都要早,是在request进入response之前就插入的部分,如果需要在response之前加东西话,就可以通过signals来实现。这个之后我也会写出来blog的
signals.request_started.send(sender=self.__class__, environ=environ)
#这个就是主要的response处理函数
response = self.get_response(request)

进入get_response,看看如何获取结果

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def get_response(self, request):
1.先是加载了配置文件中的urlconf路由信息,但是主要,这块还没有使用这些conf
urlconf = settings.ROOT_URLCONF
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
2.开始加载使用request的middleware相关内容,如果request_middleware的中间件返回一个可调用的风response,后面步骤都不会执行,直接到response_middleware
for middleware_method in self._request_middleware:
response = middleware_method(request)
if response:
break
3 如果request中间键没有返回一个response对象,开始让request加载urlconf中的内容,
if response is None:
urlconf = request.urlconf
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
4 接下来就是准备进入我们的业务逻辑了,和request的中间件一样的是,如果返回一个response对象,后面的逻辑就不会执行了
for middleware_method in self._view_middleware:
response = middleware_method(request, callback, callback_args, callback_kwargs)
if response:
break
5 开始加载view中的主逻辑
wrapped_callback = self.make_view_atomic(callback)
response = wrapped_callback(request, *callback_args, **callback_kwargs)
5.5 从进入view逻辑中出现问题之后,都会触发exception中间键的处理逻辑,对异常进行处理,同样如果出现response对象,就会忽略后面的过程
for middleware_method in self._exception_middleware:
response = middleware_method(request, e)
if response:
break
6 接下来就是模版的渲染模版的中间键了
if hasattr(response, 'render') and callable(response.render):
for middleware_method in self._template_response_middleware:
response = middleware_method(request, response)
#6.1 获取模版渲染之后的结果
response = response.render()
#注:
这整个的异常之中,如果你想加入任意的异常处理过程,比如添加个异常print方法,
signals.got_request_exception.send(sender=self.__class__, request=request)
可以使用got_request_exception这个信息函数
7 最后的最后就是我们的response中间键登场的地方啦
for middleware_method in self._response_middleware:
response = middleware_method(request, response)
8 封装response和request
response = self.apply_response_fixes(request, response)
return response

现在我们终于看到了,wsgihandle最后处理的结果,就是这个response,当然中间还有很多异常检查,我已经忽略掉了

handle传递给我们的webserver

哈哈,似乎走的有点远

1
2
3
4
5
回到我门的上面的
def inner_run(self,*args,**option):
handler = self.get_handler(*args, **options)
run(self.addr, int(self.port), handler, ipv6=self.use_ipv6, threading=threading)

下来我们看看这个run是怎么个东东,应该会是server相关内容了

1
from django.core.servers.basehttp import get_internal_wsgi_application, run

就是这个里面出来的,去看看是什么呢?

1
2
3
4
5
6
7
8
9
10
11
12
def run(addr, port, wsgi_handler, ipv6=False, threading=False):
server_address = (addr, port)
if threading:
#这里居然可以使用多线程,如果这个threading为ture就是多线程处理了
httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})
else:
httpd_cls = WSGIServer
httpd = httpd_cls(server_address,WSGIRequestHandler, ipv6=ipv6)
#这个wsgi_handler就是我们上面说的呢个response
httpd.set_app(wsgi_handler)
httpd.serve_forever()

至于webserver使用的模块,大致了一下

1
2
3
4
5
6
7
8
9
10
11
12
class WSGIServer(simple_server.WSGIServer, object):
‘’’
这个就是wsgiref下面的一个方法简单的simple_server方法
‘''
def __init__(self, *args, **kwargs):
if kwargs.pop('ipv6', False):
self.address_family = socket.AF_INET6 super(WSGIServer, self).__init__(*args, **kwargs)
def server_bind(self):
"""Override server_bind to store the server name.”"" super(WSGIServer, self).server_bind() self.setup_environ()
果然这就是wsgiref一个方法的simple_server的继承

问题

  1. wsgref实现出来的simple_server是个怎么样的webserver,这个需要看源码
  2. wsgiref下面的simple_server,接收了一个WSGIRequestHandler对像,为什么要这么接收
  3. 如果改用uwsgi或是其他的server,如何和主程序联合使用?
  4. 为什么要使用signal,会有哪些应用场景呢?