我想大部分 Python 开发者最先接触到的方向是 web 方向(因为总是有开发者希望马上给自己做个博客出来, 例如我), 既然是 Web, 免不了接触到一些 Web 框架, 例如 Django,Flask,Torando 等等, 在开发过程中, 看过一些文档总会介绍生产环境和开发环境服务器的配置问题, 服务器又设计 Web 服务器和应用服务器, 总而言之, 我们碰到最多的, 必定是这个词 - WSGI.
接下来的文章, 会分为以下几个部分:
1.WSGI 介绍
1.1 什么是 WSGI
1.2 怎么实现 WSGI
2. 由 Django 框架分析 WSGI
3. 实际环境使用的 wsgi 服务器
4.WSGI 服务器比较
开始
1 WSGI 介绍
1.1 什么是 WSGI
首先介绍几个关于 WSGI 相关的概念
WSGI: 全称是 Web Server Gateway Interface,WSGI 不是服务器, python 模块, 框架, API 或者任何软件, 只是一种规范, 描述 Web server 如何与 Web application 通信的规范. server 和 application 的规范在 PEP 3333 https://www.python.org/dev/peps/pep-3333/ 中有具体描述. 要实现 WSGI 协议, 必须同时实现 Web server 和 Web application, 当前运行在 WSGI 协议之上的 Web 框架有 Torando,Flask,Django
uwsgi: 与 WSGI 一样是一种通信协议, 是 uWSGI 服务器的独占协议, 用于定义传输信息的类型(type of information), 每一个 uwsgi packet 前 4byte 为传输信息类型的描述, 与 WSGI 协议是两种东西, 据说该协议是 fcgi 协议的 10 倍快.
uWSGI: 是一个 Web 服务器, 实现了 WSGI 协议, uwsgi 协议, http 协议等.
WSGI 协议主要包括 server 和 application 两部分:
Python
WSGI server 负责从客户端接收请求, 将 request 转发给 application, 将 application 返回的 response 返回给客户端; WSGI application 接收由 server 转发的 request, 处理请求, 并将处理结果返回给 server.application 中可以包括多个栈式的中间件(middlewares), 这些中间件需要同时实现 server 与 application, 因此可以在 WSGI 服务器与 WSGI 应用之间起调节作用: 对服务器来说, 中间件扮演应用程序, 对应用程序来说, 中间件扮演服务器.
WSGI 协议其实是定义了一种 server 与 application 解耦的规范, 即可以有多个实现 WSGI server 的服务器, 也可以有多个实现 WSGI application 的框架, 那么就可以选择任意的 server 和 application 组合实现自己的 Web 应用. 例如 uWSGI 和 Gunicorn 都是实现了 WSGI server 协议的服务器, Django,Flask 是实现了 WSGI application 协议的 Web 框架, 可以根据项目实际情况搭配使用.
以上介绍了相关的常识, 接下来我们来看看如何简单实现 WSGI 协议.
1.2 怎么实现 WSGI
上文说过, 实现 WSGI 协议必须要有 wsgi server 和 application, 因此, 我们就来实现这两个东西.
我们来看看官方 WSGI 使用 WSGI 的 wsgiref 模块实现的小 demo
有关于 wsgiref 的快速入门可以看看这篇博客 http://python.jobbole.com/88637/
- Python
- def demo_app(environ,start_response): from StringIO import StringIO stdout = StringIO() print>>stdout, "Hello world!" print>>stdout h = environ.items(); h.sort() for k,v in h: print>>stdout, k,'=', repr(v) start_response("200 OK", [('Content-Type','text/plain')]) return [stdout.getvalue()] httpd = make_server('localhost', 8002, demo_app) httpd.serve_forever() # 使用 select
实现了一个 application, 来获取客户端的环境和回调函数两个参数, 以及 httpd 服务端的实现, 我们来看看 make_server 的源代码
- Python
- def make_server( host, port, App, server_class=WSGIServer, handler_class=WSGIRequestHandler ): """Create a new WSGI server listening on `host` and `port` for `app`""" server = server_class((host, port), handler_class) server.set_app(App) return server
接受一系列函数, 返回一个 server 对象, 实现还是比较简单, 下面我们来看看在 django 中如何实现其自身的 wsgi 服务器的.
下面我们自己来实现一遍:
WSGI 规定每个 python 程序 (Application) 必须是一个可调用的对象(实现了__call__ 函数的方法或者类), 接受两个参数 environ(WSGI 的环境信息) 和 start_response(开始响应请求的函数), 并且返回 iterable. 几点说明:
Python
environ 和 start_response 由 http server 提供并实现 environ 变量是包含了环境信息的字典 Application 内部在返回前调用 start_response start_response 也是一个 callable, 接受两个必须的参数, status(HTTP 状态)和 response_headers(响应消息的头) 可调用对象要返回一个值, 这个值是可迭代的.
- Python
- # 1. 可调用对象是一个函数 def application(environ, start_response): response_body = 'The request method was %s' % environ['REQUEST_METHOD'] # HTTP response code and message status = '200 OK' # 应答的头部是一个列表, 每对键值都必须是一个 tuple. response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] # 调用服务器程序提供的 start_response, 填入两个参数 start_response(status, response_headers) # 返回必须是 iterable return [response_body] # 2. 可调用对象是一个类 class AppClass: """这里的可调用对象就是 AppClass 这个类, 调用它就能生成可以迭代的结果. 使用方法类似于: for result in AppClass(env, start_response): do_somthing(result)""" def __init__(self, environ, start_response): self.environ = environ self.start = start_response def __iter__(self): status = '200 OK' response_headers = [('Content-type', 'text/plain')] self.start(status, response_headers) yield "Hello world!\n" # 3. 可调用对象是一个实例 class AppClass: """这里的可调用对象就是 AppClass 的实例, 使用方法类似于: app = AppClass() for result in app(environ, start_response): do_somthing(result)""" def __init__(self): pass def __call__(self, environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] self.start(status, response_headers) yield "Hello world!\n"
服务器程序端
上面已经说过, 标准要能够确切地实行, 必须要求程序端和服务器端共同遵守. 上面提到, envrion 和 start_response 都是服务器端提供的. 下面就看看, 服务器端要履行的义务.
Python
准备 environ 参数 定义 start_response 函数 调用程序端的可调用对象
- Python
- import os, sys def run_with_cgi(application): # application 是程序端的可调用对象 # 准备 environ 参数, 这是一个字典, 里面的内容是一次 HTTP 请求的环境变量 environ = dict(os.environ.items()) environ['wsgi.input'] = sys.stdin environ['wsgi.errors'] = sys.stderr environ['wsgi.version'] = (1, 0) environ['wsgi.multithread'] = False environ['wsgi.multiprocess'] = True environ['wsgi.run_once'] = True environ['wsgi.url_scheme'] = 'http' headers_set = [] headers_sent = [] # 把应答的结果输出到终端 def write(data): sys.stdout.write(data) sys.stdout.flush() # 实现 start_response 函数, 根据程序端传过来的 status 和 response_headers 参数, # 设置状态和头部 def start_response(status, response_headers, exc_info=None): headers_set[:] = [status, response_headers] return write # 调用客户端的可调用对象, 把准备好的参数传递过去 result = application(environ, start_response) # 处理得到的结果, 这里简单地把结果输出到标准输出. try: for data in result: if data: # don't send headers until body appears write(data) finally: if hasattr(result,'close'): result.close()
2 由 Django 框架分析 WSGI
下面我们以 django 为例, 分析一下 wsgi 的整个流程
django WSGI application
WSGI application 应该实现为一个可调用 iter 对象, 例如函数, 方法, 类 (包含 **call** 方法). 需要接收两个参数: 一个字典, 该字典可以包含了客户端请求的信息以及其他信息, 可以认为是请求上下文, 一般叫做 environment(编码中多简写为 environ,env), 一个用于发送 HTTP 响应状态(HTTP status), 响应头(HTTP headers) 的回调函数, 也就是 start_response(). 通过回调函数将响应状态和响应头返回给 server, 同时返回响应正文(response body), 响应正文是可迭代的, 并包含了多个字符串.
下面是 Django 中 application 的具体实现部分:
- Python
- class WSGIHandler(base.BaseHandler): initLock = Lock() request_class = WSGIRequest def __call__(self, environ, start_response): # 加载中间件 if self._request_middleware is None: 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_started.send(sender=self.__class__, environ=environ) try: request = self.request_class(environ) except UnicodeDecodeError: logger.warning('Bad Request (UnicodeDecodeError)',exc_info=sys.exc_info(), extra={
- 'status_code': 400,
- } response = http.HttpResponseBadRequest() else: response = self.get_response(request) response._handler_class = self.__class__ status = '%s %s' % (response.status_code, response.reason_phrase) response_headers = [(str(k), str(v)) for k, v in response.items()] for c in response.cookies.values(): response_headers.append((str('Set-Cookie'), str(c.output(header='')))) # server 提供的回调方法, 将响应的 header 和 status 返回给 server start_response(force_str(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
可以看出 application 的流程包括: 加载所有中间件, 以及执行框架相关的操作, 设置当前线程脚本前缀, 发送请求开始信号; 处理请求, 调用 get_response()方法处理当前请求, 该方法的的主要逻辑是通过 urlconf 找到对应的 view 和 callback, 按顺序执行各种 middleware 和 callback. 调用由 server 传入的 start_response()方法将响应 header 与 status 返回给 server. 返回响应正文
django WSGI Server
负责获取 http 请求, 将请求传递给 WSGI application, 由 application 处理请求后返回 response. 以 Django 内建 server 为例看一下具体实现. 通过 runserver 运行 django
项目, 在启动时都会调用下面的 run 方法, 创建一个 WSGIServer 的实例, 之后再调用其 serve_forever()方法启动服务.
- Python
- def run(addr, port, wsgi_handler, ipv6=False, threading=False): server_address = (addr, port) if threading: httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {
- }) else: httpd_cls = WSGIServer # 这里的 wsgi_handler 就是 WSGIApplication httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6) if threading: httpd.daemon_threads = True httpd.set_app(wsgi_handler) httpd.serve_forever()
下面表示 WSGI server 服务器处理流程中关键的类和方法.
WSGIServerrun()方法会创建 WSGIServer 实例, 主要作用是接收客户端请求, 将请求传递给 application, 然后将 application 返回的 response 返回给客户端.
创建实例时会指定 HTTP 请求的 handler:WSGIRequestHandler 类, 通过 set_app 和 get_app 方法设置和获取 WSGIApplication 实例 wsgi_handler.
处理 http 请求时, 调用 handler_request 方法, 会创建 WSGIRequestHandler, 实例处理 http 请求. WSGIServer 中 get_request 方法通过 socket 接受请求数据.
WSGIRequestHandler 由 WSGIServer 在调用 handle_request 时创建实例, 传入 request,cient_address,WSGIServer 三个参数,__init__方法在实例化同时还会调用自身的 handle 方法 handle 方法会创建 ServerHandler 实例, 然后调用其 run 方法处理请求
ServerHandlerWSGIRequestHandler 在其 handle 方法中调用 run 方法, 传入 self.server.get_app()参数, 获取 WSGIApplication, 然后调用实例(__call__), 获取 response, 其中会传入 start_response 回调, 用来处理返回的 header 和 status. 通过 application 获取 response 以后, 通过 finish_response 返回 response
WSGIHandlerWSGI 协议中的 application, 接收两个参数, environ 字典包含了客户端请求的信息以及其他信息, 可以认为是请求上下文, start_response 用于发送返回 status 和 header 的回调函数
虽然上面一个 WSGI server 涉及到多个类实现以及相互引用, 但其实原理还是调用 WSGIHandler, 传入请求参数以及回调方法 start_response(), 并将响应返回给客户端.
3 实际环境使用的 wsgi 服务器
因为每个 Web 框架都不是专注于实现服务器方面的, 因此, 在生产环境部署的时候使用的服务器也不会简单的使用 Web 框架自带的服务器, 这里, 我们来讨论一下用于生产环境的服务器有哪些?
1.gunicorn
Gunicorn(从 Ruby 下面的 Unicorn 得到的启发)应运而生: 依赖 Nginx 的代理行为, 同 Nginx 进行功能上的分离. 由于不需要直接处理用户来的请求(都被 Nginx 先处理),Gunicorn 不需要完成相关的功能, 其内部逻辑非常简单: 接受从 Nginx 来的动态请求, 处理完之后返回给 Nginx, 由后者返回给用户.
由于功能定位很明确, Gunicorn 得以用纯 Python 开发: 大大缩短了开发时间的同时, 性能上也不会很掉链子. 同时, 它也可以配合 Nginx 的代理之外的别的 Proxy 模块工作, 其配置也相应比较简单.
配置上的简单, 大概是它流行的最大的原因.
2.uwsgi
因为使用 C 语言开发, 会和底层接触的更好, 配置也是比较方便, 目前和 gunicorn 两个算是部署时的唯二之选.
以下是通常的配置文件
Python
[uwsgi] http = $(HOSTNAME):9033 http-keepalive = 1 pythonpath = ../ module = service master = 1 processes = 8 daemonize = logs/uwsgi.log disable-logging = 1 buffer-size = 16384 harakiri = 5 pidfile = uwsgi.pid stats = $(HOSTNAME):1733 运行: uwsgi --INI conf.INI
3.fcgi
不多数, 估计使用的人也是比较少, 这里只是提一下
4.bjoern
Python WSGI 界最牛逼性能的 Server 其中一个是 bjoern, 纯 C, 小于 1000 行代码, 就是看不惯 uWSGI 的冗余自写的.
4 WSGI 服务器比较
综合广大 Python 开发者的实际经历, 我们可以得出, 使用最广的当属 uWSGI 以及 gunicorn, 我们这里来比较比较两者与其他服务器的区别.
1.gunicorn 本身是个多进程管理器, 需要指定相关的不同类型的 worker 去工作, 使用 gevent 作为 worker 时单机大概是 3000RPS Hello World, 胜过 torando 自带的服务器大概是 2000 左右, uWSGI 则会更高一点.
2. 相比于 tornado 对于现有代码需要大规模重构才能用上高级特性, Gevent 只需要一个 monkey, 容易对代码进行快速加工.
3.gunicorn 可以做 pre hook and post hook.
下面来对比以下 uWSGI 和 gunicorn 的速度差比
可以看到, 如果单纯追求性能, 那 uWSGI 会更好一点, 而 gunicorn 则会更易安装和结合 gevent.
结合这篇文章, 我们也可以得出相同结论, 在阻塞响应较多的情况下, gunicorn 的 gevent 模式无疑性能会更加强大.
功能实现方面, 无疑 uWSGI 会更多一些, 配置也会更加复杂一些, 可以看看 uWSGI 的配置和 gunicorn 的配置.
至于怎么去选择, 就看大家的项目结构怎么样了.
最后, 宣传一下我们的开源组织, PSC 开源组, 希望以开源项目的方式让每个人都能更有融入性的去学习, 公开化你的学习.
GitHub 地址: https://github.com/PythonScientists
官方论坛: http://www.pythonscientists.com/
来源: http://www.bubuko.com/infodetail-2891635.html