4. WSGI基础


4.1. 一系列术语

web server

指软件程序,它从客户端(一般指浏览器)接受请求,然后返回一个Response。特别需要注意,web server不创建Response,而是仅仅返回请求。所以一个server需要和web app进行通信,而web app才是创建Response的实体。

web app

web server会从该处取得Response。web app的职责是根据url创建Response然后返回给server。server仅仅将该Response返回给客户端。

wsgi

wsgi是一个接口,一个规范或者一系列规则集合。他不是一个软件,也不是一个框架。

wsgi之所以出现,是因为 app 需要和 server 进行通信。wsgi 指定 app 端和 server 端 的需要实现的接口规范,这样他们就可以互相通信。所以一个兼容 wsgi 的 server 可以和一个兼容wsgi的app通信。

在wsgi架构中,WSGI app要求能够是被调用的,并提供给 server 。 所以当 server 接收到一个 request 时,可以调用web app生成 Response。

#web_app.py
from wsgiref.simple_server import make_server

def application(environ, start_response):
    path = environ.get('PATH_INFO')
    if path == '/':
        response_body = "Index"
    else:
        response_body = "Hello"
    status = "200 OK"
    response_headers = [("Content-Length", str(len(response_body)))]
    start_response(status, response_headers)
    return [response_body]

httpd = make_server(
    '127.0.0.1', 8051, application)

httpd.serve_forever()

在命令行执行python web_app.py,然后访问http://127.0.0.1:8051/和http://127.0.0.1:8051/abcd,第一个返回index,第二个返回hello。

逐步分析代码:

  • make_server函数可以用来创建一个兼容wsgi的server;
  • 我们创建一个可调用服务application,你可以认为它是一个web app;
  • make_server创建了一个兼容wsgi的server,所以在该例子中,httpd是web server;
  • make_server的第三个参数需要传递web app,web server从该app里取得Response。

当请求来到时,监听8051端口的server会调用web app,在该例子里是application。

web app代码的更多细节:

  • web app也需要是兼容wsgi的;
  • server会用两个参数调用web app,所以web app需要带有两个参数,这是web app兼容wsgi的条件之一;
  • 第一个参数包含request的信息;示例中,我们从中获取请求的path;
  • 第二个参数应该是可调用的,app使用该参数通知server Response Status,还可以用来设置Response headers,这是web app兼容wsgi的第二个条件;
  • 我们同时满足使得application兼容wsgi的两个条件;
  • 其次application创建了一个Response,并返回给wsgi server;
  • 最终,server返回该Response给客户端;

我们可以很方便的切换到其他的web server。例如使用gunicorn代替wsgiref。

注释掉web_app.py最后两行,然后:

gunicorn web_application:application --bind=localhost:8051

同样:

  • 我们需要告诉gunicorn他所调用的application;
  • gunicorn所监听的端口和host;
  • 在实例中,我们的可调用app在文件web_app.py中,所以在命令行中使用:web_application:application

4.2. wsgi 中间件

wsgi 中间件也是一个可调用的app,它接受另一个app为参数,并返回包装后的app对象,从而实现 其他额外的功能。

请看例子,Upperware就是一个中间件,它的作用是把simple_app返回的内容全部转换成大写:

def simple_app(environ, start_response):
        status = '200 OK'
        response_headers = [('Content-type','text/plain')]
        start_response(status, response_headers)
        return ['Hello world!\n']

class Upperware:
   def __init__(self, app):
          self.wrapped_app = app

   def __call__(self, environ, start_response):
          for data in self.wrapped_app(environ, start_response):
                 return data.upper()

from wsgiref.simple_server import make_server

application = Upperware(simple_app)
httpd = make_server('127.0.0.1', 8051, application)
httpd.serve_forever()

4.3. 特别注意

Important

wsgi只规定了web server和web app之间如何通信。但是,一般而言,不同的URL path应该用不同的app进行 处理,但是wsgi对此并未规定。就上面的例子而言,由于只定义了一个app,因此,只要IP 和端口正确的所有 http 请求,都将由simple_app处理。

对于如何将不同的URL path分发给不同的app进行处理,这就是其他库的任务了。如典型的pasteDelopy,它 就是通过配置文件定义实现,在openstack等项目中使用!

4.4. Unicode问题

请看pep-3333 wsgi规范关于unicode 的描述:

HTTP协议不直接支持unicode,它的接口也不支持。因此app需要处理encoding/decoding: 所有的strings(server传来的和传递给server的)都只能是str类型或者bytes类型,决不能 是unicode。在需要string对象而返回unicode对象的地方,结果是未定义的!

同样需要指出:传递给 start_response 回调函数的strings(作为HTTP 响应状态码和 头部)需要服从RFC-2616的编码规定。因此:他们只可能是ISO-8859-1字符集或者RFC-2047多媒体编码!

在python平台上,str和StringType类型都是基于unicode的(如:Jython, IronPython, Python3); 该规范里涉及到的所有strings只能包含 ISO-8859-1 编码规则列出的码点。 wsgi app 提供包含任意其他unicode字符集或者码点的strings都是严重错误。 类似的,servers或者gateway也不应该给一个app提供包含其他unicode字符集的strings

再次强调:该规范里涉及的所有string对象只能是str或者StringType,而不能是unicode 或者UnicodeType; 即使有些平台str或者StringType对象支持超过 8bits/每字符,也可能只有低8 位字符可用。

如果该规范里涉及到的值为”bytestrings“(如:wsgi.input, 传递给write(),或者由app yield产生), 他们的类型只能是bytes(在Python3中),或者str(以前的Python版本!)