Web Server Gateway Interface 的缩写,即 Web 服务器网关接口。
它规定了服务器怎么把请求信息告诉给应用,应用怎么把执行情况回传给服务器,这样的话,服务器与应用都按一个标准办事,只要实现了这个标准,服务器与应用随意搭配就可以,灵活度大大提高。
首先,应用必须是一个可调用对象,可以是函数,也可以是实现了 __call__()
方法的对象。
每收到一个请求,服务器会通过 application_callable(environ, start_response) 调用应用。
应用在处理完毕准备返回数据的时候,先调用服务传给它的函数 start_response(status, headers, exec_info),最后再返回可迭代对象作为数据。(不理解可迭代对象的伙伴可以看下我之前的一篇文章《搞清楚Python的迭代器、可迭代对象、生成器》)
其中,environ 必须是一个字典,包括了请求的相关信息,比如请求方式、请求路径等等,start_response 是应用处理完毕后,需要调用的函数,用于告诉服务设置响应的头部信息或错误处理等等。
status 必须是 999 Message here
这样的字符串,比如 200 OK
、404 Not Found
等,headers 是一个由 (header_name, header_value) 这样的元祖组成的列表,最后一个 exec_info 是可选参数,一般在应用出现错误的时候会用到。
def simple_app(environ, start_response): status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return [f'Request {environ["REQUEST_METHOD"]}' f' {environ["PATH_INFO"]} has been' f' processed\r\n'.encode('utf-8')] 这里定义了一个函数(可调用对象),它可以使用服务器传给它的请求相关的内容 environ,这里使用了 REQUEST_METHOD 和 PATH_INFO 信息。在返回之前调用了 start_response,方便服务器设置一些头部信息。 然后是服务器 import socket import sys class WSGIServer: def __init__(self): self.listener = socket.socket() self.listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.listener.bind(('0.0.0.0', 8080)) self.listener.listen(1) print('Serving HTTP on 0.0.0.0' ' port 8080 ...') self.app = None self.headers_set = None def set_app(self, application): self.app = application def start_response(self, status, headers): self.headers_set = [status, headers] def serve_forever(self): while True: listener = self.listener client_connection, client_address = \ listener.accept() print(f'Server received connection' f' from {client_address}') request = client_connection.recv(1024) print(f'request we received: {request}') method, path, _ = request.split(b' ', 2) # 为简洁的说明问题,这里填充的内容有些随意 # 如果有需要,可以自行完善 environ = { 'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.input': request, 'wsgi.errors': sys.stderr, 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'REQUEST_METHOD': method.decode('utf-8'), 'PATH_INFO': path.decode('utf-8'), 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '8080', } app_result = self.app(environ, self.start_response) response_status, response_headers = self.headers_set response = f'HTTP/1.1 {response_status}\r\n' for header in response_headers: response += f'{header[0]}: {header[1]}\r\n' response += '\r\n' response = response.encode('utf-8') for data in app_result: response += data client_connection.sendall(response) client_connection.close() if __name__ == '__main__': if len(sys.argv) < 2: sys.exit('Argv Error') app_path = sys.argv[1] module, app = app_path.split(':') module = __import__(module) app = getattr(module, app) server = WSGIServer() server.set_app(app) server.serve_forever()
首先定义了 start_response(status, headers) 函数,自身并不会调用。
然后调用应用,将当前的请求信息 environ 和上面的 start_response 函数传给它,让其自己决定使用什么请求信息以及在处理完成准备返回数据之前调用 start_response 设置头部信息。