WSGI协议及werkzeug-概念篇

453 阅读5分钟

0.1 WSGI概念

出自python的增强性建议书:PEP-3333,由PEP-333发展而来(为了支持python3)全称Web Server Gateway Interface
在python中有各种web应用框架,不同的应用框架会限制使用他们的web服务器,相比于JAVA,它虽然也有众多的web开发框架,但自从servlet API出现之后,JAVA web框架都可以在支持servlet API的web服务器上运行。WSGI协议也就是充当了srvlet API这样的一个角色,它定义了应用或框架和web服务器之间通信的接口,使得python的web框架可以在任何支持WSGI协议的web服务器上运行。
那到底开发遵循WSGI协议去开发框架、应用、web服务器等带给我们什么好处呢
至少有了一个明确的领域划分,我们不需要在开发一个web应用或框架的同时还要去想着去实现一遍web服务器的功能,专注各自的领域,减少重复造轮子。
在就是移植性强,项目灵活,我们不要再去考虑说只能在项目里使用一种web框架,当我们的web服务器遵守了WSGI协议,应用层的框架选择不在是问题。

0.2 WSGI协议内容

WSGI协议把整个web服务端分为三个部分,Server、Application、Middleware。

0.2.1. Server

Server端每次从http客户端收到一个请求,就调用一次应用对象。需要实现的是一个将请求中包含的参数、请求头、元数据写入到一个字典中,和一个返回数据给客户端的函数,一并传入到Application中。
这个Server才是我们处理所有web请求的关键所在,对外需要去处理并发请求,对类则是加载我们的应用代码处理每个请求的逻辑
下面用python实现了一个cgi进程,通过环境变量获取一个请求的参数,并用cgi进程处理请求输出到标准输出。
import os, sys
​
enc, esc = sys.getfilesystemencoding(), 'surrogateescape'def unicode_to_wsgi(u):
    # Convert an environment variable to a WSGI "bytes-as-unicode" string
    return u.encode(enc, esc).decode('iso-8859-1')
​
def wsgi_to_bytes(s):
    return s.encode('iso-8859-1')
​
def run_with_cgi(application):
    environ = {k: unicode_to_wsgi(v) for k,v in os.environ.items()}
    environ['wsgi.input']        = sys.stdin.buffer
    environ['wsgi.errors']       = sys.stderr
    environ['wsgi.version']      = (1, 0)
    environ['wsgi.multithread']  = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once']     = Trueif environ.get('HTTPS', 'off') in ('on', '1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'
​
    headers_set = []
    headers_sent = []
​
    def write(data):
        out = sys.stdout.buffer
​
        if not headers_set:
             raise AssertionError("write() before start_response()")
​
        elif not headers_sent:
             # Before the first output, send the stored headers
             status, response_headers = headers_sent[:] = headers_set
             out.write(wsgi_to_bytes('Status: %s\r\n' % status))
             for header in response_headers:
                 out.write(wsgi_to_bytes('%s: %s\r\n' % header))
             out.write(wsgi_to_bytes('\r\n'))
​
        out.write(data)
        out.flush()
​
    def start_response(status, response_headers, exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[1].with_traceback(exc_info[2])
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")
​
        headers_set[:] = [status, response_headers]
​
        # Note: error checking on the headers should happen here,
        # *after* the headers are set.  That way, if an error
        # occurs, start_response can only be re-called with
        # exc_info set.return write
​
    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()

0.2.2. Application

应用就是一个简单的接受两个参数的可调用对象,可以是函数,方法,类,实现了call的实例,该应用对象必须可以被多次调用,web服务器会重复的调用它。
基本结构:
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['Hello World!']
下面是Application两种实现:函数和类
def simple_app(environ, start_response):
    """最简单的应用对象"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return b"Hello world!\n"

class AppClass:
    """
    生成一个AppClass的实例对象,那是一个生成器对象,当我们遍历这个对象,就会执行
    __iter__方法,来达到重复执行的效果。当然我们想通过执行这个实例对象来执行,可以去    实现__call__方法。
    """
    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  b"Hello world!\n"

0.2.3. Middleware

中间件是是一个可以与两端交互的组件,也可看做是一个Application,它接受一个Application作为参数,并返回一个Application,这正是利用了Application的可嵌套性,用法类似app = mw1(mw2(app)),常见用法
  • 重写environ,然后基于 URL,将请求对象路由给不同的应用对象
  • 支持多个应用或者框架顺序地运行于同一个进程中
  • 通过转发请求和响应,支持负载均衡和远程处理
  • 支持对内容做后处理(postprocessing)

0.3. WSGI协议的一些延伸

0.3.1 关于uWSGI和uwsgi

uWSG一个web服务器,它实现了WSGI接口。
而uwsgi是一种二进制协议,用于两个web服务器用来通信,常见是使用nginx和uWSGI一起部署,nginx是uWSGI之间通讯使用uwsgi协议,而nginx就负责把http协议包转换成uwsgi协议包。
当然uWSGI是可以不依赖于nginx的,但客户端到服务端通常用的都是http协议,那么在uWSGI服务器就有两种选择:自己将http协议解析成uwsgi协议,它起了一个http进程接受客户端请求并解析然后用uwsgi协议传递到每个uWSGI服务器的work;另外一种就是整个过程都用http协议流通

0.3.1 wsgi应用的参数envrion

其实就是在遵循wsgi协议的server传递给application所需要的所有请求内容一个字典,里面包含了一个http请求的请求头、路由、请求体等参数
0.3.2 关于uWSGI和uwsgi
中文翻译文档:[https://uwsgi-docs-zh.readthedocs.io/zh_CN/latest/Options.html](https://uwsgi-docs-zh.readthedocs.io/zh_CN/latest/Options.html)
当然uWSGI是可以不依赖于nginx的,但客户端到服务端通常用的都是http协议,那么在uWSGI服务器就有两种选择:自己将http协议解析成uwsgi协议,它起了一个http进程接受客户端请求并解析然后用uwsgi协议传递到每个uWSGI服务器的work;另外一种就是整个过程都用http协议流通。
而uwsgi是一种二进制协议,用于两个web服务器用来通信,常见是使用nginx和uWSGI一起部署,nginx是uWSGI之间通讯使用uwsgi协议,而nginx就负责把http协议包转换成uwsgi协议包。
uWSGI是一个web服务器,它实现了WSGI接口。
这是我在知乎发的第一篇原创文章,把我以前的学习笔记汇总,加入了自己的理解,希望对后来人有帮助吧。