首页 > 其他分享 >Django 深入理解WSGI协议

Django 深入理解WSGI协议

时间:2024-08-13 13:48:44浏览次数:3  
标签:WSGI start wsgi Django write headers environ 深入 response

起步

惭愧啊,惭愧啊,距离上一篇这个系列的文章已经是半年前的了,随着 Django2.0 的发布,感觉之前分析的 1.10.5 版本似乎有点老了,我看了一下,好在和我前面文章分析的内容差异不大,基本上也是可以就着前面的分析内容来品尝最新的 django 代码。

那我接下来阅读的版本就从当前能获取的 2.0.6 来分析了。不过呢,本章要将的内容,可能和 django 代码本身没太多关系。本章来理解一下 WSGI 协议,django 就是遵守这个协议的web开发框架,本章重点是协议方面的说明,顶多会讲讲django里相应的 wsgi 的代码,而不对 django 代码做分析。

什么是 WSGI

WSGI (Web Server Gateway Interface)是用来指定 Web 服务器与 Python Web 应用程序或框架之间标准接口,以促进跨各种Web服务器的Web应用程序可移植性。

在这个规范出来之前,Python 拥有各种各样的 Web 应用程序框架,这也就产生了一个问题,开发者选择Web 框架会限制他们选择web 服务器,反之亦然。

因此,python就提出了一个简单而通用的 Web 服务器与 Web 应用程序之间的接口:Python Web服务器网关接口(WSGI)

WSGI 的目标是促进现有服务器和应用程序的轻松互联,而不是创建新的Web框架

调用方式

WSGI 协议要面对两个端:一个是服务器或者说是网关端,另一个是应用程序或者说框架端。就需要处理一个问题,是谁调用了另一方。

在协议中规定了调用方式:服务器端调用应用程序端提供的 可调用 对象。

也就是说,web 应用程序需要提供一个可调用对象给web服务器调用,这个可调用的对象可以是 函数,方法,类或者带有 __call__ 方法的实例

可调用对象的构成

这个可调用对象的构成也很简单,它接收 两个参数,该对象必须允许能够调用多次,如下面的示例:

def simple_app(environ, start_response):
    """最简单的应用程序对象"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']

这样就是一个满足 WSGI 协议的web程序应用了,是不是很简单。对应的django里,可以从 wsgi.py 中看到 application = get_wsgi_application() 这个函数展开基本和我们实例的最简单的应用程序对象结构一样了:

class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest
    def __call__(self, environ, start_response):
        request = self.request_class(environ)
        response = self.get_response(request)

        status = '%d %s' % (response.status_code, response.reason_phrase)
        response_headers = list(response.items())
        start_response(status, response_headers)

        return response

服务器端

服务器的作用是接收每一个 HTTP 请求,应用程序对象调用时需要传入 environstart_response ,因此这两个参数需要由服务器端来整理并提供给应用程序使用。

environ 是一个字典,以一个简单的 CGI 网关为例,它的值可以这么设置:

import os
environ = dict(os.environ.items())
environ['wsgi.input']        = sys.stdin
environ['wsgi.errors']       = sys.stderr
environ['wsgi.version']      = (1, 0)
environ['wsgi.multithread']  = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once']     = True

if environ.get('HTTPS', 'off') in ('on', '1'):
    environ['wsgi.url_scheme'] = 'https'
else:
    environ['wsgi.url_scheme'] = 'http'

start_response 则是一个函数,原型是 start_response(status, response_headers, exc_info=None) 并且这个函数要返回一个可调用的 write(body_data) 对象。例如:

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')

headers_set = []  # 待发送的响应的header信息
headers_sent = [] # 已发送的响应的header信息
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]

    return write

这样其实一个满足 WSGI 协议的 web服务器端 就基本完成了,现在需要整合一下,由于需要涉及到请求包的分析过程,我们就直接用标准库 wsgiref.simple_server 中的 WSGIServer 作为web服务器。

整合一下:

import sys
import os
from wsgiref.simple_server import WSGIServer, WSGIRequestHandler

def demo_app(environ,start_response):
    """
    示例的 app
    """
    stdout = "Hello world!"
    h = sorted(environ.items())
    for k,v in h:
        stdout += k + '=' + repr(v) + "\r\n"
    print(start_response)
    start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')])
    return [stdout.encode("utf-8")]

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(request, client_address, server):
    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'] = True

    if 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]

        return write
    application = server.get_app()
    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()

server = WSGIServer(('', 8000), run_with_cgi)
server.set_app(demo_app)
server.serve_forever()

运行这个程序,然后用浏览器访问本地 8000 端口,就能看到终端输出了 environ

中间件

一个中间件扮演了与某些 application 相关的角色,同时,中间件也可以是某些服务器的应用程序。

中间件拥有如下功能:

  • 适当修改 environ 后,根据目标 URL 将请求分配到不同的应用程序对象;
  • 允许多个 application 在同一个进程中并行;
  • 通过网络转发请求和响应来负载平衡和远程处理;
  • 执行内容后处理,例如应用XSL样式表。

一般来说,中间件对于 "server/gateway" 和 "application/framework" 都是透明的,并且不需要特别的支持。如果用户将中间件集成到 application 中,那中间件提供给服务器调用,此时中间件就像 application 一样了;反过来,如果配置的中间件是调用 application 的调用方,那它就像服务器一样了。

因此,中间件包装的 "应用程序" 实际上也可能是另一个包装着应用程序的中间件。

大多数情况下,中间件必须符合 WSGI 服务器和应用程序端的限制和要求,django 中的中间件都是符合这些要求的。

标签:WSGI,start,wsgi,Django,write,headers,environ,深入,response
From: https://www.cnblogs.com/596014054-yangdongsheng/p/10420755.html

相关文章

  • 计算机毕业设计django+vue酒店客房管理系统【开题+论文+程序】
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着旅游业的蓬勃发展,酒店行业作为旅游产业链中的关键环节,其管理效率与服务质量直接影响到顾客的满意度与忠诚度。传统的酒店客房管理方式......
  • 在 windows 上部署 django
    环境Django4.1.7python3.11.2Apache2.4.461:安装配置Apache1.1:下载ApacheApache官方下载链接按照系统版本选择对应的,以下是64位操作系统的选择1.2:解压Apache下载完成后,解压到你要部署服务器的文件夹。建议在你要部署服务器的电脑磁盘下建立一个英文目录,如E:\pen......
  • 深入解析Node.js中的fs.watch:options与listener详解
    在Node.js中,fs.watch方法是一个功能强大的文件系统监控工具,它允许我们对文件或目录进行实时监控,并在文件或目录发生变化时触发相应的操作。在使用fs.watch时,两个关键的部分是options对象和listener回调函数。本文将详细讲解这两个部分,帮助读者更好地理解和使用fs.watch。一......
  • DEVCON初级使用教程结构,你可以根据自己的需求深入学习每个部分。深入了解 DEVCON 的中
    DEVCON(DeviceConsole)是一个用于管理Windows设备驱动程序和设备的命令行工具。它提供了一个用于列出、启用、禁用、安装、卸载和更新设备驱动程序的接口。以下是一个DEVCON初级使用教程的大纲,帮助你了解如何开始使用这个工具。DEVCON初级使用教程大纲1. 介绍什么是DEVCON?......
  • 快速认识JVM,深入掌握类加载器、JVM内存空间、JVM垃圾回收机制和双亲委派机制。
    什么是JVM    JVM(JavaVirtualMchine)java虚拟机,主要作用就是将Java字节码文件解释为能被各种操作系统理解的机器码。JVM的三大功能    1.解释和运行:将字节码文件翻译为能被各种操作系统理解的机器码。    2.内存分配:给对象及其方法分配内存,管理......
  • 计算机毕业设计django+vue代驾服务【开题+论文+程序】
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着城市化进程的加速和汽车保有量的持续增长,代驾服务作为一种便捷、安全的出行方式,逐渐受到广大消费者的青睐。然而,传统的代驾服务模式往......
  • 计算机毕业设计django+vue民宿预定管理系统625l0【开题+论文+程序】
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着互联网技术的迅猛发展和旅游业的不断壮大,民宿作为旅游住宿的重要组成部分,其市场需求日益增加。然而,传统的民宿管理方式已难以满足日益......
  • Django 解除跨域限制
    Django解除跨域限制在Web开发中,跨域资源共享(CORS)是一个重要的安全特性,它限制了网页只能与其同源的服务器进行交互。然而,在开发过程中,我们经常需要前端(如Vue.js、React等)与后端(如Django)之间进行跨域请求。为了方便开发和测试,我们需要在Django中解除跨域限制。本文将详细介绍如何......
  • 基于django+vue基于web的园区车辆出入管理系统【开题报告+程序+论文】计算机毕设
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容研究背景随着城市化进程的加快,各类产业园区、住宅小区及商业综合体等园区规模不断扩大,车辆管理成为园区管理中的重要环节。传统的车辆出入管理方式......
  • JDK自带命令:深入理解Java程序的运行机制
    JDK(JavaDevelopmentKit)是Java开发和运行环境的核心,它提供了丰富的命令和工具来帮助我们更好地理解和控制Java程序的运行。本文将详细介绍JDK自带的一些关键命令,以及它们的详细参数和执行结果。1.jps(JavaVirtualMachineProcessStatusTool)jps命令用于列出正在运行的......