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.
The availability and widespread use of such an API in web servers for Python – whether those servers are written in Python (e.g. Medusa), embed(嵌入) Python (e.g. mod_python), or invoke Python via a gateway protocol (e.g. CGI, FastCGI, etc.) – would separate choice of framework from choice of web server, freeing users to choose a pairing that suits them, while freeing framework and server developers to focus on their preferred area of specialization.
This PEP, therefore, proposes(提出) a simple and universal interface between web servers and web applications or frameworks: the Python Web Server Gateway Interface (WSGI)
看原文更有味道
Thus, WSGI must be easy to implement, so that an author’s initial investment in the interface can be reasonably low.
Again, the goal of WSGI is to facilitate easy interconnection of existing servers and applications or frameworks, not to create a new web framework.
it allows for the possibility of an entirely new kind of Python web application framework: one consisting of loosely-coupled WSGI middleware components.
简单来说就是: enable the use of any framework with any server
OverView
The Application/Framework Side
The application object is simply a callable object that accepts two arguments. The term “object” should not be misconstrued as requiring an actual object instance: a function, method, class, or instance with a __call__ method are all acceptable for use as an application object. Application objects must be able to be invoked more than once, as virtually all servers/gateways (other than CGI) will make such repeated requests.
defsimple_app(environ, start_response): """Simplest possible application object""" status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return ['Hello world!\n']
classAppClass: """Produce the same output, but using a class (Note: 'AppClass' is the "application" here, so calling it returns an instance of 'AppClass', which is then the iterable return value of the "application callable" as required by the spec. If we wanted to use *instances* of 'AppClass' as application objects instead, we would have to implement a '__call__' method, which would be invoked to execute the application, and we would need to create an instance for use by the server or gateway. """
def__init__(self, environ, start_response): self.environ = environ self.start = start_response
The server or gateway invokes the application callable once for each request it receives from an HTTP client, that is directed at the application. To illustrate, here is a simple CGI gateway, implemented as a function taking an application object. Note that this simple example has limited error handling, because by default an uncaught exception will be dumped to sys.stderr and logged by the web server.
if environ.get('HTTPS', 'off') in ('on', '1'): environ['wsgi.url_scheme'] = 'https' else: environ['wsgi.url_scheme'] = 'http'
headers_set = [] headers_sent = []
defwrite(data): ifnot headers_set: raise AssertionError("write() before start_response()")
elifnot headers_sent: # Before the first output, send the stored headers # 自动解包依次赋值给变量 status, response_headers = headers_sent[:] = headers_set # 为啥是 \r\n? sys.stdout.write('Status: %s\r\n' % status) for header in response_headers: sys.stdout.write('%s: %s\r\n' % header) sys.stdout.write('\r\n')
sys.stdout.write(data) sys.stdout.flush()
defstart_response(status, response_headers, exc_info=None): if exc_info: try: if headers_sent: # Re-raise original exception if headers sent raise exc_info[0], exc_info[1], exc_info[2] finally: exc_info = None# avoid dangling circular ref elif headers_set: raise AssertionError("Headers already set!") # 保持 headers_set id 不变,只将值赋给其 headers_set[:] = [status, response_headers] return write
result = application(environ, start_response) try: # 把所有返回的结果都写好之后再返回 for data in result: if data: # don't send headers until body appears write(data) ifnot headers_sent: write('') # send headers now if body was empty finally: ifhasattr(result, 'close'): result.close()
# 除标注定义外的一些额外字段的添加? for k, v in self.headers.items(): k=k.replace('-','_').upper(); v=v.strip() if k in env: continue# skip content length, type,etc. if'HTTP_'+k in env: env['HTTP_'+k] += ','+v # comma-separate multiple headers else: env['HTTP_'+k] = v return env
defget_stderr(self): return sys.stderr
# 处理单次请求 defhandle(self): """Handle a single HTTP request"""
ifnot self.parse_request(): # An error code has been sent, just exit return # 请求交给 handler 处理 handler = ServerHandler( self.rfile, self.wfile, self.get_stderr(), self.get_environ() ) handler.request_handler = self # backpointer for logging # 把封装的环境变量交给 ServerHandler,然后由 ServerHandler 调用 wsgi app handler.run(self.server.get_app())
defdemo_app(environ,start_response): from io import StringIO stdout = StringIO() print("Hello world!", file=stdout) print(file=stdout) h = sorted(environ.items()) for k,v in h: print(k,'=',repr(v), file=stdout) start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')]) return [stdout.getvalue().encode("utf-8")]
defmake_server( host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler ): """Create a new WSGI server listening on `host` and `port` for `app`""" server = server_class((host, port), handler_class) server.set_app(app) return server
if __name__ == '__main__': with make_server('', 8000, demo_app) as httpd: sa = httpd.socket.getsockname() print("Serving HTTP on", sa[0], "port", sa[1], "...") import webbrowser webbrowser.open('http://localhost:8000/xyz?abc') httpd.handle_request() # serve one request, then exit
简易 demo
我们可以自己尝试弄一个简易的服务器来玩
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
# app.py defhello_world_app(environ, start_response): # environ 是一个包含所有 HTTP 请求信息的 dict 对象 status = "200 OK" # HTTP响应的输出都可以通过 start_response() 加上函数返回值作为 Body headers = [("Content-type", "text/html")] start_response(status, headers) body = "<h1>hello {}</h1>".format(environ['PATH_INFO'][1:] or"Web") # 去掉第一个斜杠 return [body.encode("utf-8")]
# server.py from wsgiref.simple_server import make_server from app import hello_world_app
httpd = make_server('', 8000, hello_world_app) print("Starting server at 8000") httpd.serve_forever()