Python web开发中,服务端程序可分为2个部分:

  1. 服务器程序(用来接收、整理客户端发送的请求)

  2. 应用程序(处理服务器程序传递过来的请求)

    在开发应用程序的时候,我们会把常用的功能封装起来,成为各种框架,比如Flask,Django,Tornado(使用某框架进行web开发,相当于开发服务端的应用程序,处理后台逻辑)

    但是,服务器程序和应用程序互相配合才能给用户提供服务,而不同应用程序(不同框架)会有不同的函数、功能。 此时,我们就需要一个标准,让服务器程序和应用程序都支持这个标准,那么,二者就能很好的配合了

    WSGI:wsgi是python web开发的标准,类似于协议。它是服务器程序和应用程序的一个约定,规定了各自使用的接口和功能,以便二和互相配合

WSGI应用程序的部分规定

  1. 应用程序是一个可调用的对象

    可调用的对象有三种:

    1. 一个函数
    2. 一个类,必须实现__call__()方法
    3. 一个类的实例
  2. 这个对象接收两个参数

    从源码中,我们可以看到,这两个参数是environ, start_response. 以可调用对象为一个类为例:

    class application:
    def __call__(self, environ, start_response):
    pass
  3. 可调用对象需要返回一个可迭代的值。以可调用对象为一个类为例:

    class application:
    def __call__(self, environ, start_response):
    return [xxx]

WSGI服务器程序的部分规定

  1. 服务器程序需要调用应用程序

    def run(application):     #服务器程序调用应用程序
    environ = {} #设定参数 def start_response(xxx): #设定参数
    pass
    result = application(environ, start_response) #调用应用程序的__call__函数(这里应用程序是一个类)
    def write(data):
    pass
    def data in result: #迭代访问
    write(data)
     服务器程序主要做了以下的事:
    1. 设定应用程序所需要的参数
    2. 调用应用程序
    3. 迭代访问应用程序的返回结果,并传给客户端

Middleware

middleware是介于服务器程序和应用程序中间的部分,middleware对服务器程序和应用程序是透明的。

对于服务器程序来说,middleware就是应用程序,middleware需要伪装成应用程序,传递给服务器程序
对于应用程序来说,middleware就是服务器程序,middleware需要伪装成服务器程序,接受并调用应用程序

服务器程序获取到了客户端请求的URL,需要把URL交给不同的函数处理,这个功能可以使用middleware实现:

# URL Routing middleware
def urlrouting(url_app_mapping):
def midware_app(environ, start_response): #函数可调用,包含2个参数,返回可迭代的值
url = environ['PATH_INFO']
app = url_app_mapping[url] #获得对应url的应用程序
result = app(environ, start_response) #调用应用程序
return result
return midware_app

函数midware_app就是middleware:

一方面,midware_app函数设置了应用程序所需要的变量,并调用了应用程序。所以对于应用程序来说,它是一个服务器程序

另一方面,midware_app函数是一个可调用的对象,接收两个参数,同时可调用对象返回了一个可迭代的值。所以对于服务器程序来说,它是一个应用程序

写中间件(middleware)的逻辑:
1. middleware需要伪装成应用程序—> WSGI应用程序的要求 —> 1. 可调用 2. 两个参数 3. 返回可迭代的值
2. middleware需要伪装成服务器程序 —> WSGI服务器程序的要求 —> 调用应用程序

我们需要了解一下environ这个变量。在WSGI中, 应用程序需要两个参数:environstart_response, 在服务器程序调用应用程序之前, 需要先设定这两个参数。 其中start_response通常是个可调用的方法, 而environ则是一个字典, 它是在CGI中定义的, 查看CGI文档The Common Gateway Interface Specification, 可以找到关于environ的定义。

以下是environ中的参数:

  AUTH_TYPE
CONTENT_LENGTH #HTTP请求中Content-Length的部分
CONTENT_TYPE #HTTP请求中Content-Tpye的部分
GATEWAY_INTERFACE
HTTP_* #包含一系列变量, 如HTTP_HOST,HTTP_ACCEPT等
PATH_INFO #URL路径除了起始部分后的剩余部分,用于找到相应的应用程序对象,如果请求的路径就是根路径,这个值为空字符串
PATH_TRANSLATED
QUERY_STRING #URL路径中?后面的部分
REMOTE_ADDR
REMOTE_HOST
REMOTE_IDENT
REMOTE_USER
REQUEST_METHOD #HTTP 请求方法,例如 "GET", "POST"
SCRIPT_NAME #URL路径的起始部分对应的应用程序对象,如果应用程序对象对应服务器的根,那么这个值可以为空字符串
SERVER_NAME
SERVER_PORT
SERVER_PROTOCOL #客户端请求的协议(HTTP/1.1 HTTP/1.0)
SERVER_SOFTWARE

举例:http://localhost:5000/aaa?666, 变量值为:

REQUEST_METHOD=‘GET’
SCRIPT_NAME=''
SERVER_NAME='localhost'
SERVER_PORT=‘5000’
PATH_INFO='/aaa'
QUERY_STRING='666'
SERVER_PROTOCOL='HTTP/1.1'
CONTENT_TYPE='text/plain'
CONTEN_LENGTH='' HTTP_HOST = 'localhost:8000'
HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
HTTP_ACCEPT_ENCODING = 'gzip,deflate,sdch'
HTTP_ACCEPT_LANGUAGE = 'en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2'
HTTP_CONNECTION = 'keep-alive'
HTTP_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36'

对于start_response()函数:

start_response是HTTP响应的开始, 它的形式为:start_response(status, response_headers, exc_info=None)

status表示HTTP状态码, 比如200 OK

response_headers是一个列表,列表元素是个tuple:(header_name, header_value)

exc_info是个可选参数, 当处理请求的过程中发生错误时, 会设置该参数, 同时会调用start_response

举一个werkzeug官方文档上的例子,我稍作改进,以进行分析(这段建议看完wsgi.py第二部分的SharedDataMiddleware类再看):

class Shortly(object):

    def __init__(self, config):
self.redis = redis.Redis(config['redis_host'], config['redis_port']) def dispatch_request(self, request):
return Response('Hello World!') #初始化Response类 def wsgi_app(self, environ, start_response):
request = Request(environ)
response = self.dispatch_request(request) #response的类型为Response类
print('%%%%%%%%%%%%%%%%%%%%%%%%%%%')
return response(environ, start_response) #Response的__call__函数就是应用程序,返回迭代对象 def __call__(self, environ, start_response):
print(self.wsgi_app)
print('erghrgheoghegoierge')
return self. wsgi_app(environ, start_response) def create_app(redis_host='localhost', redis_port=6379, with_static=True):
app = Shortly({
'redis_host': redis_host,
'redis_port': redis_port
})
if with_static:
print('yes')
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
'/static': os.path.join(os.path.dirname(__file__), 'static')
})
print('33333333333333333')
return app
#开启本地服务器
if __name__ == '__main__':
from werkzeug.serving import run_simple
app = create_app() #创建应用程序的实例
run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True)

我们查看Response的源码(Response继承了BaseResponse,查看BaseResponse的源码即可)可以知道:函数dispatch_request()返回的值是Request的构造函数,即返回了一个Response类, 在函数wsgi_app()中,request的值的类型就是Response, 所以wsgi_app()的返回值response(environ, start_response)实际上是调用了Response类的__call__()函数。

看了源码我们可以发现,__call__()是一个WSGI的应用程序!

当运行这个程序的时候:

22222222222222222
yes
33333333333333333
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
22222222222222222
yes
33333333333333333

我们先不纠结为什么读取了2次。

当我们打开这个网页,控制台的输出为:

<werkzeug.wsgi.SharedDataMiddleware object at 0x1007be7b8>    #说明wsgi_app是SharedDataMiddleware的实例!
erghrgheoghegoierge
%%%%%%%%%%%%%%%%%%%%%%%%%%% #说明执行了原wsgi_app函数中的内容!
127.0.0.1 - - [22/May/2015 21:01:25] "GET / HTTP/1.1" 200 -

可以注意到,在本例中,app.wsgi_app这个方法已经变成了一个SharedDataMiddleware类的实例,我很好奇当服务器把environ和start_response传递给app后,为什么wsgi_pp还会执行原wsgi_app中的内容呢?

当我们访问主机地址的时候,服务器程序接受用户请求,然后会把environ和start_response传递给应用程序app,app会执行__call__函数,在该函数中,会执行app.wsgi_app这个函数。 然后wsgi_app会执行ShareDataMiddleware__call__()函数

这地方需要我们看SharedDataMiddleware类的__call__()的源码。看了源码我们可以发现,由于用户没有请求静态文件,所以会执行return self.app(environ, start_response),在本例中,我们可以看到在create_app()中,我们定义的ShareDataMiddleware的应用程序是app.wsgi_app,所以这里返回的是原wsgi_app函数!所以当然会执行原函数了~

同时,我们也可以在static文件夹中放一个文件,然后访问试一下:127.0.0.1/static/文件名,此时就能看到那个文件了!

通过本例,我们可以更深刻的了解Middleware的作用:

Middleware介于服务器程序和应用程序之间,它会接收服务器发来的消息(environ和
start_response),并做一定的处理,然后把需要应用程序处理的部分传递给应用程序处理

另外, 服务器程序还需要定义WSGI的相关变量:

wsgi.version
值的形式为 (1, 0) 表示 WSGI 版本 1.0
wsgi.url_scheme
表示 url 的模式,例如 "https" 还是 "http"
wsgi.input
输入流,HTTP请求的 body 部分可以从这里读取
wsgi.erros
输出流,如果出现错误,可以写往这里
wsgi.multithread
如果应用程序对象可以被同一进程中的另一线程同时调用,这个值为True
wsgi.multiprocess
如果应用程序对象可以同时被另一个进程调用,这个值为True
wsgi.run_once
如果服务器希望应用程序对象在包含它的进程中只被调用一次,那么这个值为True

WSGI的理解的更多相关文章

  1. 说说我对 WSGI 的理解

    先说下 WSGI 的表面意思,Web Server Gateway Interface 的缩写,即 Web 服务器网关接口. 之前不知道 WSGI 意思的伙伴,看了上面的解释后,我估计也还是不清楚,所 ...

  2. WSGI的理解 perfect

    https://blog.csdn.net/hzrandd/article/details/10099871 https://blog.csdn.net/cloudxli/article/detail ...

  3. 对于python WSGI的理解

    首先看看WSGI的目的是什么? 是用来定义一个统一的接口. 这个接口是针对Web服务器和python Web应用之间的. 以增加Python web应用在不同Web 服务器之间的可移植性. 也就是说如 ...

  4. OpenStack设计与实现5——RESTful API和WSGI

    转https://segmentfault.com/a/1190000004361778 Tips:文章为拜读@xingjiarong 后有感而做的分享,先对作者表示感谢,附原文地址:http://b ...

  5. WSGI 简介(使用python描述)

    WSGI 简介 背景 Python Web 开发中,服务端程序可以分为两个部分,一是服务器程序,二是应用程序.前者负责把客户端请求接收,整理,后者负责具体的逻辑处理.为了方便应用程序的开发,我们把常用 ...

  6. linux性能评估与分析工具

    linux是一个开源系统,其内核负责管理系统的进程,内存,设备驱动程序,文件和网络系统, 决定着系统的性能和稳定性.由于内核源码很容易获取,任何人都可以将自己认为优秀的代码 加入到其中.linux默认 ...

  7. Django 分析(一)Requst、Middleware 和 Response 数据流

    0. 前言 通过 Django 编写 HTTP 接口时,我们需要指定 URL.Model 和 Views 函数(或者指定 RESTBaseView 对象解析参数和编写逻辑) 编写逻辑时的基本思路就是解 ...

  8. Django Full Coverage

    Django(个人推荐, 如果项目较大 需要协同开发, 建议使用django这种重量级框架, 如果类似于纯api的后端应用建议使用 flask, 轻量小巧 , 麻雀虽小五脏俱全) 1.Django是什 ...

  9. 理解WSGI

    WSGI是什么? WSGI,全称 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是为 Python 语言定义 ...

随机推荐

  1. Python中range的用法

    函数原型:range(start, end, scan): 参数含义:start:计数从start开始.默认是从0开始.例如range(5)等价于range(0, 5); end:技术到end结束,但 ...

  2. AFNetworking了解

    AFNetworking了解   AFNetworking是一个讨人喜欢的网络库,适用于iOS以及Mac OS X. 它构建于在NSURLConnection, NSOperation, 以及其他熟悉 ...

  3. Resharper上手指南

    原文http://www.cnblogs.com/renji/archive/2007/12/11/resharper.html Resharper上手指南 我是visual studio的忠实用户, ...

  4. Friendly number

    Friendly number Long numbers can be made to look nicer, so let’s write some code to do just that. Yo ...

  5. java设计模式--结构型模式--组合模式

    什么是组合模式,这个有待研究,个人觉得是各类组合而形成的一种结构吧. 组合模式: 组合模式 概述 将对象组合成树形结构以表示"部分-整体"的层次结构."Composite ...

  6. 【转】ubuntu下安装及设置FTP服务器!!

    原文网址:http://hujizhou.blog.51cto.com/514907/1290915 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律 ...

  7. MySQL 索引优化全攻略

    所谓索引就是为特定的mysql字段进行一些特定的算法排序,比如二叉树的算法和哈希算法,哈希算法是通过建立特征值,然后根据特征值来快速查找.而用的最多,并且是mysql默认的就是二叉树算法 BTREE, ...

  8. poj 3190 Stall Reservations 贪心 + 优先队列

    题意:给定N头奶牛,每头牛有固定的时间[a,b]让农夫去挤牛奶,农夫也只能在对应区间对指定奶牛进行挤奶, 求最少要多少个奶牛棚,使得在每个棚内的奶牛的挤奶时间不冲突. 思路:1.第一个想法就是贪心,对 ...

  9. 【转】使用miniupnpd-->upnp协议 映射本地端口到外网

    miniupnpc的主要函数介绍 1>.miniupnpc库主要使用的头文件有 #include"miniwget.h" #include"miniupnpc.h& ...

  10. Linux usb子系统(三):通过usbfs操作设备的用户空间驱动

    内核中提供了USB设备文件系统(usbdevfs,Linux 2.6改为usbfs,即USB文件系统),它和/proc类似,都是动态产生的.通过在/etc/fstab文件中添加如下一行:none /p ...