Web 开发规范 — WSGI

时间:2021-08-07 21:49:07

目录

WSGI 简介

WSGI(Web Server Gateway Interface) Web 服务器网关接口。从名称上来看WSGI就是一个网关,作用就是在协议之间进行转换。具体而言,WSGI 是一个规范,它遵循这种规范将一个Web组件抽象成三个部件层:Web Server + Web Middleware + Web Application。除此之外,它还定义了 Web Server 如何与 Python 写的 Web Application进行交互,使得 Python 写的 Web Application 可以和 Web Server 能够对接起来。现在一般会使用 Apache + wsgi_mod 的组合来实现Web Services。

为什么需要 WSGI 这个规范?

在 Web Services 处理方案中,有一个方案是目前应用最广泛的:

- 部署一个 Web Server(Apache) 专门用来处理 HTTP 协议层面相关的事情,EG. 如何在一个 HOST 上提供多个不同的虚拟主机:单IP多域名,单IP多端口等。

  • 部署一个用各种语言开发( Java, PHP, Python, Ruby等 )出来的 Application,这个 Application 会接收从 Web Server 上传递过来的 Client Request,处理请求完成后,再 Return 给 Web Server,最后由 Web Server Response 给 Client 。

那么,Web Server 和 Application 之间就就需要解决交互的问题。而为了解决这个问题,就定义 Web Server 和 Application 之间交互的规范。EG. Java 的 Servlet,Python 的 WSGI 等。定义这些规范的目的是为了定义统一的标准,提升程序的可移植性。

WSGI 如何工作?

WSGI 相当于 Web Server 和 Python Application 之间的桥梁。其存在的目的有两个:

1. 让 Web Server 知道如何调用 Python Application,并且将 Client Request 传递给 Application。

2. 让 Python Application 能理解 Client Request 并执行对应操作,以及将执行结果返回给 Web Server,最终响应到Client。

当处理一个 WSGI 请求时,Server 为 Application 提供 上下文信息 和一个 回调函数, Application 处理完请求之后,使用 Server 所提供的回调函数返回相对应请求的响应。

WSGI的角色

  • Web Server end(也称之为 Server 或 Gateway)
  • Web Application end(也称之为 Application 或 Framework),WSGI Application End 的规范一般是通过具体的框架(Django/Web.py)来实现的。

Server 首先会接收到User Request,然后根据规范调用 Application 并将预处理过的 Request 传递给 Application。由 Application 处理完 Request 之后将结果返回给 Web Server,最后 Web Server 将结果封装成 HTTP Response 并响应给 User。 (重要的事情,已经是第三遍了)

Server 如何调用 Application?

每个 Application 的入口都只有一个,就是说 Server 只能通过这一个入口调用 Application 并将 Client Request 传递给 Application。所以 Server 需要知道如何找到这一个 Application 的入口:

这是由一个 Python Module(一般由框架提供) 决定的,并且这个 Python Module 中需要包含由一个名称为 application 的可调用对象(该对象无论是 Function/Class 都可以)。而这一个可调用的 application 对象就是 Application 的唯一入口了。WSGI 定义了 application 对象的样式:

def application(environ, start_response):    #该函数需要提供两个形参
pass

EXAMPLE:假设 Application 的入口文件为 /var/www/index.py ,那么首先我们需要在 Server 中配置好这一路径(让 Server 能够找到这个 Python Module),然后在 index.py 这个文件中写入下面的 Application 入口实现代码:

#使用 web.py 框架时的样式
import web #由 web.py 框架提供了可调用对象 application() 作为 Application 的入口 urls = (
'/.*', 'hello',
) class hello(object):
def GET(self):
return "Hello, world." app = web.application(urls, globals()).wsgifunc() #Server 会根据配置找到这个文件,然后调用这个 application 对象
#因为 application 对象是唯一的入口,所以不管 Client Request 的路径和数据是什么, Server 都会调用这个对象来处理,具体的 Client Request 的处理过程交由 application 对象来完成,框架的使用者一般不需要关心这个对象内部是如何工作的,框架已经对其做了封装。

application 的两个参数

Application 处理 Request 和 Response 都与 application 对象的两个参数(environ、start_response)有关。

  • environ:其引用指向一个Dict类型对象,一般存放了下列类型的数据
    • 所有和 Client 相关的上下文 Informations,这样 application 对象就可以通过 environ 参数知道 Client Request 希望访问或操作的资源是什么,还能够知道 Requuest Client 中携带了什么数据等等。
    • 一些 CGI(通用网管接口规范)中定义的环境变量
    • 至少包含了其他7个 WSGI 规范所定义的环境变量
    • 还可能包含了一些OS的环境变量以及 Web Server 相关的环境变量

CGI 规范中要求的数据成员

REQUEST_METHOD: 请求方法,String类型,'GET', 'POST','PUT','DELETE'等
SCRIPT_NAME: HTTP请求的URL_Path中的用于查找到application对象的部分(EG. Web服务器可以根据URL_Path的一部分来决定请求由哪个虚拟主机来处理这个请求)
PATH_INFO: HTTP请求的URL_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路径 EG. http://www.jmilkfan.com:80/virtual/private
SERVER_PROTOCOL: HTTP协议版本,HTTP/1.0或者HTTP/1.1
HTTP_: 和HTTP请求中的headers对应

WSGI 规范中要求的成员

wsgi.version: 表示WSGI版本,一个元组(1, 0),表示版本1.0
wsgi.url_scheme: http或者https
wsgi.input: 一个类文件的输入流,application可以通过这个获取HTTP request body
wsgi.errors: 一个输出流,当 Application 出错时,可以将错误信息写入这里
wsgi.multithread: 当application对象可能被多个线程同时调用时,这个值需要为True
wsgi.multiprocess: 当application对象可能被多个进程同时调用时,这个值需要为True
wsgi.run_once: 当server期望application对象在进程的生命周期内只被调用一次时,该值为True
  • start_resposne :start_resposne的引用指向一个回调函数(在 Server 中定义),这个回调函数会接受两个必选参数(status,resposne_headers)和一个可选参数(exc_info)。EG. start_resposne(status,resposne_headers,exc_info=None)

    当 application 对象根据 environ 参数的 Info 执行完 Client Request 的业务逻辑之后,需要返回结果给 Server 。而且 HTTP Response 需要包含 status、headers、body 等元素,所以在 application 对象将 body 作为返回值 return 之前,需要先调用 start_response()将 status、headers 先返回给 Server,同时也是告诉Server,application 对象要开始返回 body 了。当Web Server 接受完这些元素之后,就可以将这些元素封装成为 HTTP Response 响应给 Client。

    • status: 一个字符串,表示HTTP响应状态的字符串
    • response_headers: 是一个包含了以元组(header_name, header_value)作为元素的列表类型对象,分别表示HTTP响应的 headers 及其内容。
    • exc_info(可选): 在出现错误时使用,返回相关错误信息给浏览器

回调函数:就是一个通过函数指针(变量的引用)调用的函数。如果你把函数的指针(引用)作为参数传递给另一个函数,当这个指针(引用)被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。所以 application 对象会在执行完请求的操作之后才会使用参数 start_response ,回调该变量指向的函数,以此来将 status、headers return 给 Server 。

body = []
status_headers == [None,None] def status_response(status,headers):
status_headers[:] = [status,headers]
return body.append(status_headers)

application 对象的返回值

application对象的返回值 body 用于 HTTP 响应,如果没有 body 返回,那么可以返回 None。如果有 body 返回,那么要求返回 body 必须是一个可迭代的对象。Server 通过遍历这个可迭代对象可以获得 body 的全部内容。

EXAMPLE:

def application(environ, start_response):  #
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers) #将 body == ['hello, world'] 作为返回值 return 之前,先调用 start_response(status, response_headers) 将 status、headers 先返回给 Server
return ['hello, world']

再谈Server如何调用application

Server 通过配置和 Python Module 来找到 Application 的入口,在找到 application 对象之后,Server 会通过将 Client Request 所有的 Info 和一些符合规范的参数信息传递给 application 对象作为实参,以此来实现 Server 将客户端请求传递给 Application。然后 application 对象执行业务逻辑,在返回 body 之前需要调用start_resposne()回调函数将HTTP响应所需要的信息返回。最后返回一个可迭代的对象 body ,Server 最后通过遍历来获取完整的 Body 数据。

注意:environ 和 start_response() 是需要在Server中的生成和定义的。

WSGI 中间件

Web 开发规范 — WSGI

WSGI Middleware 也是 WSGI 规范的一部分。 Middleware 是一个运行在 Server 和 Application 之间的应用。它同时具备了 Server 和 Application 的角色功能,对于 Server 来说,它是一个 Application;对于 Application来说,它是一个 Server。*middleware并不修改 Server 端和 Application 端的规范,只是同时实现了这两个角色的功能而已,因此它可以在两端之间起协调作用。而且 Middleware 可以将 Cllient HTTP Request 路由给不同的 Application 。*

Middleware 是如何工作的:

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

2.Server 调用 Middleware 的 application 对象,传递的参数是environ_sstart_response_s

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

4.Middleware 再调用 Application 的 application 对象,传递参数是environ_mstart_response_m。Application 的 application 对象处理完成后,会调用 start_response_m 并且返回结果给 Middleware,存放在 result_m 中。

5.Middleware 处理 result_m,然后生成 result_s,接着调用 start_response_s,并返回结果 result_s给 Server 端。Server 端获取到 result_s 后就可以发送结果给客户端了。

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

1. Server认为middleware是一个application。

2. Application认为middleware是一个server。

3. Middleware可以有多层。

我们可以将 WSGI Middleware 了理解为 Server 和 Application 交互的一层包装,经过不同的 Middleware ,便拥有了不同的功能,EG. URL 路由转发、权限认证。因为Middleware能过处理所有通过的 request 和 response,所以要做什么都可以,没有限制。比如可以检查 request 是否有非法内容,检查 response 是否有非法内容,为 request 加上特定的 HTTP header 等。这些不同的 Middleware 组合便形成了 WSGI 的框架,比如我们将要介绍的 Paste。

WSGI的实现和部署

要使用WSGI,需要分别实现 Server 角色和 Application 角色。

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

  • Server 端的实现会比较复杂一点,这个主要是因为软件架构的原因。一般常用的Web服务器,如Apache和nginx,都不会内置WSGI的支持,而是通过扩展插件来完成。EG. Apache + mod_wsgi来支持WSGI。 Apache 和 mod_wsgi 之间通过程序内部接口传递信息,mod_wsgi 会实现 WSGI 的 Server端、进程管理以及对 application 的调用。Nginx + uWSGI,用 nginx 的协议将请求封装好,发送给应用服务器,应用服务器会实现 WSGI 的 Server 端、进程管理以及对 application 的调用。

参考资料

WSGI 简介

《Openstack 设计与实现》