Web Server Gateway Interface(wsgi),即Web服务器网关接口,是Web服务器软件和用Python编写的Web应用程序之间的标准接口。
想了解更多关于WSGI请前往: https://www.cnblogs.com/delav/p/9571865.html
wsgiref是wsgi规范的参考实现。
wsgiref目录:Python27\Lib\wsgiref
共有五个文件:
handlers.py #负责wsgi程序的处理 headers.py #处理HTTP响应头 simple_server.py #实现wsgi协议的简单服务器 util.py # 一些wsgi相关的其他处理 validate.py #检查符合wsgi规范的中间件
首先实现一个简单例子
simple.py
# coding: utf-8 from wsgiref.simple_server import make_server def app(environ, start_respponse): status = "200 OK" header = [('Content-type', 'text/html')] start_respponse(status, header) return 'Hello World' httpd = make_server('', 8080, app) print "Sever on port 8080....." httpd.serve_forever()
运行simple.py,打开浏览器输入 http://127.0.0.1:8080
页面会显示 Hello World
simple_sever.py源码
"""BaseHTTPServer that implements the Python WSGI protocol (PEP 333, rev 1.21) This is both an example of how WSGI can be implemented, and a basis for running simple web applications on a local machine, such as might be done when testing or debugging an application. It has not been reviewed for security issues, however, and we strongly recommend that you use a "real" web server for production use. For example usage, see the 'if __name__=="__main__"' block at the end of the module. See also the BaseHTTPServer module docs for other API information. """ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer import urllib, sys from wsgiref.handlers import SimpleHandler __version__ = "0.1" __all__ = ['WSGIServer', 'WSGIRequestHandler', 'demo_app', 'make_server'] server_version = "WSGIServer/" + __version__ sys_version = "Python/" + sys.version.split()[0] software_version = server_version + ' ' + sys_version class ServerHandler(SimpleHandler): server_software = software_version def close(self): try: self.request_handler.log_request( self.status.split(' ',1)[0], self.bytes_sent ) finally: SimpleHandler.close(self) class WSGIServer(HTTPServer): """BaseHTTPServer that implements the Python WSGI protocol""" application = None def server_bind(self): """Override server_bind to store the server name.""" HTTPServer.server_bind(self) self.setup_environ() def setup_environ(self): # Set up base environment env = self.base_environ = {} env['SERVER_NAME'] = self.server_name env['GATEWAY_INTERFACE'] = 'CGI/1.1' env['SERVER_PORT'] = str(self.server_port) env['REMOTE_HOST']='' env['CONTENT_LENGTH']='' env['SCRIPT_NAME'] = '' def get_app(self): return self.application def set_app(self,application): self.application = application class WSGIRequestHandler(BaseHTTPRequestHandler): server_version = "WSGIServer/" + __version__ def get_environ(self): env = self.server.base_environ.copy() env['SERVER_PROTOCOL'] = self.request_version env['REQUEST_METHOD'] = self.command if '?' in self.path: path,query = self.path.split('?',1) else: path,query = self.path,'' env['PATH_INFO'] = urllib.unquote(path) env['QUERY_STRING'] = query host = self.address_string() if host != self.client_address[0]: env['REMOTE_HOST'] = host env['REMOTE_ADDR'] = self.client_address[0] if self.headers.typeheader is None: env['CONTENT_TYPE'] = self.headers.type else: env['CONTENT_TYPE'] = self.headers.typeheader length = self.headers.getheader('content-length') if length: env['CONTENT_LENGTH'] = length for h in self.headers.headers: k,v = h.split(':',1) 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 def get_stderr(self): return sys.stderr def handle(self): """Handle a single HTTP request""" self.raw_requestline = self.rfile.readline(65537) if len(self.raw_requestline) > 65536: self.requestline = '' self.request_version = '' self.command = '' self.send_error(414) return if not self.parse_request(): # An error code has been sent, just exit return handler = ServerHandler( self.rfile, self.wfile, self.get_stderr(), self.get_environ() ) handler.request_handler = self # backpointer for logging handler.run(self.server.get_app()) def demo_app(environ,start_response): from StringIO import StringIO stdout = StringIO() print >>stdout, "Hello world!" print >>stdout h = environ.items(); h.sort() for k,v in h: print >>stdout, k,'=', repr(v) start_response("200 OK", [('Content-Type','text/plain')]) return [stdout.getvalue()] def make_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__': httpd = make_server('', 8000, demo_app) 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 httpd.server_close()
服务是通过 make_sever 这个函数来启动的
def make_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
调用启动make_server后,会监听host主机(为空表示本地主机)的port端口,当收到客户端的请求后,先经过WSGIServer和WSGIRequestHandler的处理,再把处理后的请求发送给app应用程序,app返回请求的结果。
WSDIServer类的内容如下:
class WSGIServer(HTTPServer): """BaseHTTPServer that implements the Python WSGI protocol""" application = None def server_bind(self): """Override server_bind to store the server name.""" HTTPServer.server_bind(self) self.setup_environ() def setup_environ(self): # Set up base environment env = self.base_environ = {} env['SERVER_NAME'] = self.server_name env['GATEWAY_INTERFACE'] = 'CGI/1.1' env['SERVER_PORT'] = str(self.server_port) env['REMOTE_HOST']='' env['CONTENT_LENGTH']='' env['SCRIPT_NAME'] = '' def get_app(self): return self.application def set_app(self,application): self.application = application
WSDIServer继承HTTPServer,(使用pycharm不断追踪,会找到最终的TCPServer原始的绑定套接字方法),在HTTPServer的基础上添加符合wsgi规范的内容。
这里的server_bind方法覆盖了原来HTTPServer的server_bind方法,增加setup_environ的功能,setup_environ方法是用来初始化environ变量,是一个字典。
还有get_app和set_app方法,添加了符合wsgi的application
WSGIRequestHandler类的内容如下:
class WSGIRequestHandler(BaseHTTPRequestHandler): server_version = "WSGIServer/" + __version__ def get_environ(self): env = self.server.base_environ.copy() env['SERVER_PROTOCOL'] = self.request_version env['REQUEST_METHOD'] = self.command if '?' in self.path: path,query = self.path.split('?',1) else: path,query = self.path,'' env['PATH_INFO'] = urllib.unquote(path) env['QUERY_STRING'] = query host = self.address_string() if host != self.client_address[0]: env['REMOTE_HOST'] = host env['REMOTE_ADDR'] = self.client_address[0] if self.headers.typeheader is None: env['CONTENT_TYPE'] = self.headers.type else: env['CONTENT_TYPE'] = self.headers.typeheader length = self.headers.getheader('content-length') if length: env['CONTENT_LENGTH'] = length for h in self.headers.headers: k,v = h.split(':',1) 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 def get_stderr(self): return sys.stderr def handle(self): """Handle a single HTTP request""" self.raw_requestline = self.rfile.readline(65537) if len(self.raw_requestline) > 65536: self.requestline = '' self.request_version = '' self.command = '' self.send_error(414) return if not self.parse_request(): # An error code has been sent, just exit return handler = ServerHandler( self.rfile, self.wfile, self.get_stderr(), self.get_environ() ) handler.request_handler = self # backpointer for logging handler.run(self.server.get_app())
WSGIRequestHandler继承BaseHTTPRequestHandler类,BaseHTTPRequestHandler是对客户端的请求进行处理,WSGIRequestHandler在这个的基础上添加符合wsgi规范的相关内容。
get_environ方法用来解析environ变量
get_stderr方法作异常处理
handle方法用来处理请求,把封装的environ变量交给 ServerHandler,然后由 ServerHandler 调用app应用程序。
app应用程序必须接受两个参数,一个是environ,另一个是start_response。
在前面实现的例子simple.py的app函数下打印environ(print environ),会输出一个字典,记录着CGI中的环境变量。
而start_response函数是handlers.py文件里面的,源码如下:
def start_response(self, status, headers,exc_info=None): """'start_response()' callable as specified by PEP 333""" if exc_info: try: if self.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 self.headers is not None: raise AssertionError("Headers already set!") assert type(status) is StringType,"Status must be a string" assert len(status)>=4,"Status must be at least 4 characters" assert int(status[:3]),"Status message must begin w/3-digit code" assert status[3]==" ", "Status message must have a space after code" if __debug__: for name,val in headers: assert type(name) is StringType,"Header names must be strings" assert type(val) is StringType,"Header values must be strings" assert not is_hop_by_hop(name),"Hop-by-hop headers not allowed" self.status = status self.headers = self.headers_class(headers) return self.write def write(self, data): """'write()' callable as specified by PEP 333""" assert type(data) is StringType,"write() argument must be string" if not self.status: raise AssertionError("write() before start_response()") elif not self.headers_sent: # Before the first output, send the stored headers self.bytes_sent = len(data) # make sure we know content-length self.send_headers() else: self.bytes_sent += len(data) # XXX check Content-Length and truncate if too many bytes written? self._write(data) self._flush()
start_response接受两个参数status(HTTP状态)和headers(HTTP响应头header),返回write方法,write方法返回的是HTTP响应体body,必须返回一个可调用对象。
把前面的simple.py改一下
simple.py # coding: utf-8 from wsgiref.simple_server import make_server def app(environ, start_respponse): status = "200 OK" header = [('Content-type', 'text/html')] # start_respponse(status, header) write = start_response(status, header) write('Delav! ') return 'Hello World' httpd = make_server('', 8080, app) print "Sever on port 8080....." httpd.serve_forever()
再运行打开浏览器127.0.0.1:8080,你会发现输出为 Delav! Hello World
我们在调用app应用程序的时候,会对应用执行结果迭代输出。
app应用程序必须在第一次返回可迭代数据(return "Hello World")之前调用 start_response 方法。
这是因为可迭代数据是返回数据的body部分,在它返回之前,需要使用start_response返回 response_headers 数据。
一个HTTP请求过程:
- 服务器程序创建 socket,并监听8080端口,等待客户端的连接
- 客户端发送 http 请求(浏览器访问127.0.0.1:8080)
- socket server 读取请求的数据,交给 http server
- http server 根据 http 的规范解析请求,然后把请求交给 WSGIServer
- WSGIServer 把客户端的信息存放在 environ 变量里,然后交给绑定的 handler 处理请求
- HTTPHandler 解析请求,把 method、path 等放在 environ,然后 WSGIRequestHandler 把服务器端的信息也放到 environ 里
- WSGIRequestHandler 调用绑定的 wsgi ServerHandler,把上面包含了服务器信息、客户端信息、本次请求信息的 environ 传递过去
- wsgi ServerHandler 调用注册的 wsgi app,把 environ 和 start_response 传递过去
- wsgi app 将reponse header、status、body 回传给 wsgi handler
- 然后 handler 逐层传递,最后把这些信息通过 socket 发送到客户端
- 客户端的程序接到应答,解析应答,并把结果打印出来