# WGSI基础教程 ## WGSI基础教程 WSGI,英文全称Web Server Gateway Interface,中文对应翻译是Web服务器网关接口。从具体定义来说,WSGI是一个标准规范,其定义了WEB服务器如何与Python应用程序进行交互。使基于python语言写的上层应用,在满足规范的前提下,同Web服务器交互。 该规范的最新版本在PEP-3333中定义,最新修订日期04-Oct-2010。 https://peps.python.org/pep-3333/ ## 需要WGSI的原因 远古时代,关于Web的部署方案有很多,在长时间的竞争和淘汰下,大家对Web的部署达成了一个共识。 *我们需要一个专门的Web服务器来处理HTTP协议层的事务。例如HTTP层的数据包解包、封包,如果在一台物理机上,提供不同种类的web服务。单ip绑定多个域名,对外提供web访问。或者单ip绑定多个port,对外提供访问。 *该web服务器可以部署各种语言写的应用程序,应用程序不直接获得Client端请求,它从WebServer处获得Client请求,待业务逻辑处理完成后,返回响应给WebServer,由WebServer返给CLient。 仔细想想,上面的这套构架方案,有点将webserver当中间层的意味在里面了。为了满足上面的要求,需要应用程式和Web服务器双方以规定的标准进行交互。这些标准慢慢形成不同规范。这种规范里最早的是CGI。后来又有了FastCGI,Java的Servlet规范,本文章主要介绍Python专用的WSGI规范。提出这些规范的目的就是为了定义统一的标准,提升程序的可移植性。 ## WSGI如何工作 WSGI规范很重要,相当于app与WEBServer之间的桥梁,这个桥梁的作用有2个: *让Web服务器知道如何调用Python应用程序,并且把用户的请求告诉应用程序 *让Python应用程序知道用户的具体请求是什么,以及如何返回结果给Web服务器 ## WSGI中定义的role 在WSGI中定义了两个角色,Web服务器端称为server或者gateway,应用程序端称为application或者framework(因为WSGI的应用程序端的规范一般都是由具体的框架来实现的)。我们下面统一使用server和application这两个术语。 server端会先收到用户的请求,然后会根据规范的要求调用application端,如下图所示: ![wsgi01](/pics/wsgi01.png "wsgi01") 最后,等待业务逻辑执行完毕,结果会被封装成HTTP包后再发送给Client端。 ## Server端如何通知Application端 规范规定,每个application入口都只有一个,所有Client端的请求都从同一个入口,进入应用程序处理。 处于中间层的Server,需要知道Application的入口位置,那么需要在Server端执行一个PythonModule,这个Module表现为Python应用的文件,并且这个模块中需要包含一个命名为application的可调用对象,至此这个application对象就是该应用程序的唯一入口。 WSGI还定义了application对象的具体形式。 ```python def simple_app(environ, start_response): pass ``` 代码中的`environ`和`start_response`就是server端调用application对象时传递的两个参数。 举个例子,假设我们的应用程序的入口文件是/www/app_index.py,那么我们就需要在server端配置好这个路径(如何配置取决于server端的实现),然后在app_index.py中的代码如下所示: 使用标准库,也就是python内置的一个wsgi参考实现,实现了标准的wsgi协议 ```python import wsgiref application = wsgiref.simple_server.demo_application ``` python应用程序文件中需要有一个名字为application的可调用对象,server端会找到这个文件,然后调用这个对象。 ## application对象的任务 application对象需要是一个可调用对象,而且其定义需要满足如下形式: ```python def simple_app(environ, start_response): pass ``` ### environ参数 environ参数的类型是Python的字典,里面存放了所有和客户端相关的信息,这样application对象就能知道客户端请求的资源是什么,请求中带了什么数据等。 我们来看一些environ中常用的成员。 首先是CGI规范中要求的变量: REQUEST_METHOD: 'GET', 'POST'等HTTP方法,字符串类型 SCRIPT_NAME: HTTP请求的path中的用于查找到application对象的部分,比如Web服务器可以根据path的一部分来决定请求由哪个virtual host处理 PATH_INFO: HTTP请求的path中剩余的部分,也就是application要处理的部分 QUERY_STRING: HTTP请求中的查询字符串,URL中?后面的内容 CONTENT_TYPE: HTTP headers中的content-type内容 CONTENT_LENGTH: HTTP headers中的content-length内容 SERVER_NAME和SERVER_PORT: 服务器名和端口,这两个值和前面的SCRIPT_NAME, PATH_INFO拼起来可以得到完整的URL路径 SERVER_PROTOCOL: HTTP协议版本,HTTP/1.0或者HTTP/1.1 HTTP_: 和HTTP请求中的headers对应。 WSGI规范中还要求environ包含下列成员: wsgi.version:表示WSGI版本,一个元组(1, 0),表示版本1.0 wsgi.url_scheme:http或者https wsgi.input:一个类文件的输入流,application可以通过这个获取HTTP request body wsgi.errors:一个输出流,当应用程序出错时,可以将错误信息写入这里 wsgi.multithread:当application对象可能被多个线程同时调用时,这个值需要为True wsgi.multiprocess:当application对象可能被多个进程同时调用时,这个值需要为True wsgi.run_once:当server期望application对象在进程的生命周期内只被调用一次时,该值为True 上面列出的这些内容已经包括了客户端请求的所有数据,足够application对象处理客户端请求了。 ## start_resposne参数 start_response是一个可调用对象,接收两个必选参数和一个可选参数: status: 一个字符串,表示HTTP响应状态字符串 response_headers: 一个列表,包含有如下形式的元组:(header_name, header_value),用来表示HTTP响应的headers exc_info(可选): 用于出错时,server需要返回给浏览器的信息 在application对象将body作为返回值return之前,需要先调用start_response(),将status和headers的内容返回给server,这同时也是告诉server,application对象要开始返回body了。 ## application对象的返回值 application对象的返回值用于为HTTP响应提供body,如果没有body,那么可以返回None。如果有body,那么需要返回一个可迭代的对象。server端通过遍历这个可迭代对象可以获得body的全部内容。 ```python start_response(force_str(status), response_headers) ``` 这一步发送HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。start_response()函数接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP Header,每个Header用一个包含两个str的tuple表示。 通常情况下,都应该把Content-Type头发送给浏览器。其他很多常用的HTTP Header也应该发送。 最后面一句返回body,也可以为空。 ## WSGI中间件 WSGI Middleware(中间件)也是WSGI规范的一部分。我们之前讲过WSGI中的两个角色:server和application。 而middleware是运行在server和application中间的应用(一般也是python应用)。middleware同时具备server和application角色, 对于server来讲它就是个application,而对于application来说,它就是个server。middleware并不修改server端和application端的规范, 只是同时实现了这两种角色的功能而已。你可以将middleware形象比喻成批发商,对于厂商而已它是购买者,而对于零售店而已它是供应方。 下面我用一张图来形象的说明这个中间件的工作原理: ![wsgi20.png](/pics/wsgi20.png "wsgi20") 上图中最上面的三个彩色框表示角色,中间的白色框表示操作,操作的发生顺序按照1 ~ 5进行了排序,我们直接对着上图来说明middleware是如何工作的: 1. Server收到客户端的HTTP请求后,生成了environ_s,并且已经定义了start_response_s。 2. Server调用Middleware的application对象,传递的参数是environ_s和start_response_s。 3. Middleware会根据environ执行业务逻辑,生成environ_m,并且已经定义了start_response_m。 4. Middleware决定调用Application的application对象,传递参数是environ_m和start_response_m。Application的application对象处理完成后,会调用start_response_m并且返回结果给Middleware,存放在result_m中。 5. Middleware处理result_m,然后生成result_s,接着调用start_response_s,并返回结果result_s给Server端。Server端获取到result_s后就可以发送结果给客户端了。 上面的流程可以看出middleware应用的几个特点: 1. Server认为middleware是一个application。 2. Application认为middleware是一个server。 3. Middleware可以有多层。 Server认为middleware是一个application。 Application认为middleware是一个server。 Middleware可以有多层。 ## WSGI的实现和部署 Server端的实现会比较复杂一点,这个主要是因为软件架构的原因。一般常用的Web服务器,如Apache和nginx,都不会内置WSGI的支持,而是通过扩展来完成。比如Apache服务器,会通过扩展模块mod_wsgi来支持WSGI。 Apache和mod_wsgi之间通过程序内部接口传递信息,mod_wsgi会实现WSGI的server端、进程管理以及对application的调用。Nginx上一般是用proxy的方式,用nginx的协议将请求封装好,发送给应用服务器,比如uWSGI,应用服务器会实现WSGI的服务端、进程管理以及对application的调用。 ## WSGI例子 Talk is cheap, Show me code! Python内置了一个WSGI服务器,这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。所谓”参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。 ```python # newworld.py def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) # 这里我们使用PATH_INFO变量获取请求的URL body = '