python WSGI框架详解
<h1 style="text-align: center">python WSGI框架详解</h1><p> </p>
<h1>WSGI</h1>
<p> </p>
<p><strong>几个关于WSGI相关的概念</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web server如何与web application通信的规范。server和application的规范在PEP 3333中有具体描述。要实现WSGI协议,必须同时实现web server和web application,当前运行在WSGI协议之上的web框架有Torando,Flask,Django
uwsgi:与WSGI一样是一种通信协议,是uWSGI服务器的独占协议,用于定义传输信息的类型(type of information),每一个uwsgi packet前4byte为传输信息类型的描述,与WSGI协议是两种东西,据说该协议是fcgi协议的10倍快。
uWSGI:是一个web服务器,实现了WSGI协议、uwsgi协议、http协议等。</span></pre>
</div>
<p> </p>
<p><strong> </strong></p>
<p>PEP 0333 – Python Web Server Gateway Interface 是一种 web server or gateway 和 python web application or framework 之间简单通用的接口,符合这种接口的 application 可运行在所有符合该接口的 server 上。通俗的讲,WSGI 规范了一种简单的接口,解耦了 server 和 application,使得双边的开发者更加专注自身特性的开发。</p>
<p> </p>
<p><strong>WSGI</strong>协议主要包括<strong>server</strong>和<strong>application</strong>两部分:</p>
<ul>
<li>Web server/gateway: 即 HTTP Server,处理 HTTP 协议,接受用户 HTTP 请求和提供并发,调用 web application 处理业务逻辑。通常采用 C/C++ 编写,代表:apache, nginx 和 IIS。<span class="crayon-e">WSGI <span class="crayon-i">server负责从客户端接收请求,将<span class="crayon-i">request转发给<span class="crayon-i">application,将<span class="crayon-i">application返回的<span class="crayon-i">response返回给客户端;</span></span></span></span></span></span></li>
<li>Python Web application/framework: <span class="crayon-e">WSGI <span class="crayon-i">application接收由<span class="crayon-i">server转发的<span class="crayon-i">request,处理请求,并将处理结果返回给<span class="crayon-i">server。<span class="crayon-i">application中可以包括多个栈式的中间件<span class="crayon-sy">(<span class="crayon-v">middlewares<span class="crayon-sy">),这些中间件需要同时实现<span class="crayon-i">server与<span class="crayon-i">application,因此可以在<span class="crayon-i">WSGI服务器与<span class="crayon-i">WSGI应用之间起调节作用:对服务器来说,中间件扮演应用程序,对应用程序来说,中间件扮演服务器。</span></span></span></span></span></span></span></span></span></span></span></span></span></li>
<li>
<div class="cnblogs_code">
<pre>WSGI协议其实是定义了一种server与application解耦的规范,即可以有多个实现WSGI server的服务器,也可以有多个实现WSGI application的框架,那么就可以选择任意的server和application组合实现自己的web应用。例如uWSGI和Gunicorn都是实现了WSGI server协议的服务器,Django,Flask是实现了WSGI application协议的web框架,可以根据项目实际情况搭配使用。</pre>
</div>
</li>
</ul>
<p><img src="https://img2018.cnblogs.com/blog/1477786/201905/1477786-20190517222328672-1438762618.png" alt=""></p>
<p> </p>
<p> </p>
<h1 id="id-the-applicationframework-side">Application/Framework</h1>
<p>Application/framework 端必须定义一个 callable object,callable object 可以是以下三者之一:</p>
<ul>
<li>function, method</li>
<li>class</li>
<li>instance with a __call__ method</li>
</ul>
<p>Callable object 必须满足以下两个条件:</p>
<ul>
<li>接受两个参数:字典(environ),回调函数(start_response,返回 HTTP status,headers 给 web server)</li>
<li>返回一个可迭代的值</li>
</ul>
<p>基于 callable function 的 application/framework 样例如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> application(environ, start_response):
start_response(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">200 OK</span><span style="color: rgba(128, 0, 0, 1)">'</span>, [(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Content-Type</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text/plain</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)])
</span><span style="color: rgba(0, 0, 255, 1)">return</span> [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">This is a python application!</span><span style="color: rgba(128, 0, 0, 1)">'</span>]</pre>
</div>
<p>基于 callable class 的 application/framework 样例如下:</p>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> ApplicationClass(object):
</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span><span style="color: rgba(0, 0, 0, 1)">(self, environ, start_response):
self.environ </span>=<span style="color: rgba(0, 0, 0, 1)"> environ
self.start_response </span>=<span style="color: rgba(0, 0, 0, 1)"> start_response
</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__iter__</span><span style="color: rgba(0, 0, 0, 1)">(self):
self.start_response(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">200 OK</span><span style="color: rgba(128, 0, 0, 1)">'</span>, [(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Content-type</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text/plain</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)])
</span><span style="color: rgba(0, 0, 255, 1)">yield</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello world!n</span><span style="color: rgba(128, 0, 0, 1)">"</span></pre>
</div>
<p> </p>
</div>
<p> </p>
<h1 id="id-the-servergateway-side">Server/Gateway </h1>
<p>Server/gateway 端主要专注 HTTP 层面的业务,重点是接收 HTTP 请求和提供并发。每当收到 HTTP 请求,server/gateway 必须调用 callable object:</p>
<ul>
<li>接收 HTTP 请求,但是不关心 HTTP url, HTTP method 等</li>
<li>为 environ 提供必要的参数,实现一个回调函数 start_response,并传给 callable object</li>
<li>调用 callable object</li>
</ul>
<p>我们直接使用支持 WSGI 框架的 wsgiref 库,编写一个样例:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> application/framework side</span>
<span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> application(environ, start_response):
start_response(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">200 OK</span><span style="color: rgba(128, 0, 0, 1)">'</span>, [(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Content-Type</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text/plain</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)])
</span><span style="color: rgba(0, 0, 255, 1)">return</span> [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">This is a python application!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> server/gateway side</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(128, 0, 128, 1)">__name__</span> == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">__main__</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">from</span> wsgiref.simple_server <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> make_server
server </span>= make_server(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">0.0.0.0</span><span style="color: rgba(128, 0, 0, 1)">'</span>, 8080<span style="color: rgba(0, 0, 0, 1)">, application)
server.serve_forever()</span></pre>
</div>
<p> </p>
<h1 id="id-middleware-components-that-play-both-sides">Middleware: Components that Play Both Sides</h1>
<div class="cnblogs_code">
<pre>Unix philosophy: do one thing <span style="color: rgba(0, 0, 255, 1)">and</span> do it well.</pre>
</div>
<p><img src="https://img2018.cnblogs.com/blog/1477786/201905/1477786-20190517224404146-1687548670.png" alt=""></p>
<p>Middleware 处于 server/gateway 和 application/framework 之间,对 server/gateway 来说,它相当于 application/framework;对 application/framework 来说,它相当于 server/gateway。每个 middleware 实现不同的功能,我们通常根据需求选择相应的 middleware 并组合起来,实现所需的功能。比如,可在 middleware 中实现以下功能:</p>
<ul>
<li>根据 url 把用户请求调度到不同的 application 中。</li>
<li>负载均衡,转发用户请求</li>
<li>预处理 XSL 等相关数据</li>
<li>限制请求速率,设置白名单</li>
</ul>
<p> <img src="https://img2018.cnblogs.com/blog/1477786/201905/1477786-20190517224426591-1518920296.png" alt=""></p>
<p>WSGI 的 middleware 体现了 unix 的哲学之一:do one thing and do it well。事实上,在定义 WSGI 框架的时候,设计者就要求 server/gateway 和 application/framework 双方尽可能的简单,同时也要求 middleware 设计的简单而专一,PEP 333 提到:</p>
<blockquote>
<p>If middleware can be both simple and robust, and WSGI is widely available in servers and frameworks, it allows for the possibility of an entirely new kind of Python web application framework: one consisting of loosely-coupled WSGI middleware components.</p>
</blockquote>
<p>本例实现了一个 IPBlacklist 的 middleware:</p>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> IPBlacklistMiddleware(object):
</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span><span style="color: rgba(0, 0, 0, 1)">(self, app):
self.app </span>=<span style="color: rgba(0, 0, 0, 1)"> app
</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__call__</span><span style="color: rgba(0, 0, 0, 1)">(self, environ, start_response):
ip_addr </span>= environ.get(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">HTTP_HOST</span><span style="color: rgba(128, 0, 0, 1)">'</span>).split(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">:</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> ip_addr <span style="color: rgba(0, 0, 255, 1)">not</span> <span style="color: rgba(0, 0, 255, 1)">in</span> (<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">127.0.0.1</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">):
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> forbidden(start_response)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> self.app(environ, start_response)
</span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> forbidden(start_response):
start_response(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">403 Forbidden</span><span style="color: rgba(128, 0, 0, 1)">'</span>, [(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Content-Type</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text/plain</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)])
</span><span style="color: rgba(0, 0, 255, 1)">return</span> [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Forbidden</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]
</span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> application(environ, start_response):
start_response(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">200 OK</span><span style="color: rgba(128, 0, 0, 1)">'</span>, [(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Content-Type</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text/plain</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)])
</span><span style="color: rgba(0, 0, 255, 1)">return</span> [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">This is a python application!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]
</span><span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(128, 0, 128, 1)">__name__</span> == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">__main__</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">from</span> wsgiref.simple_server <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> make_server
application </span>=<span style="color: rgba(0, 0, 0, 1)"> IPBlacklistMiddleware(application)
server </span>= make_server(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">0.0.0.0</span><span style="color: rgba(128, 0, 0, 1)">'</span>, 8080<span style="color: rgba(0, 0, 0, 1)">, application)
server.serve_forever()</span></pre>
</div>
<p>测试如下:</p>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 从本机测试</span>
$ curl 127.0.0.1:8080/<span style="color: rgba(0, 0, 0, 1)">test
This </span><span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> a python application!
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 从其它主机测测试 </span>
$ curl 10.10.10.2:8080/<span style="color: rgba(0, 0, 0, 1)">test
Forbidden</span></pre>
</div>
<p> </p>
<p> </p>
<p> </p>
<h1 id="id-path-dispatching">Path Dispatching</h1>
<p> </p>
<p>至此样例的一个不足之处是对于任意的 url 和 method,程序的返回值均为 ‘This is a python application!’,所以我们需要增加 path dispatch 功能。由于 WSGI 框架下的 server/gateway 不处理 url 和 method,所以 url mapping 需由 application/framework 端完成。注意到参数 environ,它包含以下变量:</p>
<ul>
<li>REQUEST_METHOD: 即 HTTP method</li>
<li>PATH_INFO: 即 HTTP url</li>
</ul>
<p>所以 application/framework 可以根据 environ 的 REQUEST_METHOD 和 PATH_INFO 实现 path dispatch,样例如下:</p>
<div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> IPBlacklistMiddleware(object):
</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__init__</span><span style="color: rgba(0, 0, 0, 1)">(self, app):
self.app </span>=<span style="color: rgba(0, 0, 0, 1)"> app
</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__call__</span><span style="color: rgba(0, 0, 0, 1)">(self, environ, start_response):
ip_addr </span>= environ.get(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">HTTP_HOST</span><span style="color: rgba(128, 0, 0, 1)">'</span>).split(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">:</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> ip_addr <span style="color: rgba(0, 0, 255, 1)">not</span> <span style="color: rgba(0, 0, 255, 1)">in</span> (<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">127.0.0.1</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">):
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> forbidden(start_response)
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> self.app(environ, start_response)
</span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> dog(start_response):
start_response(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">200 OK</span><span style="color: rgba(128, 0, 0, 1)">'</span>, [(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Content-Type</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text/plain</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)])
</span><span style="color: rgba(0, 0, 255, 1)">return</span> [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">This is dog!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]
</span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> cat(start_response):
start_response(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">200 OK</span><span style="color: rgba(128, 0, 0, 1)">'</span>, [(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Content-Type</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text/plain</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)])
</span><span style="color: rgba(0, 0, 255, 1)">return</span> [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">This is cat!</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]
</span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> not_found(start_response):
start_response(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">404 NOT FOUND</span><span style="color: rgba(128, 0, 0, 1)">'</span>, [(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Content-Type</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text/plain</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)])
</span><span style="color: rgba(0, 0, 255, 1)">return</span> [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Not Found</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]
</span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> forbidden(start_response):
start_response(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">403 Forbidden</span><span style="color: rgba(128, 0, 0, 1)">'</span>, [(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Content-Type</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text/plain</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)])
</span><span style="color: rgba(0, 0, 255, 1)">return</span> [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Forbidden</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]
</span><span style="color: rgba(0, 0, 255, 1)">def</span><span style="color: rgba(0, 0, 0, 1)"> application(environ, start_response):
path </span>= environ.get(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">PATH_INFO</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">''</span>).lstrip(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)
mapping </span>= {<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">dog</span><span style="color: rgba(128, 0, 0, 1)">'</span>: dog, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">cat</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">: cat}
call_back </span>= mapping <span style="color: rgba(0, 0, 255, 1)">if</span> path <span style="color: rgba(0, 0, 255, 1)">in</span> mapping <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> not_found
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> call_back(start_response)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> <span style="color: rgba(128, 0, 128, 1)">__name__</span> == <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">__main__</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">:
</span><span style="color: rgba(0, 0, 255, 1)">from</span> wsgiref.simple_server <span style="color: rgba(0, 0, 255, 1)">import</span><span style="color: rgba(0, 0, 0, 1)"> make_server
application </span>=<span style="color: rgba(0, 0, 0, 1)"> IPBlacklistMiddleware(application)
server </span>= make_server(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">0.0.0.0</span><span style="color: rgba(128, 0, 0, 1)">'</span>, 8080<span style="color: rgba(0, 0, 0, 1)">, application)
server.serve_forever()</span></pre>
</div>
<p>测试如下:</p>
</div>
<div class="cnblogs_code">
<pre>$ curl 127.0.0.1:8080/<span style="color: rgba(0, 0, 0, 1)">dog
This </span><span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> dog!
$ curl </span>127.0.0.1:8080/<span style="color: rgba(0, 0, 0, 1)">cat
This </span><span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> cat!
$ curl </span>127.0.0.1:8080/<span style="color: rgba(0, 0, 0, 1)">monkey
Not Found</span></pre>
</div>
<p> </p>
<p> </p>
<p> </p>
<h1>Django框架分析WSGI</h1>
<p>下面我们以<strong>django</strong>为例,分析一下<strong>wsgi</strong>的整个流程</p>
<p> </p>
<h2>django WSGI application</h2>
<p><strong>WSGI application</strong>应该实现为一个可调用<strong>iter</strong>对象,例如函数、方法、类(包含<strong>**call**</strong>方法)。需要接收两个参数:一个字典,该字典可以包含了客户端请求的信息以及其他信息,可以认为是请求上下文,一般叫做<strong>environment</strong>(编码中多简写为environ、env),一个用于发送HTTP响应状态(HTTP status)、响应头(HTTP headers)的回调函数,也就是<strong>start_response()</strong>。通过回调函数将响应状态和响应头返回给server,同时返回响应正文(response body),响应正文是可迭代的、并包含了多个字符串。</p>
<p>下面是Django中application的具体实现部分:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> WSGIHandler(base.BaseHandler):
initLock </span>=<span style="color: rgba(0, 0, 0, 1)"> Lock()
request_class </span>=<span style="color: rgba(0, 0, 0, 1)"> WSGIRequest
</span><span style="color: rgba(0, 0, 255, 1)">def</span> <span style="color: rgba(128, 0, 128, 1)">__call__</span><span style="color: rgba(0, 0, 0, 1)">(self, environ, start_response):
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 加载中间件 </span>
<span style="color: rgba(0, 0, 255, 1)">if</span> self._request_middleware <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> None:
with self.initLock:
</span><span style="color: rgba(0, 0, 255, 1)">try</span>: <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> Check that middleware is still uninitialized. </span>
<span style="color: rgba(0, 0, 255, 1)">if</span> self._request_middleware <span style="color: rgba(0, 0, 255, 1)">is</span><span style="color: rgba(0, 0, 0, 1)"> None:
self.load_middleware()
</span><span style="color: rgba(0, 0, 255, 1)">except</span>: <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> Unload whatever middleware we got </span>
self._request_middleware = None <span style="color: rgba(0, 0, 255, 1)">raise</span><span style="color: rgba(0, 0, 0, 1)">
set_script_prefix(get_script_name(environ)) </span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 请求处理之前发送信号 </span>
signals.request_started.send(sender=self.<span style="color: rgba(128, 0, 128, 1)">__class__</span>, environ=<span style="color: rgba(0, 0, 0, 1)">environ)
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">:
request </span>=<span style="color: rgba(0, 0, 0, 1)"> self.request_class(environ)
</span><span style="color: rgba(0, 0, 255, 1)">except</span><span style="color: rgba(0, 0, 0, 1)"> UnicodeDecodeError:
logger.warning(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Bad Request (UnicodeDecodeError)</span><span style="color: rgba(128, 0, 0, 1)">'</span>,exc_info=sys.exc_info(), extra={<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">status_code</span><span style="color: rgba(128, 0, 0, 1)">'</span>: 400<span style="color: rgba(0, 0, 0, 1)">,}
response </span>=<span style="color: rgba(0, 0, 0, 1)"> http.HttpResponseBadRequest()
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">:
response </span>=<span style="color: rgba(0, 0, 0, 1)"> self.get_response(request)
response._handler_class </span>= self.<span style="color: rgba(128, 0, 128, 1)">__class__</span> status = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">%s %s</span><span style="color: rgba(128, 0, 0, 1)">'</span> %<span style="color: rgba(0, 0, 0, 1)"> (response.status_code, response.reason_phrase)
response_headers </span>= [(str(k), str(v)) <span style="color: rgba(0, 0, 255, 1)">for</span> k, v <span style="color: rgba(0, 0, 255, 1)">in</span> response.items()] <span style="color: rgba(0, 0, 255, 1)">for</span> c <span style="color: rgba(0, 0, 255, 1)">in</span> response.cookies.values(): response_headers.append((str(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Set-Cookie</span><span style="color: rgba(128, 0, 0, 1)">'</span>), str(c.output(header=<span style="color: rgba(128, 0, 0, 1)">''</span><span style="color: rgba(0, 0, 0, 1)">))))
</span><span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> server提供的回调方法,将响应的header和status返回给server </span>
<span style="color: rgba(0, 0, 0, 1)"> start_response(force_str(status), response_headers)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> getattr(response, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">file_to_stream</span><span style="color: rgba(128, 0, 0, 1)">'</span>, None) <span style="color: rgba(0, 0, 255, 1)">is</span> <span style="color: rgba(0, 0, 255, 1)">not</span> None <span style="color: rgba(0, 0, 255, 1)">and</span> environ.get(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">wsgi.file_wrapper</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">):
response </span>= environ[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">wsgi.file_wrapper</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">](response.file_to_stream)
</span><span style="color: rgba(0, 0, 255, 1)">return</span> response</pre>
</div>
<h2> </h2>
<h2>django WSGI Server</h2>
<p>负责获取http请求,将请求传递给WSGI application,由application处理请求后返回response。以Django内建server为例看一下具体实现。通过runserver运行django<br>项目,在启动时都会调用下面的run方法,创建一个WSGIServer的实例,之后再调用其serve_forever()方法启动服务。</p>
<div id="crayon-5cdec4ab23f18303863998" class="crayon-syntax crayon-theme-github crayon-font-monaco crayon-os-pc print-yes notranslate" data-settings=" minimize scroll-always">
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">def</span> run(addr, port, wsgi_handler, ipv6=False, threading=<span style="color: rgba(0, 0, 0, 1)">False):
server_address </span>=<span style="color: rgba(0, 0, 0, 1)"> (addr, port)
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> threading:
httpd_cls </span>= type(str(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">WSGIServer</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">), (socketserver.ThreadingMixIn, WSGIServer), {})
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">:
httpd_cls </span>= WSGIServer <span style="color: rgba(0, 128, 0, 1)">#</span><span style="color: rgba(0, 128, 0, 1)"> 这里的wsgi_handler就是WSGIApplication </span>
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=<span style="color: rgba(0, 0, 0, 1)">ipv6)
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> threading:
httpd.daemon_threads </span>=<span style="color: rgba(0, 0, 0, 1)"> True httpd.set_app(wsgi_handler)
httpd.serve_forever()</span></pre>
</div>
<p>下面表示WSGI server服务器处理流程中关键的类和方法。</p>
<p><img src="https://img2018.cnblogs.com/blog/1477786/201905/1477786-20190517225551043-754347805.png" alt=""></p>
<p><strong>WSGIServer</strong>run()方法会创建<strong>WSGIServer</strong>实例,主要作用是接收客户端请求,将请求传递给<strong>application</strong>,然后将<strong>application</strong>返回的<strong>response</strong>返回给客户端。</p>
<p>创建实例时会指定<strong>HTTP</strong>请求的<strong>handler:WSGIRequestHandler</strong>类,通过<strong>set_app</strong>和<strong>get_app</strong>方法设置和获取<strong>WSGIApplication</strong>实例<strong>wsgi_handler。</strong></p>
<p>处理http请求时,调用<strong>handler_request</strong>方法,会创建<strong>WSGIRequestHandler,</strong>实例处理http请求。WSGIServer中<strong>get_request</strong>方法通过<strong>socket</strong>接受请求数据。</p>
<p><strong>WSGIRequestHandler</strong>由WSGIServer在调用handle_request时创建实例,传入<strong>request</strong>、<strong>cient_address</strong>、<strong>WSGIServer</strong>三个参数,__init__方法在实例化同时还会调用自身的<strong>handle</strong>方法handle方法会创建<strong>ServerHandler</strong>实例,然后调用其<strong>run</strong>方法处理请求</p>
<p><strong>ServerHandler</strong>WSGIRequestHandler在其handle方法中调用run方法,传入self.server.get_app()参数,获取<strong>WSGIApplication</strong>,然后调用实例(__call__),获取<strong>response</strong>,其中会传入<strong>start_response</strong>回调,用来处理返回的<strong>header</strong>和<strong>status</strong>。通过application获取response以后,通过finish_response返回response</p>
<p><strong>WSGIHandler</strong>WSGI协议中的application,接收两个参数,environ字典包含了客户端请求的信息以及其他信息,可以认为是请求上下文,start_response用于发送返回status和header的回调函数</p>
<p>虽然上面一个WSGI server涉及到多个类实现以及相互引用,但其实原理还是调用WSGIHandler,传入请求参数以及回调方法start_response(),并将响应返回给客户端。</p>
<p> </p>
<p> </p>
<p> renfence</p>
<p>http://python.jobbole.com/84372/</p>
<p>http://python.jobbole.com/88653/?utm_source=blog.jobbole.com&utm_medium=relatedPosts</p>
</div>
<p> </p><br><br>
来源:https://www.cnblogs.com/-wenli/p/10884168.html
頁:
[1]