WSGI原理与简单实现

WSGI指定需要由Web应用程序端和Web服务器端实现的规则,以便它们可以彼此交互。因此,符合WSGI的服务器将能够与符合WSGI的Web应用程序进行通信。

在WSGI架构中,WSGI应用程序必须是可调用的,并且必须被提供给Web服务器,因此,只要服务器收到请求,Web服务器就可以调用Web应用程序。


实现一个符合WSGI的应用程序

  • Server/Gateway Side:这里使用参考库wsgiref提供的WSGI Server作为服务器

  • Application/Framework Side:创建一个函数作为可调用对象作为web应用程序


from wsgiref.simple_server import make_server

def app(environ, start_response):
    path = environ.get('PATH_INFO')
    if path == '/':
        response_body = "<h1>Hello, world</h1>"
    else:
        response_body = "<h1>nothing</h1>"

    start_response("200 OK", [('Content-Type','text/html; charset=utf-8')])
    return [response_body.encode()]

httpd = make_server('127.0.0.1', 5000, app)

try:
    print('server start in http://127.0.0.1:5000')
    httpd.serve_forever()
except Exception as e:
    print(e)
except KeyboardInterrupt:
    print('server stop...')
    httpd.server_close()

  • 服务器将传入两个参数来调用应用程序。因此,它必须接受两个参数。这是application符合WSGI的条件之一

  • 传递给它的第一个参数将是一个变量,其中包含有关请求的各种信息。在示例中,我们使用它来读取请求路径,如果路径名是/则返回Hello, world,否则返回nothing。

  • 传递给它的第二个参数将是可调用的。application必须使用此可调用对象来通知服务器响应状态并设置各种标头。这是application符合WSGI的第二个条件

  • 最后返回的必须是一个已经encode了的可迭代对象,响应被application返回到WSGI服务器。

  • 服务器最终将此响应传回客户端。


Application/Framework 的实现

  • 应用程序应该是一个可调用对象,Python中可以是函数、类、实现了__call__方法的类的实例

  • 这个可调用对象应该接收之前说的两个参数

  • 调用这个可调用对象后必须返回一个可迭代对象


1、函数实现

见上面的代码


2、类实现(方法一)

response_body = "Hello, world"

class app:
    def __call__(self, environ, start_response):
        path = environ.get('PATH_INFO')
        if path == '/':
            response_body = "<h1>Hello, world</h1>"
        else:
            response_body = "<h1>nothing</h1>"
        start_response("200 OK", [('Content-Type','text/html; charset=utf-8')])
        return [response_body.encode()]

httpd = make_server('127.0.0.1', 5000, app())

try:
    print('server start in http://127.0.0.1:5000')
    httpd.serve_forever()
except Exception as e:
    print(e)
except KeyboardInterrupt:
    print('server stop...')
    httpd.server_close()

3、类实现(方法二)

response_body = "Hello, world"

class app:
    def __init__(self, environ, start_response):
        path = environ.get('PATH_INFO')
        if path == '/':
            response_body = "<h1>Hello, world</h1>"
        else:
            response_body = "<h1>nothing</h1>"
        start_response("200 OK", [('Content-Type','text/html; charset=utf-8')])
        self.res = [response_body.encode()]

    def __iter__(self):
        yield from self.res

httpd = make_server('127.0.0.1', 5000, app)

try:
    print('server start in http://127.0.0.1:5000')
    httpd.serve_forever()
except Exception as e:
    print(e)
except KeyboardInterrupt:
    print('server stop...')
    httpd.server_close()

关于environ

environ字典包含了一些CGI规范要求的数据,以及WSGI规范新增的数据,还可能包含一些操作系统的环境变量以及Web服务器相关的环境变量,具体见environ

首先是CGI规范中要求的变量:


参数 说明
REQUEST_METHOD HTTP请求方法,'GET', 'POST'等,不能为空
SCRIPT_NAME HTTP请求path中的初始部分,用来确定对应哪一个application,当application对应于服务器的根,可以为空
PATH_INFO path中剩余的部分,application要处理的部分,可以为空
QUERY_STRING HTTP请求中的查询字符串,URL中?后面的内容
CONTENT_TYPE HTTP headers中的Content-Type内容
CONTENT_LENGTH HTTP headers中的Content-Length内容
SERVER_NAME
SERVER_PORT
服务器域名和端口,这两个值和前面的SCRIPT_NAME, PATH_INFO拼起来可以得到完整的URL路径
SERVER_PROTOCOL HTTP协议版本,'HTTP/1.0'或'HTTP/1.1'
HTTP_Variables 和HTTP请求中的headers对应,比如'User-Agent'写成'HTTP_USER_AGENT'的格式

WSGI规范中还要求environ包含下列成员:


参数 说明
wsgi.version 一个元组(1, 0),表示WSGI版本1.0
wsgi.url_scheme http或者https
wsgi.input 一个类文件的输入流,application可以通过这个获取HTTP请求的body
wsgi.errors 一个输出流,当应用程序出错时,可以将错误信息写入这里
wsgi.multithread 当application对象可能被多个线程同时调用时,这个值需要为True
wsgi.multiprocess 当application对象可能被多个进程同时调用时,这个值需要为True
wsgi.run_once 当server期望application对象在进程的生命周期内只被调用一次时,该值为True

关于start_resposne

start_response是一个可调用对象,接收两个必选参数和一个可选参数:

  • status: 一个字符串,表示HTTP响应状态字符串,比如'200 OK'、'404 Not Found'

  • headers: 一个列表,包含有如下形式的元组:(header_name, header_value),用来表示HTTP响应的headers

  • exc_info(可选): 用于出错时,server需要返回给浏览器的信息

start_response必须返回一个write(body_data)。我们知道HTTP的响应需要包含status,headers和body,所以在application对象将body作为返回值return之前,需要先调用start_response,将status和headers的内容返回给server,这同时也是告诉server,application对象要开始返回body了。


参考

posted @ 2021-03-02 22:10  yangblood  阅读(428)  评论(0编辑  收藏  举报