Python 创建WSGI应用程序

Python 创建WSGI应用程序,首先使用一个简单的URL模式匹配表达式来定义应用程序中的唯一路由。在一个更大或更复杂的应用程序中,模式可能不止一种:

import re
path_pat= re.compile(r"^/anscombe/(?P<dataset>.*?)/?$")

通过这种模式,可以在路径顶层定义一个关于WSGI的完整脚本。在本例中,该脚本是anscombe。我们把下一级路径作为通过安斯库姆四重奏选取的数据集,数据集的值落于IIIIIIIV

使用命名参数作为选取条件。在许多情况下,可以使用如下语法来描述RESTful API:

/anscombe/{dataset}/

这种理想化的模式转化为了适当的正则表达式,并在路径中保留了数据集选择器的名称。

一些URL路径示例如下,它们演示了这种模式的工作原理:

>>> m1 = path_pat.match( "/anscombe/I" )
>>> m1.groupdict()
{'dataset': 'I'}
>>> m2 = path_pat.match( "/anscombe/II/" )
>>> m2.groupdict()
{'dataset': 'II'}
>>> m3 = path_pat.match( "/anscombe/" )
>>> m3.groupdict()
{'dataset': ''}

这些示例都显示了从URL路径中解析得到的详细信息。如果指定了某个序列的名称,则它会出现在路径中;如果没有指定序列名称,则模式返回的是一个空字符串。

完整的WSGI应用程序如下:

import traceback
import urllib.parse
def anscombe_app(
        environ: Dict, start_response: SR_Func
    ) -> Iterable[bytes]:
    log = environ['wsgi.errors']
    try:
        match = path_pat.match(environ['PATH_INFO'])
        set_id = match.group('dataset').upper()
        query = urllib.parse.parse_qs(environ['QUERY_STRING'])
        print(environ['PATH_INFO'], environ['QUERY_STRING'],
              match.groupdict(), file=log)

        dataset = anscombe_filter(set_id, raw_data())
        content_bytes, mime = serialize(
            query['form'][0], set_id, dataset)
        headers = [
            ('Content-Type', mime),
            ('Content-Length', str(len(content_bytes))),
        ]
        start_response("200 OK", headers)
        return [content_bytes]
    except Exception as e:  # pylint: disable=broad-except
        traceback.print_exc(file=log)
        tb = traceback.format_exc()
        content = error_page.substitute(
            title="Error", message=repr(e), traceback=tb)
        content_bytes = content.encode("utf-8")
        headers = [
            ('Content-Type', "text/html"),
            ('Content-Length', str(len(content_bytes))),
        ]
        start_response("404 NOT FOUND", headers)
        return [content_bytes]

该应用程序会从请求中提取两条信息:环境字典中的PATH_INFO键和QUERY_STRING键。PATH_INFO请求会定义要提取的数据集。QUERY_STRING请求将指定输出的格式。

请注意,查询字符串可能非常复杂。我们使用了urllib.parse模块来准确定位查询字符串中所有名称和值的配对,而不是简单地假定它是一个类似于?form=json的字符串。在通过查询字符串提取得到的字典中,可以在query['form'][0]中找到以form为键的值。这应该是定义过的格式之一,否则会引发异常,并显示一个错误页面。

定位到路径和查询字符串后,用粗体强调了应用程序的处理过程。这两条语句依赖三个函数来对结果进行收集、过滤和序列化。

  • 函数raw_data()从文件中读取原始数据,结果是一个包含一组Pair对象的字典。
  • 函数anscombe_filter()接收一个选择字符串和源数据字典,并返回Pair对象的单个列表。
  • serialize()函数随后将该配对列表序列化为字节码。序列化器会生成字节码,这样就可以用合适的报头打包并返回了。

我们选择了生成一个HTTP的Content-Length报头作为结果的一部分。这个报头并不是必需的,但有助于下载大文件。由于我们决定发送该报头,因此必须用序列化的数据创建字节对象,以便统计这些字节。

如果选择忽略Content-Length报头,那么可以大规模地改变这个应用程序的结构。可以把每个序列化器都更改为生成器函数,并且它们在创建时会生成字节对象。对于大型数据集来说,这种优化可能有益,然而对于关注下载进度的用户可能不太友好,因为浏览器无法显示下载的完成情况。

一种常见的优化是将事务分解为两部分。第一部分用于计算结果并将文件放入Downloads目录。响应是一个带有Location报头的302 FOUND,用于标识需下载的文件。通常大部分客户端会基于这个原始响应来请求文件。可以用不涉及Python应用的Apache httpd或者Nginx下载文件。

本例将所有错误视为404 NOT FOUND错误会引起误导,因为可能是其他环节出了问题。更复杂的错误处理会引入更多的try:/except:块来提供更多信息反馈。

出于调试的目的,在生成的Web页面中提供了Python的栈跟踪。在没有调试需求的环境中,这是一种非常糟糕的做法。来自API的反馈应该只用于处理请求,无他。栈跟踪会向潜在的恶意用户提供过多信息。

Python教程

Java教程

Web教程

数据库教程

图形图像教程

大数据教程

开发工具教程

计算机教程