这个系列我们学习自己手动撸一个Web框架,参考轻量级框架web.py的实现,进行简化实现最基本功能。在实现的过程中了解Python的各种特性。
这篇文章也是自己学习的一个过程,开始之前,先介绍一下什么是WSGI(Web Server Gateway Interface),即Web服务器网关接口。WSGI本质是一个规范,定义了Web服务器如何与Python应用程序进行交互,使得使用Python写的Web应用程序可以和Web服务器对接起来。关于规范细节感兴趣的可以查看PEP3333。
为什么需要WSGI?
Web部署一般采用以下方案:
- 部署一个Web服务器专门用来处理HTTP协议层面的各种相关任务
- 部署一个由某种语言(Java, PHP,Python...)开发的应用程序,这个程序从Web服务器接收客户端请求,完成处理后,再返回响应信息给Web服务器,最后通过Web服务器返回给客户端。
这个方案需要解决一个问题,Web服务器与应用服务器之间如何进行交互。为了规范Web服务器与应用程序之间的交互,最早出现的是CGI标准。WSGI是Python专用的标准,这些规范出现的目的就是统一交互规范,提高程序的可移植性。
WSGI如何工作?
在开始撸我们自己的框架之前,先了解一下WSGI是如何工作的。WSGI规范了Web服务器与Python应用程序之间的桥梁该如何搭建,这个桥梁解决以下两个问题:
1. 让Web服务器知道如何调用Python应用程序,并且把用户的请求告诉应用程序。
2. 让Python应用程序知道用户的具体请求是什么,以及如何返回结果给Web服务器。
WSGI定义了两个角色,Web服务器端称为server或者gateway,应用程序端称为application或者framework。下面我们统一使用server和application这两个术语。一般调用流程是这样的:server端会先收到用户的请求,然后会根据规范的要求调用application端,调用的结果会被封装成HTTP响应后再发送给客户端。参考如下示意图:
例子分析
我们先看一段非常简单的代码,Python3提供了一个简单的server实现:
from wsgiref.simple_server import make_server
def application(env, start_response):
response_body = ['%s: %s' % (k, v) for k, v in sorted(env.items())]
response_body = '\n'.join(response_body).encode('utf-8')
status = '200 OK'
headers = [('Content-Type', 'text/plain'),
('Content-Length', str(len(response_body)))]
start_response(status, headers)
return [response_body]
if __name__ == '__main__':
server = make_server('localhost', 8080, application)
server.serve_forever()
- make_server创建一个符合WSGI标准的服务器,使用application函数处理请求。
- application函数有两个参数,env封装了客户端请求信息,服务器的各种系统参数和环境变量。start_response负责把response的状态码和头部信息告诉make_server创建的服务器。
- server.serve_forever()启动服务器,一直处理响应。
- 注意:返回的内容必须是bytes类型,所以必须对字符串使用utf-8格式就行编码。
启动程序后,在浏览器输入http://localhost:8080/xxx/yyy,我们可以看到以下响应:
...
HTTP_HOST: localhost:8080
...
PATH_INFO: /xxx/yyy
...
wsgi.errors: <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>
wsgi.file_wrapper: <class 'wsgiref.util.FileWrapper'>
wsgi.input: <_io.BufferedReader name=624>
wsgi.multiprocess: False
wsgi.multithread: False
wsgi.run_once: False
wsgi.url_scheme: http
wsgi.version: (1, 0)