首先来介绍下WSGI.我们在写django或者flask程序的时候,可以通过request直接将客户端浏览器上的信息取下来.这也省去了我们自己去解析HTTP协议的时间.这其中的就是python自己实现的WSGI解析程序.

WSGI全称是Web Service Gateway Interface, WEB服务器网关接口.这个是python语音中所定义的web服务器和web应用程序之间或框架之间的通用接口标准.

WSGI就是一座桥梁,桥梁的一端成为服务器或网关端,另一端称为应用端或者框架端,WSGI的作用就是在协议之间相互转化.WSIG将web组建分成了三类,WEB服务器,WEB中间件与web应用程序.

接受HTTP请求、解析HTTP请求、发送HTTP响应都是重复的苦力活,如果我们自己来写这些底层代码,还没开始写HTML,先要花时间研读HTTP规范。所以底层的代码应该由专门的服务器软件实现,我们用python专注于生成HTML文档。

因为我们不想要接触TCP连接、HTTP原始请求和响应格式。所以需要一个统一的接口,专心用python编写Web业务。这个接口就是 WSGI(Web 服务器网关接口)

python中内置了一个WSGI服务器,这个模块叫wsgiref, 它是用纯python编写的WSGI服务器的参考实现,我们来看一个具体的例子:

from wsgiref.simple_server import  make_server

def application(environ,start_response):

    print environ

    start_response(\’200 OK\’,[(\’Content-type\’,\’text/html\’)])

    return \'<h1>hello world\n</>\’

 

httpd=make_server(\’127.0.0.1\’,8888,application)

httpd.serve_forever()

运行该程序并在浏览器中输入http://127.0.0.1:8888/.可以看到返回的结果

在这个程序中,首先定义了一个函数application.其中有2个参数,一个是environ,一个是start_response.

environ: 一个包含所有HTTP请求消息的dict对象,

start_response:一个发送HTTP响应的函数

那么这个appliation是如何被调用的呢,如果自己调用肯定拿不到environstart_response这两个参数,因为这2个参数我们无法自己提供,所以application函数必须由WSGI服务器来调用.在这个程序中是被make_server调用的.我们来看下make_server的代码.

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_server的代码:

头两个参数分别是地址和端口,第三个参数app也就是我们传入的application.另外还有两个参数是WSGIServer和WSGIRequestHandler.在代码中返回一个server_class也就是WSGIServer实例,这个初始化的过程中就是获取客户端参数并设置environ的过程.最后通过set_appapplication函数注册到这个实例中去.

 

/usr/bin/python2.7 /home/zhf/py_prj/web_server/webserver2.py

我们在代码中打印了print environ可以看到如下获取的各种类型参数

127.0.0.1 – – [19/Feb/2018 15:20:34] “GET / HTTP/1.1” 200 19

{\’SERVER_SOFTWARE\’: \’WSGIServer/0.1 Python/2.7.14\’, \’SCRIPT_NAME\’: \’\’, \’XDG_SESSION_TYPE\’: \’x11\’, \’REQUEST_METHOD\’: \’GET\’, \’SERVER_PROTOCOL\’: \’HTTP/1.1\’, \’CONTENT_LENGTH\’: \’\’, \’SHELL\’: \’/bin/bash\’, \’XDG_DATA_DIRS\’: \’/usr/share/ukui:/usr/share/ukui:/usr/local/share:/usr/share:/var/lib/snapd/desktop\’, \’MANDATORY_PATH\’: \’/usr/share/gconf/ukui.mandatory.path\’, \’CLUTTER_IM_MODULE\’: \’xim\’, \’TEXTDOMAIN\’: \’im-config\’, \’XMODIFIERS\’: \’@im=fcitx\’, \’LIBVIRT_DEFAULT_URI\’: \’qemu:///system\’, \’JAVA_HOME\’: \’/usr/lib/jvm/jdk1.8.0_151\’, \’XDG_RUNTIME_DIR\’: \’/run/user/1000\’, \’PYTHONPATH\’: \’/home/zhf/py_prj/web_server\’, \’HTTP_UPGRADE_INSECURE_REQUESTS\’: \’1\’, \’XDG_SESSION_ID\’: \’c2\’, \’DBUS_SESSION_BUS_ADDRESS\’: \’unix:path=/run/user/1000/bus\’, \’HTTP_ACCEPT\’: \’text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\’, \’DESKTOP_SESSION\’: \’ukui\’, \’wsgi.version\’: (1, 0), \’GTK_MODULES\’: \’gail:atk-bridge\’, \’wsgi.multiprocess\’: False, \’PYCHARM_HOSTED\’: \’1\’, \’GNOME_DESKTOP_SESSION_ID\’: \’this-is-deprecated\’, \’XDG_CURRENT_DESKTOP\’: \’UKUI\’, \’USER\’: \’zhf\’, \’XDG_VTNR\’: \’7\’, \’PYTHONUNBUFFERED\’: \’1\’, \’HTTP_USER_AGENT\’: \’Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:58.0) Gecko/20100101 Firefox/58.0\’, \’HTTP_CONNECTION\’: \’keep-alive\’, \’XAUTHORITY\’: \’/home/zhf/.Xauthority\’, \’LANGUAGE\’: \’zh_CN:\’, \’SESSION_MANAGER\’: \’local/zhf-maple:@/tmp/.ICE-unix/2271,unix/zhf-maple:/tmp/.ICE-unix/2271\’, \’SHLVL\’: \’0\’, \’DISPLAY\’: \’:0\’, \’wsgi.url_scheme\’: \’http\’, \’QT_ACCESSIBILITY\’: \’1\’, \’GTK_OVERLAY_SCROLLING\’: \’0\’, \’LANG\’: \’zh_CN.UTF-8\’, \’CLASSPATH\’: \’/home/zhf/pycharm-2017.2.4/lib/bootstrap.jar:/home/zhf/pycharm-2017.2.4/lib/extensions.jar:/home/zhf/pycharm-2017.2.4/lib/util.jar:/home/zhf/pycharm-2017.2.4/lib/jdom.jar:/home/zhf/pycharm-2017.2.4/lib/log4j.jar:/home/zhf/pycharm-2017.2.4/lib/trove4j.jar:/home/zhf/pycharm-2017.2.4/lib/jna.jar\’, \’GDMSESSION\’: \’ukui\’, \’wsgi.multithread\’: True, \’XDG_SEAT_PATH\’: \’/org/freedesktop/DisplayManager/Seat0\’, \’GTK_IM_MODULE\’: \’fcitx\’, \’XDG_CONFIG_DIRS\’: \’/etc/xdg/xdg-ukui:/etc/xdg\’, \’wsgi.file_wrapper\’: <class wsgiref.util.FileWrapper at 0x7fefc5ff3598>, \’REMOTE_HOST\’: \’localhost\’, \’HTTP_ACCEPT_ENCODING\’: \’gzip, deflate\’, \’XDG_GREETER_DATA_DIR\’: \’/var/lib/lightdm-data/zhf\’, \’QT4_IM_MODULE\’: \’fcitx\’, \’HOME\’: \’/home/zhf\’, \’LD_LIBRARY_PATH\’: \’/home/zhf/pycharm-2017.2.4/bin:\’, \’XDG_SESSION_DESKTOP\’: \’ukui\’, \’UNZIP\’: \’-O GBK\’, \’SERVER_PORT\’: \’8888\’, \’HTTP_HOST\’: \’127.0.0.1:8888\’, \’DEFAULTS_PATH\’: \’/usr/share/gconf/ukui.default.path\’, \’wsgi.run_once\’: False, \’wsgi.errors\’: <open file \'<stderr>\’, mode \’w\’ at 0x7fefc82561e0>, \’HTTP_ACCEPT_LANGUAGE\’: \’zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\’, \’JRE_HOME\’: \’/usr/lib/jvm/jdk1.8.0_151/jre\’, \’PATH_INFO\’: \’/\’, \’PYTHONIOENCODING\’: \’UTF-8\’, \’QUERY_STRING\’: \’\’, \’QT_IM_MODULE\’: \’fcitx\’, \’LOGNAME\’: \’zhf\’, \’XDG_SEAT\’: \’seat0\’, \’PATH\’: \'{JAVA_HOME}/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin\’, \’SSH_AGENT_PID\’: \’2354\’, \’XDG_SESSION_PATH\’: \’/org/freedesktop/DisplayManager/Session0\’, \’SERVER_NAME\’: \’localhost\’, \’IM_CONFIG_PHASE\’: \’2\’, \’GIO_LAUNCHED_DESKTOP_FILE_PID\’: \’2986\’, \’GIO_LAUNCHED_DESKTOP_FILE\’: \’/home/zhf/\xe6\xa1\x8c\xe9\x9d\xa2/Pycharm.desktop\’, \’SSH_AUTH_SOCK\’: \’/run/user/1000/keyring/ssh\’, \’wsgi.input\’: <socket._fileobject object at 0x7fefc811e7d0>, \’TEXTDOMAINDIR\’: \’/usr/share/locale/\’, \’GATEWAY_INTERFACE\’: \’CGI/1.1\’, \’OLDPWD\’: \’/home/zhf/pycharm-2017.2.4/bin\’, \’REMOTE_ADDR\’: \’127.0.0.1\’, \’GDM_LANG\’: \’zh_CN\’, \’PWD\’: \’/home/zhf/py_prj/web_server\’, \’DESKTOP_STARTUP_ID\’: \’peony-2395-zhf-maple-sh-0_TIME90535\’, \’CONTENT_TYPE\’: \’text/plain\’, \’ZIPINFO\’: \’-O GBK\’}

WSGI的出现,让开发者可以将网络框架与网络服务器的选择分隔开来,不再相互限制。现在,你可以真正地将不同的网络服务器与网络开发框架进行混合搭配,选择满足自己需求的组合。例如,你可以使用GunicornNginx/uWSGIWaitress服务器来运行DjangoFlaskPyramid应用。正是由于服务器和框架均支持WSGI,才真正得以实现二者之间的自由混合搭配

那么接下来我们继续深入了解WSGI的原理,我们自己来做一个简单的WSGI.代码如下:

class WSGIServer(object):

    address_family=socket.AF_INET

    socket_type=socket.SOCK_STREAM

    request_queue_size=1

    def __init__(self,server_address):

        self.lisen_socket=listen_socket=socket.socket(self.address_family,self.socket_type)

        listen_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

        listen_socket.bind(server_address)

        listen_socket.listen(self.request_queue_size)

        host,port=self.lisen_socket.getsockname()[:2]

        self.server_name=socket.getfqdn(host)

        self.server_port=port

        self.headers_set=[]

    def set_app(self,application):

        self.application=application

    def server_forever(self):

        listen_socket=self.lisen_socket

        while True:

            self.client_connection,client_address=listen_socket.accept()

            self.hand_one_request()

    def hand_one_request(self):

        self.request_data=request_data=self.client_connection.recv(1024)

        print \’\’.join(\'<{line}\n\’.format(line=line) for line in request_data.splitlines())

        self.parse_request(request_data)

        env=self.get_environ()

        result=self.application(env,self.start_response)

        self.finish_response(result)

    def parse_request(self,text):

        request_line=text.splitlines()[0]

        request_line=request_line.rstrip(\’\r\n\’)

        (self.request_method,self.path,self.request_version)=request_line.split()

 

    def get_environ(self):

        env={}

        env[\’wsgi.version\’]=(1,0)

        env[\’wsgi.url_scheme\’] = \’http\’

        env[\’wsgi.input\’] = StringIO.StringIO(self.request_data)

        env[\’wsgi.errors\’] = sys.stderr

        env[\’wsgi.multithread\’] = False

        env[\’wsgi.multiprocess\’] = False

        env[\’wsgi.run_once\’] = False

        env[\’REQUEST_METHOD\’] = self.request_method

        env[\’PATH_INFO\’] = self.path

        env[\’SERVER_NAME\’] = self.server_name

        env[\’SERVER_PORT\’] = str(self.server_port)

        return env

    def start_response(self,status,response_headers,exc_info=None):

        server_headers=[(\’Date\’,\’Tue,20 Feb 2018 07:30:30 GMT\’),(\’Server\’,\’WSGIServer 0.2\’)]

        self.headers_set=[status,response_headers+server_headers]

    def finish_response(self,result):

        try:

            status,response_headers=self.headers_set

            response=\’HTTP/1.1 {status}\r\n\’.format(status=status)

            for header in response_headers:

                response+=\'{0}: {1}\r\n\’.format(*header)

            response+=\’\r\n\’

            for data in result:

                response+=data

            print \’\’.join(\’>{line}\n\’.format(line=line) for line in response.splitlines())

            self.client_connection.sendall(response)

        finally:

            self.client_connection.close()\

 

 

SERVER_ADDRESS = (HOST, PORT) = \’\’, 8888

 

def make_server(server_address,application):

    server=WSGIServer(server_address)

    server.set_app(application)

    return server

 

 

if __name__==”__main__”:

    if len(sys.argv) < 2:

        sys.exit(\’Provide a WSGI application object as module:callable\’)

    app_path=sys.argv[1]

    module,application=app_path.split(\’:\’)

    module=__import__(module)

    application=getattr(module,application)

    httpd=make_server(SERVER_ADDRESS,application)

    print \’WSGIServer: Serving HTTP on port {port} …\n\’.format(port=PORT)

    httpd.server_forever()

再另外创建一个flask的应用,保存为flaskapp文件

from flask import Flask

from flask import Response

flask_app=Flask(\’flaskapp\’)

@flask_app.route(\’/hello\’)

def hello_world():

    return Response(\’Hello world from Flask!\n\’,mimetype=\’text/plain\’)

app=flask_app.wsgi_app

通过命令行运行

zhf@zhf-maple:~/py_prj/web_server$ python webserver2.py flaskapp:app

WSGIServer: Serving HTTP on port 8888 …

此时在浏览器中输入http://127.0.0.1:8888/hello

可以看到反馈的响应.

下面给大家解释一下上述代码的工作原理:

  1. 网络框架提供一个命名为application的可调用对象(WSGI协议并没有指定如何实现这个对象)。在这里我们通过创建一个flask应用,并传入flask中的application. 当然这个application我们也可以按照之前的方法自己定义一个.
  2. 服务器每次从HTTP客户端接收请求之后,调用application。它会向可调用对象传递一个名叫environ的字典作为参数,其中包含了WSGI/CGI的诸多变量,以及一个名为start_response的可调用对象。
  3. 框架/应用生成HTTP状态码以及HTTP响应报头(HTTP response headers),然后将二者传递至start_response,等待服务器保存。此外,框架/应用还将返回响应的正文。
  4. 服务器将状态码、响应报头和响应正文组合成HTTP响应,并返回给客户端(这一步并不属于WSGI协议)

流程图如下所示

截至目前,我们已经成功创建了自己的支持WSGI协议的网络服务器,还利用不同的网络框架开发了多个网络应用。另外,还自己开发了一个极简的网络框架。本文介绍的内容不可谓不丰富。我们接下来回顾一下WSGI网络服务器如何处理HTTP请求:

·  首先,服务器启动并加载网络框架/应用提供的application可调用对象

·  然后,服务器读取一个请求信息

·  然后,服务器对请求进行解析

·  然后,服务器使用请求数据创建一个名叫environ的字典

·  然后,服务器以environ字典和start_response可调用对象作为参数,调用application,并获得应用生成的响应正文。

·  然后,服务器根据调用application对象后返回的数据,以及start_response设置的状态码和响应标头,构建一个HTTP响应。

·  最后,服务器将HTTP响应返回至客户端

整个流程如下:

我们已经实现了一个简单的WSGI, 具体WSGI内部规范可以参考PEP333文档.链接为http://legacy.python.org/dev/peps/pep-0333/#rationale-and-goals

 

版权声明:本文为zhanghongfeng原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/zhanghongfeng/p/8454775.html