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

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

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

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

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

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

WSGI应用程序的部分规定

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

    可调用的对象有三种:

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

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

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

    1. class application:
    2. def __call__(self, environ, start_response):
    3. return [xxx]

WSGI服务器程序的部分规定

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

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

Middleware

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

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

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

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

函数midware_app就是middleware:

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

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

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

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

以下是environ中的参数:

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

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

  1. REQUEST_METHOD=‘GET
  2. SCRIPT_NAME=''
  3. SERVER_NAME='localhost'
  4. SERVER_PORT=‘5000
  5. PATH_INFO='/aaa'
  6. QUERY_STRING='666'
  7. SERVER_PROTOCOL='HTTP/1.1'
  8. CONTENT_TYPE='text/plain'
  9. CONTEN_LENGTH=''
  10. HTTP_HOST = 'localhost:8000'
  11. HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
  12. HTTP_ACCEPT_ENCODING = 'gzip,deflate,sdch'
  13. HTTP_ACCEPT_LANGUAGE = 'en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2'
  14. HTTP_CONNECTION = 'keep-alive'
  15. 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类再看):

  1. class Shortly(object):
  2. def __init__(self, config):
  3. self.redis = redis.Redis(config['redis_host'], config['redis_port'])
  4. def dispatch_request(self, request):
  5. return Response('Hello World!') #初始化Response类
  6. def wsgi_app(self, environ, start_response):
  7. request = Request(environ)
  8. response = self.dispatch_request(request) #response的类型为Response类
  9. print('%%%%%%%%%%%%%%%%%%%%%%%%%%%')
  10. return response(environ, start_response) #Response的__call__函数就是应用程序,返回迭代对象
  11. def __call__(self, environ, start_response):
  12. print(self.wsgi_app)
  13. print('erghrgheoghegoierge')
  14. return self. wsgi_app(environ, start_response)
  15. def create_app(redis_host='localhost', redis_port=6379, with_static=True):
  16. app = Shortly({
  17. 'redis_host': redis_host,
  18. 'redis_port': redis_port
  19. })
  20. if with_static:
  21. print('yes')
  22. app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
  23. '/static': os.path.join(os.path.dirname(__file__), 'static')
  24. })
  25. print('33333333333333333')
  26. return app
  1. #开启本地服务器
  2. if __name__ == '__main__':
  3. from werkzeug.serving import run_simple
  4. app = create_app() #创建应用程序的实例
  5. 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的应用程序!

当运行这个程序的时候:

  1. 22222222222222222
  2. yes
  3. 33333333333333333
  4. * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
  5. * Restarting with stat
  6. 22222222222222222
  7. yes
  8. 33333333333333333

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

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

  1. <werkzeug.wsgi.SharedDataMiddleware object at 0x1007be7b8> #说明wsgi_app是SharedDataMiddleware的实例!
  2. erghrgheoghegoierge
  3. %%%%%%%%%%%%%%%%%%%%%%%%%%% #说明执行了原wsgi_app函数中的内容!
  4. 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的作用:

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

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

  1. wsgi.version
  2. 值的形式为 (1, 0) 表示 WSGI 版本 1.0
  3. wsgi.url_scheme
  4. 表示 url 的模式,例如 "https" 还是 "http"
  5. wsgi.input
  6. 输入流,HTTP请求的 body 部分可以从这里读取
  7. wsgi.erros
  8. 输出流,如果出现错误,可以写往这里
  9. wsgi.multithread
  10. 如果应用程序对象可以被同一进程中的另一线程同时调用,这个值为True
  11. wsgi.multiprocess
  12. 如果应用程序对象可以同时被另一个进程调用,这个值为True
  13. wsgi.run_once
  14. 如果服务器希望应用程序对象在包含它的进程中只被调用一次,那么这个值为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. Memory Region

    A program's memory usage typically includes four different regions: Code -- The region where the pro ...

  2. Django 反向生成 从数据库生成Model

    Django 反向生成 从数据库生成Model 使用Django生成Model python manage.py inspectdb或python manage.py inspectdb > m ...

  3. Qt String 与char* char int之间的转换

    下面CSDN的博客已经描述的很好了.不写了 references: http://blog.csdn.net/ei__nino/article/details/7297791 http://blog. ...

  4. GitHub使用说明

    登陆https://github.com/,并注册账号 从如下地址下载windows客户端:https://msysgit.googlecode.com/files/Git-1.8.4-preview ...

  5. 工具:BT Sync 同步文件

    随着互联网的发展,文件共享变得越来越便捷,但是文件的共享过程是不是安全,这一直是人们关心的问题,今天向大家介绍一个共享工具,可以实现便捷的安全共享.      这个工具分为两个部分,一个是服务器部分, ...

  6. Java迭代器深入理解及使用

    Iterator(迭代器) 作为一种设计模式,迭代器可以用于遍历一个对象,对于这个对象的底层结构开发人员不必去了解. java中的Iterator一般称为“轻量级”对象,创建它的代价是比较小的.这里笔 ...

  7. Linux硬盘分区和格式化

    分区与格式化   先用fdisk分区,分区完成后再用mkfs格式化并创建文件系统,挂载,磁盘就能使用啦.   分区的原理:        MBR:主引导扇区 主分区表:64bytes,最多只能分四个主 ...

  8. hdu 5312 Sequence(数学推导+线性探查(两数相加版))

    Problem Description Today, Soda has learned a sequence whose n-th (n≥) item )+. Now he wants to know ...

  9. JQuery 选择器 *很重要 多记

    1)基本选择器: 跟CSS选择器类似 2) 层次选择器 div>span   紧接这div同一级下的全部span .one+div     同一等级的div #two~div    同一等级di ...

  10. c++面试知识点

    static #include<stdio.h> #include<iostream> #include<assert.h> using namespace std ...