WSGI协议详解

简介

Python Web开发中,后台服务端程序可以分为两个部分,1.是服务器程序 2.是应用程序.服务器通常是TCP服务器,负责处理来自客户端(一般都是浏览器/APP)请求的整理/分发/响应.2.后者负责具体的逻辑处理.这时候为了方便应用程序的开发,我们把常用的功能封装起来,成为各种Web开发框架,Django/Flask/Tornado.不同的框架有不同的开发方式,但是开发出来的应用程序都需要和服务器程序配合,才能算是完整的后台服务.
如果服务器程序和应用程序之间没有标准的协议,我们在开发一套应用程序的时候是不是要针对某个具体的服务器程序来写,而不能随便来写.这时候我们就需要规定出一套特定协议,将应用程序和服务器程序解耦出来.而 WSGI就是这个作用.Python Web开发中,这套标准协议就是 The Web Server Gateway Interface,即 WSGI,这套标准在官方的 PEP 333中描述.

WSGI具体是什么?

WSGI是服务器程序与应用程序的一个约定,它规定了双方各自需要实现什么接口,提供了什么功能,以便二者能够配合使用.WSGI 不能规定的太复杂,否则对已有的服务器来说,实现起来会困难,不利于WSGI的普及。同时WSGI也不能规定的太多,例如cookie处理就没有在WSGI中规定,这是为了给框架最大的灵活性。要知道WSGI最终的目的是为了方便服务器与应用程序配合使用,而不是成为一个Web框架的标准。


WSGI-应用程序

WSGI规定:
  • 1.应用程序需要是一个可调用的对象
我们看下官方文档给出的定义
[Python]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
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
40
HELLO_WORLD = b "Hello world!\n"
 
# callable function
def simple_app(environ, start_response):
     """Simplest possible application object"""
     status = '200 OK'
     response_headers = [( 'Content-type' , 'text/plain' )]
     start_response(status, response_headers)
     return [HELLO_WORLD]
 
# callable class
class AppClass:
     """Produce the same output, but using a class
 
     (Note: 'AppClass' is the "application" here, so calling it
     returns an instance of 'AppClass', which is then the iterable
     return value of the "application callable" as required by
     the spec.
 
     If we wanted to use *instances* of 'AppClass' as application
     objects instead, we would have to implement a '__call__'
     method, which would be invoked to execute the application,
     and we would need to create an instance for use by the
     server or gateway.
     """
 
     def __init__( self , environ, start_response):
         self .environ = environ
         self .start = start_response
 
     def __iter__( self ):
         status = '200 OK'
         response_headers = [( 'Content-type' , 'text/plain' )]
         self .start(status, response_headers)
         yield HELLO_WORLD
 
# callable object
class ApplicationObj( object ):
     def __call__( self , environ, start_response):
         return [HELL_WORLD]
翻译过来就是:
  • 应用程序可以是函数
  • 可以是一个 instance实例,实例的类需要实现 __call__方法
  • 可以是一个 Class类,内部实现了 __iter__方法.
同时, WSGI规定
  • 2.可调用对象需要接收两个参数 environ和 start_response
  • 3.可调用对象要返回一个值,这个值是可迭代的
这时候了解Python Web开发的可能就会讲了,平时我们写的应用程序并不是这个样子的,应该是下面这种
[Python]  纯文本查看 复制代码
?
1
2
3
class Index(View):
         def get( self ):
                 return 'Hello world'
这是因为框架已经帮我们把WSGI规定的一些东西封装起来,我们平时用框架时看不到,只需要直接实现我们的逻辑,再返回一个值就好了。其它的东西框架帮我们做好了。这也是框架的价值所在,把常用的东西封装起来,让使用者只需要关注最重要的东西。
比如 Django框架的 WSGI Application 就采用的 callable object的形式
[Python]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# lib/python3.7/site-packages/django/core/handlers/wsgi.py 146 line
class WSGIHandler(base.BaseHandler):
     request_class = WSGIRequest
 
     def __init__( self , * args, * * kwargs):
         super (WSGIHandler, self ).__init__( * args, * * kwargs)
         self .load_middleware()
 
     def __call__( self , environ, start_response):
         set_script_prefix(get_script_name(environ))
         signals.request_started.send(sender = self .__class__, environ = environ)
         request = self .request_class(environ)
         response = self .get_response(request)
 
         response._handler_class = self .__class__
 
         status = '%d %s' % (response.status_code, response.reason_phrase)
         response_headers = [( str (k), str (v)) for k, v in response.items()]
         for c in response.cookies.values():
             response_headers.append(( str ( 'Set-Cookie' ), str (c.output(header = ''))))
         start_response(force_str(status), response_headers)
         if getattr (response, 'file_to_stream' , None ) is not None and environ.get( 'wsgi.file_wrapper' ):
             response = environ[ 'wsgi.file_wrapper' ](response.file_to_stream)
         return response

WSGI-服务器程序

服务器程序会在每次客户端的请求传来时,调用我们写好的应用程序,并将处理好的结果(response)返回客户端.
  • 4.服务器程序需要调用应用程序 Application
服务器程序大概是这个样子
[Python]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
# 具体见PEP333规范
def run_with_cgi(application):
     environ = {}
 
     def write(data):
         pass
 
     def start_response(status, response_headers, exc_info = None ):
        pass
         # 调用应用程序
     result = application(environ, start_response)
     try :
         for data in result:
             if data:    # don't send headers until body appears
                 write(data)
         if not headers_sent:
             write('')   # send headers now if body was empty
     finally :
         if hasattr (result, 'close' ):
             result.close()
这里可以看出服务器程序是如何与应用程序配合完成用户请求的。
WSGI规定了应用程序需要一个可调用对象,有两个参数,返回一个可迭代对象。在服务器 程序中,针对这几个规定,做了以下几件事:
  • 把应用程序需要的两个参数设置好
  • 调用应用程序
  • 迭代访问应用程序的返回结果,并将其传回客户端

WSGI-Middleware

有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。
有点类似于语法糖 装饰器,
[Python]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
# URL Routing middleware
def urlrouting(url_app_mapping):
     def midware_app(environ, start_response):
         url = environ[ 'PATH_INFO' ]
         app = url_app_mapping[url]
  
         result = app(environ, start_response)
  
         return result
  
     return midware_app
'''
中间件作用说明:
服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。
'''

WSGI-详解
应用程序

  • 应用程序是可调用对象(函数/类/类的实例) application(environ, start_response)
  • 可调用对象有两个位置参数(注意是位置参数不是关键字参数)
2个参数名称不是固定的,可以随便起,不过为了表达清楚所以一般都叫 environ和 start_response
  • 第一个参数 environ是Dict字典对象,参数包含WSGI需要的一些变量
  • 第二个参数 start_response参数是一个可调用对象,也有两个位置参数和一个可选参数.

    • start_response(status, response_headers, exc_info=None)

      • status是状态码:比如201,404…..
      • response_headers参数是一个list类型,内容[header_name,header_value],
      • exc_info主要异常处理的时候使用,默认是None
    • start_response必须返回一个可调用对象: write(body_data)
  • 应用程序必须返回一个可迭代对象
  • 应用程序必须在第一次返回可迭代数据之前调用 start_response 方法。
    这是因为可迭代数据是 返回数据的 body 部分,在它返回之前,需要使用 start_response 返回 response_headers 响应头数据,通过响应头(response_headers)+响应体(body)拼接返回给客户端。

服务器程序

  • 服务器必须将可迭代对象的内容传递给客户端,可迭代对象会产生bytestrings,必须完全完成每个bytestring后才能请求下一个。
  • 假设result 为应用程序的返回的可迭代对象。如果len(result) 调用成功,那么result必须是可累积的。
    如果result有close方法,那么每次完成对请求的处理时,必须调用它,无论这次请求正常完成,还是遇到了错误。
  • 服务器程序禁止使用可迭代对象的其它属性,除非这个可迭代对象是一个特殊类的实例,这个类会被 wsgi.file_wrapper定义。
[Python]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
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
# server programmed
def run(application):
     environ = {}
  
     # set environ
     def write(data):
         pass
  
     def start_response(status, response_headers, exc_info = None ):
         return write
  
     try :
         result = application(environ, start_response)
     finally :
         if hasattr (result, 'close' ):
             result.close()
  
     if hasattr (result, '__len__' ):
         # result must be accumulated
         pass
  
  
     for data in result:
         write(data)
 
 
HELLO_WORLD = b "Hello world!\n"
  
  
# callable function
def application(environ, start_response):
     status = '200 OK'
     response_headers = [( 'Content-type' , 'text/plain' )]
     start_response(status, response_headers)
  
     return [HELLO_WORLD]



environ变量

environ 变量需要包含 CGI 环境变量,它们在The Common Gateway Interface Specification 中定义,下面列出的变量必须包含在 enciron变量中:
  • REQUEST_METHOD
    HTTP 请求方法,例如 “GET”, “POST”
  • SCRIPT_NAME
    URL 路径的起始部分对应的应用程序对象,如果应用程序对象对应服务器的根,那么这个值可以为空字符串
  • PATH_INFO
    URL 路径除了起始部分后的剩余部分,用于找到相应的应用程序对象,如果请求的路径就是根路径,这个值为空字符串
  • QUERY_STRING
    URL路径中 ? 后面的部分
  • CONTENT_TYPE
    HTTP 请求中的 Content-Type 部分
  • CONTENT_LENGTH
    HTTP 请求中的Content-Lengh 部分
  • SERVER_NAME, SERVER_PORT
    与 SCRIPT_NAME,PATH_INFO 共同构成完整的 URL,它们永远不会为空。但是,如果 HTTP_HOST 存在的话,当构建 URL 时, HTTP_HOST优先于SERVER_NAME。
  • SERVER_PROTOCOL
    客户端使用的协议,例如 “HTTP/1.0”, “HTTP/1.1”, 它决定了如何处理 HTTP 请求的头部。这个名字其实应该叫REQUEST_PROTOCOL,因为它表示的是客户端请求的协议,而不是服务端响应的协议。但是为了和CGI兼容,我们只好叫这个名字了。 *HTTP_ Variables
    这个是一个系列的变量名,都以HTTP开头,对应客户端支持的HTTP请求的头部信息。
[Python]  纯文本查看 复制代码
?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
REQUEST_METHOD = 'GET'
SCRIPT_NAME = ''
PATH_INFO = '/xyz'
QUERY_STRING = 'abc'
CONTENT_TYPE = 'text/plain'
CONTENT_LENGTH = ''
SERVER_NAME = 'minix-ubuntu-desktop'
SERVER_PORT = '8000'
SERVER_PROTOCOL = 'HTTP/1.1'
  
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_HOST = 'localhost:8000'
HTTP_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36'


Unicode

HTTP 不支持 Unicode, 所有编码/解码都必须由应用程序完成,所有传递给或者来自server的字符串都必须是 str 或者bytes类型,而不是unicode。
注意:WSGI中的 bytestrings在Python3中指 bytes,在Python2中指 str
更多技术资讯可关注:gzitcast

猜你喜欢

转载自www.cnblogs.com/heimaguangzhou/p/11790402.html