要读懂本篇,你至少得写过一个python的web程序,并且把它部署到web服务器上过。

什么是wsgi

假设你写了一个python的web程序,并部署到了nginx上,那么一个http request的处理流程一般是下面这样:

client/浏览器(发送请求)  - - - - > web服务器(转发该请求) - - - - > 你的程序(1. 处理请求。2.生成结果。3.返回结果)
|
|
V
client/浏览器(收到结果) <- - - -web服务器(接受你的程序返回的结果,并返回给浏览器)

问题是,开发web程序的人和开发web服务器的人并没有沟通,为什么web application 能够部署在web 服务器上呢? 这是因为他们都遵循了相同的规则 -- WSGI。 WSGI 全名叫 web server gateway interface, 是python 中web程序和web服务器沟通的标准。 简单的说,只要你写的web application 遵守这个规则,另一个人开发的web 服务器也遵循这个规则,那么你写的程序就能运行在他开发的web服务器上。

wsgi application

WSGI对application和server做出了不同的规定,大部分人不需要写server,因此我们只了解一下application的规定即可。首先看一个标准的WSGI application。该application实现的逻辑就是,返回client所使用的http method(GET/POST等):

def application(env, callback):
"""
我们知道,web server 接到浏览器请求后会转发请求给 application。 请求的内容,以及一些环境变量如client IP, http method等会放在一个字典对象中传递给application。 就是这里的env参数。
同时,server还会传递一个callback对象给application. 这样application可以通过该对象返回给server一些信息。所以,wsgi的application必须要有这两个参数
""" # EVN中包含了client 的各种环境变量,从中可以获取http method
response_body = 'The request method was %s' % env['REQUEST_METHOD'] # HTTP response的结果通常会有一个状态值和message。
# 比如, 状态有500/404/403/200等, message 有OK/Not Found/Internal error/Forbidden 等。
# 这里我们返回200 OK. 代表成功处理请求
status = '200 OK' # HTTP response 的header 会包含一些必要的信息以便浏览器方便处理response。 这些必要的header信息需要按照以下格式放入list中
response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))] # 利用callback 告诉server这次访问的状态信息以及 response header。
callback(status, response_headers) # response 的body必须是一个iterable的对象。 我们这里把response body 放入一个list。(string虽然也是可迭代对象,但string的迭代次数显然远远大于这个list。会影响效率)
return [response_body]

总结上面的代码,可知道WSGI 对application 只做了如下几点规定:

1. application 必须是可调用的object, 如函数,实现了__call__的对象

2. application 必须接收一个字典类型的参数用于保存环境变量,一个callback function

3. application 内要使用callback 返回 http status 和 response header

4. 返回值必须是iterable的

我们用一个简单的wsgi server来调用一下上面的 application, 看一下效果。

from wsgiref.simple_server import make_server
httpd = make_server('localhost', 8051, application)
httpd.serve_forever()

上面代码启动了一个wsgi server监听在localhost:8051, 并且把我们的application 部署到了该server上。 我们访问localhost 8051,看一下效果

    [root@netflow-AIO ~]# curl -i http://127.0.0.1:8051
HTTP/1.0 200 OK
Date: Sat, 26 Nov 2016 07:34:38 GMT
Server: WSGIServer/0.1 Python/2.7.5
Content-Type: text/plain
Content-Length: 26 The request method was GET

可以看到, application 成功返回了代码中指定的response message,并且http code / http msg / response header 也都和代码中指定的一样。

wsgi server

大多数情况我们不需要编写server,因此不必了解太深。 但至少我们应该知道,在server中一定有这样的代码

callable(env, callback)

这里是server 调用application的地方。callable 是application对象的名字, env 是包含环境变量的字典,callback 是server传递给application的callback函数。想在applicaiton 被调用之前加一些逻辑,你可以在这行代码之前做改动。想修改application的返回结果,你可以在这行代码之后加逻辑

wsgi 中间件

WSGI 标准中除了application / server 还有一个很重要的概念--middleware。 其实 middleware 很好理解。 我们知道 server 会调用 application 处理请求, application 会把请求结果返回给server. Middleware,顾名思义就是在server 和 application 中间的一个对象。 对于server 来说 middleware 是一个application, 对于 application 来说, middleware 是一个server。也就是说,middleware同时实现了WSGI中对 application 和 server所做的规定。

因为WSGI的这一特性,很多按照wsgi构建的系统都会有如下这样的结构

app1 app2 app3 ... appN server

当一个http request发给server 时, 该请求会依次传递给 appN ... app3 app2 app1, 然后response 又会从app1 开始往回传递直到 server 最后返回给客户端。也就是说,WSGI的系统是可以无限堆叠中间件的,你可以把自己的业务逻辑包装成一个个的中间件,需要的时候部署上去,不需要就拿下来。 openstack中所有的服务都是依照wsgi写的,因此也遵循这种结构。 比如neutron 服务的结构:

request_id catch_errors authtoken keystonecontext extensions neutronapiapp_v2_0

其中 catch_errors , authtoken, keystonecontext , extensions 都是WSGI的中间件。 了解了这些之后,我们可以做很多事情, 比如拿掉 authoken 和 keystonecontext, 这样调用neutron 的API就可以绕过权限校验了,因为这两个中间件是做权限校验用的。

App 中间件 server示例

接下来我们用一个例子展示一下 wsgi application middleware server 到底是什么样的。

首先我们设计一个app,假设带着一个参数访问该app,它将告诉你该参数是奇数还是偶数。 api的url 假设是 check_number/, 代码如下:

def check_number(env, start_response):

    number = int(env.get('PATH_INFO').split('/')[-1])
response_body = 'even' if number%2 == 0 else 'odd' status = '200 OK'
response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))]
start_response(status, response_headers)
return [response_body]

代码非常简单,因为我们假设url为check_number/, 所以int(env.get('PATH_INFO').split('/')[-1])可以很轻松得到参数。这里我们不考虑异常。

接下来,我们设计一个middleware, 用于校验, 该middleware会检查http 访问的header, 如果带有特定的token,则认为是可信任用户,可以放行,继续访问check_number, 否则返回403 Forbidden。 代码如下:

class AuthToken(object):
def __init__(self, app):
self.app = app def __call__(self, env, start_response):
if env.get('HTTP_TOKEN') == '222':
return self.app(env, start_response)
else:
response_body = 'Auth failed'
status = '403 forbidden'
response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))]
start_response(status, response_headers)
return [response_body]

这里稍微解释一下。 所谓中间件,肯定要能被server 调用, 所以必须提供__call__(env, start_response)接口,并且返回可迭代的return 如[response_body]。 但同时它又需要调用别的app。

看我们AuthToken的代码,可以看到,它的逻辑就是,如果token合法(等于222) ,则继续调用application, 否则返回错误。

有了验证中间件AuthToken, 有了业务逻辑check_number, 我们还可以再加一个中间件 CheckError。 该中间件捕捉所有的error / exception, 返回一个用户友好的消息,如 service maintain 等, 而不是直接把异常抛给用户。 代码如下:

class CheckError(object):
def __init__(self, app):
self.app = app def __call__(self, env, start_response):
try:
return self.app(env, start_response)
except Exception as e:
response_body = 'Server is maintaining '
status = '503 service maintain'
response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))]
start_response(status, response_headers)
return [response_body]

逻辑很简单,如果有错误,则返回 service maintain, 否则返回正常的业务处理结果。

接下来可以部署这些程序到服务器

from wsgiref.simple_server import make_server
httpd = make_server('10.79.99.86', 8051, CheckError(AuthToken(check_number)))
httpd.serve_forever()

尝试访问:

[root@netflow-AIO bin]# curl http://10.79.99.86:8051/check_number/100 -H "token:222"
even [root@netflow-AIO bin]# curl http://10.79.99.86:8051/check_number/100 -H "token:wrong"
Auth failed [root@netflow-AIO bin]# curl http://10.79.99.86:8051/check_number/aaa -H "token:222"
Server is maintaining

后面可以看到,网上对这种wsgi模块堆叠的部署方式叫pipeline。 但其实它和linux的pipeline不太一样, 其实是一种堆栈式的调用,调用流程图如下:

request ----> server 生成 env 和 start_response
----> Check_Error.__call__
---->AuthToken.__call__
---->Check_Number(env,start_response)
<----Check_Number 返回
<----AuthToken 返回
<----Check_Error 返回
response <----

WSGI in openstack

前面提到,写一个wsgi application 有若干规则要遵守,比如提供两个参数 env 和 callback , 再比如return 一个可迭代对象。 这些规则其实跟业务逻辑无关。作为一个程序员,你可能更关心业务逻辑而不是wsgi的语法。你可能更希望写一个下面这样的程序

def application(req):
# some logic
return response

req 是客户的http 请求, response 是程序进行处理后的返回。这样对程序员来说就简单多了,只需要关心业务逻辑而不用关心wsgi稍显繁琐的语法。openstack中就实现了这样的简化,在openstack中,实现一个wsgi 程序可以这样写

import webob
import webob.dec
from webob.response import Response @webob.dec.wsgify
def myapp(req):
return Response(body=req.url) from wsgiref.simple_server import make_server
httpd = make_server('localhost', 8051, myapp)
httpd.serve_forever()

webob是用于web编程的一个model,通过它的wsgify这个装饰器,你可以很方便的把一个 接受 request 参数,返回response参数的函数转成wsgi application。现在你可以不必关心wsgi的细节,只要实现一个接受request 返回 response的函数就可以了。 默认情况下, req 是webob.request.Request类型的对象。

how to read openstack code : wsgi的更多相关文章

  1. how to read openstack code : routes

    When coding a web system, you have to think about an important problem, how to map urls to logic. Op ...

  2. how to read openstack code: loading process

    之前我们了解了neutron的结构,plugin 和 extension等信息.这一章我们看一下neutron如何加载这些plugin和extension.也就是neutron的启动过程.本文涉及的代 ...

  3. how to read openstack code : stevedore

    学习了WSGI/Paste deploy后,还需要对一些在openstack中一些package有一些了解,才能更好的理解openstack的代码 What is stevedore 我们在写代码的时 ...

  4. how to read openstack code

    本文的目的不是介绍openstack.我们这里假设你已经知道了openstack是什么,能够做什么.所以目的是介绍如何阅读openstack的代码.通过读代码来进一步学习openstack. 转载要求 ...

  5. how to read openstack code: request extension

    We have learned resource extension and action extension. This post we will write a request extension ...

  6. how to read openstack code: action extension

    之前我们看过了core plugin, service plugin 还有resource extension. resource extension的作用是定义新的资源.而我们说过还有两种exten ...

  7. how to read openstack code: service plugin

    We have learned core plugin, service plugin and extension in last post. Now let`s review: Core Plugi ...

  8. how to read openstack code: Core plugin and resource extension

    本章我们将写一个自己的core plugin 和一个resource extension来加深理解.(阅读本文的前提是你已经理解了restful以及stevedore等内容) 什么是 core plu ...

  9. how to read openstack code: Neutron architecture

    今天这一章节非常重要.我们知道neutron是一个非常复杂的系统,由很多组件构成.研究这样一个复杂的系统,正确的顺序应该是现在宏观上对其整体结构有所了解,然后再由针对性的对其组件进行深入了解.本章要做 ...

随机推荐

  1. springmvc 的配置 annotation-config/annotation-drive/ component-scan 区别

    1. <context:annotation-config /> 作用隐式的配置注解的加载类,默认的加载了AutowiredAnnotationBeanPostProcessor(auto ...

  2. Farseer.net轻量级ORM开源框架 V1.2.1版本升级消息

    提交版本V1.2.11.修复实体未设置主键时,无法找到主键ID字段,改为无主键时默认为"ID”字段2.新增:SqlServer2000Provider数据库驱动3.新增:DbContextI ...

  3. Swift 关键字 inout - 让值类型以引用方式传递

    两种参数传递方式 值类型 传递的是参数的一个副本,这样在调用参数的过程中不会影响原始数据. 引用类型 把参数本身引用(内存地址)传递过去,在调用的过程会影响原始数据. 在 Swift 众多数据类型中, ...

  4. cf536d——优先队列的运用

    题目 题目:Lunar New Year and a Wander 题目大意:给定一个n个顶点(编号1~n).m条边的图,求从顶点1出发的字典序最小的路径(途径的边可重复). 思路 使用一个优先队列就 ...

  5. 反射(hasattr,getattr,delattr,setattr)

    反射(hasattr,getattr,setattr,delattr) 反射在类中的使用 反射就是通过字符串来操作类或者对象的属性 反射本质就是在使用内置函数,其中反射有四个内置函数: hasattr ...

  6. Less简介及安装

    CSS的短板 作为前端学习者的我们 或多或少都要学些 CSS ,它作为前端开发的三大基石之一,时刻引领着 Web 的发展潮向. 而 CSS 作为一门标记性语言,可能 给初学者第一印象 就是简单易懂,毫 ...

  7. ORA-39126: Worker unexpected fatal error in KUPW$WORKER.PUT_DDLS

    末尾加上EXCLUDE=STATISTICS /home/opt/oracle/11g/bin/expdp user/password directory=backup2 network_link=d ...

  8. Drop和Truncate与Delete的区别

    1.Drop DROP TABLE test; 删除表test,并释放空间,将test删除的一干二净.(结构也被完全删除) 2.Truncate TRUNCATE test; 删除表test里的内容, ...

  9. zay大爷的神仙题目 D1T3-膜你抄

    依旧是外链 锦鲤抄 [题目背景] 你在尘世中辗转了千百年 却只让我看你最后一眼 火光描摹容颜燃尽了时间 别留我一人,孑然一身 凋零在梦境里面. ——银临&云の泣<锦鲤抄> [问题描 ...

  10. Jackson入门

    Jackson 框架,轻易转换JSON Jackson可以轻松的将Java对象转换成json对象和xml文档,同样也可以将json.xml转换成Java对象. 前面有介绍过json-lib这个框架,在 ...