您的位置:首页 > 编程语言 > Python开发

Python笔记:wsgi简介

2015-08-13 12:27 746 查看


WSGI是什么

WSGI的全称是Web Server Gateway Interface,翻译过来就是Web服务器网关接口。具体的来说,WSGI是一个规范,定义了Web服务器如何与Python应用程序进行交互,使得使用Python写的Web应用程序可以和Web服务器对接起来。WSGI一开始是在PEP-0333中定义的,最新版本是在Python的PEP-3333定义的。

对于初学者来说,上面那段就是废话,说了跟没说一样。本文的主要内容就是说清楚,WSGI到底是如何工作的。


为什么需要WSGI这个规范

在Web部署的方案上,有一个方案是目前应用最广泛的:

首先,部署一个Web服务器专门用来处理HTTP协议层面相关的事情,比如如何在一个物理机上提供多个不同的Web服务(单IP多域名,单IP多端口等)这种事情。
然后,部署一个用各种语言编写(Java, PHP, Python, Ruby等)的应用程序,这个应用程序会从Web服务器上接收客户端的请求,处理完成后,再返回响应给Web服务器,最后由Web服务器返回给客户端。

那么,要采用这种方案,Web服务器和应用程序之间就要知道如何进行交互。为了定义Web服务器和应用程序之间的交互过程,就形成了很多不同的规范。这种规范里最早的一个是CGI(Common
Gateway Interface),1993年开发的。后来又出现了很多这样的规范。比如改进CGI性能的FasgCGI,Java专用的Servlet规范,还有Python专用的WSGI规范等。提出这些规范的目的就是为了定义统一的标准,提升程序的可移植性。在WSGI规范的最开始的PEP-333中一开始就描述了为什么需要WSGI规范。

如PEP中描述,在没有一个统一协议规范以前,某个Python应用(Web框架、或者应用程序),可能仅支持运行在某一个Web Server下,应用开发者选择某个Web框架的同时,也就绑死了某个Web Server;另外,在某个Web框架写的应用代码,迁移到另外一个Web框架,不一定能跑起来;所以,为了能让用户自由选择、组合使用任意的Web框架和Web Server,该PEP规范了必要的交互方式和数据结构;

注意,WSGI不是一个软件工具,它是一种协议规范的描述,它规范了“交互方式和数据结构”,任何Web Server、Gateway和Framework、Application,只要遵从这个规范去实现,就一定能互联互通;既然是“交互”,就一定至少包含两方吧,PEP指出:

the "server" or "gateway" side(下文称为“server端”)
the "application" or "framework" side(下文称为“application端”,framework本质上也是一种应用)

“application端”,必须提供一个“可调用的对象”供“server端”调用,“可调用的对象”一般是个入口函数,应用对HTTP请求的处理,要在这个入口函数内完成,函数最终返回HTTP Respone,比如生成的HTML;

下面的示例摘自PEP 3333:
HELLO_WORLD = b"Hello world!\n"

def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [HELLO_WORLD]


“server端”,一般就是Web Server啦,最主要的任务就是接受到HTTP请求后,调用“application端提供的入口函数(比如上面的“simple_app”),另外应用程序里可能需要很多HTTP请求的信息(比如HTTP Method,GET/POST),这些信息,如何获取呢?需要“server端”在调用应用入口函数的时候作为函数参数传递,就是上面的“environ”,关于“environ”这个数据结构的规范,PEP中也是有详细描述的;

关于“server端”实现的实例代码,稍稍复杂一点,搬运到这里,也不见得帮助理解,自行到PEP 3333查阅吧;

关于Python WSGI的理论介绍,最核心的都在这儿了,更细节的协议介绍,自行查阅吧;


Quick Start

好了,终于可以动手实践了,早已按耐不住啦!我们先把上面的“simple_app”跑起来吧;
$ touch simple_app.py
$ vim simple_app.py

HELLO_WORLD = b"Hello world!\n"

def application(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [HELLO_WORLD]


好了,“application端”已经好了,那“server端”呢?我们需要搞来一个实现了Python WSGI的Web Server,这里,我选择了,uWSGI,不要被它的名字迷惑,它其实是一个功能非常全、实现了各种协议、支持各种的语言的“a
full stack for building hosting services”,因为最早支持Python,所以取了这样一个名字,官方都说,名字已经支撑不起它的野心,哈哈;

本实践中,我们要基于Python安装uWSGI:
$ pip install uwsgi


根据你的Python安装路径,uWSGI的二进制程序安装路径也会不同,比我电脑上:
$ ls /usr/local/python2.7/bin/uwsgi


现在,“application端”和“server端”,都已经准备就绪了,我们可以启动的这个简单的WSGI应用了:
uwsgi --http :9090 --wsgi-file simple_app.py


uWSGI会到simple_app.py文件中,找一个固定名字“application”的入口函数,当uWSGI收到HTTP请求时,就会调用该入口函数;

你在浏览器请求http://127.0.0.1:9090,应该会看到uWSGI的输出:
$ uwsgi --http :9090 --wsgi-file simple_app.py
[pid: 1492|app: 0|req: 1/1] 127.0.0.1 () {34 vars in 626 bytes} [Sat Jun 20 15:32:14 2015] GET / => generated 11 bytes in 0 msecs (HTTP/1.1 200) 1 headers in 44 bytes (1 switches on core 0)
[pid: 1492|app: 0|req: 2/2] 127.0.0.1 () {36 vars in 615 bytes} [Sat Jun 20 15:32:14 2015] GET /favicon.ico => generated 11 bytes in 0 msecs (HTTP/1.1 200) 1 headers in 44 bytes (1 switches on core 0)


此时,你的浏览器应该也已经有“Hello World”页面输出,大功告成!


Learn More

本文的“Hello World”示例,仅仅是展示Python WSGI的框架结构,“server端”提供的HTTP请求信息“environ”完全没有使用,无脑返回一个不变的字符串;

真正的应用,是需要根据功能要求,定制、填充那个application的入口函数的,应用开发者,可以自己代码实现,也可以依托于一些流行的WSGI框架和Python模块,比如:Flask框架、werkzeug

“server端”就没有什么发挥的空间了,选择一个支持WSGI协议的就好了,就像本文尝试的uWSGI,除非你想自己实现个Web Server;Nginx虽然不直接支持WSGI,但它支持uWSGI,这样把Nginx挡在uWSGI前面,uWSGI就可以专门处理动态的WSGI请求了,形成的架构,非常类似于PHP场景下的Nginx+PHP-FPM;


WSGI如何工作

从上文可以知道,WSGI相当于是Web服务器和Python应用程序之间的桥梁。那么这个桥梁是如何工作的呢?首先,我们明确桥梁的作用,WSGI存在的目的有两个:

让Web服务器知道如何调用Python应用程序,并且把用户的请求告诉应用程序。
让Python应用程序知道用户的具体请求是什么,以及如何返回结果给Web服务器。


WSGI中的角色

在WSGI中定义了两个角色,Web服务器端称为server或者gateway,应用程序端称为application或者framework(因为WSGI的应用程序端的规范一般都是由具体的框架来实现的)。我们下面统一使用server和application这两个术语。

server端会先收到用户的请求,然后会根据规范的要求调用application端,如下图所示:



调用的结果会被封装成HTTP响应后再发送给客户端。


server如何调用application

首先,每个application的入口只有一个,也就是所有的客户端请求都同一个入口进入到应用程序。

接下来,server端需要知道去哪里找application的入口。这个需要在server端指定一个Python模块,也就是Python应用中的一个文件,并且这个模块中需要包含一个名称为application的可调用对象(函数和类都可以),这个application对象就是这个应用程序的唯一入口了。WSGI还定义了application对象的形式:
def simple_app(environ, start_response):
pass


上面代码中的
environ
start_response
就是server端调用application对象时传递的两个参数。

我们来看具体的例子。假设我们的应用程序的入口文件是
/var/www/index.py
,那么我们就需要在server端配置好这个路径(如何配置取决于server端的实现),然后在
index.py
中的代码如下所示:

使用标准库(这个只是demo)
import wsgiref

application = wsgiref.simple_server.demo_app


使用web.py框架
import web

urls = (
'/.*', 'hello',
)

class hello(object):
def GET(self):
return "Hello, world."

application = web.application(urls, globals()).wsgifunc()


你可以看到,文件中都需要有一个application对象,server端会找到这个文件,然后调用这个对象。所以支持WSGI的Python框架最终都会有这么一个application对象,不过框架的使用者不需要关心这个application对象内部是如何工作的,只需要关心路由定义、请求处理等具体的业务逻辑。

因为application对象是唯一的入口,所以不管客户端请求的路径和数据是什么,server都是调用这个application对象,具体的客户端请求的处理有application对象完成。


application对象需要做什么

上面已经提到了,application对象需要是一个可调用对象,而且其定义需要满足如下形式:
def simple_app(environ, start_response):
pass


当server按照WSGI的规范调用了application之后,application就可以开始处理客户端的请求了,处理请求之后,application对象需要返回处理结果给server端。处理请求和返回结果这两个事情,都和server调用application对象时传递的两个参数有关。


environ参数

environ参数是一个Python的字典,里面存放了所有和客户端相关的信息,这样application对象就能知道客户端请求的资源是什么,请求中带了什么数据等。environ字典包含了一些CGI规范要求的数据,以及WSGI规范新增的数据,还可能包含一些操作系统的环境变量以及Web服务器相关的环境变量。我们来看一些environ中常用的成员:

首先是CGI规范中要求的变量:

REQUEST_METHOD: 请求方法,是个字符串,'GET', 'POST'等
SCRIPT_NAME: HTTP请求的path中的用于查找到application对象的部分,比如Web服务器可以根据path的一部分来决定请求由哪个virtual host处理
PATH_INFO: HTTP请求的path中剩余的部分,也就是application要处理的部分
QUERY_STRING: HTTP请求中的查询字符串,URL中?后面的内容
CONTENT_TYPE: HTTP headers中的content-type内容
CONTENT_LENGTH: HTTP headers中的content-length内容
SERVER_NAME和SERVER_PORT: 服务器名和端口,这两个值和前面的SCRIPT_NAME, PATH_INFO拼起来可以得到完整的URL路径
SERVER_PROTOCOL: HTTP协议版本,HTTP/1.0或者HTTP/1.1
HTTP_: 和HTTP请求中的headers对应。

WSGI规范中还要求environ包含下列成员:

wsgi.version:表示WSGI版本,一个元组(1, 0),表示版本1.0
wsgi.url_scheme:http或者https
wsgi.input:一个类文件的输入流,application可以通过这个获取HTTP request body
wsgi.errors:一个输出流,当应用程序出错时,可以将错误信息写入这里
wsgi.multithread:当application对象可能被多个线程同时调用时,这个值需要为True
wsgi.multiprocess:当application对象可能被多个进程同时调用时,这个值需要为True
wsgi.run_once:当server期望application对象在进程的生命周期内只被调用一次时,该值为True

上面列出的这些内容已经包括了客户端请求的所有数据,足够application对象处理客户端请求了。


start_resposne参数

start_response是一个可调用对象,接收两个必选参数和一个可选参数:

status: 一个字符串,表示HTTP响应状态字符串
response_headers: 一个列表,包含有如下形式的元组:(header_name, header_value),用来表示HTTP响应的headers
exc_info(可选): 用于出错时,server需要返回给浏览器的信息

当application对象根据environ参数的内容执行完业务逻辑后,就需要返回结果给server端。我们知道HTTP的响应需要包含status,headers和body,所以在application对象将body作为返回值return之前,需要先调用
start_response()
,将status和headers的内容返回给server,这同时也是告诉server,application对象要开始返回body了。


application对象的返回值

application对象的返回值用于为HTTP响应提供body,如果没有body,那么可以返回None。如果有body的化,那么需要返回一个可迭代的对象。server端通过遍历这个可迭代对象可以获得body的全部内容。


application demo

PEP-3333中有一个application的实现demo,我把它再简化之后如下:
def simple_app(environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return ['hello, world']


可以将这段代码和前面的说明对照起来理解。


再谈server如何调用application

前面已经知道server如何定位到application的入口了,也知道了application的入口的形式以及application对象内部需要完成的工作。那么,我们还需要再说一下,
environ
start_response()
是需要在server端的生成和定义的,其中关于
start_response()
的部分在规范中也有明确的要求。这部分内容太长了,不适合放在本文中,有兴趣的读者可以去看下PEP-3333,里面有一段server端的demo实现。


WSGI中间件

WSGI Middleware(中间件)也是WSGI规范的一部分。上一章我们已经说明了WSGI的两个角色:server和application。那么middleware是一种运行在server和application中间的应用(一般都是Python应用)。middleware同时具备server和application角色,对于server来说,它是一个application;对于application来说,它是一个server。middleware并不修改server端和application端的规范,只是同时实现了这两个角色的功能而已。

我们可以通过下图来说明middleware是如何工作的:



上图中最上面的三个彩色框表示角色,中间的白色框表示操作,操作的发生顺序按照1 ~ 5进行了排序,我们直接对着上图来说明middleware是如何工作的:

Server收到客户端的HTTP请求后,生成了
environ_s
,并且已经定义了
start_response_s

Server调用Middleware的application对象,传递的参数是
environ_s
start_response_s

Middleware会根据
environ
执行业务逻辑,生成
environ_m
,并且已经定义了
start_response_m

Middleware决定调用Application的application对象,传递参数是
environ_m
start_response_m
。Application的application对象处理完成后,会调用
start_response_m
并且返回结果给Middleware,存放在
result_m
中。
Middleware处理
result_m
,然后生成
result_s
,接着调用
start_response_s
,并返回结果
result_s
给Server端。Server端获取到result_s后就可以发送结果给客户端了。

从上面的流程可以看出middleware应用的几个特点:

Server认为middleware是一个application。
Application认为middleware是一个server。
Middleware可以有多层。


WSGI的实现和部署

要使用WSGI,需要分别实现server角色和application角色。

Application端的实现一般是由Python的各种框架来实现的,比如Django, web.py等,一般开发者不需要关心WSGI的实现,框架会会提供接口让开发者获取HTTP请求的内容以及发送HTTP响应。

Server端的实现会比较复杂一点,这个主要是因为软件架构的原因。一般常用的Web服务器,如Apache和nginx,都不会内置WSGI的支持,而是通过扩展来完成。比如Apache服务器,会通过扩展模块mod_wsgi来支持WSGI。Apache和mod_wsgi之间通过程序内部接口传递信息,mod_wsgi会实现WSGI的server端、进程管理以及对application的调用。Nginx上一般是用proxy的方式,用nginx的协议将请求封装好,发送给应用服务器,比如uWSGI,应用服务器会实现WSGI的服务端、进程管理以及对application的调用。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: