先说说WSGI的表面含义,WebServerGatewayInterface的缩写,即Web服务器网关接口。对于之前不知道WSGI含义的小伙伴,看了上面的解释,估计还是不太了解,下面结合实际场景描述,给大家一个大概的了解。最后我们自己实现一个来加深对WSGI的理解。我们现在使用Python来编写Web应用程序。我们可以使用比较流行的Flask和Django框架,也可以直接按照自己的想法自己写一个。可选的服务器软件也很多,比如Apache、Nginx、IIS等,也有很多小众软件。但现在的问题是,我该如何部署呢?在WSGI规范之前,一台服务器使用这种方法来调度Python应用程序,另一台服务器使用那种方法。这样,编写的应用程序在部署时只能选择一台或有限的几台服务器。效果一般。注意:以下代码基于Python3.6。如果有这样的服务器wsgi/server.py#coding=utf-8importsocketlistener=socket.socket()listener.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)listener.bind(('0.0.0.0',8080))listener.listen(1)print('在0.0.0.0端口8080上提供HTTP...')whileTrue:client_connection,client_address=\listener.accept()print(f'Serverreceivedconnection'f'from{client_address}')request=client_connection.recv(1024)print(f'requestwereceived:{request}')response=b"""HTTP/1.1200OKHello,World!"""client_connection.sendall(response)client_connection.close()实现起来比较简单,就是监听8080端口,如果有请求就在终端打印,返回一个Hello,World!回复。在终端启动服务器?wsgipythonserver.pyServingHTTPon0.0.0.0port8080...打开另一个终端并请求?~curl127.0.0.1:8080HTTP/1.1200OKHello,World!这意味着服务器工作正常。另外还有一个web应用wsgi/app.py#coding=utf-8defsimple_app():returnb'Hello,World!\r\n'Nowtodeploy(也就是让整体运行起来),simple而粗暴的方式就是直接在服务端调用app中对应的方法。像这样wsgi/server2.py#coding=utf-8importsocketlistener=socket.socket()listener.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)listener.bind(('0.0.0.0',8080))listener。listen(1)print('在0.0.0.0端口8080上提供HTTP...')whileTrue:client_connection,client_address=\listener.accept()print(f'Serverreceivedconnection'f'from{client_address}')request=client_connection.recv(1024)print(f'requestwereceived:{request}')fromappimportsimple_appresponse='HTTP/1.1200OK\r\n\r\n'response=response.encode('utf-8')response+=simple_app()client_connection.sendall(response)client_connection.close()运行脚本注意:因为同一个端口是使用时,请先关闭最后一个脚本再执行,否则会因端口冲突错误而失败。?wsgipythonserver2.pyServingHTTPon0.0.0.0port8080...然后请求看效果?~curl127.0.0.1:8080Hello,World!嗯,没关系。但是,上面的服务器和应用程序是作为一个整体运行的,所以我应该换一个服务器或应用程序。由于没有规范服务器和应用程序之间如何交互,比如服务器应该如何将请求信息传递给应用程序,应用程序处理完后如何告诉服务器开始返回响应,如果它们是都不同,服务端需要定制应用,应用也需要定制应用。要自定义服务器,应用程序运行起来太麻烦了。因此,WSGI的出现就是为了解决以上问题。它规定了服务器如何将请求信息告诉应用程序,应用程序如何将执行状态返回给服务器。在这种情况下,服务器和应用程序按照相同的标准工作。标准、服务器和应用可以随意搭配,灵活性大大提高。什么是WSGI标准化,下图可以很直观。[Image]首先,应用程序必须是一个可调用对象,可以是一个函数,也可以是一个实现了__call__()方法的对象。每次收到请求,服务器都会通过application_callable(environ,start_response)调用应用。当应用程序处理完准备返回数据时,它首先调用服务传递给它的函数start_response(status,headers,exec_info),最后返回一个可迭代对象作为数据。(不了解可迭代对象的小伙伴可以看我之前的文章《搞清楚Python的迭代器、可迭代对象、生成器》)其中environ必须是一个字典,包含了请求的相关信息,比如请求方法,请求路径等,start_response是应用处理completed之后需要调用的函数是用来告诉服务设置响应的头信息或者错误处理等,status这里必须是像999Message这样的字符串,比如200OK,404NotFound,etc.headers是由(header_name,header_value)这样的元组组成的列表,最后的exec_info是一个可选参数,一般在应用程序出现错误的时候会用到。知道了WSGI的大致概念,我们来实现一个。首先是应用wsgi/wsgi_app.py#coding=utf-8defsimple_app(environ,start_response):status='200OK'response_headers=[('Content-type','text/plain')]start_response(status,response_headers)return[f'Request{environ["REQUEST_METHOD"]}'f'{environ["PATH_INFO"]}hasbeen'f'processed\r\n'.encode('utf-8')]这里定义了一个函数(可调用对象),它可以使用服务器传递给它的与请求相关的内容环境,这里使用了REQUEST_METHOD和PATH_INFO信息。返回前调用start_response,方便服务器设置一些头信息。然后是服务器wsgi/wsgi_server.py#coding=utf-8importsocketlistener=socket.socket()listener.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)listener.bind(('0.0.0.0',8080))listener.listen(1)print('在0.0.0.0端口8080上提供HTTP...')whileTrue:client_connection,client_address=\listener.accept()print(f'Serverreceivedconnection'f'from{client_address}')request=client_connection.recv(1024)print(f'requestwereceived:{request}')headers_set=Nonedefstart_response(status,headers):globalheaders_setheaders_set=[status,headers]方法,路径,_=request.split(b'',2)environ={'REQUEST_METHOD':method.decode('utf-8'),'PATH_INFO':path.decode('utf-8')}fromwsgi_appimportsimple_appapp_result=simple_app(environ,start_response)response_status,response_headers=headers_setresponse=f'HTTP/1.1{response_status}\r\n'forheaderinresponse_headers:response+=f'{header[0]}:{header[1]}\r\n'response+='\r\n'response=response.encode('utf-8')forapp_result中的数据:response+=dataclient_connection.sendall(response)client_connection.close()服务器监控的相关代码没有太大变化,主要是处理请求的时候有些差异。首先,定义了start_response(status,headers)函数,这个函数本身并不传递。然后调用application,将当前请求信息environ和上面的start_response函数传递给它,让它决定使用什么请求信息,在处理完成准备返回数据之前调用start_response设置header信息。好了,启动服务器后(也就是执行服务器代码,和上一个差不多,这里就不细说了),然后请求看结果?~curl127.0.0.1:8080/user/1请求GET/user/1已经处理好了,程序正常。为了便于上图说明,代码耦合度比较高。如果服务器需要更改应用程序,则必须修改服务器代码,这显然是有问题的。现在原理差不多说清楚了,我们把代码优化下wsgi/wsgi_server_oop.py#coding=utf-8importsocketimportsysclassWSGIServer: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('ServingHTTPon0.0.0.0''port8080...')self.app=Noneself.headers_set=Nonedefset_app(self,application):self.app=applicationdefstart_response(self,status,headers):self.headers_set=[status,headers]defserve_forever(self):whileTrue:listener=self.listenerclient_connection,client_address=\listener.accept()print(f'Serverreceivedconnection'f'from{client_address}')request=client_connection.recv(1024)print(f'请求我们重新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':错误,'wsgi.run_once':错误,'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_setresponse=f'HTTP/1.1{response_status}\r\n'forheaderinresponse_headers:response+=f'{header[0]}:{header[1]}\r\n'响应+='\r\n'response=response.encode('utf-8')fordatainapp_result:response+=dataclient_connection.sendall(response)client_connection.close()if__name__=='__main__':iflen(sys.argv)<2:sys.exit('ArgvError')app_path=sys.argv[1]module,app=app_path.split(':')module=__import__(module)app=getattr(module,app)server=WSGIServer()服务器。set_app(app)server.serve_forever()的基本原理没有改变,只是面向对象的方式修改了原来的代码,并且environ增加了一些必要的环境信息以使用之前的应用?wsgipythonwsgi_server_oop.pywsgi_app:simple_appServingHTTPon0.0.0.0port8080...Request?~curl127.0.0.1:8080/user/1RequestGET/user/1hasbeenprocessed得到和之前一样的结果。Flask应用程序可以工作吗?试试看,先新建一个wsgi/flask_app.py#coding=utf-8fromflaskimportFlaskfromflaskimportResponseflask_app=Flask(__name__)@flask_app.route('/user/
