<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title><![CDATA[Mocimy's Island]]></title>
  
  <link href="/atom.xml" rel="self"/>
  <link href="https://mocimy.github.io/"/>
  <updated>2016-03-24T07:02:38.300Z</updated>
  <id>https://mocimy.github.io/</id>
  
  <author>
    <name><![CDATA[Mocimy]]></name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title><![CDATA[A Simple Web Server (翻译文章)]]></title>
    <link href="https://mocimy.github.io/2016/03/03/A-Simple-Web-Server-%E7%BF%BB%E8%AF%91%E6%96%87%E7%AB%A0/"/>
    <id>https://mocimy.github.io/2016/03/03/A-Simple-Web-Server-翻译文章/</id>
    <published>2016-03-03T03:44:15.000Z</published>
    <updated>2016-03-24T07:02:38.300Z</updated>
    <content type="html"><![CDATA[<blockquote>
<p>这是Greg Wilson在500lines上的文章。翻译水平有限，仅供参考。</p>
</blockquote>
<h1 id="介绍">介绍</h1><p>Web在过去的20年里大幅的改变了社会，但它的核心却改变的非常少。大多数系统仍遵循着Tim Berners-Lee 25年前制定的规则。特别的，大多数web服务器仍以和以前一样的方式处理同类的消息。<br>本章将探索它们的实现方式。同时将探索开发者如何创建不需重写就可以添加新特性的软件系统。</p>
<h1 id="后台">后台</h1><p>几乎Web上的每个程序都运行在被称为因特网协议(IP)的通信标准的家族上。家族的每个成员使用传输控制协议(TCP/IP)，使计算机间的交流看起来像读写文件。<br>程序通过socket使用IP交流。每个socket是点对点交流通道的一端，就像手机是移动通信的终端。一个Socket由机器的IP地址和端口号组成。IP地址由4个8bit数字组成，例如<code>174.136.14.108</code>;域名系统(DNS)将这些数字匹配到像<code>aosabook.org</code>这样非常适合人类记忆的名字。<br><a id="more"></a><br>端口号是0-65355的数字，是主机上socket的独有标识。(如果把IP地址比作公司的电话号码，那么端口号就是电话分机。) 端口0-1023被操作系统使用;任何程序都可以使用其余端口。<br>超文本传输协议(HTTP)描述了程序在IP间交换数据的方式。HTTP有意简化为:客户端发送请求指定它想到达的socket连接，然后服务器发送一些数据作为回应。数据可以被从磁盘文件拷贝，由程序动态生成，或两者兼而有之。<br><img src="http://aosabook.org/en/500L/web-server-images/http-response.png" alt="An HTTP Response"><br>版本，标题和正文具有相同的形式和意义。状态码是一串数字，它指出在请求提交的过程中发送了什么：200意味着“一切工作正常”，404意味着“没有找到页面”，其它代码有着其它意思。状态语句将信息重复为人类可以理解的语句，如”OK”或”not found”。<br>本章的目的只有两个，我们需要了解HTTP其他事情。<br>首先它是无状态的：每个请求由程序自身解决，服务器不记得一个请求和下一个请求之间的任何事。如果应用程序想跟踪一些信息，例如用户身份，它必须自行完成。<br>通常的方式是使用cookie——一种由服务器发送给客户端，再由客户端稍后返回给服务器的短字符。当用户执行一些需要状态被跨数个请求保存的动作时，服务器创建一个新的cookie，存到数据库中，再把它发送给浏览器。每当浏览器返回cookie，服务器用它来查找用户正在用的信息。<br>我们需要了解的关于HTTP的第二件事是URL能补充参数，来提供更多信息。例如，当我们使用搜索引擎时，我们必须指定搜索的关键词。我们能把它添加到URL中的路径，但我们必须做的是把参数添加到URL中。我们可以在URL中加入”?”,后面是由”&amp;”分割的键值对”key=value”。例如，对于<code>http://www.google.ca?q=Python</code>这个URL，让google搜索和Python有关的页面:键是字母”q”，值是”Python”。<code>http://www.google.ca/search?q=Python&amp;amp;client=Firefox</code>是一个更长的查询URL，告诉了google我们在使用Firefox。我们可以加入我们想要的任何参数，但同样，由网站上运行的应用决定要关注哪些参数，以及如何解释它们。<br>当然，如果’?’和’&amp;’是特殊字符，必须有一种方式来转义它们，就像必须有一种方式将双引号字符放入双引号包裹的字符串中。URL编码标准代表的特殊字符使用2位数代码加’％’，并以’+’字符代替空格。比如在google中搜索”grade = A+”(包含空格),我们需要使用<code>http://www.google.ca/search?q=grade+%3D+A%2B</code>这个URL。<br>打开socket，构建HTTP请求，并解析响应是乏味的，所以人们用库来完成大部分工作。Python提供这样的一个库，称为<code>urllib2</code>(因为它是一个更早的，被称为<code>urllib</code>的库的替代)，但它也暴露了许多大多数人不想关注的管道。<a href="https://pypi.python.org/pypi/requests" target="_blank" rel="external">Requests</a>库是<code>urllib2</code>更易使用的替代品。下面一个例子，你可以从AOSA book的网站上下载它:<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">import requests</span><br><span class="line">response = requests.<span class="function"><span class="title">get</span><span class="params">(<span class="string">'http://aosabook.org/en/500L/web-server/testpage.html'</span>)</span></span></span><br><span class="line">print <span class="string">'status code:'</span>, response<span class="class">.status_code</span></span><br><span class="line">print <span class="string">'content length:'</span>, response<span class="class">.headers</span>[<span class="string">'content-length'</span>]</span><br><span class="line">print response.text</span><br></pre></td></tr></table></figure></p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">status code: 200</span><br><span class="line">content length: 61</span><br><span class="line"><span class="tag">&lt;<span class="title">html</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="title">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="title">p</span>&gt;</span>Test page.<span class="tag">&lt;/<span class="title">p</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;/<span class="title">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="title">html</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p><code>request.get</code>向服务器发送一个HTTP GET请求，并返回一个包含响应的对象。这个对象的<code>status_code</code>成员是响应的状态码；它的<code>content_length</code>成员是响应数据的字节数，<code>text</code>是真实数据(本例中是HTML页面)。</p>
<h1 id="Hello,_Web">Hello, Web</h1><p>我们现在准备写我们的第一个简单web服务器。基本思想很简单:<br>1.等待某人连接到我们的服务器并发送HTTP请求；<br>2.分析请求；<br>3.找出它请求的内容；<br>4.命中数据(或动态生成它)；<br>5.将数据格式化为HTML；<br>6.将数据发送回去。<br>第1,2,6步对于不同应用是相同的，所以Python基础库有一个<code>BaseHTTPServer</code>模块为我们完成这些。我们只需关注步骤3-5，正如我们在下面的程序中所做的:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> BaseHTTPServer</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RequestHandler</span><span class="params">(BaseHTTPServer.BaseHTTPRequestHandler)</span>:</span></span><br><span class="line">    <span class="string">'''Handle HTTP requests by returning a fixed 'page'.'''</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># Page to send back.</span></span><br><span class="line">    Page = <span class="string">'''\</span><br><span class="line">&lt;html&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">&lt;p&gt;Hello, web!&lt;/p&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br><span class="line">'''</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># Handle a GET request.</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">do_GET</span><span class="params">(self)</span>:</span></span><br><span class="line">        self.send_response(<span class="number">200</span>)</span><br><span class="line">        self.send_header(<span class="string">"Content-Type"</span>, <span class="string">"text/html"</span>)</span><br><span class="line">        self.send_header(<span class="string">"Content-Length"</span>, str(len(self.Page)))</span><br><span class="line">        self.end_headers()</span><br><span class="line">        self.wfile.write(self.Page)</span><br><span class="line"></span><br><span class="line"><span class="comment">#----------------------------------------------------------------------</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line">    serverAddress = (<span class="string">''</span>, <span class="number">8080</span>)</span><br><span class="line">    server = BaseHTTPServer.HTTPServer(serverAddress, RequestHandler)</span><br><span class="line">    server.serve_forever()</span><br></pre></td></tr></table></figure></p>
<p>这个库的<code>BaseHTTPRequestHandler</code>类专注于分析收到的HTTP请求并决定它包含的method。如果method是GET，这个类调用名叫<code>do_GET</code>的方法。我们的类<code>RequestHandler</code>优先于这个method动态生成简单页面:文本被存储在类层次变量<code>Page</code>中，我们把它在发送200响应码之后发回给客户端，<code>Content-Type</code>头告诉客户端把我们的数据解释为HTML，并告知页的长度(<code>end_headers</code>方法调用插入空白行来把我们的头从页中分离)。<br>但<code>RequestHandler</code>仍不是全部:我们还需要最后三行才能让服务器开始运行。第一行将服务器地址定义为一个元组:空字符串意味着”运行在当前机器上”,8080是端口号。然后我们创建一个<code>BaseHTTPServer.HTTPServer</code>实例，它把地址和请求处理器类名作为参数，然后用它来持续运行服务器(一直运行直到我们按Control-C终止它)。<br>如果我们从命令行运行这个程序，它不显示任何信息:<br><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="keyword">python</span> server.<span class="keyword">py</span></span><br></pre></td></tr></table></figure></p>
<p>在浏览器中打开<code>http://localhost:8080</code>，我们会在浏览器中看到:<br><figure class="highlight erlang-repl"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">Hello</span>, <span class="function_or_atom">web</span><span class="exclamation_mark">!</span></span><br></pre></td></tr></table></figure></p>
<p>在我们的shell中看到:<br><figure class="highlight accesslog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="number">127.0.0.1</span> - - <span class="string">[24/Feb/2014 10:26:28]</span> <span class="string">"<span class="keyword">GET</span> / HTTP/1.1"</span> <span class="number">200</span> -</span><br><span class="line"><span class="number">127.0.0.1</span> - - <span class="string">[24/Feb/2014 10:26:28]</span> <span class="string">"<span class="keyword">GET</span> /favicon.ico HTTP/1.1"</span> <span class="number">200</span> -</span><br></pre></td></tr></table></figure></p>
<p>第一行是直截了当的:当我们不请求特定文件时,浏览器请求”/“(/是服务器提供服务的跟目录)。第二行由于我们的浏览器自动发送二次请求来获取<code>/favicon.ico</code>这个图片文件而出现,如果这个图片存在则会在浏览器标签页上显示一个图标。</p>
<h1 id="显示值">显示值</h1><p>让我们修改我们的web服务器,来显示HTTP请求中包含的一些值。(我们将在调试时经常用到它，所以我们可以先来做个练习。)为了保持代码整洁，我们把创建页面的代码分离出来:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RequestHandler</span><span class="params">(BaseHTTPServer.BaseHTTPRequestHandler)</span>:</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># ...page template...</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">do_GET</span><span class="params">(self)</span>:</span></span><br><span class="line">        page = self.create_page()</span><br><span class="line">        self.send_page(page)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">create_page</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="comment"># ...fill in...</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">send_page</span><span class="params">(self, page)</span>:</span></span><br><span class="line">        <span class="comment"># ...fill in...</span></span><br></pre></td></tr></table></figure></p>
<p><code>send_page</code>比我们前面做的漂亮多了:<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">def <span class="function"><span class="title">send_page</span><span class="params">(self, page)</span></span>:</span><br><span class="line">    self.<span class="function"><span class="title">send_response</span><span class="params">(<span class="number">200</span>)</span></span></span><br><span class="line">    self.<span class="function"><span class="title">send_header</span><span class="params">(<span class="string">"Content-type"</span>, <span class="string">"text/html"</span>)</span></span></span><br><span class="line">    self.<span class="function"><span class="title">send_header</span><span class="params">(<span class="string">"Content-Length"</span>, str(len(page)</span></span>))</span><br><span class="line">    self.<span class="function"><span class="title">end_headers</span><span class="params">()</span></span></span><br><span class="line">    self<span class="class">.wfile</span><span class="class">.write</span>(page)</span><br></pre></td></tr></table></figure></p>
<p>我们要显示页面的模板仅仅是包含HTML表的字符串，HTML表中有一些格式占位符:<br><figure class="highlight pf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">  Page = '''\</span><br><span class="line"><span class="variable">&lt;html&gt;</span></span><br><span class="line"><span class="variable">&lt;body&gt;</span></span><br><span class="line"><span class="variable">&lt;table&gt;</span></span><br><span class="line"><span class="variable">&lt;tr&gt;</span>  <span class="variable">&lt;td&gt;</span>Header<span class="variable">&lt;/td&gt;</span>         <span class="variable">&lt;td&gt;</span>Value<span class="variable">&lt;/td&gt;</span>          <span class="variable">&lt;/tr&gt;</span></span><br><span class="line"><span class="variable">&lt;tr&gt;</span>  <span class="variable">&lt;td&gt;</span>Date and time<span class="variable">&lt;/td&gt;</span>  <span class="variable">&lt;td&gt;</span>&#123;date_time&#125;<span class="variable">&lt;/td&gt;</span>    <span class="variable">&lt;/tr&gt;</span></span><br><span class="line"><span class="variable">&lt;tr&gt;</span>  <span class="variable">&lt;td&gt;</span>Client host<span class="variable">&lt;/td&gt;</span>    <span class="variable">&lt;td&gt;</span>&#123;client_host&#125;<span class="variable">&lt;/td&gt;</span>  <span class="variable">&lt;/tr&gt;</span></span><br><span class="line"><span class="variable">&lt;tr&gt;</span>  <span class="variable">&lt;td&gt;</span>Client <span class="keyword">port</span><span class="variable">&lt;/td&gt;</span>    <span class="variable">&lt;td&gt;</span>&#123;client_port&#125;s<span class="variable">&lt;/td&gt;</span> <span class="variable">&lt;/tr&gt;</span></span><br><span class="line"><span class="variable">&lt;tr&gt;</span>  <span class="variable">&lt;td&gt;</span>Command<span class="variable">&lt;/td&gt;</span>        <span class="variable">&lt;td&gt;</span>&#123;command&#125;<span class="variable">&lt;/td&gt;</span>      <span class="variable">&lt;/tr&gt;</span></span><br><span class="line"><span class="variable">&lt;tr&gt;</span>  <span class="variable">&lt;td&gt;</span>Path<span class="variable">&lt;/td&gt;</span>           <span class="variable">&lt;td&gt;</span>&#123;path&#125;<span class="variable">&lt;/td&gt;</span>         <span class="variable">&lt;/tr&gt;</span></span><br><span class="line"><span class="variable">&lt;/table&gt;</span></span><br><span class="line"><span class="variable">&lt;/body&gt;</span></span><br><span class="line"><span class="variable">&lt;/html&gt;</span></span><br><span class="line">'''</span><br></pre></td></tr></table></figure></p>
<p>用于填充它们的方法为:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">create_page</span><span class="params">(self)</span>:</span></span><br><span class="line">    values = &#123;</span><br><span class="line">        <span class="string">'date_time'</span>   : self.date_time_string(),</span><br><span class="line">        <span class="string">'client_host'</span> : self.client_address[<span class="number">0</span>],</span><br><span class="line">        <span class="string">'client_port'</span> : self.client_address[<span class="number">1</span>],</span><br><span class="line">        <span class="string">'command'</span>     : self.command,</span><br><span class="line">        <span class="string">'path'</span>        : self.path</span><br><span class="line">    &#125;</span><br><span class="line">    page = self.Page.format(**values)</span><br><span class="line">    <span class="keyword">return</span> page</span><br></pre></td></tr></table></figure></p>
<p>程序的主体没有变:和之前一样，它把地址和请求处理器类名作为参数建立了一个<code>HTTPServer</code>类的实例，然后持续服务。如果我们运行它并向浏览器发送一个<code>http://localhost:8080/something.html</code>的请求，我们会看到:<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Date and time  Mon, <span class="number">24</span> Feb <span class="number">2014</span> <span class="number">17</span>:<span class="number">17</span>:<span class="number">12</span> GMT</span><br><span class="line">Client host    <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line">Client port    <span class="number">54548</span></span><br><span class="line">Command        GET</span><br><span class="line">Path           /something.html</span><br></pre></td></tr></table></figure></p>
<p>注意到我们没有收到404错误，尽管磁盘上并不存在一个叫<code>something.html</code>的文件。这是因为web服务器只是一个程序，当它收到一个请求时可以作任何事:返回上一次请求命名的文件，随机打开维基百科，或者其它任何我们计划好的事情。</p>
<h1 id="提供静态页面">提供静态页面</h1><p>下一步明显是从磁盘启动服务页，而不是凭空生成它们。我们将由重写<code>do_GET</code>开始:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">do_GET</span><span class="params">(self)</span>:</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line"></span><br><span class="line">        <span class="comment"># Figure out what exactly is being requested.</span></span><br><span class="line">        full_path = os.getcwd() + self.path</span><br><span class="line"></span><br><span class="line">        <span class="comment"># It doesn't exist...</span></span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> os.path.exists(full_path):</span><br><span class="line">            <span class="keyword">raise</span> ServerException(<span class="string">"'&#123;0&#125;' not found"</span>.format(self.path))</span><br><span class="line"></span><br><span class="line">        <span class="comment"># ...it's a file...</span></span><br><span class="line">        <span class="keyword">elif</span> os.path.isfile(full_path):</span><br><span class="line">            self.handle_file(full_path)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># ...it's something we don't handle.</span></span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="keyword">raise</span> ServerException(<span class="string">"Unknown object '&#123;0&#125;'"</span>.format(self.path))</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Handle errors.</span></span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> msg:</span><br><span class="line">        self.handle_error(/msg)</span><br></pre></td></tr></table></figure></p>
<p>这个方法假设它允许用服务器运行目录(通过<code>os.getcwd</code>得到)中任何文件提供服务。将它与URL提供的路径(库会自动将它放到<code>self.path</code>中，总是由’/‘开始)联合起来,得到用户想要的联合路径。<br>如果它不存在，或者不是一个文件，方法通过抛出和捕获异常报告错误。如果路径命中文件，另一方面，它就会调用一个叫做<code>handle_file</code>的辅助方法来读和返回内容。这个方法就会读文件并用我们存在的<code>send_content</code>把它返回客户端:<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">def <span class="function"><span class="title">handle_file</span><span class="params">(self, full_path)</span></span>:</span><br><span class="line">    try:</span><br><span class="line">        with <span class="function"><span class="title">open</span><span class="params">(full_path, <span class="string">'rb'</span>)</span></span> as reader:</span><br><span class="line">            <span class="attribute">content</span> = reader.<span class="function"><span class="title">read</span><span class="params">()</span></span></span><br><span class="line">        self.<span class="function"><span class="title">send_content</span><span class="params">(content)</span></span></span><br><span class="line">    except IOError as msg:</span><br><span class="line">        msg = <span class="string">"'&#123;0&#125;' cannot be read: &#123;1&#125;"</span>.<span class="function"><span class="title">format</span><span class="params">(/self.path, msg)</span></span></span><br><span class="line">        self.<span class="function"><span class="title">handle_error</span><span class="params">(/msg)</span></span></span><br></pre></td></tr></table></figure></p>
<p>注意我们用二进制模式打开文件——‘rb’中的’b’——所有Python不会尝试”帮助”我们改变字符序列来使它看起来像Windows行尾。还应注意服务时把整个文件读进内存中在真实情况下是一个糟糕的主意，该文件可能是一个数千兆字节的视频文件。处理这种情况在本章的范围之内。<br>要完成这个类，我们需要编写错误解决方法和错误报告页的模板:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">Error_Page = <span class="string">"""\</span><br><span class="line">    &lt;html&gt;</span><br><span class="line">    &lt;body&gt;</span><br><span class="line">    &lt;h1&gt;Error accessing &#123;path&#125;&lt;/h1&gt;</span><br><span class="line">    &lt;p&gt;&#123;msg&#125;&lt;/p&gt;</span><br><span class="line">    &lt;/body&gt;</span><br><span class="line">    &lt;/html&gt;</span><br><span class="line">    """</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">handle_error</span><span class="params">(/self, msg)</span>:</span></span><br><span class="line">    content = self.Error_Page.format(/path=self.path, msg=msg)</span><br><span class="line">    self.send_content(content)</span><br></pre></td></tr></table></figure></p>
<p>这个程序可以工作，前提是我们不看的过于仔细。问题在于它总是返回状态码200，甚至页面不存在时也是这样。是的，这个例子中返回了包含错误信息的页面，但我们的浏览器不懂英语，它不知道该请求实际上失败了。为了使它明白这一点，我们需要像下面这样修改<code>handle_error</code>和<code>send_content</code>:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Handle unknown objects.</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">handle_error</span><span class="params">(/self, msg)</span>:</span></span><br><span class="line">    content = self.Error_Page.format(/path=self.path, msg=msg)</span><br><span class="line">    self.send_content(content, <span class="number">404</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Send actual content.</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">send_content</span><span class="params">(self, content, status=<span class="number">200</span>)</span>:</span></span><br><span class="line">    self.send_response(status)</span><br><span class="line">    self.send_header(<span class="string">"Content-type"</span>, <span class="string">"text/html"</span>)</span><br><span class="line">    self.send_header(<span class="string">"Content-Length"</span>, str(len(content)))</span><br><span class="line">    self.end_headers()</span><br><span class="line">    self.wfile.write(content)</span><br></pre></td></tr></table></figure></p>
<p>注意我们没有在找不到文件时抛出一个<code>ServerException</code>异常，而是生成了一个错误页面。<code>ServerException</code>意味着服务器代码中的内部错误，即我们得到了一些错误的信息。这个错误页面由<code>handle_error</code>创建，同时会在用户得到错误信息即发送给我们文件的URL不存在时出现。</p>
<h1 id="列出目录">列出目录</h1><p>下一步，我们要教会web服务器当URL不是文件，而是目录时，显示目录内容的列表。我们甚至可以更进一步，如果存在<code>index.html</code>目录中则则显示它，否则才显示目录的内容列表。<br>但是将这些规则构建进<code>do_GET</code>是错误的，因为结果方法将会长时间困于<code>if</code>状态控制的特殊行为。正确的解决方法是返回上一步，弄清楚如何处理URL的生成问题，这是重写的<code>do_GET</code>方法:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">do_GET</span><span class="params">(self)</span>:</span></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line"></span><br><span class="line">        <span class="comment"># Figure out what exactly is being requested.</span></span><br><span class="line">        self.full_path = os.getcwd() + self.path</span><br><span class="line"></span><br><span class="line">        <span class="comment"># Figure out how to handle it.</span></span><br><span class="line">        <span class="keyword">for</span> case <span class="keyword">in</span> self.Cases:</span><br><span class="line">            handler = case()</span><br><span class="line">            <span class="keyword">if</span> handler.test(self):</span><br><span class="line">                handler.act(self)</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># Handle errors.</span></span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> msg:</span><br><span class="line">        self.handle_error(/msg)</span><br></pre></td></tr></table></figure></p>
<p>第一步是一样的:弄清楚被请求的完整路径。之后，虽然代码看起来完全不一样。取而代之的是一系列的内联测试，它的版本循环存储在列表的case集合中。每个case都是有两个方法的对象:<code>test</code>，告诉我们它是否能解决请求，还有<code>act</code>，采取一些实际行为。当我们找到正确的case，我们让它来解决请求并跳出循环。<br>这是重现我们以前服务器行为的三个case类:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">case_no_file</span><span class="params">(object)</span>:</span></span><br><span class="line">    <span class="string">'''File or directory does not exist.'''</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">test</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">not</span> os.path.exists(handler.full_path)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">act</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">raise</span> ServerException(<span class="string">"'&#123;0&#125;' not found"</span>.format(handler.path))</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">case_existing_file</span><span class="params">(object)</span>:</span></span><br><span class="line">    <span class="string">'''File exists.'''</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">test</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> os.path.isfile(handler.full_path)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">act</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        handler.handle_file(handler.full_path)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">case_always_fail</span><span class="params">(object)</span>:</span></span><br><span class="line">    <span class="string">'''Base case if nothing else worked.'''</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">test</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">True</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">act</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">raise</span> ServerException(<span class="string">"Unknown object '&#123;0&#125;'"</span>.format(handler.path))</span><br></pre></td></tr></table></figure></p>
<p>这是我们如何在<code>RequestHandler</code>类顶部构建case处理器:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RequestHandler</span><span class="params">(BaseHTTPServer.BaseHTTPRequestHandler)</span>:</span></span><br><span class="line">    <span class="string">'''</span><br><span class="line">    If the requested path maps to a file, that file is served.</span><br><span class="line">    If anything goes wrong, an error page is constructed.</span><br><span class="line">    '''</span></span><br><span class="line"></span><br><span class="line">    Cases = [case_no_file(),</span><br><span class="line">             case_existing_file(),</span><br><span class="line">             case_always_fail()]</span><br><span class="line"></span><br><span class="line">    ...everything <span class="keyword">else</span> <span class="keyword">as</span> before...</span><br></pre></td></tr></table></figure></p>
<p>现在，表面看起来我们的服务器更加复杂了，而不是相反:文件从74行增加到99行，有了额外的间接层，却没有增加任何功能。这样的好处是，当我们返回本章的开始处，尝试教我们的服务器当存在<code>index.html</code>时提供该页面，不存在时提供目录列表。原来的处理器是:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">case_directory_index_file</span><span class="params">(object)</span>:</span></span><br><span class="line">    <span class="string">'''Serve index.html page for a directory.'''</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">index_path</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> os.path.join(handler.full_path, <span class="string">'index.html'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">test</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> os.path.isdir(handler.full_path) <span class="keyword">and</span> \</span><br><span class="line">               os.path.isfile(self.index_path(handler))</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">act</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        handler.handle_file(self.index_path(handler))</span><br></pre></td></tr></table></figure></p>
<p>这里，辅助方法<code>index_path</code>构建了<code>index.html</code>文件的路径;把它放入在主<code>RequestHandler</code>中防止混乱的case处理器。<code>test</code>检查路径是不是包含<code>index.html</code>页的文件，<code>act</code>让主请求处理器提供页。<br><code>RequestHandler</code>唯一的改变是包含了添加<code>case_directory_index_file</code>对象到我们<code>Cases</code>列表的逻辑:<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Cases = [<span class="function"><span class="title">case_no_file</span><span class="params">()</span></span>,</span><br><span class="line">         <span class="function"><span class="title">case_existing_file</span><span class="params">()</span></span>,</span><br><span class="line">         <span class="function"><span class="title">case_directory_index_file</span><span class="params">()</span></span>,</span><br><span class="line">         <span class="function"><span class="title">case_always_fail</span><span class="params">()</span></span>]</span><br></pre></td></tr></table></figure></p>
<p>如果目录不包含<code>index.html</code>页呢？test和上面一样，只是插入了一个<code>not</code>，但<code>act</code>方法呢？它该干什么？<br><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">case_directory_no_index_file</span>(<span class="title">object</span>):</span></span><br><span class="line">    <span class="string">''</span><span class="string">'Serve listing for a directory without an index.html page.'</span><span class="string">''</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">index_path</span><span class="params">(<span class="keyword">self</span>, handler)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> os.path.join(handler.full_path, <span class="string">'index.html'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">test</span><span class="params">(<span class="keyword">self</span>, handler)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> os.path.isdir(handler.full_path) <span class="keyword">and</span> \</span><br><span class="line">               <span class="keyword">not</span> os.path.isfile(<span class="keyword">self</span>.index_path(handler))</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">act</span><span class="params">(<span class="keyword">self</span>, handler)</span>:</span></span><br><span class="line">        ???</span><br></pre></td></tr></table></figure></p>
<p>我们似乎把自己带入了困境。逻辑上说，<code>act</code>方法应该创建并返回目录列表，但是我们存在的代码不允许这样做:<code>RequestHandler.do_GET</code>调用<code>act</code>，但不期望和处理它的返回值。现在开始，让我们为<code>RequestHandler</code>创建一个方法来生成目录列表，并由case处理器的<code>act</code>调用它:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">case_directory_no_index_file</span><span class="params">(object)</span>:</span></span><br><span class="line">    <span class="string">'''Serve listing for a directory without an index.html page.'''</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># ...index_path and test as above...</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">act</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        handler.list_dir(handler.full_path)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RequestHandler</span><span class="params">(BaseHTTPServer.BaseHTTPRequestHandler)</span>:</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># ...all the other code...</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># How to display a directory listing.</span></span><br><span class="line">    Listing_Page = <span class="string">'''\</span><br><span class="line">        &lt;html&gt;</span><br><span class="line">        &lt;body&gt;</span><br><span class="line">        &lt;ul&gt;</span><br><span class="line">        &#123;0&#125;</span><br><span class="line">        &lt;/ul&gt;</span><br><span class="line">        &lt;/body&gt;</span><br><span class="line">        &lt;/html&gt;</span><br><span class="line">        '''</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">list_dir</span><span class="params">(self, full_path)</span>:</span></span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            entries = os.listdir(full_path)</span><br><span class="line">            bullets = [<span class="string">'&lt;li&gt;&#123;0&#125;&lt;/li&gt;'</span>.format(e) <span class="keyword">for</span> e <span class="keyword">in</span> entries <span class="keyword">if</span> <span class="keyword">not</span> e.startswith(<span class="string">'.'</span>)]</span><br><span class="line">            page = self.Listing_Page.format(<span class="string">'\n'</span>.join(bullets))</span><br><span class="line">            self.send_content(page)</span><br><span class="line">        <span class="keyword">except</span> OSError <span class="keyword">as</span> msg:</span><br><span class="line">            msg = <span class="string">"'&#123;0&#125;' cannot be listed: &#123;1&#125;"</span>.format(/self.path, msg)</span><br><span class="line">            self.handle_error(/msg)</span><br></pre></td></tr></table></figure></p>
<h1 id="CGI协议">CGI协议</h1><p>当然，大多数人不想编辑他们web服务器的源代码来添加新的功能。为了从不得不这么做中拯救他们，服务器也支持一种叫通用网关接口的机制，它为web服务器提供了一种标准方法，通过运行外部程序来满足请求。<br>例如，假设我们希望服务器能在HTML页面中显示本地时间。我们可以在一个独立程序中，只需几行代码就做到这一点：<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> datetime <span class="keyword">import</span> datetime</span><br><span class="line"><span class="keyword">print</span> <span class="string">'''\</span><br><span class="line">&lt;html&gt;</span><br><span class="line">&lt;body&gt;</span><br><span class="line">&lt;p&gt;Generated &#123;0&#125;&lt;/p&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;'''</span>.format(datetime.now())</span><br></pre></td></tr></table></figure></p>
<p>为了让服务器为我们运行这个程序，我们添加这个case处理器:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">case_cgi_file</span><span class="params">(object)</span>:</span></span><br><span class="line">    <span class="string">'''Something runnable.'''</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">test</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> os.path.isfile(handler.full_path) <span class="keyword">and</span> \</span><br><span class="line">               handler.full_path.endswith(<span class="string">'.py'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">act</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        handler.run_cgi(handler.full_path)</span><br></pre></td></tr></table></figure></p>
<p>test很简单:文件路径是不是以<code>.py</code>结尾？act也很简单:告诉<code>RequestHandler</code>运行这个程序。<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">def <span class="function"><span class="title">run_cgi</span><span class="params">(self, full_path)</span></span>:</span><br><span class="line">    cmd = <span class="string">"python "</span> + full_path</span><br><span class="line">    child_stdin, child_stdout =Sweeping that <span class="tag">aside</span> os.<span class="function"><span class="title">popen2</span><span class="params">(cmd)</span></span></span><br><span class="line">    child_stdin.<span class="function"><span class="title">close</span><span class="params">()</span></span></span><br><span class="line">    data = child_stdout.<span class="function"><span class="title">read</span><span class="params">()</span></span></span><br><span class="line">    child_stdout.<span class="function"><span class="title">close</span><span class="params">()</span></span></span><br><span class="line">    self.<span class="function"><span class="title">send_content</span><span class="params">(data)</span></span></span><br></pre></td></tr></table></figure></p>
<p>有一点不安全:如果有人知道在我们服务器上的Python文件的路径，我们就运行他们运行它，没考虑它能访问的数据，它是否包含无限循环，或者其它一些事情。<br>核心思想很简单:<br>1.在子进程中运行程序。<br>2.捕捉子进程向标准输出发送了什么。<br>3.把它发回提出请求的客户端。<br>完整的CGI协议比这更丰富——特别的，它允许URL中服务器运行时传给程序的参数——但这些细节不影响系统的总体结构…<br>…这将再次变得相当纠结。<code>RequestHandler</code>原来有一个方法，<code>handle_file</code>，用于处理内容。我们现在以<code>list_dir</code>和<code>run_cgi</code>的形式加入了两个特殊case。这三个方法不是真的属于它们在的地方，因为它们主要是被其它地方调用。<br>修补也很简单:为我们的所有case处理器创建一个父类，把其余方法挪进这个类，当且仅当它们被两个或以上处理器使用的时候。完成这一点后，<code>RequestHandler</code>类看起来像这样:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">RequestHandler</span><span class="params">(BaseHTTPServer.BaseHTTPRequestHandler)</span>:</span></span><br><span class="line"></span><br><span class="line">    Cases = [case_no_file(),</span><br><span class="line">             case_cgi_file(),</span><br><span class="line">             case_existing_file(),</span><br><span class="line">             case_directory_index_file(),</span><br><span class="line">             case_directory_no_index_file(),</span><br><span class="line">             case_always_fail()]</span><br><span class="line"></span><br><span class="line">    <span class="comment"># How to display an error.</span></span><br><span class="line">    Error_Page = <span class="string">"""\</span><br><span class="line">        &lt;html&gt;</span><br><span class="line">        &lt;body&gt;</span><br><span class="line">        &lt;h1&gt;Error accessing &#123;path&#125;&lt;/h1&gt;</span><br><span class="line">        &lt;p&gt;&#123;msg&#125;&lt;/p&gt;</span><br><span class="line">        &lt;/body&gt;</span><br><span class="line">        &lt;/html&gt;</span><br><span class="line">        """</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># Classify and handle request.</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">do_GET</span><span class="params">(self)</span>:</span></span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line"></span><br><span class="line">            <span class="comment"># Figure out what exactly is being requested.</span></span><br><span class="line">            self.full_path = os.getcwd() + self.path</span><br><span class="line"></span><br><span class="line">            <span class="comment"># Figure out how to handle it.</span></span><br><span class="line">            <span class="keyword">for</span> case <span class="keyword">in</span> self.Cases:</span><br><span class="line">                <span class="keyword">if</span> case.test(self):</span><br><span class="line">                    case.act(self)</span><br><span class="line">                    <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># Handle errors.</span></span><br><span class="line">        <span class="keyword">except</span> Exception <span class="keyword">as</span> msg:</span><br><span class="line">            self.handle_error(/msg)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Handle unknown objects.</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">handle_error</span><span class="params">(/self, msg)</span>:</span></span><br><span class="line">        content = self.Error_Page.format(/path=self.path, msg=msg)</span><br><span class="line">        self.send_content(content, <span class="number">404</span>)</span><br><span class="line"></span><br><span class="line">    <span class="comment"># Send actual content.</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">send_content</span><span class="params">(self, content, status=<span class="number">200</span>)</span>:</span></span><br><span class="line">        self.send_response(status)</span><br><span class="line">        self.send_header(<span class="string">"Content-type"</span>, <span class="string">"text/html"</span>)</span><br><span class="line">        self.send_header(<span class="string">"Content-Length"</span>, str(len(content)))</span><br><span class="line">        self.end_headers()</span><br><span class="line">        self.wfile.write(content)</span><br></pre></td></tr></table></figure></p>
<p>case处理器的父类:<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">base_case</span><span class="params">(object)</span>:</span></span><br><span class="line">    <span class="string">'''Parent for case handlers.'''</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">handle_file</span><span class="params">(self, handler, full_path)</span>:</span></span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            <span class="keyword">with</span> open(full_path, <span class="string">'rb'</span>) <span class="keyword">as</span> reader:</span><br><span class="line">                content = reader.read()</span><br><span class="line">            handler.send_content(content)</span><br><span class="line">        <span class="keyword">except</span> IOError <span class="keyword">as</span> msg:</span><br><span class="line">            msg = <span class="string">"'&#123;0&#125;' cannot be read: &#123;1&#125;"</span>.format(/full_path, msg)</span><br><span class="line">            handler.handle_error(/msg)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">index_path</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> os.path.join(handler.full_path, <span class="string">'index.html'</span>)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">test</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">assert</span> <span class="keyword">False</span>, <span class="string">'Not implemented.'</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">act</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">assert</span> <span class="keyword">False</span>, <span class="string">'Not implemented.'</span></span><br></pre></td></tr></table></figure></p>
<p>已存在文件的处理器(只是挑选随机的例子):<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">case_existing_file</span><span class="params">(base_case)</span>:</span></span><br><span class="line">    <span class="string">'''File exists.'''</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">test</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        <span class="keyword">return</span> os.path.isfile(handler.full_path)</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">act</span><span class="params">(self, handler)</span>:</span></span><br><span class="line">        self.handle_file(handler, handler.full_path)</span><br></pre></td></tr></table></figure></p>
<h1 id="讨论">讨论</h1><p>我们原来的代码和重构版本之间的差异反映了两个重要思想。首先是把类作为的相关服务的集合。请求处理器和base_case不作决定或采取行动;它们提供了其他类可以用来做这些事情的工具。<br>第二个是可扩展性：人们可以通过编写一个外部CGI程序，或通过添加case处理器类，向我们的Web服务器添加新功能。后者确实需要对<code>RequestHandler</code>做一行修改（在<code>Cases</code>列表中插入case处理器），但我们可以通过让Web服务器读取配置文件，从其中加载处理器类摆脱它。在这两种情况下，它们可以忽略最下级的细节，正如<code>BaseHTTPRequestHandler</code>类的作者允许我们忽略的处理套接字连接和解析HTTP请求的细节。<br>这些想法通常是有用的;看看你是否能找到在自己的项目中使用它们的机会。</p>
]]></content>
    <summary type="html">
    <![CDATA[<blockquote>
<p>这是Greg Wilson在500lines上的文章。翻译水平有限，仅供参考。</p>
</blockquote>
<h1 id="介绍">介绍</h1><p>Web在过去的20年里大幅的改变了社会，但它的核心却改变的非常少。大多数系统仍遵循着Tim Berners-Lee 25年前制定的规则。特别的，大多数web服务器仍以和以前一样的方式处理同类的消息。<br>本章将探索它们的实现方式。同时将探索开发者如何创建不需重写就可以添加新特性的软件系统。</p>
<h1 id="后台">后台</h1><p>几乎Web上的每个程序都运行在被称为因特网协议(IP)的通信标准的家族上。家族的每个成员使用传输控制协议(TCP/IP)，使计算机间的交流看起来像读写文件。<br>程序通过socket使用IP交流。每个socket是点对点交流通道的一端，就像手机是移动通信的终端。一个Socket由机器的IP地址和端口号组成。IP地址由4个8bit数字组成，例如<code>174.136.14.108</code>;域名系统(DNS)将这些数字匹配到像<code>aosabook.org</code>这样非常适合人类记忆的名字。<br>]]>
    
    </summary>
    
      <category term="Python" scheme="https://mocimy.github.io/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[使用uwsgi和Nginx部署Flask应用]]></title>
    <link href="https://mocimy.github.io/2016/02/26/%E4%BD%BF%E7%94%A8uwsgi%E5%92%8CNginx%E9%83%A8%E7%BD%B2Flask%E5%BA%94%E7%94%A8/"/>
    <id>https://mocimy.github.io/2016/02/26/使用uwsgi和Nginx部署Flask应用/</id>
    <published>2016-02-26T04:04:16.000Z</published>
    <updated>2016-03-22T03:37:27.668Z</updated>
    <content type="html"><![CDATA[<h1 id="部署方案">部署方案</h1><p>任何的部署方案都不应该脱离实际需求。Flask+uWSGI+Nginx 可以应对那些高并发、高性能的页面需求。<br>如果你需要：</p>
<ul>
<li>轻量、可扩展的Web程序</li>
<li>易于使用的语法</li>
<li>适于高连接并发的情况</li>
<li>高性能、低占用</li>
<li>多app管理</li>
<li>高度可定制</li>
</ul>
<p>这样的Web应用，那么Flask+uWSGI+Nginx 是你应该考虑的方案。</p>
<h3 id="Flask">Flask</h3><p>Flask是一个使用 Python 编写的轻量级 Web 应用框架。</p>
<h3 id="uWSGI">uWSGI</h3><p>The uWSGI project aims at developing a full stack for building hosting services.</p>
<h3 id="Nginx">Nginx</h3><p>Nginx 是一个高性能的HTTP和 反向代理 服务器 (这里主要用于处理静态文件)。</p>
<a id="more"></a>
<h1 id="部署环境">部署环境</h1><p>我们这里使用了Ubuntu 14.04 x64的VPS来部署我们的应用。</p>
<h1 id="开始部署">开始部署</h1><h2 id="配置virtualenv">配置virtualenv</h2><p>生产环境中不同的Application往往依赖于不同的Python包。如果直接安装这些包，可能由于依赖的版本不同产生各种各样的冲突。为了避免这种情况，我们使用Virtualenv来为每个应用创建单独的虚拟环境，避免冲突。<br>Ubuntu的软件源中包含了virtualenv，可以直接安装:<br><figure class="highlight vala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor"># apt-get install python-virtualenv</span></span><br></pre></td></tr></table></figure></p>
<p>如果使用Python3：<br><figure class="highlight vala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor"># apt-get install python3-virtualenv</span></span><br></pre></td></tr></table></figure></p>
<p>或者使用pip安装：<br><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ pip <span class="keyword">install</span> virtualenv</span><br></pre></td></tr></table></figure></p>
<p>创建一个virtualenv环境是非常简单的。<br><figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$ </span>virtualenv venv</span><br></pre></td></tr></table></figure></p>
<p>Python环境和一些必要的工具(如pip)会被安装到当前目录的venv文件夹下。<br>当你想激活它时，执行<br><figure class="highlight applescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ . venv/bin/<span class="command">activate</span></span><br></pre></td></tr></table></figure></p>
<p>好了，你已经切换到了venv的shell下。值得注意的是，只有Python环境是虚拟的，其他环境(如C++ Lib)仍与之前没有任何区别。</p>
<h2 id="上传应用">上传应用</h2><p>可以通过scp命令将本地的项目上传到远程主机中:<br><figure class="highlight inform7"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">usage: scp <span class="comment">[-12346BCpqrv]</span> <span class="comment">[-c cipher]</span> <span class="comment">[-F ssh_config]</span> <span class="comment">[-i identity_file]</span>        </span><br><span class="line">           <span class="comment">[-l limit]</span> <span class="comment">[-o ssh_option]</span> <span class="comment">[-P port]</span> <span class="comment">[-S program]</span>                  </span><br><span class="line">           <span class="comment">[<span class="comment">[user@]</span>host1:]</span>file1 ... <span class="comment">[<span class="comment">[user@]</span>host2:]</span>file2</span><br></pre></td></tr></table></figure></p>
<p>也可以通过git等其它方式进行部署。</p>
<h2 id="使用Manager启动Server">使用Manager启动Server</h2><p>Manager是一个flask脚本，包含在flask.ext.script中。<br>新建manage.py，内容如下:<br><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"># <span class="operator"><span class="keyword">Set</span> the <span class="keyword">path</span></span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> <span class="keyword">sys</span></span><br><span class="line"><span class="keyword">sys</span>.<span class="keyword">path</span>.append(os.<span class="keyword">path</span>.abspath(os.<span class="keyword">path</span>.<span class="keyword">join</span>(os.<span class="keyword">path</span>.dirname(__file__), <span class="string">'..'</span>)))</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> flask.ext.script <span class="keyword">import</span> Manager, <span class="keyword">Server</span>, Shell</span><br><span class="line"><span class="keyword">from</span> yourpackagename <span class="keyword">import</span> app</span><br><span class="line"></span><br><span class="line">manager = Manager(app)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># Turn <span class="keyword">on</span> debugger <span class="keyword">by</span> <span class="keyword">default</span> <span class="keyword">and</span> reloader</span><br><span class="line">manager.add_command(<span class="string">"runserver"</span>, <span class="keyword">Server</span>(</span><br><span class="line">    use_debugger=<span class="literal">True</span>,</span><br><span class="line">    use_reloader=<span class="literal">True</span>,</span><br><span class="line">    host=<span class="string">'0.0.0.0'</span>)</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">manager.add_command(<span class="string">"shell"</span>, Shell())</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">"__main__"</span>:</span><br><span class="line">    manager.run()</span></span><br></pre></td></tr></table></figure></p>
<p>这样，我们就可以通过下面的命令，更方便地运行和调试项目:<br><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="keyword">python</span> manage.<span class="keyword">py</span> runserver    #服务器模式</span><br><span class="line">$ <span class="keyword">python</span> manage.<span class="keyword">py</span> <span class="keyword">shell</span>        #Shell模式</span><br></pre></td></tr></table></figure></p>
<h2 id="安装所有需要的包">安装所有需要的包</h2><p>对Python而言，迁移我们的依赖是非常容易的。<br>pip freeze &gt; requirements.txt将包依赖信息保存，而pip install -r requirements.txt会自动从网上下载并安装所有包。<br>在你Application的本地的virtualenv环境中生成requirements.txt，然后在remote的virtualenv环境中安装它们。</p>
<blockquote>
<p>注意：保证对每个应用的环境是干净的。</p>
</blockquote>
<h2 id="配置uwsgi">配置uwsgi</h2><p>先安装uwsgi:<br><figure class="highlight cmake"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ pip <span class="keyword">install</span> uwsgi</span><br></pre></td></tr></table></figure></p>
<p>在应用目录中新建config.ini,内容如下:<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">[uwsgi]</span><br><span class="line"></span><br><span class="line"><span class="preprocessor"># uwsgi 启动时所使用的地址与端口</span></span><br><span class="line">socket = <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">8001</span></span><br><span class="line"></span><br><span class="line"><span class="preprocessor"># python 启动程序文件</span></span><br><span class="line">wsgi-file = manage.py</span><br><span class="line"></span><br><span class="line"><span class="preprocessor"># python 程序内用以启动的 application 变量名</span></span><br><span class="line">callable = app</span><br><span class="line"></span><br><span class="line"><span class="preprocessor"># 处理器数</span></span><br><span class="line">processes = <span class="number">4</span></span><br><span class="line"></span><br><span class="line"><span class="preprocessor"># 线程数</span></span><br><span class="line">threads = <span class="number">2</span></span><br><span class="line"></span><br><span class="line"><span class="preprocessor"># 状态检测地址</span></span><br><span class="line">stats = <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span>:<span class="number">9191</span></span><br><span class="line"></span><br><span class="line"><span class="preprocessor"># uwsgi 使用的协议</span></span><br><span class="line">protocol=http</span><br></pre></td></tr></table></figure></p>
<p>一些选项(如处理器数、线程数)不是必要的，更多选项可以参考<a href="https://uwsgi-docs.readthedocs.org/en/latest/" target="_blank" rel="external">uwsgi文档</a>。<br>测试一下是否成功:<br><figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$ </span>uwsgi config.ini</span><br></pre></td></tr></table></figure></p>
<h2 id="配置supervisor">配置supervisor</h2><p>Supervisor是一个进程管理工具。这里使用它进行uwsgi进程的管理，以实现后台运行、自动重启。<br>在Ubuntu下可以直接通过下面的命令安装Supervisor:<br><figure class="highlight vala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor"># apt-get install supervisor</span></span><br></pre></td></tr></table></figure></p>
<p>安装好后，只需要在/etc/supervisor/conf.d/ 新建一个配置文件，就可以增加一个Supervisor进程。<br>例如，我们的应用目录在主目录下,名为flask-app。新建一个flask-app.conf，内容如下:<br><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="title">[program:flask-app]</span></span><br><span class="line"><span class="comment"># 启动命令入口</span></span><br><span class="line"><span class="setting">command= <span class="value">/home/your-username/flask-app/venv/bin/uwsgi /home/mocimy/flask-app/config.ini</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 命令程序所在目录</span></span><br><span class="line"><span class="setting">directory=<span class="value">/home/your-username/flask-app</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 运行命令的用户名</span></span><br><span class="line"><span class="setting">user=<span class="value">your-username</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 自动启动</span></span><br><span class="line"><span class="setting">autostart=<span class="value"><span class="keyword">true</span></span></span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 自动重启</span></span><br><span class="line"><span class="setting">autorestart=<span class="value"><span class="keyword">true</span></span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">#日志地址</span></span><br><span class="line"><span class="setting">stdout_logfile=<span class="value">/log/uwsgi_supervisor.log</span></span></span><br></pre></td></tr></table></figure></p>
<p>快使用supervisor启动我们的应用看看吧!<br><figure class="highlight vala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor"># service supervisor restart</span></span><br><span class="line"><span class="preprocessor"># supervisorctl   #进程管理</span></span><br></pre></td></tr></table></figure></p>
<p>supervisorctl可以管理/etc/supervisor/conf.d 下配置好的所有进程。<br>start  启动进程<br>stop   终止进程<br>status 查看进程状态</p>
<h2 id="配置nginx">配置nginx</h2><p>为什么还要使用nginx？可以看这里:<a href="https://serverfault.com/questions/590819/why-do-i-need-nginx-when-i-have-uwsgi" target="_blank" rel="external">https://serverfault.com/questions/590819/why-do-i-need-nginx-when-i-have-uwsgi</a><br>简单来说nginx更安全、帮助我们更好地处理静态资源。<br><figure class="highlight vala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor"># apt-get install nginx   #安装</span></span><br></pre></td></tr></table></figure></p>
<p>推荐修改/ext/nginx/sites-available/default文件，不需要改动/etc/nginx/nginx.conf<br>修改server配置如下:<br><figure class="highlight dust"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="xml">server </span><span class="expression">&#123;</span><br><span class="line">        <span class="variable">listen</span>       80;</span><br><span class="line">        <span class="variable">server</span>_<span class="variable">name</span>  <span class="variable">localhost</span>; #填写公网<span class="variable">IP</span>/域名</span><br><span class="line"></span><br><span class="line">        <span class="variable">location</span> <span class="end-block">/ </span>&#123;            </span><br><span class="line">            <span class="variable">include</span>  <span class="variable">uwsgi</span>_<span class="variable">params</span>;</span><br><span class="line">            <span class="variable">uwsgi</span>_<span class="variable">pass</span>  127<span class="variable">.</span>0<span class="variable">.</span>0<span class="variable">.</span>1:8001;              //和<span class="variable">uwsgi</span>中的设置保存一致</span><br><span class="line">            <span class="variable">uwsgi</span>_<span class="variable">param</span> <span class="variable">UWSGI</span>_<span class="variable">PYHOME</span> <span class="end-block">/home</span><span class="end-block">/your-username</span><span class="end-block">/flask-app</span><span class="end-block">/venv</span>; <span class="begin-block"># </span>指向虚拟环境目录</span><br><span class="line">            <span class="variable">uwsgi</span>_<span class="variable">param</span> <span class="variable">UWSGI</span>_<span class="variable">CHDIR</span>  <span class="end-block">/home</span><span class="end-block">/your-username</span><span class="end-block">/flask-app</span>; <span class="begin-block"># </span>指向网站根目录</span><br><span class="line">            <span class="variable">uwsgi</span>_<span class="variable">param</span> <span class="variable">UWSGI</span>_<span class="variable">SCRIPT</span> <span class="variable">manage</span>:<span class="variable">app</span>; <span class="begin-block"># </span>指定启动程序</span><br><span class="line">        &#125;</span><span class="xml"></span><br><span class="line">    &#125;</span></span><br></pre></td></tr></table></figure></p>
<p>重启nginx:<br><figure class="highlight vala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor"># service nginx restart</span></span><br></pre></td></tr></table></figure></p>
<p>大功告成，快打开浏览器查看一下吧！</p>
<h1 id="总结">总结</h1><p>许多教程给出的解决方案由于版本不同、环境配置不同、搭建需求不同等等原因不能照搬到实际部署中。查阅官方文档、总结经验、反复调试是解决问题的最佳途径。Python的Server搭建相对PHP来说更困难，但也有着其独有的优势，值得尝试。</p>
]]></content>
    <summary type="html">
    <![CDATA[<h1 id="部署方案">部署方案</h1><p>任何的部署方案都不应该脱离实际需求。Flask+uWSGI+Nginx 可以应对那些高并发、高性能的页面需求。<br>如果你需要：</p>
<ul>
<li>轻量、可扩展的Web程序</li>
<li>易于使用的语法</li>
<li>适于高连接并发的情况</li>
<li>高性能、低占用</li>
<li>多app管理</li>
<li>高度可定制</li>
</ul>
<p>这样的Web应用，那么Flask+uWSGI+Nginx 是你应该考虑的方案。</p>
<h3 id="Flask">Flask</h3><p>Flask是一个使用 Python 编写的轻量级 Web 应用框架。</p>
<h3 id="uWSGI">uWSGI</h3><p>The uWSGI project aims at developing a full stack for building hosting services.</p>
<h3 id="Nginx">Nginx</h3><p>Nginx 是一个高性能的HTTP和 反向代理 服务器 (这里主要用于处理静态文件)。</p>]]>
    
    </summary>
    
      <category term="Linux" scheme="https://mocimy.github.io/tags/Linux/"/>
    
      <category term="Python" scheme="https://mocimy.github.io/tags/Python/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[Linux下配置8086汇编环境]]></title>
    <link href="https://mocimy.github.io/2015/11/09/Linux%E4%B8%8B%E9%85%8D%E7%BD%AE8086%E6%B1%87%E7%BC%96%E7%8E%AF%E5%A2%83/"/>
    <id>https://mocimy.github.io/2015/11/09/Linux下配置8086汇编环境/</id>
    <published>2015-11-09T05:19:27.000Z</published>
    <updated>2016-03-22T03:37:29.586Z</updated>
    <content type="html"><![CDATA[<h1 id="准备">准备</h1><p>编写8086汇编程序需要Dos环境，我在Linux下使用了dosemu工具模拟dos环境。<br>MASM是微软开发的汇编环境，这里使用了MASM 5.0版本。</p>
<h1 id="安装">安装</h1><p>dosemu可以通过发行版自带的包管理工具安装。在Archlinux下：<br><figure class="highlight vala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor"># pacman -S dosemu</span></span><br></pre></td></tr></table></figure></p>
<p>运行一下dosemu：<br><figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$ </span>dosemu</span><br></pre></td></tr></table></figure></p>
<p>此时还不能编译汇编程序。需要我们将下载到的MASM.exe LINK.exe拷贝到dosemu的工作环境下：<br><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># <span class="keyword">cp</span> MASM.<span class="keyword">exe</span> LINK.<span class="keyword">exe</span> ~/.dosemu/drives/<span class="keyword">d</span>/bin/</span><br></pre></td></tr></table></figure></p>
<a id="more"></a>
<h1 id="编译">编译</h1><p>再次执行dosemu,可以执行MASM编译代码。<br><figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">MASM test_1<span class="class">.asm</span></span><br><span class="line">LINK test_1<span class="class">.obj</span></span><br><span class="line">test_1.exe</span><br></pre></td></tr></table></figure></p>
<p>效果如下：<br><img src="/images/test_1.png" alt="效果图"></p>
]]></content>
    <summary type="html">
    <![CDATA[<h1 id="准备">准备</h1><p>编写8086汇编程序需要Dos环境，我在Linux下使用了dosemu工具模拟dos环境。<br>MASM是微软开发的汇编环境，这里使用了MASM 5.0版本。</p>
<h1 id="安装">安装</h1><p>dosemu可以通过发行版自带的包管理工具安装。在Archlinux下：<br><figure class="highlight vala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor"># pacman -S dosemu</span></span><br></pre></td></tr></table></figure></p>
<p>运行一下dosemu：<br><figure class="highlight crystal"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$ </span>dosemu</span><br></pre></td></tr></table></figure></p>
<p>此时还不能编译汇编程序。需要我们将下载到的MASM.exe LINK.exe拷贝到dosemu的工作环境下：<br><figure class="highlight vim"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># <span class="keyword">cp</span> MASM.<span class="keyword">exe</span> LINK.<span class="keyword">exe</span> ~/.dosemu/drives/<span class="keyword">d</span>/bin/</span><br></pre></td></tr></table></figure></p>]]>
    
    </summary>
    
  </entry>
  
  <entry>
    <title><![CDATA[PCL点云处理实践(三):点云的曲面重建、GUI显示]]></title>
    <link href="https://mocimy.github.io/2015/10/11/PCL%E7%82%B9%E4%BA%91%E5%A4%84%E7%90%86%E5%AE%9E%E8%B7%B5-%E4%B8%89-%E7%82%B9%E4%BA%91%E7%9A%84%E6%9B%B2%E9%9D%A2%E9%87%8D%E5%BB%BA%E3%80%81GUI%E6%98%BE%E7%A4%BA/"/>
    <id>https://mocimy.github.io/2015/10/11/PCL点云处理实践-三-点云的曲面重建、GUI显示/</id>
    <published>2015-10-11T03:49:51.000Z</published>
    <updated>2016-03-22T05:52:43.368Z</updated>
    <content type="html"><![CDATA[<h1 id="曲面重建">曲面重建</h1><p>曲面重建可以为点云构建光滑的表面。这里介绍快速三角化方法和Poisson方法。</p>
<h2 id="快速三角化">快速三角化</h2><p>贪婪投影三角化算法的步骤是先将有向点云投影到某一局部二维坐标平面内，再在坐标平面内进行平面内的三角化，再根据平面内三位点的拓扑连接关系获得一个三角网格曲面模型。</p>
<h2 id="Poisson算法">Poisson算法</h2><p>Poisson算法可以进行闭包点云的曲面重建。关于Poisson算法的具体实现，可以参见<a href="https://github.com/atduskgreg/pcl-poisson-example" target="_blank" rel="external">这里</a>。</p>
<a id="more"></a>
<h1 id="GUI显示">GUI显示</h1><p>Qt是跨平台C++图形用户界面应用程序开发框架。PCL包含了对Qt4、Qt5的支持。因此如果我们有构建图形化程序的需求的话，不妨使用Qt。</p>
<h2 id="编译PCL的Qt组件">编译PCL的Qt组件</h2><p>PCL的Qt组件是QVTKWidget。不同版本的Qt对应的组件是不同的，因此应仔细查看PCL的编译选项。</p>
<h2 id="使用方法">使用方法</h2><p>PCL在Qt Designer中提供了一个QVTKWidget组件，我们可以使用它来将点云显示窗口嵌入我们的Qt GUI程序中。<br>在Designer中添加QVTKWidget(这里为qvtkWidget)后,通过以下代码来显示点云。<br><figure class="highlight lasso"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">boost<span class="tag">::shared_ptr</span>&lt;pcl<span class="tag">::visualization</span><span class="tag">::PCLVisualizer</span>&gt; viewer;</span><br><span class="line">pcl<span class="tag">::PointCloud</span>&lt;PointT&gt;<span class="tag">::Ptr</span> cloud;</span><br><span class="line"></span><br><span class="line"><span class="comment">//此处读入点云数据...</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//关联viewer与qvtkWidget</span></span><br><span class="line">viewer<span class="built_in">.</span>reset (<span class="literal">new</span> pcl<span class="tag">::visualization</span><span class="tag">::PCLVisualizer</span> (<span class="string">"viewer"</span>, <span class="literal">false</span>));</span><br><span class="line">ui<span class="subst">-&gt;</span>qvtkWidget<span class="subst">-&gt;</span>SetRenderWindow (viewer<span class="subst">-&gt;</span>getRenderWindow ());</span><br><span class="line">viewer<span class="subst">-&gt;</span>setupInteract<span class="subst">or</span> (ui<span class="subst">-&gt;</span>qvtkWidget<span class="subst">-&gt;</span>GetInteract<span class="subst">or</span> (), ui<span class="subst">-&gt;</span>qvtkWidget<span class="subst">-&gt;</span>GetRenderWindow ());</span><br><span class="line"></span><br><span class="line"><span class="comment">//将cloud添加到viewer</span></span><br><span class="line">viewer<span class="subst">-&gt;</span>addPointCloud (cloud, <span class="string">"cloud"</span>);</span><br><span class="line">viewer<span class="subst">-&gt;</span>resetCamera ();</span><br><span class="line">ui<span class="subst">-&gt;</span>qvtkWidget<span class="subst">-&gt;</span>update ();</span><br></pre></td></tr></table></figure></p>
<p>下面是PCL官方提供的一个示例:<br><img src="http://pointclouds.org/documentation/tutorials/_images/pcl_visualizer.gif" alt="PCL Qt程序"><br>点击<a href="http://pointclouds.org/documentation/tutorials/qt_visualizer.php#qt-visualizer" target="_blank" rel="external">这里</a>查看这个示例的代码。</p>
<h1 id="点云测量">点云测量</h1><h2 id="周长测量">周长测量</h2><p>使用Graham算法可以测量周长。参考:<a href="https://github.com/kartikkukreja/blog-codes/blob/master/src/Graham%20Scan%20Convex%20Hull.cpp" target="_blank" rel="external">https://github.com/kartikkukreja/blog-codes/blob/master/src/Graham%20Scan%20Convex%20Hull.cpp</a></p>
]]></content>
    <summary type="html">
    <![CDATA[<h1 id="曲面重建">曲面重建</h1><p>曲面重建可以为点云构建光滑的表面。这里介绍快速三角化方法和Poisson方法。</p>
<h2 id="快速三角化">快速三角化</h2><p>贪婪投影三角化算法的步骤是先将有向点云投影到某一局部二维坐标平面内，再在坐标平面内进行平面内的三角化，再根据平面内三位点的拓扑连接关系获得一个三角网格曲面模型。</p>
<h2 id="Poisson算法">Poisson算法</h2><p>Poisson算法可以进行闭包点云的曲面重建。关于Poisson算法的具体实现，可以参见<a href="https://github.com/atduskgreg/pcl-poisson-example">这里</a>。</p>]]>
    
    </summary>
    
      <category term="C/C++" scheme="https://mocimy.github.io/tags/C-C/"/>
    
      <category term="PCL" scheme="https://mocimy.github.io/tags/PCL/"/>
    
      <category term="Qt" scheme="https://mocimy.github.io/tags/Qt/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[PCL点云处理实践(二):点云的处理和拼接]]></title>
    <link href="https://mocimy.github.io/2015/09/17/PCL%E7%82%B9%E4%BA%91%E5%A4%84%E7%90%86%E5%AE%9E%E8%B7%B5-%E4%BA%8C-%E7%82%B9%E4%BA%91%E7%9A%84%E5%A4%84%E7%90%86%E5%92%8C%E6%8B%BC%E6%8E%A5/"/>
    <id>https://mocimy.github.io/2015/09/17/PCL点云处理实践-二-点云的处理和拼接/</id>
    <published>2015-09-17T12:42:31.000Z</published>
    <updated>2016-03-27T05:33:57.287Z</updated>
    <content type="html"><![CDATA[<h1 id="点云处理">点云处理</h1><h2 id="滤除背景">滤除背景</h2><p>我们获得的点云可能包含一部分背景的点云。要去除背景，只保留人体信息，最简单的方式是使用直通滤波器滤除较远点。这部分代码如下：<br><figure class="highlight pf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">pcl::PassThrough<span class="variable">&lt;pcl::PointXYZ&gt;</span><span class="built_in">pass</span>;     //设置滤波器对象</span><br><span class="line"><span class="built_in">pass</span>.<span class="built_in">set</span>InputCloud(cloud);                //设置输入点云</span><br><span class="line"><span class="built_in">pass</span>.<span class="built_in">set</span>FilterFieldName(<span class="string">"z"</span>);             //设置过滤时所需要点云类型的z字段</span><br><span class="line"><span class="built_in">pass</span>.<span class="built_in">set</span>FilterLimits(<span class="number">0.0</span>,<span class="number">1.0</span>);           //设置在过滤字段上的范围</span><br><span class="line">//<span class="built_in">pass</span>.<span class="built_in">set</span>FilterLimitsNegative (true);     //设置保留范围内的还是过滤掉范围内的</span><br><span class="line"><span class="built_in">pass</span>.filter(*cloud_filtered);              //执行滤波，保存过滤结果在cloud_filtered</span><br></pre></td></tr></table></figure></p>
<blockquote>
<p>滤除背景也可以通过Kinect SDK实现，这部分可以参考SDK的“游戏者ID”。</p>
</blockquote>
<a id="more"></a>
<h2 id="移除离群点">移除离群点</h2><p>激光扫描通常会产生密度不均匀的点云数据集。另外，测量中的误差会产生稀疏的离群点，使效果更糟。因此对每个点的邻域进行一个统计分析，并修剪掉那些不符合一定标准的点。<br><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">pcl:</span><span class="symbol">:StatisticalOutlierRemoval&lt;pcl</span><span class="symbol">:</span><span class="symbol">:PointXYZ&gt;</span> sor;<span class="regexp">//</span> 创建滤波器对象</span><br><span class="line">sor.setInputCloud(cloud);                        <span class="regexp">//</span>设置呆滤波的点云</span><br><span class="line">sor.setMeanK(<span class="number">50</span>);                                <span class="regexp">//</span>设置在进行统计时考虑查询点邻近点数</span><br><span class="line">sor.setStddevMulThresh(<span class="number">1.0</span>);                    <span class="regexp">//</span>设置判断是否为离群点的阈值</span><br><span class="line">sor.filter(*cloud_filtered);                    <span class="regexp">//</span>执行滤波处理保存内点到cloud_filtered</span><br></pre></td></tr></table></figure></p>
<h2 id="下采样">下采样</h2><p>为什么要进行下采样？</p>
<p>Kinect直接得到的点云数据非常庞大，由于我们下一步要对其进行配准和拼接处理，如果不对点云进行适当精简，运算时间可能非常长。因此，要等效一个Point较少的点云，取代原始点云进行配准操作。</p>
<p>PCL实现的VoxelGrid类通过输入的点云数据创建一个三维体素栅格（可把体素栅格想象为微小的空间三维立方体的集合），然后在每个体素（即，三维立方体）内，用体素中所有点的重心来近似显示体素中其他点，这样该体素就内所有点就用一个重心点最终表示，对于所有体素处理后得到过滤后的点云。<br><figure class="highlight elixir"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="symbol">pcl:</span><span class="symbol">:VoxelGrid&lt;sensor_msgs</span><span class="symbol">:</span><span class="symbol">:PointCloud2&gt;sor</span>;  <span class="regexp">//</span>创建滤波对象</span><br><span class="line">sor.setInputCloud(cloud);                       <span class="regexp">//</span>设置需要过滤的点云给滤波对象</span><br><span class="line">sor.setLeafSize(<span class="number">0</span>.<span class="number">01</span>f,<span class="number">0</span>.<span class="number">01</span>f,<span class="number">0</span>.<span class="number">01</span>f);           <span class="regexp">//</span>设置滤波时创建的体素大小为<span class="number">1</span>cm立方体</span><br><span class="line">sor.filter(*cloud_filtered);                   <span class="regexp">//</span>执行滤波处理，存储输出cloud_filtered</span><br></pre></td></tr></table></figure></p>
<h1 id="点云拼接">点云拼接</h1><h2 id="配准">配准</h2><h3 id="什么是配准">什么是配准</h3><p>配准是将一个点云找到与另一个点云相对应的部分，并得到两个点云之间的转换矩阵。</p>
<p>配准之后,我们就可以将一个点云转换到另一个点云所在的坐标系内。在同一个坐标系内的点云可以进行拼接，形成一个更大的点云。</p>
<p>PCL内置了许多配准算法,例如迭代最近点对(ICP)算法，正态分布变换算法，随机一致采样(ransac)算法，等等。实际使用中，往往需要根据点云的特征选取合适的算法。这里使用了ransac算法。</p>
<h3 id="随机抽样一致性算法(RANSAC)">随机抽样一致性算法(RANSAC)</h3><p>Wiki : <a href="https://en.wikipedia.org/wiki/RANSAC" target="_blank" rel="external">https://en.wikipedia.org/wiki/RANSAC</a></p>
<h4 id="概述">概述</h4><p>RANSAC是“RANdom SAmple Consensus（随机抽样一致）”的缩写。它可以从一组包含“局外点”的观测数据集中，通过迭代方式估计数学模型的参数。<br>RANSAC通过反复选择数据中的一组随机子集来达成目标。被选取的子集被假设为局内点，并进行验证。<br><img src="/images/ransac.png" alt="示例"></p>
<h4 id="算法">算法</h4><p>RANSAC算法的输入是一组观测数据，一个可以解释或者适应于观测数据的参数化模型，一些可信的参数。<br>模型对应的是空间中一个点云数据到另外一个点云数据的旋转以及平移。<br>第一步随机选取点云中的一个点对，利用其不变特征（两点距离，两点法向量夹角）作为哈希表的索引值搜索另一个点云中的一个对应点对，然后计算得到旋转及平移的参数值。<br>用得到的变换模型去测试其它点，如果某个点适用于估计的模型，认为它也是局内点。<br>用所有假设的局内点去重新估计模型,重新计算旋转及平移的参数。<br>和上一个模型进行比较:是否有更多的局内点和更小的错误率。<br>然后迭代上述过程，直到找到最好的模型,或达到迭代次数。<br><img src="/images/ransac2.png" alt="示例2"></p>
<h4 id="优势和缺点">优势和缺点</h4><p>RANSAC的优点是它能从包含大量局外点的数据集中估计出高精度的参数。<br>RANSAC的缺点是它计算参数的迭代次数没有上限；如果设置迭代次数的上限，得到的结果可能不是最优的结果，甚至可能得到错误的结果。RANSAC只有一定的概率得到可信的模型，概率与迭代次数成正比。RANSAC的另一个缺点是它要求设置跟问题相关的阀值。</p>
<h3 id="如何提高配准的精确度">如何提高配准的精确度</h3><p>对于两个点云来说，提高精确度的方法是选取合适的算法、增加迭代次数、修改参数。</p>
<p>实际配准中，我们可能要连续配准多个点云。这样，在两个点云匹配中出现的误差可能被放大。尝试了以下几种思路：</p>
<blockquote>
<ul>
<li>第n个点云与第n+1个点云配准，得到转换矩阵。将第n-1个转换矩阵乘以这个转换矩阵，得到第n个转换矩阵。第n+1个点云乘以第n个转换矩阵，得到它投影到第1个点云所在坐标系的新点云。</li>
<li>上一方法的改进策略:同时由第1个点云向后出发，第n个点云向前出发进行配准，最终重合。</li>
<li>第n个点云与第n+1个点云配准，得到转换矩阵,并将第n+1个点云乘以转换矩阵，将得到的新点云替换第n+1个点云。</li>
<li>第n个点云与第n+1个点云配准，得到转换矩阵,并将第n+1个点云乘以转换矩阵，将得到的新点云拼接上第n个点云，然后替换第n+1个点云。</li>
</ul>
</blockquote>
<p>最终选择了第二种方法，它可以达到较好的效果。<br>配准部分的代码，根据采取的算法不同有所变化，建议从<a href="http://pointclouds.org/documentation/" target="_blank" rel="external">官方文档</a>参看这部分代码。</p>
<h1 id="最终效果">最终效果</h1><p>实际使用中，很难做到短时间高精度地配准出点云,对于一些情况，可能要进行有针对性的优化。这是测试中配准效果较好的一个:<br><img src="/images/pcl_res_1.png" alt="效果1"><br><img src="/images/pcl_res_2.png" alt="效果2"></p>
]]></content>
    <summary type="html">
    <![CDATA[<h1 id="点云处理">点云处理</h1><h2 id="滤除背景">滤除背景</h2><p>我们获得的点云可能包含一部分背景的点云。要去除背景，只保留人体信息，最简单的方式是使用直通滤波器滤除较远点。这部分代码如下：<br><figure class="highlight pf"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">pcl::PassThrough<span class="variable">&lt;pcl::PointXYZ&gt;</span><span class="built_in">pass</span>;     //设置滤波器对象</span><br><span class="line"><span class="built_in">pass</span>.<span class="built_in">set</span>InputCloud(cloud);                //设置输入点云</span><br><span class="line"><span class="built_in">pass</span>.<span class="built_in">set</span>FilterFieldName(<span class="string">"z"</span>);             //设置过滤时所需要点云类型的z字段</span><br><span class="line"><span class="built_in">pass</span>.<span class="built_in">set</span>FilterLimits(<span class="number">0.0</span>,<span class="number">1.0</span>);           //设置在过滤字段上的范围</span><br><span class="line">//<span class="built_in">pass</span>.<span class="built_in">set</span>FilterLimitsNegative (true);     //设置保留范围内的还是过滤掉范围内的</span><br><span class="line"><span class="built_in">pass</span>.filter(*cloud_filtered);              //执行滤波，保存过滤结果在cloud_filtered</span><br></pre></td></tr></table></figure></p>
<blockquote>
<p>滤除背景也可以通过Kinect SDK实现，这部分可以参考SDK的“游戏者ID”。</p>
</blockquote>]]>
    
    </summary>
    
      <category term="C/C++" scheme="https://mocimy.github.io/tags/C-C/"/>
    
      <category term="PCL" scheme="https://mocimy.github.io/tags/PCL/"/>
    
  </entry>
  
  <entry>
    <title><![CDATA[PCL点云处理实践:从kinect读取点云]]></title>
    <link href="https://mocimy.github.io/2015/09/14/PCL%E7%82%B9%E4%BA%91%E5%A4%84%E7%90%86%E5%AE%9E%E8%B7%B5-%E4%BB%8Ekinect%E8%AF%BB%E5%8F%96%E7%82%B9%E4%BA%91/"/>
    <id>https://mocimy.github.io/2015/09/14/PCL点云处理实践-从kinect读取点云/</id>
    <published>2015-09-14T13:56:57.000Z</published>
    <updated>2016-03-22T03:37:28.659Z</updated>
    <content type="html"><![CDATA[<h2 id="PCL介绍">PCL介绍</h2><p>Point Cloud Library (PCL) 是一个独立的大型的处理二维/三维图像和点云数据的开源工程。PCL在三维图像处理上的地位相当于opencv在二维图像处理的地位。<br>更多信息可查看PCL官网：<a href="http://pointclouds.org/" target="_blank" rel="external">http://pointclouds.org/</a></p>
<h2 id="kinect介绍">kinect介绍</h2><p>Kinect是巨硬推出的一款体感外设。它可以帮助我们获取摄像头范围内的深度数据和彩色数据。</p>
<h1 id="安装">安装</h1><blockquote>
<p>对于kinect 1.0版本，推荐使用SensorKinect开源驱动。<br>对于kinect 2.0版本，不存在开源驱动，请使用Kinect SDK for Windows。</p>
</blockquote>
<p>这里使用Kinect SDK for Windows 2.0（以下简称sdk）。</p>
<h2 id="安装sdk">安装sdk</h2><p><a href="https://dev.windows.com/en-us/kinect" target="_blank" rel="external">https://dev.windows.com/en-us/kinect</a></p>
<a id="more"></a>
<h2 id="安装PCL">安装PCL</h2><p>推荐从源代码安装：<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git <span class="built_in">clone</span> https://github.com/PointCloudLibrary/pcl</span><br></pre></td></tr></table></figure></p>
<p>使用cmake针对所在的平台进行编译。<br>由于windows平台解决Library问题较为麻烦，也可以使用 <a href="http://unanancyowen.com/?p=1255&amp;lang=en" target="_blank" rel="external">All in one Installer</a>。</p>
<h2 id="下载KinectGrabber">下载KinectGrabber</h2><p>sdk本身提供了丰富的API，便于开发者实现各种功能。如手势识别、骨骼识别等。我们在这里仅使用它的基础功能——读取深度数据，并转换为我们需要的点云。</p>
<p><a href="https://github.com/UnaNancyOwen/KinectGrabber/tree/Kinect2Grabber/Sample/Sample" target="_blank" rel="external">KinectGrabber项目地址</a></p>
<h1 id="使用">使用</h1><h2 id="使用Kinect2Grabber">使用Kinect2Grabber</h2><p>修改Sample.cpp，将扫描的点云保存为PCD文件，以便于后续处理：<br><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor">#<span class="keyword">include</span> <span class="string">"stdafx.h"</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Disable Error C4996 that occur when using Boost.Signals2.</span></span><br><span class="line"><span class="preprocessor">#<span class="keyword">ifdef</span> _DEBUG</span></span><br><span class="line">	<span class="preprocessor">#<span class="keyword">define</span> _SCL_SECURE_NO_WARNINGS</span></span><br><span class="line"><span class="preprocessor">#<span class="keyword">endif</span></span></span><br><span class="line"></span><br><span class="line"><span class="preprocessor">#<span class="keyword">include</span> <span class="string">"kinect2_grabber.h"</span></span></span><br><span class="line"><span class="preprocessor">#<span class="keyword">include</span> <span class="string">&lt;pcl/visualization/cloud_viewer.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">int</span> _tmain( <span class="keyword">int</span> argc, _TCHAR* argv[] )</span><br><span class="line">&#123;</span><br><span class="line">	<span class="comment">// Create Cloud Viewer</span></span><br><span class="line">	pcl::visualization::<span class="function">CloudViewer <span class="title">viewer</span><span class="params">( <span class="string">"Point Cloud Viewer"</span> )</span></span>;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Callback Function to be called when Updating Data</span></span><br><span class="line">	boost::function&lt;<span class="keyword">void</span>( <span class="keyword">const</span> pcl::PointCloud&lt;pcl::PointXYZRGB&gt;::ConstPtr&amp; )&gt; function =</span><br><span class="line">		[&amp;viewer]( <span class="keyword">const</span> pcl::PointCloud&lt;pcl::PointXYZRGB&gt;::ConstPtr &amp;cloud )&#123;</span><br><span class="line">		<span class="keyword">if</span>( !viewer.wasStopped() )&#123;</span><br><span class="line">			viewer.showCloud( cloud );</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Create Kinect2Grabber</span></span><br><span class="line">	pcl::Grabber* grabber = <span class="keyword">new</span> pcl::Kinect2Grabber();</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Regist Callback Function</span></span><br><span class="line">	grabber-&gt;registerCallback( function );</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Start Retrieve Data</span></span><br><span class="line">	grabber-&gt;start();</span><br><span class="line"></span><br><span class="line">	<span class="keyword">while</span>( !viewer.wasStopped() )&#123;</span><br><span class="line">		<span class="comment">// Input Key ( Exit ESC key )</span></span><br><span class="line">		<span class="keyword">if</span>( GetKeyState( VK_ESCAPE ) &lt; <span class="number">0</span> )&#123;</span><br><span class="line">			<span class="keyword">break</span>;</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line"></span><br><span class="line">	<span class="comment">// Stop Retrieve Data</span></span><br><span class="line">	grabber-&gt;stop();</span><br><span class="line"></span><br><span class="line">	<span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<h2 id="预览效果">预览效果</h2><p>PCL自带的pcl_viewer工具可以帮助我们查看pcd文件。<br><figure class="highlight nimrod"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ pcl_viewer <span class="literal">result</span>.pcd</span><br></pre></td></tr></table></figure></p>
<p>拍摄的单侧点云效果如下:<br><img src="/images/pcl_viewer.png" alt="单侧点云"></p>
]]></content>
    <summary type="html">
    <![CDATA[<h2 id="PCL介绍">PCL介绍</h2><p>Point Cloud Library (PCL) 是一个独立的大型的处理二维/三维图像和点云数据的开源工程。PCL在三维图像处理上的地位相当于opencv在二维图像处理的地位。<br>更多信息可查看PCL官网：<a href="http://pointclouds.org/">http://pointclouds.org/</a></p>
<h2 id="kinect介绍">kinect介绍</h2><p>Kinect是巨硬推出的一款体感外设。它可以帮助我们获取摄像头范围内的深度数据和彩色数据。</p>
<h1 id="安装">安装</h1><blockquote>
<p>对于kinect 1.0版本，推荐使用SensorKinect开源驱动。<br>对于kinect 2.0版本，不存在开源驱动，请使用Kinect SDK for Windows。</p>
</blockquote>
<p>这里使用Kinect SDK for Windows 2.0（以下简称sdk）。</p>
<h2 id="安装sdk">安装sdk</h2><p><a href="https://dev.windows.com/en-us/kinect">https://dev.windows.com/en-us/kinect</a></p>]]>
    
    </summary>
    
      <category term="C/C++" scheme="https://mocimy.github.io/tags/C-C/"/>
    
      <category term="PCL" scheme="https://mocimy.github.io/tags/PCL/"/>
    
  </entry>
  
</feed>
