Flask这个人人称赞的微框架就是构建在werkzeug之上,werkzeug给自己的定位就是工具集合。它实现了wsgi server、Requests/Response封装、DEBUG、热重启、路由控制以及其他的一些辅助功能。接下来的几篇文章会从一些大的方面去分析它。本篇如标题:D

WSGI Server

这个初略说一下。和它相关的代码不多。和wsgiref一样它构建在python自带模块SocketServer和BaseHTTPRequestHandler之上。甚至比自带的wsgiref代码要少。它主要增加了SSL和socket.fromfd支持。并且将debug、静态文件分发、热重启组合再了一起

Request、Response封装

这个可能算是重点了。有个库webob就是专门做这个的。占得代码量也很大。可惜我并不想详细写每一个的过程,太麻烦了,了解了大概就好啦。以后会主要分析下datastructures.py这个文件。下面是超简洁的原理代码

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
class Request():
def __init__(self, env):
pass
@classmethod
def application(cls, f):
def decorator(env, start_response):
request = cls(env)
return f(request)(env, start_response)
return decorator
class Response():
def __init__(self, str):
self.body = str
def __call__(self, env, start_response):
start_response('200 OK', [])
return [self.body]
@Request.application
def app(req):
return Response(b'Hello')
from wsgiref.simple_server import make_server
make_server('', 8000, app).serve_forever()

可以大概了解是这么回事:

  • Request它主要封装的是headers,然后嘞,各种属性都用property就好了,剩下的body和其他的也差不多啦。
  • Request.application这个装饰器的作用就是将原来的env,start_response参数变成req给我们使用
  • Response它就是一个wsgiapp对象
    看下它们2个的继承图
    werkzeug.wrappers.inherit
    1
    2
    3
    4
    5
    class PlainRequest(StreamOnlyMixin, Request):
    class Response(BaseResponse, ETagResponseMixin, ResponseStreamMixin,
    CommonResponseDescriptorsMixin,
    WWWAuthenticateMixin):
    翻源码可以看到它们的继承大部分都是对headers的操作

热重启实现原理

上简图:
werkzeug_autoreload
上原理代码:

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
import os
import sys
import time
import subprocess
import threading
def main():
print('Hello')
def file_change():
mtimes = {}
while 1:
filename = __file__
mtime = os.stat(filename).st_mtime
old_time = mtimes.get(filename)
if old_time is None:
mtimes[filename] = mtime
continue
elif mtime > old_time:
print(' * Detected change in {}, reloading'.format(filename))
os._exit(3)
time.sleep(1)
if os.environ.get('secord_process'):
threading.Thread(target=main, args=()).start()
file_change()
else:
while 1:
env = os.environ.copy()
env['secord_process'] = 'true'
exit_code = subprocess.call([sys.executable] + sys.argv, env=env)
if exit_code != 3:
break

做法就是主线程啥事没做,跑一个死循环,生成子进程(就相当运行自身,区别就是os.environ)。这个进程内使用单独的线程跑需要运行的函数,另外就是检查相关文件是否被改变。改变就执行sys.exit。然后就又被主线程的死循环生成了新的子进程