David Wheeler 有一句名言:"计算机科学中的任何问题, 都可以通过加上另一层间接的中间层解决."
为了提高 Python 网络服务的可移植性, Python 社区在 PEP 333 中提出了 web 服务器网关接口(WSGI,Web Server Gateway Interface).
WSGL 标准就是添加了一层中间层. 通过这一个中间层, 用 Python 编写的 HTTP 服务就能够与任何 Web 服务器进行交互了. 现在, WSGI 已经成为了使用 Python 进行 HTTP 操作的标准方法.
按照标准的定义, WSGI 应用程序是可以被调用的, 并且有两个输入参数.
1,WSGI
下面是第一段代码, 第一个参数是 environ, 用于接收一个字典, 字典中提供的键值对是旧式的 CGI 环境集合的拓展. 第二个参数本身也是可以被调用的, 习惯上会将其命名为 start_response(),WSGI 应用程序通过这个参数来声明响应头信息.
- # 用 WSGI 应用形式编写的简单 HTTP 服务.
- #!/usr/bin/env python3
- # A simple HTTP service built directly against the low-level WSGI spec.
- from pprint import pformat
- from wsgiref.simple_server import make_server
- def App(environ, start_response):
- headers = {'Content-Type': 'text/plain; charset=utf-8'}
- start_response('200 OK', list(headers.items()))
- yield 'Here is the WSGI environment:
- '.encode('utf-8')
- yield pformat(environ).encode('utf-8')
- if __name__ == '__main__':
- httpd = make_server('', 8000, App)
- host, port = httpd.socket.getsockname()
- print('Serving on', host, 'port', port)
- httpd.serve_forever()
上述只是一个简单的情况. 但是在编写服务器程序时, 复杂度就大大提升了. 这是因为要完全考虑标准中的描述的许多注意点和边界情况.
2, 前向代理与反向代理
无论前向代理还是反向代理, HTTP 代理其实就是一个 HTTP 服务器, 用于接收请求, 然后对接收到的请求 (至少是部分请求) 进行转发. 转发请求时代理会扮演客户端的角色, 将转发的 HTTP 请求发送至真正的服务器, 最后将从服务器接受到的响应发挥扮演客户端的角色, 将转发的请求发送至真正的服务器, 最后将从服务器接受到的响应发回给最初的客户端.
下面是前向代理和反向代理的简图.
反向代理已经广泛应用于大型的 HTTP 服务当中. 反向代理是 Web 服务的一部分, 对于 HTTP 客户端并不可见.
3, 四种架构
架构师一般都使用很多种复杂的机制来将多个子模块组合建成一个 HTTP 服务. 现在在 Python 社区中, 已经形成了 4 种基本的模式. 如果已经编写了用于生成动态内容的 Python 代码, 并且已经选择了某个支持 WSGI 的 API 或框架, 应该如何将 HTTP 服务部署到线上呢?
运行一个使用 Python 编写的服务器, 服务器的代码中可以直接调用 WSGI 接口. 现在最流行的是 Green Unicorn(Gunicorn)服务器, 不过也有其他已经可以用于生产环境的纯 Python 服务器.
配置 mod_wsgi 并运行 Apache, 在一个独立的 WSFIDaemonProcess 中运行 Python 代码, 由 mod_wsgi 启动守护进程.
在后端运行一个类似于 Gunicorn 的 Python HTTP 服务器(或者支持所选异步框架的任何服务器), 然后在前端运行一个既能返回静态文件, 又能对 Python 编写的动态资源服务进行反向代理的 Web 服务器.
在最前端运行一个纯粹的反向代理(如 Varnish), 在该反向代理后端运行 Apache 或者 nginx, 在后端运行 Python 编写的 HTTP 服务器. 这是一个三层的架构. 这些反向代理可以分布在不同的地理位置, 这样子就能够将离客户端最近的反向代理上的缓存资源返回给发送请求的客户端.
长期以来, 对这 4 个架构的选择主要基于 CPython 的 3 个运行时的特性, 即解释器占用内存大, 解释器运行慢, 全局解释器 (GIL,Global Interpreter Lock) 禁止多个线程同时运行 Python 字节码. 但同时带来了内存中只能载入一定数量的 Python 实例.
4, 平台即服务
这个概念的出现是因为现在的自动化部署, 持续集成以及高性能大规模服务的相关技术的出现和处理有一些繁杂. 所以有一些提供商提出了 PaaS(Platform as a Service), 现在只需关心应该如何打包自己的应用程序, 以便将自己的应用部署到这些服务之上.
PaaS 提供商会解决构建和运行 HTTP 服务中的出现的各种烦心事. 不需要再关心服务器, 或者是提供 IP 地址之类的事情.
PaaS 会根据客户规模提供负载均衡器. 只需要给 PaaS 提供商提供配置文件即可完成各种复杂的步骤.
现阶段比较常用的有 Heroku 和 Docker.
大多数 PaaS 提供商不支持静态内容, 除非我们在 Python 应用程序中实现了对静态内容的更多支持或者向容器中加入了 Apache 或 ngnix. 尽管我们可以将静态资源和动态页面的路径放在两个完全不同的 URL 空间内, 但是许多架构师还是倾向于将两者放在同一个名字空间内.
5, 不使用 Web 框架编写 WSGI 可调用对象
下面第一段代码是用于返回当前时间的原始 WSGI 可调用对象.
- #!/usr/bin/env python3
- # A simple HTTP service built directly against the low-level WSGI spec.
- import time
- def App(environ, start_response):
- host = environ.get('HTTP_HOST', '127.0.0.1')
- path = environ.get('PATH_INFO', '/')
- if ':' in host:
- host, port = host.split(':', 1)
- if '?' in path:
- path, query = path.split('?', 1)
- headers = [('Content-Type', 'text/plain; charset=utf-8')]
- if environ['REQUEST_METHOD'] != 'GET':
- start_response('501 Not Implemented', headers)
- yield b'501 Not Implemented'
- elif host != '127.0.0.1' or path != '/':
- start_response('404 Not Found', headers)
- yield b'404 Not Found'
- else:
- start_response('200 OK', headers)
- yield time.ctime().encode('ascii')
第一段比较冗长. 下面使用第三方库简化原始 WGSI 的模式方法.
第一个示例是使用 WebOb 编写的可调用对象返回当前时间.
- #!/usr/bin/env python3
- # A WSGI callable built using webob.
- import time, webob
- def App(environ, start_response):
- request = webob.Request(environ)
- if environ['REQUEST_METHOD'] != 'GET':
- response = webob.Response('501 Not Implemented', status=501)
- elif request.domain != '127.0.0.1' or request.path != '/':
- response = webob.Response('404 Not Found', status=404)
- else:
- response = webob.Response(time.ctime())
- return response(environ, start_response)
第二个是使用 Werkzeug 编写的 WSGI 可调用对象返回当前时间.
- #!/usr/bin/env python3
- # A WSGI callable built using Werkzeug.
- import time
- from werkzeug.wrappers import Request, Response
- @Request.application
- def App(request):
- host = request.host
- if ':' in host:
- host, port = host.split(':', 1)
- if request.method != 'GET':
- return Response('501 Not Implemented', status=501)
- elif host != '127.0.0.1' or request.path != '/':
- return Response('404 Not Found', status=404)
- else:
- return Response(time.ctime())
大家可以对比这两个库在简化操作时的不同之处, Werkzeug 是 Flask 框架的基础.
来源: http://server.51cto.com/sOS-600979.htm