Web应用的本质:
1. 浏览器发送一个HTTP请求
2. 服务器收到请求,生成一个HTML文档
3. 服务器把HTML文档作为HTTP响应的Body发送给浏览器
4. 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示
所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。我们上两篇博客已经详细讲解并实现了这样的HTTP服务器zjhttp,除此外Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的。
如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。
Web服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是为Python语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。自从WSGI被开发出来以后,许多其它语言中也出现了类似接口。
以前,如何选择合适的Web应用程序框架成为困扰Python初学者的一个问题,这是因为,一般而言,Web应用框架的选择将限制可用的Web服务器的选择,反之亦然。那时的Python应用程序通常是为CGI,FastCGI,mod_python中的一个而设计,甚至是为特定Web服务器的自定义的API接口而设计的。
WSGI(有时发音作’wiz-gee’)是作为Web服务器与Web应用程序或应用框架之间的一种低级别的接口,以提升可移植Web应用开发的共同点。WSGI是基于现存的CGI标准而设计的。WSGI没有官方的实现, 因为WSGI更像一个协议。只要遵照这些协议,WSGI应用(Application)都可以在任何服务器(Server)上运行, 反之亦然。WSGI就是Python的CGI包装,相对于Fastcgi是PHP的CGI包装
WSGI区分为两个部分
1. 为“服务器”或“网关”。它用于接收、整理客户端发送的请求
2. 为“应用程序”或“应用框架”。处理服务器程序传递过来的请求
如上图,Web服务器即第一部分,接收、整理客户端发送的请求,咱们前两篇博客使用C语言实现的zjhttp就是属于Web服务器部分;Web框架即为第二部分,即所谓的Web应用程序。开发Web应用程序的时候,通常会把常用的功能封装起来,成为各种框架,比如Flask,Django,Tornado(使用某框架进行web开发,相当于开发服务端的应用程序,处理后台逻辑)。但是,服务器程序和应用程序互相配合才能给用户提供服务,而不同应用程序(不同框架)会有不同的函数、功能。 此时,我们就需要一个标准,让服务器程序和应用程序都支持这个标准,那么,二者就能很好的配合了,这个标准就是 WSGI。
在处理一个WSGI请求时,服务器会为应用程序提供环境信息及一个回调函数(Callback Function)。当应用程序完成处理请求后,透过前述的回调函数,将结果回传给服务器。
所谓的 WSGI 中间件同时实现了API的两方,因此可以在WSGI服务器和WSGI应用之间起调解作用。从Web服务器的角度来说,中间件扮演应用程序,而从应用程序的角度来说,中间件扮演服务器。“中间件”组件可以执行以下功能:
1. 重写环境变量后,根据目标URL,将请求消息路由到不同的应用对象。
2. 允许在一个进程中同时运行多个应用程序或应用框架。
3. 负载均衡和远程处理,通过在网络上转发请求和响应消息。
4. 进行内容后处理,例如应用XSLT样式表。
WSGI 将 Web 组件分为三类
wsgi基本处理模式为:
WSGI Server -> WSGI Middleware -> WSGI Application
wsgi server可以理解为一个符合wsgi规范的web server,接收request请求,封装一系列环境变量,按照wsgi规范调用注册的wsgi app,最后将response返回给客户端。以python自带的wsgiref为例,wsgiref是按照wsgi规范实现的一个简单wsgi server。它的代码不复杂。
wsgi application就是一个普通的callable对象,当有请求到来时,wsgi server会调用这个wsgi app。这个对象接收两个参数,通常为environ,start_response。environ就像前面介绍的,可以理解为环境变量,跟一次请求相关的所有信息都保存在了这个环境变量中,包括服务器信息,客户端信息,请求信息。start_response是一个callback函数,wsgi application通过调用start_response,将response headers/status 返回给wsgi server。此外这个wsgi app会return 一个iterator对象 ,这个iterator就是response body。这么空讲感觉很虚,对着下面这个简单的例子看就明白很多了。
有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器。这就告诉我们,middleware需要把自己伪装成一个服务器,接受应用程序,调用它,同时middleware还需要把自己伪装成一个应用程序,传给服务器程序。
论是服务器程序、middleware 还是应用程序,都在服务端,为客户端提供服务,之所以把他们抽象成不同层,就是为了控制复杂度,使得每一次都不太复杂,各司其职。
原理说得太多未免过于抽象,现在使用Python内置的纯Python代码编写的wsgiref服务器来体验一把WSGI服务器是如何工作的
编写hello.py
作为一个Web应用程序
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'Hello, World!
']
编写server.py
作为一个WSGI服务器
from wsgiref.simple_server import make_server
# 导入编写的application函数
from hello import application
# 创建一个服务器,IP地址为空,端口是8000,传入函数application
httpd = make_server('', 8000, application)
print('Serving HTTP on port 8000...')
# 开始监听HTTP请求:
httpd.serve_forever()
启动WSGI服务器
python server.py
使用客户端访问
打开浏览器,输入http://localhost:8000/ ,在浏览器正常显示“Hello, World!”
上面的
application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
dict
对象而在application()
函数中又调用了start_response
函数
该函数发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次
start_response()
函数。start_response()
函数接收两个参数,一个是HTTP响应码,一个是一组list
表示的HTTP Header,每个Header用一个包含两个str
的tuple
表示。
通常情况下,都应该把Content-Type
头发送给浏览器。其他很多常用的HTTP Header也应该发送。然后,函数的返回值b'
将作为HTTP响应的Body发送给浏览器。Hello, web!
'
有了WSGI,我们关心的就是如何从environ
这个dict
对象拿到HTTP请求信息,然后构造HTML,通过start_response()
发送Header,最后返回Body。
整个application()
函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。
需要注意的是,application()
函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,我们可以挑选一个来用。但是我们仅将内置的wsgiref服务器用于测试,使我们编写的Web应用程序立马跑起来。
为了了解wsgi的工作原理,我们可以参照wsgiref源码,使用Python简单实现一个WSGI服务器
import socket
import StringIO
import sys
class WSGIServer(object):
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM
request_queue_size = 1
def __init__(self, server_address):
# 创建socket,利用socket获取客户端的请求
self.listen_socket = listen_socket = socket.socket(self.address_family, self.socket_type)
# 设置socket的工作模式
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定socket地址
listen_socket.bind(server_address)
# socket active, 监听文件描述符
listen_socket.listen(self.request_queue_size)
# 获得serve的host name和port
host, port = self.listen_socket.getsockname()[:2]
self.server_name = socket.getfqdn(host)
self.server_port = port
self.headers_set = []
def set_app(self, application):
self.application = application
#启动WSGI server服务,不停的监听并获取socket数据。
def serve_forever(self):
listen_socket = self.listen_socket
while True:
self.client_connection, client_address = listen_socket.accept() #接受客户端请求
#处理请求
self.handle_one_request()
def handle_one_request(self):
self.request_data = request_data = self.client_connection.recv(1024)
self.parse_request(request_data)
# Construct environment dictionary using request data
env = self.get_environ()
#给flask\tornado传递两个参数,environ,start_response
result = self.application(env, self.start_response)
self.finish_response(result)
#处理socket的http协议
def parse_request(self, data):
format_data = data.splitlines()
if len(format_data):
request_line = data.splitlines()[0]
request_line = request_line.rstrip('\r\n')
(self.request_method, self.path, self.request_version) = request_line.split() ## ['GET', '/', 'HTTP/1.1']
# 获取environ数据并设置当前server的工作模式
def get_environ(self):
env = {}
env['wsgi.version'] = (1, 0)
env['wsgi.url_scheme'] = 'http'
env['wsgi.input'] = StringIO.StringIO(self.request_data)
env['wsgi.errors'] = sys.stderr
env['wsgi.multithread'] = False
env['wsgi.multiprocess'] = False
env['wsgi.run_once'] = False
# Required CGI variables
env['REQUEST_METHOD'] = self.request_method # GET
env['PATH_INFO'] = self.path # /hello
env['SERVER_NAME'] = self.server_name # localhost
env['SERVER_PORT'] = str(self.server_port) # 8888
return env
def start_response(self, status, response_headers, exc_info=None):
server_headers = [('Date', 'Tue, 31 Mar 2015 12:54:48 GMT'), ('Server', 'WSGIServer 0.2')]
self.headers_set = [status, response_headers + server_headers]
#把application返回给WSGI的数据返回给客户端。
def finish_response(self, result):
try:
status, response_headers = self.headers_set
response = 'HTTP/1.1 {status}\r\n'.format(status=status)
for header in response_headers:
response += '{0}: {1}\r\n'.format(*header)
response += '\r\n'
for data in result:
response += data
self.client_connection.sendall(response)
print(''.join(['> {line}\n'.format(line=line) for line in response.splitlines()]))
finally:
self.client_connection.close()
SERVER_ADDRESS = (HOST, PORT) = '', 8888
def make_server(server_address, application):
server = WSGIServer(server_address)
server.set_app(application)
return server
if __name__ == '__main__':
if len(sys.argv) < 2:
sys.exit('Provide a WSGI application object as module:callable')
app_path = sys.argv[1]
module, application = app_path.split(':') # 第一个参数是文件名,第二个参数时长文件内app的命名
module = __import__(module)
application = getattr(module, application) # getattr(object, name[, default]) -> value
httpd = make_server(SERVER_ADDRESS, application)
print('WSGIServer: Serving HTTP on port {port} ...\n'.format(port=PORT))
httpd.serve_forever()
每个web框架都不是专注于实现服务器方面的,因此,在生产环境部署的时候,使用的服务器也不会简单的使用web框架自带的服务器,那么用于生产环境的服务器有哪些呢?
Gunicorn(从Ruby下面的Unicorn得到的启发)应运而生:依赖Nginx的代理行为,同Nginx进行功能上的分离。由于不需要直接处理用户来的请求(都被Nginx先处理),Gunicorn不需要完成相关的功能,其内部逻辑非常简单:接受从Nginx来的动态请求,处理完之后返回给Nginx,由后者返回给用户。
由于功能定位很明确,Gunicorn得以用纯Python开发:大大缩短了开发时间的同时,性能上也不会很掉链子。同时,它也可以配合Nginx的代理之外的别的Proxy模块工作,其配置也相应比较简单
使用C语言开发,和底层接触的更好,配置也比较方便,目前和gunicorn两个算是部署时的唯二之选。由于其可扩展的架构,它能够被无限制的扩展用来支持更多的平台和语言。目前,可以使用C,C++和Objective-C来编写插件
uWSGI 既不使用wsgi协议也不用FastCGI协议,而是自创了一个uwsgi的协议,uwsgi协议是一个uWSGI服务器自有的协议,它用于定义传输信息的类型(type of information),每一个uwsgi packet前4byte为传输信息类型描述,它与WSGI相比是两样东西。据说该协议大约是fcgi协议的10倍那么快
主要特点如下:
uWSGI 服务器自己实现了基于uwsgi
协议的server部分,因此我们只需要在uwsgi
的配置文件中指定application
的地址,uWSGI 就能直接和应用框架中的WSGI application
通信
是一个用C语言编写的,快速超轻量级的 Python WSGI服务器。
它是最快速的,最小的并且是最轻量级的WSGI服务器。有以下特性:
如果单纯追求性能,那uWSGI会更好一点,而Gunicorn则会更易安装和结合gevent。在阻塞响应较多的情况下,Gunicorn的gevent模式无疑性能会更加强大。功能实现方面,uWSGI会更多一些,配置也会更加复杂一些。
常见的Python Web应用框架:
Nginx(发音同engine x)是一个异步框架的 Web服务器,也可以用作反向代理,负载平衡器 和 HTTP缓存。该软件由 Igor Sysoev 创建,并于2004年首次公开发布。同名公司成立于2011年,以提供支持。
Nginx是一款免费的开源软件,根据类BSD许可证的条款发布。一大部分Web服务器使用Nginx,通常作为负载均衡器。
Nginx是一款面向性能设计的HTTP服务器,相较于Apache、lighttpd具有占有内存少,稳定性高等优势。与旧版本(<=2.2)的Apache不同,Nginx不采用每客户机一线程的设计模型,而是充分使用异步逻辑从而削减了上下文调度开销,所以并发服务能力更强。整体采用模块化设计,有丰富的模块库和第三方模块库,配置灵活。 在Linux操作系统下,Nginx使用epoll事件模型,得益于此,Nginx在Linux操作系统下效率相当高。同时Nginx在OpenBSD或FreeBSD操作系统上采用类似于epoll的高效事件模型kqueue。
Nginx在官方测试的结果中,能够支持五万个并行连接,而在实际的运作中,可以支持二万至四万个并行连接
正向代理是指浏览器主动请求代理服务器,代理服务器转发请求到对应的目标服务器。而反向代理则部署在Web服务器上,代理所有外部网络对内部网络的访问。浏览器访问服务器,必须经过这个代理,是被动的。正向代理的主动方是客户端,反向代理的主动方是Web服务器
在Python的Web开发中,较为成熟稳定的服务器架构一般是Nginx + uWSGI + Django
。而实际上Nginx服务器并不是必须的,直接使用uWSGI + Djang
完全是可以的,但这样一来,直接将uWSGI服务器暴露给了浏览器客户端,由此会导致诸多隐患。
支持WSGI的Web应用框架有很多
参考
[参考资料1]
[参考资料2]
[参考资料3]
[参考资料4]
[参考资料5]