WSGI简介

WSGI

WSGI(Web Server Gateway Interface) ,作为一个规范,定义了Web服务器如何与Python应用程序进行交互,使得使用Python写的Web应用程序可以和Web服务器对接起来。

为什么需要WSGI

在Web部署的方案上,有一个方案是目前应用最广泛的:

  • 首先,部署一个Web服务器专门用来处理HTTP协议层面相关的事情,比如如何在一个物理机上提供多个不同的Web服务(单IP多域名,单IP多端口等)这种事情。
  • 然后,部署一个用各种语言编写(Java, PHP, Python, Ruby等)的应用程序,这个应用程序会从Web服务器上接收客户端的请求,处理完成后,再返回响应给Web服务器,最后由Web服务器返回给客户端。

那么 Web Server 和 Application 之间就要知道如何进行交互。为了定义Web服务器和应用程序之间的交互过程,就形成了很多不同的规范。比如改进CGI性能的FasgCGI,Java专用的Servlet规范,还有Python专用的WSGI规范等。提出这些规范的目的就是为了定义统一的标准,提升程序的可移植性。

WSGI原理

在这里插入图片描述

WSGI 相当于 Web Server 和 Python Application 之间的桥梁,隐藏了很多HTTP相关的细节。其存在的目的有两个:

  1. 让 Web Server 知道如何调用 Python Application,并且将 Client Request 传递给 Application。
  2. 让 Python Application 能理解 Client Request 并执行对应操作,以及将执行结果返回给 Web Server,最终响应到Client。

Server 调用 Application

从上图可知,Server端调用Application端必须以WSGI为桥梁,因此WSGI定义了application可调用对象以提供Server端和Application端通信,这个可调用对象既可以是函数也可以是类。

# 函数形式
def application(environ, start_response):
    '''
    doing something
    '''
    return [response_body]

# 类形式
class Application:
	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

# 上面的类形式是将“Application”类作为服务端调用的application,
# 调用这个类会返回它的实例,其结果会返回规范中要求的可迭代响应值。
# 如果要使用这个类的实例作为application对象,就需要实现__call__方法,服务端会调用这个实例去执行应用。
# 下面是pecan的实现方法。
class PecanBase(object):
    def __init__(self, root, default_renderer='mako',
                 template_path='templates', hooks=lambda: [],
                 custom_renderers={}, extra_template_vars={},
                 force_canonical=True, guess_content_type_from_ext=True,
                 context_local_factory=None, request_cls=Request,
                 response_cls=Response, **kw):
    '''
    省略
    '''

    def __call__(self, environ, start_response):
        '''
        Implements the WSGI specification for Pecan applications, utilizing
        ``WebOb``.
        '''

        # create the request and response object
        req = self.request_cls(environ)
        resp = self.response_cls()
        state = RoutingState(req, resp, self)
        environ['pecan.locals'] = {
            'request': req,
            'response': resp
        }
        controller = None

        # track internal redirects
        internal_redirect = False

        # handle the request
        try:
            # add context and environment to the request
            req.context = environ.get('pecan.recursive.context', {})
            req.pecan = dict(content_type=None)

            controller, args, kwargs = self.find_controller(state)
            self.invoke_controller(controller, args, kwargs, state)
        except Exception as e:
            # if this is an HTTP Exception, set it as the response
            if isinstance(e, exc.HTTPException):
                # if the client asked for JSON, do our best to provide it
                accept_header = acceptparse.create_accept_header(
                    getattr(req.accept, 'header_value', '*/*') or '*/*')
                offers = accept_header.acceptable_offers(
                    ('text/plain', 'text/html', 'application/json'))
                best_match = offers[0][0] if offers else None
                state.response = e
                if best_match == 'application/json':
                    json_body = dumps({
                        'code': e.status_int,
                        'title': e.title,
                        'description': e.detail
                    })
                    if isinstance(json_body, six.text_type):
                        e.text = json_body
                    else:
                        e.body = json_body
                    state.response.content_type = best_match
                environ['pecan.original_exception'] = e

            # note if this is an internal redirect
            internal_redirect = isinstance(e, ForwardRequestException)

            # if this is not an internal redirect, run error hooks
            on_error_result = None
            if not internal_redirect:
                on_error_result = self.handle_hooks(
                    self.determine_hooks(state.controller),
                    'on_error',
                    state,
                    e
                )

            # if the on_error handler returned a Response, use it.
            if isinstance(on_error_result, WebObResponse):
                state.response = on_error_result
            else:
                if not isinstance(e, exc.HTTPException):
                    raise

            # if this is an HTTP 405, attempt to specify an Allow header
            if isinstance(e, exc.HTTPMethodNotAllowed) and controller:
                allowed_methods = _cfg(controller).get('allowed_methods', [])
                if allowed_methods:
                    state.response.allow = sorted(allowed_methods)
        finally:
            # if this is not an internal redirect, run "after" hooks
            if not internal_redirect:
                self.handle_hooks(
                    self.determine_hooks(state.controller),
                    'after',
                    state
                )

        self._handle_empty_response_body(state)

        # get the response
        return state.response(environ, start_response)

这个可调用对象需要满足两个条件:

  • 两个参数
    • 一个dict对象,Web Server会将HTTP请求相关的信息添加到这个字典中,供Web application使用
    • 一个callback函数,Web application通过这个函数将HTTP status code和headers发送给Web Server
  • 以字符串的形式返回response,并且包含在可迭代的list中

Server端将http请求相关信息、wsgi变量以及一些服务端环境变量添加到environ传给Application端,Application端处理完所需信息后将http状态码和header通过start_response回调函数传给Server端,而http响应body则以返回值的形式传给服务端。

可以看出,仅仅一个application(environ, start_response)仍然显得太底层,在web应用开发过程中效率不高,因此衍生出各种 Web 框架来帮助开发人员快速的开发Web应用,开发人员只需要关注业务层逻辑,不需要过多的处理http相关信息。

示例

在Python中就有一个WSGI server,提供给开发人员测试使用。

# WSGI server in Python
from wsgiref.simple_server import make_server

def application(environ, start_response):
    response_body = ['%s: %s' % (key, value)
                    for key, value in sorted(environ.items())]
    response_body = '\n'.join(response_body)
    
    status = '200 OK'
    response_headers = [('Content-Type', 'text/plain'),
                ('Content-Length', str(len(response_body)))]
    start_response(status, response_headers)
    
    return [response_body.encode('utf8')]

httpd = make_server(
    'localhost',
    8080,
    application
    )

# 请求处理完退出
httpd.handle_request()

访问http://localhost:8080返回结果:

COLORTERM: truecolor
CONTENT_LENGTH: 
CONTENT_TYPE: text/plain
GATEWAY_INTERFACE: CGI/1.1
GIT_ASKPASS: /home/lem/.vscode-server/bin/899d46d82c4c95423fb7e10e68eba52050e30ba3/extensions/git/dist/askpass.sh
GOPATH: /home/lem/WorkSpace/GOPATH
GOROOT: /usr/local/go
HOME: /home/lem
HOSTTYPE: x86_64
HTTP_ACCEPT: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
HTTP_ACCEPT_ENCODING: gzip, deflate, br
HTTP_ACCEPT_LANGUAGE: zh-CN,zh;q=0.9
HTTP_CACHE_CONTROL: max-age=0
HTTP_CONNECTION: keep-alive
HTTP_HOST: localhost:8080
HTTP_SEC_CH_UA: " Not;A Brand";v="99", "Google Chrome";v="97", "Chromium";v="97"
HTTP_SEC_CH_UA_MOBILE: ?0
HTTP_SEC_CH_UA_PLATFORM: "Windows"
HTTP_SEC_FETCH_DEST: document
HTTP_SEC_FETCH_MODE: navigate
HTTP_SEC_FETCH_SITE: none
HTTP_SEC_FETCH_USER: ?1
HTTP_UPGRADE_INSECURE_REQUESTS: 1
HTTP_USER_AGENT: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.8 Safari/537.36
JAVA_HOME: /usr/local/java8
LANG: C.UTF-8
LESSCLOSE: /usr/bin/lesspipe %s %s
LESSOPEN: | /usr/bin/lesspipe %s
LOGNAME: lem
MOTD_SHOWN: update-motd
NAME: LAPTOP-CAL2DKNC
OLDPWD: /home/lem
PATH: /home/lem/WorkSpace/github.com/openstack/neutron/.venv/bin:/home/lem/.vscode-server/bin/899d46d82c4c95423fb7e10e68eba52050e30ba3/bin:/home/lem/.local/bin:/home/lem/.local/bin:/usr/local/go/bin:/home/lem/WorkSpace/GOPATH/bin:/usr/local/python3.6/bin:/usr/local/java8/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/usr/lib/wsl/lib:/mnt/c/Windows/system32:/mnt/c/Windows:/mnt/c/Windows/System32/Wbem:/mnt/c/Windows/System32/WindowsPowerShell/v1.0/:/mnt/c/Windows/System32/OpenSSH/:/mnt/c/Program Files (x86)/NVIDIA Corporation/PhysX/Common:/mnt/c/Program Files/NVIDIA Corporation/NVIDIA NvDLISR:/mnt/c/Users/lem/AppData/Local/Microsoft/WindowsApps:/mnt/d/WorkProgram/Microsoft VS Code/bin:/mnt/c/Users/lem/AppData/Local/GitHubDesktop/bin:/snap/bin
PATH_INFO: /
PS1: (.venv) \[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ 
PWD: /home/lem/WorkSpace/github.com/openstack/neutron
PYTHON36_HOME: /usr/local/python3.6
QUERY_STRING: 
REMOTE_ADDR: 127.0.0.1
REMOTE_HOST: 
REQUEST_METHOD: GET
SCRIPT_NAME: 
SERVER_NAME: localhost
SERVER_PORT: 8080
SERVER_PROTOCOL: HTTP/1.1
SERVER_SOFTWARE: WSGIServer/0.2
SHELL: /bin/bash
SHLVL: 1
TERM: xterm-256color
TERM_PROGRAM: vscode
TERM_PROGRAM_VERSION: 1.63.2
USER: lem
VIRTUAL_ENV: /home/lem/WorkSpace/github.com/openstack/neutron/.venv
VSCODE_GIT_ASKPASS_EXTRA_ARGS: 
VSCODE_GIT_ASKPASS_MAIN: /home/lem/.vscode-server/bin/899d46d82c4c95423fb7e10e68eba52050e30ba3/extensions/git/dist/askpass-main.js
VSCODE_GIT_ASKPASS_NODE: /home/lem/.vscode-server/bin/899d46d82c4c95423fb7e10e68eba52050e30ba3/node
VSCODE_GIT_IPC_HANDLE: /tmp/vscode-git-4b78d7aee1.sock
VSCODE_IPC_HOOK_CLI: /tmp/vscode-ipc-8d0c519f-65dd-4046-9d4f-7e4c1a4a79ce.sock
WSLENV: VSCODE_WSL_EXT_LOCATION/up
WSL_DISTRO_NAME: Ubuntu-20.04
WSL_INTEROP: /run/WSL/11_interop
XDG_DATA_DIRS: /usr/local/share:/usr/share:/var/lib/snapd/desktop
_: /home/lem/WorkSpace/github.com/openstack/neutron/.venv/bin/python
wsgi.errors: <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>
wsgi.file_wrapper: <class 'wsgiref.util.FileWrapper'>
wsgi.input: <_io.BufferedReader name=4>
wsgi.multiprocess: False
wsgi.multithread: False
wsgi.run_once: False
wsgi.url_scheme: http
wsgi.version: (1, 0)

environ参数

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

CGI规范中要求的变量:

  • REQUEST_METHOD: 请求方法,是个字符串,‘GET’, 'POST’等
  • SCRIPT_NAME: HTTP请求的path中的用于查找到application对象的部分,比如Web服务器可以根据path的一部分来决定请求由哪个virtual host处理
  • PATH_INFO: HTTP请求的path中剩余的部分,也就是application要处理的部分
  • QUERY_STRING: HTTP请求中的查询字符串,URL中?后面的内容
  • CONTENT_TYPE: HTTP headers中的content-type内容
  • CONTENT_LENGTH: HTTP headers中的content-length内容
  • SERVER_NAMESERVER_PORT: 服务器名和端口,这两个值和前面的SCRIPT_NAME, PATH_INFO拼起来可以得到完整的URL路径
  • SERVER_PROTOCOL: HTTP协议版本,HTTP/1.0或者HTTP/1.1
  • HTTP_: 和HTTP请求中的headers对应。

WSGI规范中相关变量:

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

start_resposne参数

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

  • status: 一个字符串,表示HTTP响应状态字符串
  • response_headers: 一个列表,包含有如下形式的元组:(header_name, header_value),用来表示HTTP响应的headers
  • exc_info(可选): 用于出错时,server需要返回给浏览器的信息

application对象的返回值

application对象的返回值用于为HTTP响应提供body,如果没有body,那么可以返回None。如果有body,那么需要返回一个可迭代的对象。server端通过遍历这个可迭代对象可以获得body的全部内容。

WSGI的实现和部署

要使用WSGI,需要分别实现server端和application端。

Application端的实现一般是由Python的各种框架来实现的,比如Django, web.py等,一般开发者不需要关心WSGI的实现,框架会会提供接口让开发者获取HTTP请求的内容以及发送HTTP响应。

Server端的实现会比较复杂一点,这个主要是因为软件架构的原因。一般常用的Web服务器,如Apache和nginx,都不会内置WSGI的支持,而是通过扩展来完成。比如Apache服务器,会通过扩展模块mod_wsgi来支持WSGI。Apache和mod_wsgi之间通过程序内部接口传递信息,mod_wsgi会实现WSGI的server端、进程管理以及对application的调用。Nginx上一般是用proxy的方式,用nginx的协议将请求封装好,发送给应用服务器,比如uWSGI,应用服务器会实现WSGI的服务端、进程管理以及对application的调用。

参考资料:
WSGI简介

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_李少侠_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值