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

什么是wsgi

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

  1. client/浏览器(发送请求) - - - - > web服务器(转发该请求) - - - - > 你的程序(1. 处理请求。2.生成结果。3.返回结果)
  2. |
  3. |
  4. V
  5. 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等):

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

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

  1. 1. application 必须是可调用的object, 如函数,实现了__call__的对象
  2. 2. application 必须接收一个字典类型的参数用于保存环境变量,一个callback function
  3. 3. application 内要使用callback 返回 http status response header
  4. 4. 返回值必须是iterable

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

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

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

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

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

wsgi server

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

  1. 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构建的系统都会有如下这样的结构

  1. app1 app2 app3 ... appN server

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

  1. 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/, 代码如下:

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

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

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

  1. class AuthToken(object):
  2. def __init__(self, app):
  3. self.app = app
  4. def __call__(self, env, start_response):
  5. if env.get('HTTP_TOKEN') == '222':
  6. return self.app(env, start_response)
  7. else:
  8. response_body = 'Auth failed'
  9. status = '403 forbidden'
  10. response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))]
  11. start_response(status, response_headers)
  12. return [response_body]

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

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

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

  1. class CheckError(object):
  2. def __init__(self, app):
  3. self.app = app
  4. def __call__(self, env, start_response):
  5. try:
  6. return self.app(env, start_response)
  7. except Exception as e:
  8. response_body = 'Server is maintaining '
  9. status = '503 service maintain'
  10. response_headers = [('Content-Type', 'text/plain'), ('Content-Length', str(len(response_body)))]
  11. start_response(status, response_headers)
  12. return [response_body]

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

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

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

尝试访问:

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

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

  1. request ----> server 生成 env start_response
  2. ----> Check_Error.__call__
  3. ---->AuthToken.__call__
  4. ---->Check_Number(env,start_response)
  5. <----Check_Number 返回
  6. <----AuthToken 返回
  7. <----Check_Error 返回
  8. response <----

WSGI in openstack

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

  1. def application(req):
  2. # some logic
  3. return response

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

  1. import webob
  2. import webob.dec
  3. from webob.response import Response
  4. @webob.dec.wsgify
  5. def myapp(req):
  6. return Response(body=req.url)
  7. from wsgiref.simple_server import make_server
  8. httpd = make_server('localhost', 8051, myapp)
  9. 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. 观锁和乐观锁——《POJOs in Action》

    1        事务隔离 事务隔离是数据库提供的功能. SQL Server通过SET TRANSACTION ISOLATION LEVEL语句设置事务隔离级别: SET TRANSACTION ...

  2. 洛谷P2863 [USACO06JAN]牛的舞会The Cow Prom

    代码是粘的,庆幸我还能看懂. #include<iostream> #include<cstdio> #include<cmath> #include<alg ...

  3. chosen-bootstrap使用技巧

    1.页面加载完成后,通过js方式设置值,无法有效显示的问题. 解决:先设置值,让后在进行初始化操作. // 设置select选中值 $("#type").val(type); // ...

  4. Java常用工具类---image图片处理工具类、Json工具类

    package com.jarvis.base.util; import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStre ...

  5. 签名mobileconfig 重签ipa

    .mobileconfig文件的签名 生成Apache专用的三个证书 双击[2331135_zgp.ink_chain.crt]可以看到它是个根证书,在mac上,双击它,导入到证书中心,然后再导出为p ...

  6. 服务器设置禁ping

    //设置Linux服务器禁ping!!!终端命令行直接输入 echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all 这个是关闭ping的命令. 如果你想要 ...

  7. java匹配http或https的url的正则表达式20180912

    package org.jimmy.autosearch20180821.test; import java.util.regex.Matcher; import java.util.regex.Pa ...

  8. 初始化react项目

    react脚手架 npm install -g create-react-app 国内npm一般下载比较慢或者是常出现下载失败的情况,我们可以指定下载的仓库: npm install -g creat ...

  9. 学习React从接受JSX开始

    详情参考官方JSX规范 虽然JSX是扩展到ECMAScript的类XML语法,但是它本身并没有定义任何语义.也就是说它本身不在ECMAScript标准范围之内.它也不会被引擎或者浏览器直接执行.通常会 ...

  10. 启发式合并 CodeForces - 600E

    启发式合并最重要的思想就是指的是每次将小集合拷贝合并至大集合.考虑每个元素的合并开销.对于合并次数最多的那个元素来说,它每合并一次,所在集合的规模扩大两倍,最多只会合并 logN 次,因而对于所有元素 ...