文档章节

理解 WSGI 框架

好铁
 好铁
发布于 2016/03/16 17:27
字数 1349
阅读 94
收藏 3
This document specifies a proposed standard interface between web servers and Python web applications or frameworks, to promote web application portability across a variety of web servers.
-—–PEP 0333

wsgi
图片源自 A Primer On WSGI


An Introduction to WSGI

在 java web 领域,支持 servlet API 的 java application 都可运行在支持 servlet API 的 web server (http server) 上。随着 web 不断发展,python application/framework 也如雨后春笋般涌出,如:zope, webware, skunkweb 等等。但是它们的接口存在差异,导致难以跨 web server 运行,所以 python 社区定义了一种如 servlet API 般标准通用的接口——WSGI。

PEP 0333 – Python Web Server Gateway Interface 是一种 web server or gateway 和 python web application or framework 之间简单通用的接口,符合这种接口的 application 可运行在所有符合该接口的 server 上。通俗的讲,WSGI 规范了一种简单的接口,解耦了 server 和 application,使得双边的开发者更加专注自身特性的开发。

  • Web server/gateway: 即 HTTP Server,处理 HTTP 协议,接受用户 HTTP 请求和提供并发,调用 web application 处理业务逻辑。通常采用 C/C++ 编写,代表:apache, nginx 和 IIS。
  • Python Web application/framework: 专注业务逻辑的 python 应用或者框架。

wsgi framework


The Application/Framework Side

Application/framework 端必须定义一个 callable object,callable object 可以是以下三者之一:

  • function, method
  • class
  • instance with a __call__ method

Callable object 必须满足以下两个条件:

  • 接受两个参数:字典(environ),回调函数(start_response,返回 HTTP status,headers 给 web server)
  • 返回一个可迭代的值

基于 callable function 的 application/framework 样例如下:

Python
1
2
3
def application ( environ , start_response ) :
     start_response ( '200 OK' , [ ( 'Content-Type' , 'text/plain' ) ] )
     return [ 'This is a python application!' ]

基于 callable class 的 application/framework 样例如下:

Python
1
2
3
4
5
6
7
8
class ApplicationClass ( object ) :
     def __init__ ( self , environ , start_response ) :
         self . environ = environ
         self . start_response = start_response
 
     def __iter__ ( self ) :
         self . start_response ( '200 OK' , [ ( 'Content-type' , 'text/plain' ) ] )
         yield "Hello world!n"

The Server/Gateway Side

Server/gateway 端主要专注 HTTP 层面的业务,重点是接收 HTTP 请求和提供并发。每当收到 HTTP 请求,server/gateway 必须调用 callable object:

  • 接收 HTTP 请求,但是不关心 HTTP url, HTTP method 等
  • 为 environ 提供必要的参数,实现一个回调函数 start_response,并传给 callable object
  • 调用 callable object

我们直接使用支持 WSGI 框架的 wsgiref 库,编写一个样例:

1
2
3
4
5
6
7
8
9
10
# application/framework side
def application ( environ , start_response ) :
     start_response ( '200 OK' , [ ( 'Content-Type' , 'text/plain' ) ] )
     return [ 'This is a python application!' ]
 
# server/gateway side
if __name__ == '__main__' :
     from wsgiref . simple_server import make_server
     server = make_server ( '0.0.0.0' , 8080 , application )
     server . serve_forever ( )

运行后,对于任意的 url 和 method,本例的返回值均为 ‘This is a python application!’:

1
2
3
4
5
$ curl 127.0.0.1 : 8080
This is a python application !                                                                                                                                                                                                                                                   
 
$ curl 127.0.0.1 : 8080 / test
This is a python application !

Middleware: Components that Play Both Sides

Unix philosophy: do one thing and do it well.

wsgi framework middleware

Middleware 处于 server/gateway 和 application/framework 之间,对 server/gateway 来说,它相当于 application/framework;对 application/framework 来说,它相当于 server/gateway。每个 middleware 实现不同的功能,我们通常根据需求选择相应的 middleware 并组合起来,实现所需的功能。比如,可在 middleware 中实现以下功能:

  • 根据 url 把用户请求调度到不同的 application 中。
  • 负载均衡,转发用户请求
  • 预处理 XSL 等相关数据
  • 限制请求速率,设置白名单

middleware

WSGI 的 middleware 体现了 unix 的哲学之一:do one thing and do it well。事实上,在定义 WSGI 框架的时候,设计者就要求 server/gateway 和 application/framework 双方尽可能的简单,同时也要求 middleware 设计的简单而专一,PEP 333 提到:

If middleware can be both simple and robust, and WSGI is widely available in servers and frameworks, it allows for the possibility of an entirely new kind of Python web application framework: one consisting of loosely-coupled WSGI middleware components.

本例实现了一个 IPBlacklist 的 middleware:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class IPBlacklistMiddleware ( object ) :
     def __init__ ( self , app ) :
         self . app = app
 
     def __call__ ( self , environ , start_response ) :
         ip_addr = environ . get ( 'HTTP_HOST' ) . split ( ':' ) [ 0 ]
         if ip_addr not in ( '127.0.0.1' ) :
             return forbidden ( start_response )
 
         return self . app ( environ , start_response )
 
def forbidden ( start_response ) :
     start_response ( '403 Forbidden' , [ ( 'Content-Type' , 'text/plain' ) ] )
     return [ 'Forbidden' ]
 
def application ( environ , start_response ) :
     start_response ( '200 OK' , [ ( 'Content-Type' , 'text/plain' ) ] )
     return [ 'This is a python application!' ]
 
if __name__ == '__main__' :
     from wsgiref . simple_server import make_server
     application = IPBlacklistMiddleware ( application )
     server = make_server ( '0.0.0.0' , 8080 , application )
     server . serve_forever ( )

测试如下:

1
2
3
4
5
6
7
# 从本机测试
$ curl 127.0.0.1 : 8080 / test
This is a python application !
 
# 从其它主机测测试                                                                                                                                                                                                
$ curl 10.10.10.2 : 8080 / test                                                                                                                                                                                                                                    
Forbidden

Path Dispatching

至此样例的一个不足之处是对于任意的 url 和 method,程序的返回值均为 ‘This is a python application!’,所以我们需要增加 path dispatch 功能。由于 WSGI 框架下的 server/gateway 不处理 url 和 method,所以 url mapping 需由 application/framework 端完成。注意到参数 environ,它包含以下变量:

  • REQUEST_METHOD: 即 HTTP method
  • PATH_INFO: 即 HTTP url

所以 application/framework 可以根据 environ 的 REQUEST_METHOD 和 PATH_INFO 实现 path dispatch,样例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class IPBlacklistMiddleware ( object ) :
     def __init__ ( self , app ) :
         self . app = app
 
     def __call__ ( self , environ , start_response ) :
         ip_addr = environ . get ( 'HTTP_HOST' ) . split ( ':' ) [ 0 ]
         if ip_addr not in ( '127.0.0.1' ) :
             return forbidden ( start_response )
 
         return self . app ( environ , start_response )
 
def dog ( start_response ) :
     start_response ( '200 OK' , [ ( 'Content-Type' , 'text/plain' ) ] )
     return [ 'This is dog!' ]
 
def cat ( start_response ) :
     start_response ( '200 OK' , [ ( 'Content-Type' , 'text/plain' ) ] )
     return [ 'This is cat!' ]
 
def not_found ( start_response ) :
     start_response ( '404 NOT FOUND' , [ ( 'Content-Type' , 'text/plain' ) ] )
     return [ 'Not Found' ]
 
def forbidden ( start_response ) :
     start_response ( '403 Forbidden' , [ ( 'Content-Type' , 'text/plain' ) ] )
     return [ 'Forbidden' ]
 
def application ( environ , start_response ) :
     path = environ . get ( 'PATH_INFO' , '' ) . lstrip ( '/' )
     mapping = { 'dog' : dog , 'cat' : cat }
 
     call_back = mapping [ path ] if path in mapping else not_found
     return call_back ( start_response )
 
if __name__ == '__main__' :
     from wsgiref . simple_server import make_server
     application = IPBlacklistMiddleware ( application )
     server = make_server ( '0.0.0.0' , 8080 , application )
     server . serve_forever ( )

测试如下:

1
2
3
4
5
6
7
8
$ curl 127.0.0.1 : 8080 / dog
This is dog !                                                                                                                                                                                                                                                                   
 
$ curl 127.0.0.1 : 8080 / cat
This is cat !                                                                                                                                                                                                                                                                   
 
$ curl 127.0.0.1 : 8080 / monkey
Not Found

本文转载自:http://python.jobbole.com/84372/

共有 人打赏支持
好铁
粉丝 36
博文 266
码字总数 78672
作品 0
朝阳
程序员
私信 提问
深入理解 Python WSGI:一起写一个 Web 服务器

导读: 本系列深入浅出的讲述了如何用 Python 从 0 开始,写一个 web 服务器,并让其与业界流行的 web 框架协同工作,最后还进一步完善了开头的 web 服务器 demo,让其可以支持多并发请求的处...

大数据之路
2015/08/03
0
0
从零开始搭建论坛(二):Web服务器网关接口

在 从零开始搭建论坛(一):Web服务器与Web框架 中我们弄清楚了Web 服务器、Web 应用程序、Web框架的概念。对于 Python 来说,越来越多的 Web 框架面世,在给我们更多选择机会的同时,也限制...

selfboot
2016/08/07
0
0
深入理解 nova-api 的 WSGI

本文是 理解 WSGI 框架 的下篇,重点介绍 WSGI 框架下一些常用的 python module,并使用这些 module 编写一个类似 nova-api 里 WSGI 的简单样例,最后分析 nova 是如何使用这些 module 构建其...

koala bear
2013/10/18
0
0
如何理解 Python Web 开发?

因为 python代码的优雅美观且易于维护这一特点,越来越多的人选择使用Python做Web开发。而Python的Web框架百花齐放,目前比较流行的框架有大包大揽的Django,小巧灵活的Flask、Bottle,还有性...

Python资料
2018/06/04
0
0
网关协议

CGI CGI即通用网关接口(Common Gateway Interface),是外部应用程序(CGI程序)与Web服务器之间的接口标准,是在CGI程序和Web服务器之间传递信息的规程。CGI规范允许Web服务器执行外部程序,...

lls3018
2016/07/12
9
0

没有更多内容

加载失败,请刷新页面

加载更多

想问一下C++里queue要怎么遍历

如题,想知道怎么遍历<queue>对象的元素? 貌似不能遍历。要么全部pop push一遍,要么换个容器呗。 queue是先进后出的数据类型,只能不断读top()然后再pop()掉。故意把遍历操作隐藏掉了,...

shzwork
昨天
2
0
Ubuntu 18.04.2 LTS nvidia-docker2 : 依赖: docker-ce (= 5:18.09.0~3-0~ubuntu-bionic)

平台:Ubuntu 18.04.2 LTS nvidia-docker2 版本:2.0.3 错误描述:在安装nvidia-docker2的时候报dpkg依赖错误 nvidia-docker2 : 依赖: docker-ce (= 5:18.09.0~3-0~ubuntu-bionic) 先看一下依......

Pulsar-V
昨天
2
0
学习笔记1-goland结构体(struct)

写在前面:若有侵权,请发邮件by.su@qq.com告知。 转载者告知:如果本文被转载,但凡涉及到侵权相关事宜,转载者需负责。请知悉! 本文永久更新地址:https://my.oschina.net/bysu/blog/3036...

不最醉不龟归
昨天
3
0
【转】go get命令使用socket代理

由于某些不可描述的原因,国内使用go get命令安装某些包的时候会超时导致失败,比如net包、sys包、tools包等。第一种解决办法就是自己从git上下载后添加链接到GOPATH中,比如: 1234...

yiduwangkai
昨天
6
0
从上往下打印出二叉树的每个节点,同层节点从左至右打印。

//第一种做法 public class Solution { public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) { ArrayList <Integer> li=new ArrayList<Integer>(); ArrayList <TreeN......

南桥北木
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部