#12 WSGI

2014-03-01
  • https://peps.python.org/pep-3333/
  • http://wsgi.tutorial.codepoint.net/
  • https://wsgi.readthedocs.io/en/latest/index.html
  • https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface

The Web Server Gateway Interface (WSGI, pronounced whiskey or WIZ-ghee) is a simple calling convention for web servers to forward requests to web applications or frameworks written in the Python programming language.
The current version of WSGI, version 1.0.1, is specified in Python Enhancement Proposal (PEP) 3333.
Web 服务器网关接口(WSGI,发音为威士忌或 WIZ-ghee)是一种简单的调用约定,用于将请求转发到用 Python 编写的 Web 应用程序或框架的 Web 服务器。
当前版本的 WSGI,即 1.0.1 版本,由 Python 增强提案(PEP)3333 指定。

WSGI was originally specified as PEP-333 in 2003.
PEP-3333, published in 2010, updates the specification for Python 3.
WSGI 最初是在 2003 年的 PEP-333 中指定的。
2010 年发布的 PEP-3333 更新了 Python 3 的规范。

Background

In 2003, Python web frameworks were typically written against only CGI, FastCGI, mod_python, or some other custom API of a specific web server. To quote PEP 333:
2003 年,Python Web 框架通常只针对 CGI、FastCGI、mod_python 或某些特定 Web 服务器的其他自定义 API 编写。引用 PEP 333 的话:

Python currently boasts a wide variety of web application frameworks, such as Zope, Quixote, Webware, SkunkWeb, PSO, and Twisted Web -- to name just a few. This wide variety of choices can be a problem for new Python users, because generally speaking, their choice of web framework will limit their choice of usable web servers, and vice versa... By contrast, although Java has just as many web application frameworks available, Java's "servlet" API makes it possible for applications written with any Java web application framework to run in any web server that supports the servlet API.
Python 目前拥有众多 Web 应用程序框架,例如 Zope、Quixote、Webware、SkunkWeb、PSO 和 Twisted Web 等等。这种广泛的选择可能会成为新 Python 用户的问题,因为一般来说,他们选择的 Web 框架将限制他们可用的 Web 服务器的选择,反之亦然……相比之下,尽管 Java 也拥有同样多的 Web 应用程序框架,但 Java 的“servlet”API 使得使用任何 Java Web 应用程序框架编写的应用程序都能在支持 servlet API 的任何 Web 服务器中运行。

WSGI was thus created as an implementation-neutral interface between web servers and web applications or frameworks to promote common ground for portable web application development.
因此,WSGI 被创建为实现中立的 Web 服务器和 Web 应用程序或框架之间的接口,以促进可移植 Web 应用程序的开发和共同基础。

Specification overview

The WSGI has two sides:
WSGI 有两个方面:

  • the server/gateway side. This is often running full web server software such as Apache or Nginx, or is a lightweight application server that can communicate with a webserver, such as flup.
    服务器/网关方面。这通常运行完整的 Web 服务器软件,例如 Apache 或 Nginx,或者是可以与 Web 服务器通信的轻量级应用程序服务器,例如 flup。
  • the application/framework side. This is a Python callable, supplied by the Python program or framework.
    应用程序/框架方面。这是由 Python 程序或框架提供的 Python 可调用对象。

Between the server and the application, there may be one or more WSGI middleware components, which implement both sides of the API, typically in Python code.
在服务器和应用程序之间,可能会有一个或多个WSGI 中间件组件,它们在 Python 代码中实现了 API 的两个方面。

WSGI does not specify how the Python interpreter should be started, nor how the application object should be loaded or configured, and different frameworks and webservers achieve this in different ways.
WSGI 不指定 Python 解释器应如何启动,也不指定应用程序对象如何加载或配置,不同的框架和 Web 服务器以不同的方式实现这些功能。

WSGI middleware

A WSGI middleware component is a Python callable that is itself a WSGI application, but may handle requests by delegating to other WSGI applications. These applications can themselves be WSGI middleware components.
一个 WSGI 中间件组件是一个 Python 可调用对象,它本身是一个 WSGI 应用程序,但可以通过委托给其他 WSGI 应用程序来处理请求。这些应用程序本身可以是 WSGI 中间件组件。

A middleware component can perform such functions as:
中间件组件可以执行以下功能:

  • Routing a request to different application objects based on the target URL, after changing the environment variables accordingly.
    根据目标 URL 将请求路由到不同的应用程序对象,同时相应地更改环境变量。
  • Allowing multiple applications or frameworks to run side-by-side in the same process
    允许多个应用程序或框架在同一进程中并行运行。
  • Load balancing and remote processing, by forwarding requests and responses over a network
    负载均衡和远程处理,通过网络转发请求和响应。
  • Performing content post-processing, such as applying XSLT stylesheets
    执行内容后处理,例如应用 XSLT 样式表。

Examples

Example application

A WSGI-compatible "Hello, World!" application written in Python:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    yield b'Hello, World!\n'

Where:

  • Line 1 defines a function named application, which takes two parameters, environ and start_response. environ is a dictionary containing CGI environment variables as well as other request parameters and metadata under well-defined keys. start_response is a callable itself, taking two positional parameters, status and response_headers.
    第 1 行定义了一个名为 application 的函数,它接受两个参数 environ 和 start_response。environ 是一个包含 CGI 环境变量以及其他请求参数和元数据的字典,这些参数和元数据在定义良好的键下。start_response 本身是一个可调用对象,接受两个位置参数,status 和 response_headers。
  • Line 2 calls start_response, specifying "200 OK" as the HTTP status and a "Content-Type" response header.
    第 2 行调用 start_response,指定 "200 OK" 作为 HTTP 状态和 "Content-Type" 响应头。
  • Line 3 makes the function into a generator. The body of the response is returned as an iterable of byte strings.
    第 3 行将函数转换为生成器。响应正文被返回为一个字节串的可迭代对象。

Example of calling an application

A full example of a WSGI network server is outside the scope of this article. Below is a sketch of how one would call a WSGI application and retrieve its HTTP status line, response headers, and response body, as Python objects. Details of how to construct the environ dict have been omitted.
完整的 WSGI 网络服务器示例不再本文范围之内。以下只是一个 Demo,说明如何调用 WSGI 应用程序并检索其 HTTP 状态行、响应头和响应正文,作为 Python 对象。如何构造 environ 字典的详细信息已被省略。

from io import BytesIO

def call_application(app, environ):
    status = None
    headers = None
    body = BytesIO()

    def start_response(rstatus, rheaders):
        nonlocal status, headers
        status, headers = rstatus, rheaders

    app_iter = app(environ, start_response)
    try:
        for data in app_iter:
            assert status is not None and headers is not None, \
                "start_response() was not called"
            body.write(data)
    finally:
        if hasattr(app_iter, 'close'):
            app_iter.close()
    return status, headers, body.getvalue()

environ = {...}  # "environ" dict
status, headers, body = call_application(app, environ)

PEP 3333

PEP 3333 引入了一些新特性和更严格的规范,以提高 Python Web 应用程序的可移植性和互操作性。其中一些变化包括:

  • 强制要求 WSGI 服务器和应用程序使用相同的字符编码。
  • 确保应用程序能够正确处理 HTTP 头部中的 Unicode 值。
  • 对异常处理进行了更好的规范,以允许更好的错误处理和调试。
  • 更好地定义了 WSGI 环境变量,以提供更一致的行为。

此外,PEP 3333 还引入了一些新的建议,包括使用可选的服务器和客户端请求信息,提供更好的错误处理和日志记录,以及更好地处理请求和响应的二进制数据。

#9 Python 命名元组

2013-12-23

collections.namedtuple 是一个工厂方法,用来创建一个 tuple 子类。

# python2
collections.namedtuple(typename, field_names[, verbose=False][, rename=False])
# verbose = True 对于 Python 初学者来说非常好,创建类型时,会输出类的完整定义,用来学习 Python 类的一些特性。
# 这个参数在 python3.7 版被移除

# python3
collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)
from collections import namedtuple

DBConn = namedtuple('MySQL', ('host', 'port', 'username', 'password', 'db'))
conn = DBConn('localhost', '3306', 'root', '123456', 'test')

conn
# MySQL(host='localhost', port='3306', username='root', password='123456', db='test')

conn._asdict()  # python2 输出 OrderedDict 类型
# {'host': 'localhost',
#  'port': '3306',
#  'username': 'root',
#  'password': '123456',
#  'db': 'test'}

conn.__class__
# __main__.MySQL

conn.host
# 'localhost'

#6 Tornado 1,2,3

2013-10-13

Tornado web server 是使用 Python 编写出來的一个极轻量级、高可伸缩性和非阻塞 IO 的 Web 服务器软件,著名的 Friendfeed 网站就是使用它搭建的。

Tornado 跟其他主流的 Web 服务器框架(主要是 Python 框架)不同是采用 epoll 非阻塞 IO,响应快速,可处理数千并发连接,特别适用用于实时的 Web 服务。

要使用它,必须按照以下套件:

1)Python(建议使用 Python 2.5 / Python 2.6)
2)Simplejson(建议使用 simplejson 2.0.9)
3)cURL(建议使用 curl 7.19.7 或以上版本)
4)Pycurl(建议使用 pycurl 7.16.2.1)
5)Tornado Web Server(这才是主角,版本就照官網上最新的安裝吧)

一个最简单的服务:

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

application = tornado.web.Application([
    (r"/", MainHandler),
])

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

在 Tornado 中运行 Django

#!/usr/bin/env python
# *-* encoding: utf-8 *-*

import os
import sys
import tornado.web
from tornado import autoreload
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import url
from django.conf import settings
from django.core.handlers.wsgi import WSGIHandler

if not os.path.dirname(__file__) in sys.path[:1]:
    sys.path.insert(0, os.path.dirname(__file__))

os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
            url(r"/static/(.+)", tornado.web.StaticFileHandler, dict(path=settings.MEDIA_ROOT), name='static_path'),
            url(r"/media/(.+)", tornado.web.StaticFileHandler, dict(path=settings.MEDIA_ROOT), name='media_path'),
        ]
        handlers.append(('.*', tornado.web.FallbackHandler, dict(fallback=WSGIContainer(WSGIHandler()))))

tornado.web.Application.__init__(self, handlers)
http_server = HTTPServer(Application())
http_server.listen(8080)
loop = IOLoop.instance()
autoreload.start(loop) #自动加载修改过的代码
loop.start()

#3 Python 可迭代对象

2013-04-30

可迭代对象(Iterable)就是可以用 for 循环遍历的数据结构,包括字符串、元组、列表、字典、集合等内置类型,也可以通过一些方法来是实现自定义可迭代对象。

https://docs.python.org/3/glossary.html#term-iterable
https://docs.python.org/3/glossary.html#term-iterator

迭代协议与序列协议

  • 迭代器协议(Iterator Protocol):如果一个对象包含 __next__ 方法,那就是一个迭代器。

迭代器可以使用 next 函数连续获取数据,内部实现就是调用 __next__ 方法获取一个数据项,如果循环结束,就会抛出 StopIteration。

上面讲的是 CPython 实现,实际上 Python 语法规范要求迭代器一定要有 __iter__ 方法。
一般实现中,都有加上 __iter__ 方法,直接 return self。

  • 可迭代对象协议(Iterable Protocol):如果一个对象包含 __iter__ 方法(返回一个迭代器对象),那么就是一个可迭代对象,

可以用在 for 循环,zip,map,filter 等需要可迭代对象的场景中。

  • 序列对象协议(Sequence Protocol):如果一个对象包含 __getitem__ 方法(使用 0 开始的整数作为索引),和 __len__ 方法,
    就可以执行一些序列对象的操作,比如...(?)。
    字符串,元组,列表都是序列对象。set 无序,dict 是按 key 索引,所以都不是序列对象。

  • 迭代器协议(Iterator Protocol):如果一个对象包含 __iter__ 方法,和 __next__ 方法,那就是一个迭代器。
    迭代器可以使用 next 函数连续获取数据,内部实现就是调用 __next__ 方法获取一个数据项,如果循环结束,就会抛出 StopIteration。
    CPython 没有按照语言规范要求迭代器一定要有 __iter__ 方法。但一般实现中,都是加上 __iter__ 方法,return self。

  • https://docs.python.org/3/c-api/abstract.html
  • Iterator Protocol https://docs.python.org/3/c-api/iter.html
  • Sequence Protocol https://docs.python.org/3/c-api/sequence.html

iter

iter(iterable)
iter(callable, sentinel) # sentinel:任意值都行

iter(1)
TypeError: 'int' object is not iterable
iter(1, 1)
TypeError: iter(v, w): v must be callable

iter 用于生成一个迭代器。

  • for 循环遍历的时候,会通过 iter 方法创建一个迭代器。
  • 可以创建迭代器的对象,必须支持 iterable protocol (包含 __iter__() 方法),
    或者支持 sequence protocol (包含 __getitem__() 方法,使用 0 开始的整数作为索引)

原理

迭代器(Iterator)

迭代器是可迭代对象的一种。它是一个具有 __iter__() 和 __next__() 方法的对象,可以逐个返回可迭代对象的元素。
字符串、元组、列表、字典、集合这些内置类型就都不是迭代器。

PEP 234 – Iterators
https://peps.python.org/pep-0234/

自定义可迭代对象

class MyIterable:
    def __init__(self, data):
        self.data = data

    def __iter__(self):
        self.index = 0
        return self

    def __next__(self):
        if self.index < len(self.data):
            result = self.data[self.index]
            self.index += 1
            return result
        else:
            raise StopIteration

a = MyIterable(['apple', 'banana', 'cherry'])
for item in a:
    print(item)