为您推荐:
精华内容
最热下载
问答
  • 310KB qq_36419553 2018-10-01 16:56:40
  • 3星
    748KB qq_15819023 2018-01-31 13:41:55
  • Tornado是一种异步的Python网络框架,源自FriendFeed。因为使用了非阻塞的网络IO,Tornado可以处理数十万的链接,非常适合需要长连接的应用程序。   传送门 下载4.0.2版本:tornado-4.0.2.tar.gz(发布日志...

    Tornado是一种异步的Python网络框架,源自FriendFeed。得利于对非阻塞的运用Tornado可以处理数以千计的连接,非常适合需要长连接的应用程序。

     

    传送门

     

    入门——HelloWorld

    让我们从一个简单的“Hello World”例程开始:

    import tornado.ioloop
    import tornado.web
    
    class MainHandler(tornado.web.RequestHandler):
        def get(self):
            self.write("Hello, world")
    
    application = tornado.web.Application([
        (r"/", MainHandler),
    ])
    
    if __name__ == "__main__":
        application.listen(8888)
        tornado.ioloop.IOLoop.instance().start()


    这个例程没有使用到Tornado很重要的特点——异步,想了解更多,请查看使用了异步的例程——简单聊天室

     

    安装

    • 自动安装

    pip install tornado

    Tornado已经列入了PyPi,你可以使用pip或者easy_install方便地安装它。注意,这种安装方式不会得到Tornado源码发布包里的示例程序(demo),如果希望得到示例程序,请参见下面的方法。

    • 手动安装

    tar xvzf tornado-4.0.2.tar.gz
    cd tornado-4.0.2
    python setup.py build
    sudo python setup.py install


    Tornado的源码是托管在GitHub上的。

     

    • 前提

    1. Tornado支持的版本包括:Python 2.62.73.23.33.4
    2. 在所有的版本上,需要certifi包;
    3. Python 2.X版本上,需要backports.ssl_match_hostname(如果使用pipeasy_install安装,这些依赖包都将被自动安装)
    4. 一些Tornado的功能可能还需要下面这些可选的依赖包:

    • unittest2,测试套件,Python 2.6版本需要安装,2.6以后的版本中就不用安装了。
    • concurrent.futuresTornado建议使用的线程池,其中包含ThreadedResolverPython 2.X版本需要安装,Python 3.X已经存在于标准库中了。
    • pycurl,包含以使用tornado.curl_httpclient,最低使用7.18.2版本,建议使用不低于7.21.2的版本。
    • Twisted,如果使用了tornado.platform.twisred,需包含;
    • pycares,可选的非阻塞DNS解析器,当使用线程不方便使用的时候可以考虑它;
    • Monotime,扩展单调时钟功能,如果经常要改变时钟,使用它可提高安全性。Python3.3版本中不再需要。

    •  运行环境

    Tornado可以运行在所有类Unix操作系统上,不过为了更好的性能表现和可扩展性,我们建议部署在Linux(使用了epoll)或者BSD(使用了kqueue)Mac OS虽然是BSD的衍生版本,但他的网络性能通常较差,部署在Mac OS不是我们建议的。Tornado同样可以运行于Windows系列的操作系统上,但这也不是我们建议的部署方案。

     译者:小怪博士,(大二计科系学生,通过翻译文档学习英语和技术,水平有限,如文中有误请谅解并指出,O(∩_∩)O谢谢大家)


    展开全文
    uutotos 2014-11-03 15:12:37
  • 本文为《Introduction to Tornado中文翻译,将在https://github.com/alioth310/itt2zh上面持续更新,本文内容可能不是最新状态,请在GitHub上获得最新版本。 本文也可在http://demo.pythoner.com/itt2zh上进行...

    http://www.pythoner.com/294.html

    本文为《Introduction to Tornado》中文翻译,将在https://github.com/alioth310/itt2zh上面持续更新,本文内容可能不是最新状态,请在GitHub上获得最新版本。

    本文也可在http://demo.pythoner.com/itt2zh上进行格式化的预览。

    第五章:异步Web服务

    到目前为止,我们已经看到了许多使Tornado成为一个Web应用强有力框架的功能。它的简单性、易用性和便捷性使其有足够的理由成为许多Web项目的不错的选择。然而,Tornado受到最多关注的功能是其异步取得和提供内容的能力,它有着很好的理由:它使得处理非阻塞请求更容易,最终导致更高效的处理以及更好的可扩展性。在本章中,我们将看到Tornado异步请求的基础,以及一些推送技术,这种技术可以使你使用更少的资源来提供更多的请求以编写更简单的Web应用。

    5.1 异步Web请求

    大部分Web应用(包括我们之前的例子)都是阻塞性质的,也就是说当一个请求被处理时,这个进程就会被挂起直至请求完成。在大多数情况下,Tornado处理的Web请求完成得足够快使得这个问题并不需要被关注。然而,对于那些需要一些时间来完成的操作(像大数据库的请求或外部API),这意味着应用程序被有效的锁定直至处理结束,很明显这在可扩展性上出现了问题。

    不过,Tornado给了我们更好的方法来处理这种情况。应用程序在等待第一个处理完成的过程中,让I/O循环打开以便服务于其他客户端,直到处理完成时启动一个请求并给予反馈,而不再是等待请求完成的过程中挂起进程。

    为了实现Tornado的异步功能,我们构建一个向Twotter搜索API发送HTTP请求的简单Web应用。这个Web应用有一个参数q作为查询字符串,并确定多久会出现一条符合搜索条件的推文被发布在Twitter上(”每秒推数”)。确定这个数值的方法非常粗糙,但足以达到例子的目的。图5-1展示了这个应用的界面。

    Figure5-1
    图5-1 异步HTTP示例:推率

    我们将展示这个应用的三个不同版本:首先,是一个使用同步HTTP请求的版本,然后是一个使用带有回调函数的Tornado异步HTTP客户端版本。最后,我们将展示如何使用Tornado 2.1版本新增的gen模块来使异步HTTP请求更加清晰和易实现。为了理解这些例子,你不需要成为关于Twitter搜索API的专家,但一定的熟悉不会有害。你可以在https://dev.twitter.com/docs/api/1/get/search阅读关于搜索API的开发者文档。

    5.1.1 从同步开始

    代码清单5-1包含我们的推率计算器的同步版本的代码。记住我们在顶部导入了Tornado的httpclient模块:我们将使用这个模块的HTTPClient类来执行HTTP请求。之后,我们将使用这个模块的AsyncHTTPClient。

     

     

    这个程序的结构现在对你而言应该已经很熟悉了:我们有一个RequestHandler类和一个处理到应用根路径请求的IndexHandler。在IndexHandler的get方法中,我们从查询字符串中抓取参数q,然后用它执行一个到Twitter搜索API的请求。下面是最相关的一部分代码:

     

     

    这里我们实例化了一个Tornado的HTTPClient类,然后调用结果对象的fetch方法。fetch方法的同步版本使用要获取的URL作为参数。这里,我们构建一个URL来抓取Twitter搜索API的相关搜索结果(rpp参数指定我们想获得搜索结果首页的100个推文,而result_type参数指定我们只想获得匹配搜索的最近推文)。fetch方法会返回一个HTTPResponse对象,其 body属性包含我们从远端URL获取的任何数据。Twitter将返回一个JSON格式的结果,所以我们可以使用Python的json模块来从结果中创建一个Python数据结构。

    tipfetch方法返回的HTTPResponse对象允许你访问HTTP响应的任何部分,不只是body。可以在官方文档[1]阅读更多相关信息。

    处理函数的其余部分关注的是计算每秒推文数。我们使用搜索结果中最旧推文与最新推文时间戳之差来确定搜索覆盖的时间,然后使用这个数值除以搜索取得的推文数来获得我们的最终结果。最后,我们编写了一个拥有这个结果的简单HTML页面给浏览器。

    5.1.2 阻塞的困扰

    到目前为止,我们已经编写了 一个请求Twitter API并向浏览器返回结果的简单Tornado应用。尽管应用程序本身响应相当快,但是向Twitter发送请求到获得返回的搜索数据之间有相当大的滞后。在同步(到目前为止,我们假定为单线程)应用,这意味着同时只能提供一个请求。所以,如果你的应用涉及一个2秒的API请求,你将每间隔一秒才能提供(最多!)一个请求。这并不是你所称的高可扩展性应用,即便扩展到多线程和/或多服务器 。

    为了更具体的看出这个问题,我们对刚编写的例子进行基准测试。你可以使用任何基准测试工具来验证这个应用的性能,不过在这个例子中我们使用优秀的Siege utility工具进行测试。它可以这样使用:

     

     

    在这个例子中,Siege对我们的应用在10秒内执行大约10个并发请求,输出结果如图5-2所示。我们可以很容易看出,这里的问题是无论每个请求自身返回多么快,API往返都会以至于产生足够大的滞后,因为进程直到请求完成并且数据被处理前都一直处于强制挂起状态。当一两个请求时这还不是一个问题,但达到100个(甚至10个)用户时,这意味着整体变慢。

    Figure5-2
    图5-2 同步推率获取

    此时,不到10秒时间10个相似用户的平均响应时间达到了1.99秒,共计29次。请记住,这个例子只提供了一个非常简单的网页。如果你要添加其他Web服务或数据库的调用的话,结果会更糟糕。这种代码如果被 用到网站上,即便是中等强度的流量都会导致请求增长缓慢,甚至发生超时或失败。

    5.1.3 基础异步调用

    幸运的是,Tornado包含一个AsyncHTTPClient类,可以执行异步HTTP请求。它和代码清单5-1的同步客户端实现有一定的相似性,除了一些我们将要讨论的重要区别。代码清单5-2是其源代码。

     

     

    AsyncHTTPClient的fetch方法并不返回调用的结果。取而代之的是它指定了一个callback参数;你指定的方法或函数将在HTTP请求完成时被调用,并使用HTTPResponse作为其参数。

     

     

    在这个例子中,我们指定on_response方法作为回调函数。我们之前使用期望的输出转化Twitter搜索API请求到网页中的所有逻辑被搬到了on_response函数中。还需要注意的是@tornado.web.asynchronous装饰器的使用(在get方法的定义之前)以及在回调方法结尾处调用的self.finish()。我们稍后将简要的讨论他们的细节。

    这个版本的应用拥有和之前同步版本相同的外观,但其性能更加优越。有多好呢?让我们看看基准测试的结果吧。

    正如你在图5-3中所看到的,我们从同步版本的每秒3.20个事务提升到了12.59,在相同的时间内总共提供了118次请求。这真是一个非常大的改善!正如你所想象的,当扩展到更多用户和更长时间时,它将能够提供更多连接,并且不会遇到同步版本遭受的变慢的问题。

    Figure5-3
    图5-3 异步推率获取

    5.1.4 异步装饰器和finish方法

    Tornado默认在函数处理返回时关闭客户端的连接。在通常情况下,这正是你想要的。但是当我们处理一个需要回调函数的异步请求时,我们需要连接保持开启状态直到回调函数执行完毕。你可以在你想改变其行为的方法上面使用@tornado.web.asynchronous装饰器来告诉Tornado保持连接开启,正如我们在异步版本的推率例子中IndexHandler的get方法中所做的。下面是相关的代码片段:

     

     

    记住当你使用@tornado.web.asynchonous装饰器时,Tornado永远不会自己关闭连接。你必须在你的RequestHandler对象中调用finish方法来显式地告诉Tornado关闭连接。(否则,请求将可能挂起,浏览器可能不会显示我们已经发送给客户端的数据。)在前面的异步示例中,我们在on_response函数的write后面调用了finish方法:

     

     

    5.1.5 异步生成器

    现在,我们的推率程序的异步版本运转的不错并且性能也很好。不幸的是,它有点麻烦:为了处理请求 ,我们不得不把我们的代码分割成两个不同的方法。当我们有两个或更多的异步请求要执行的时候,编码和维护都显得非常困难,每个都依赖于前面的调用:不久你就会发现自己调用了一个回调函数的回调函数的回调函数。下面就是一个构想出来的(但不是不可能的)例子:

     

     

    幸运的是,Tornado 2.1版本引入了tornado.gen模块,可以提供一个更整洁的方式来执行异步请求。代码清单5-3就是使用了tornado.gen版本的推率应用源代码。让我们先来看一下,然后讨论它是如何工作的。

     

     

    正如你所看到的,这个代码和前面两个版本的代码非常相似。主要的不同点是我们如何调用Asynchronous对象的fetch方法。下面是相关的代码部分:

     

     

    我们使用Python的yield关键字以及tornado.gen.Task对象的一个实例,将我们想要的调用和传给该调用函数的参数传递给那个函数。这里,yield的使用返回程序对Tornado的控制,允许在HTTP请求进行中执行其他任务。当HTTP请求完成时,RequestHandler方法在其停止的地方恢复。这种构建的美在于它在请求处理程序中返回HTTP响应,而不是回调函数中。因此,代码更易理解:所有请求相关的逻辑位于同一个位置。而HTTP请求依然是异步执行的,所以我们使用tornado.gen可以达到和使用回调函数的异步请求版本相同的性能,正如我们在图5-4中所看到的那样。

    Figure5-4
    图5-4 使用tornado.gen的异步推率获取

    记住@tornado.gen.engine装饰器的使用需要刚好在get方法的定义之前;这将提醒Tornado这个方法将使用tornado.gen.Task类。tornado.gen模块还哟一些其他类和函数可以方便Tornado的异步编程。查阅一下文档[1]是非常值得的。

    使一切异步

    在本章中我们使用了Tornado的异步HTTP客户端作为如何执行异步任务的实现。其他开发者也编写了针对其他任务的异步客户端库。志愿者们在Tornado wiki上维护了一个关于这些库的相当完整的列表。

    一个重要的例子是bit.ly的asyncmongo,它可以异步的调用MongoDB服务器。这个库是我们的一个非常不错的选择,因为它是专门给Tornado开发者开发提供异步数据库访问的,不过对于使用其他数据库的用户而言,在这里也可以找到不错的异步数据存储库的选择。

    5.1.6 异步操作总结

    正如我们在前面的例子中所看到的,Tornado异步Web发服务不仅容易实现也在实践中有着不容小觑的能力。使用异步处理可以让我们的应用在长时间的API和数据库请求中免受阻塞之苦,最终更快地提供更多请求。尽管不是所有的处理都能从异步中受益–并且实际上尝试整个程序非阻塞会迅速使事情变得复杂–但Tornado的非阻塞功能可以非常方便的创建依赖于缓慢查询或外部服务的Web应用。

    不过,值得注意的是,这些例子都非常的做作。如果你正在设计一个任何规模下带有该功能的应用,你可能希望客户端浏览器来执行Twitter搜索请求(使用JavaScript),而让Web服务器转向提供其他请求。在大多数情况下,你至少希望将结果缓存以便两次相同搜索项的请求不会导致再次向远程API执行完整请求。通常,如果你在后端执行HTTP请求提供网站内容,你可能希望重新思考如何建立你的应用。

    考虑到这一点,在下一组示例中,我们将看看如何在前端使用像JavaScript这样的工具处理异步应用,让客户端承担更多工作,以提高你应用的扩展性。

    5.2 使用Tornado进行长轮询

    Tornado异步架构的另一个优势是它能够轻松处理HTTP长轮询。这是一个处理实时更新的方法,它既可以应用到简单的数字标记通知,也可以实现复杂的多用户聊天室。

    部署提供实时更新的Web应用对于Web程序员而言是一项长期的挑战。更新用户状态、发送新消息提醒、或者任何一个需要在初始文档完成加载后由服务器向浏览器发送消息方法的全局活动。一个早期的方法是浏览器以一个固定的时间间隔向服务器轮询新请求。这项技术带来了新的挑战:轮询频率必须足够快以便通知是最新的,但又不能太频繁,当成百上千的客户端持续不断的打开新的连接会使HTTP请求面临严重的扩展性挑战。频繁的轮询使得Web服务器遭受”凌迟”之苦。

    所谓的”服务器推送”技术允许Web应用实时发布更新,同时保持合理的资源使用以及确保可预知的扩展。对于一个可行的服务器推送技术而言,它必须在现有的浏览器上表现良好。最流行的技术是让浏览器发起连接来模拟服务器推送更新。这种方式的HTTP连接被称为长轮询或Comet请求。

    长轮询意味着浏览器只需启动一个HTTP请求,其连接的服务器会有意保持开启。浏览器只需要等待更新可用时服务器”推送”响应。当服务器发送响应并关闭连接后,(或者浏览器端客户请求超时),客户端只需打开一个新的连接并等待下一个更新。

    本节将包括一个简单的HTTP长轮询实时应用以及证明Tornado架构如何使这些应用更简单。

    5.2.1 长轮询的好处

    HTTP长轮询的主要吸引力在于其极大地减少了Web服务器的负载。相对于客户端制造大量的短而频繁的请求(以及每次处理HTTP头部产生的开销),服务器端只有当其接收一个初始请求和再次发送响应时处理连接。大部分时间没有新的数据,连接也不会消耗任何处理器资源。

    浏览器兼容性是另一个巨大的好处。任何支持AJAX请求的浏览器都可以执行推送请求。不需要任何浏览器插件或其他附加组件。对比其他服务器端推送技术,HTTP长轮询最终成为了被广泛使用的少数几个可行方案之一。

    我们已经接触过长轮询的一些使用。实际上,前面提到的状态更新、消息通知以及聊天消息都是目前流行的网站功能。像Google Docs这样的站点使用长轮询同步协作,两个人可以同时编辑文档并看到对方的改变。Twitter使用长轮询指示浏览器在新状态更新可用时展示通知。Facebook使用这项技术在其聊天功能中。长轮询如此流行的一个原因是它改善了应用的用户体验:访客不再需要不断地刷新页面来获取最新的内容。

    5.2.2 示例:实时库存报告

    这个例子演示了一个根据多个购物者浏览器更新的零售商库存实时计数服务。这个应用提供一个带有”Add to Cart”按钮的HTML书籍细节页面,以及书籍剩余库存的计数。一个购物者将书籍添加到购物车之后,其他访问这个站点的访客可以立刻看到库存的减少。

    为了提供库存更新,我们需要编写一个在初始化处理方法调用后不会立即关闭HTTP连接的RequestHandler子类。我们使用Tornado内建的asynchronous装饰器完成这项工作,如代码清单5-4所示。

     

     

    让我们在看模板和脚本文件之前先详细看下shopping_cart.py。我们定义了一个ShoppingCart类来维护我们的库存中商品的数量,以及把商品加入购物车的购物者列表。然后,我们定义了DetailHandler用于渲染HTML;CartHandler用于提供操作购物车的接口;StatusHandler用于查询全局库存变化的通知。

    DetailHandler为每个页面请求产生一个唯一标识符,在每次请求时提供库存数量,并向浏览器渲染index.html模板。CartHandler为浏览器提供了一个API来请求从访客的购物车中添加或删除物品。浏览器中运行的JavaScript提交POST请求来操作访客的购物车。我们将在下面的StatusHandler和ShoppingCart类的讲解中看到这些方法是如何作用域库存数量查询的。

     

     

    关于StatusHandler首先需要注意的是get方法上面的@tornado.web.asynchronous装饰器。这使得Tornado在get方法返回时不会关闭连接。在这个方法中,我们只是注册了一个带有购物车控制器的回调函数。我们使用self.async_callback包住回调函数以确保回调函数中引发的异常不会使RequestHandler关闭连接。

    tip在Tornado 1.1之前的版本中,回调函数必须被包在self.async_callback()方法中来捕获被包住的函数可能会产生的异常。不过,在Tornado 1.1或更新版本中,这不再是显式必须的了。

     

     

    每当访客操作购物车,ShoppingCart控制器为每个已注册的回调函数调用on_message方法。这个方法将当前库存数量写入客户端并关闭连接。(如果服务器不关闭连接的话,浏览器可能不会知道请求已经被完成,也不会通知脚本有过更新。)既然长轮询连接已经关闭,购物车控制器必须删除已注册的回调函数列表中的回调函数。在这个例子中,我们只需要将回调函数列表替换为一个新的空列表。在请求处理中被调用并完成后删除已注册的回调函数十分重要,因为随后在调用回调函数时将在之前已关闭的连接上调用finish(),这会产生一个错误。

    最后,ShoppingCart控制器管理库存分批和状态回调。StatusHandler通过register方法注册回调函数,即添加这个方法到内部的callbacks数组。

     

     

    此外,ShoppingCart控制器还实现了CartHandler中的addItemToCart和removeItemFromCart。当CartHandler调用这些方法,请求页面的唯一标识符(传给这些方法的session变量)被用于在调用notifyCallbacks之前标记库存。[2]

     

     

    已注册的回调函数被以当前可用库存数量调用,并且回调函数列表被清空以确保回调函数不会在一个已经关闭的连接上调用。

    代码清单5-5是展示书籍列表变化的模板。

     

     

    当DetailHandler渲染index.html模板时,我们只是渲染了图书的详细信息并包含了必需的的JavaScript代码。此外,我们通过session变量动态地包含了一个唯一ID,并以count变量保存当前库存值。

    最后,我们将讨论客户端的JavaScript代码。由于这是一本关于Tornado的书籍,因此我们直到现在一直使用的是Python,而这个例子中的客户端代码是至关重要的,我们至少要能够理解它的要点。在代码清单5-6中,我们使用了jQuery库来协助定义浏览器的页面行为。

     

     

    当文档完成加载时,我们为”Add to Cart”按钮添加了点击事件处理函数,并隐藏了”Remove form Cart”按钮。这些事件处理函数关联服务器的API调用,并交换添加到购物车接口和从购物车移除接口。

     

     

    requestInventory函数在页面完成加载后经过一个短暂的延迟再进行调用。在函数主体中,我们通过到/cart/status的HTTP GET请求初始化一个长轮询。延迟允许在浏览器完成渲染页面时使加载进度指示器完成,并防止Esc键或停止按钮中断长轮询请求。当请求成功返回时,count的内容更新为当前的库存量。图5-5所示为展示全部库存的两个浏览器窗口。

    Figure5-5
    图5-5 长轮询示例:全部库存

    现在,当你运行服务器,你将可以加载根URL并看到书籍的当前库存数量。打开多个细节页的浏览器窗口,并在其中一个窗口点击”Add to Cart”按钮。其余窗口的剩余库存数量会立刻更新,如果5-6所示。

    Figure5-6
    图5-6 长轮询示例:一个物品在购物车中

    这是一个非常简单的购物车实现,可以肯定的是–没有逻辑确保我们不会跌破总库存量,更不用说数据无法在Tornado应用的不同调用间或同一服务器并行的应用实例间保留。我们将这些改善作为练习留给读者。

    5.2.3 长轮询的缺陷

    正如我们所看到的,HTTP长轮询在站点或特定用户状态的高度交互反馈通信中非常有用。但我们也应该知道它的一些缺陷。

    当使用长轮询开发应用时,记住对于浏览器请求超时间隔无法控制是非常重要的。由浏览器决定在任何中断情况下重新开启HTTP连接。另一个潜在的问题是许多浏览器限制了对于打开的特定主机的并发请求数量。当有一个连接保持空闲时,剩下的用来下载网站内容的请求数量就会有限制。

    此外,你还应该明白请求是怎样影响服务器性能的。再次考虑购物车应用。由于在库存变化时所有的推送请求同时应答和关闭,使得在浏览器重新建立连接时服务器受到了新请求的猛烈冲击。对于像用户间聊天或消息通知这样的应用而言,只有少数用户的连接会同时关闭,这就不再是一个问题了。

    5.3 Tornado与WebSockets

    WebSockets是HTML5规范中新提出的客户-服务器通讯协议。这个协议目前仍是草案,只有最新的一些浏览器可以支持它。但是,它的好处是显而易见的,随着支持它的浏览器越来越多,我们将看到它越来越流行。(和以往的Web开发一样,必须谨慎地坚持依赖可用的新功能并能在必要时回滚到旧技术的务实策略。)

    WebSocket协议提供了在客户端和服务器间持久连接的双向通信。协议本身使用新的ws://URL格式,但它是在标准HTTP上实现的。通过使用HTTP和HTTPS端口,它避免了从Web代理后的网络连接站点时引入的各种问题。HTML5规范不只描述了协议本身,还描述了使用WebSockets编写客户端代码所需要的浏览器API。

    由于WebSocket已经在一些最新的浏览器中被支持,并且Tornado为之提供了一些有用的模块,因此来看看如何使用WebSockets实现应用是非常值得的。

    5.3.1 Tornado的WebSocket模块

    Tornado在websocket模块中提供了一个WebSocketHandler类。这个类提供了和已连接的客户端通信的WebSocket事件和方法的钩子。当一个新的WebSocket连接打开时,open方法被调用,而on_message和on_close方法分别在连接接收到新的消息和客户端关闭时被调用。

    此外,WebSocketHandler类还提供了write_message方法用于向客户端发送消息,close方法用于关闭连接。

     

     

    正如你在我们的EchoHandler实现中所看到的,open方法只是使用WebSocketHandler基类提供的write_message方法向客户端发送字符串”connected!”。每次处理程序从客户端接收到一个新的消息时调用on_message方法,我们的实现中将客户端提供的消息原样返回给客户端。这就是全部!让我们通过一个完整的例子看看实现这个协议是如何简单的吧。

    5.3.2 示例:使用WebSockets的实时库存

    在本节中,我们可以看到把之前使用HTTP长轮询的例子更新为使用WebSockets是如何简单。但是,请记住,WebSockets还是一个新标准,只有最新的浏览器版本可以支持它。Tornado支持的特定版本的WebSocket协议版本只在Firefox 6.0或以上、Safari 5.0.1或以上、Chrome 6或以上、IE 10预览版或以上版本的浏览器中可用。

    不去管免责声明,让我们先看看源码吧。除了服务器应用需要在ShoppingCart和StatusHandler类中做一些修改外,大部分代码保持和之前一样。代码清单5-7看起来会很熟悉。

     

     

    除了额外的导入语句外,我们只需要改变ShoppingCart和StatusHandler类。首先需要注意的是,为了获得WebSocketHandler的功能,需要使用tornado.websocket模块。

    在ShoppingCart类中,我们只需要在通知回调函数的方式上做一个轻微的改变。因为WebSOckets在一个消息发送后保持打开状态,我们不需要在它们被通知后移除内部的回调函数列表。我们只需要迭代列表并调用带有当前库存量的回调函数:

     

     

    另一个改变是添加了unregisted方法。StatusHandler会在WebSocket连接关闭时调用该方法移除一个回调函数。

     

     

    大部分改变是在继承自tornado.websocket.WebSocketHandler的StatusHandler类中的。WebSocket处理函数实现了open和on_message方法,分别在连接打开和接收到消息时被调用,而不是为每个HTTP方法实现处理函数。此外,on_close方法在连接被远程主机关闭时被调用。

     

     

    在实现中,我们在一个新连接打开时使用ShoppingCart类注册了callback方法,并在连接关闭时注销了这个回调函数。因为我们依然使用了CartHandler类的HTTP API调用,因此不需要监听WebSocket连接中的新消息,所以on_message实现是空的。(我们覆写了on_message的默认实现以防止在我们接收消息时Tornado抛出NotImplementedError异常。)最后,callback方法在库存改变时向WebSocket连接写消息内容。

    这个版本的JavaScript代码和之前的非常相似。我们只需要改变其中的requestInventory函数。我们使用HTML5 WebSocket API取代长轮询资源的AJAX请求。参见代码清单5-8.

     

     

    在创建了一个到ws://localhost:8000/cart/status的心得WebSocket连接后,我们为每个希望响应的事件添加了处理函数。在这个例子中我们唯一关心的事件是onmessage,和之前版本的requestInventory函数一样更新count的内容。(轻微的不同是我们必须手工解析服务器送来的JSON对象。)

    就像前面的例子一样,在购物者添加书籍到购物车时库存量会实时更新。不同之处在于一个持久的WebSocket连接取代了每次长轮询更新中重新打开的HTTP请求。

    5.3.3 WebSockets的未来

    WebSocket协议目前仍是草案,在它完成时可能还会修改。然而,因为这个规范已经被提交到IETF进行最终审查,相对而言不太可能会再面临重大的改变。正如本节开头所提到的那样,WebSocket的主要缺陷是目前只支持最新的一些浏览器。

    尽管有上述警告,WebSockets仍然是在浏览器和服务器之间实现双向通信的一个有前途的新方法。当协议得到了广泛的支持后,我们将开始看到更加著名的应用的实现。

    —————————————————
    [1] 书中网页已不存在,替换为当前网址。
    [2] 下面的这组代码书中使用的不是前面的代码,这里为了保持一致修改为和前面的代码一样。

    ========================================================
    各章链接:
    《Introduction to Tornado》中文翻译计划——第一章:引言
    《Introduction to Tornado》中文翻译计划——第二章:表单和模板
    《Introduction to Tornado》中文翻译计划——第三章:模板扩展
    《Introduction to Tornado》中文翻译计划——第四章:数据库
    《Introduction to Tornado》中文翻译计划——第五章:异步Web服务
    《Introduction to Tornado》中文翻译计划——第六章:编写安全应用
    《Introduction to Tornado》中文翻译计划——第七章:外部服务认证
    《Introduction to Tornado》中文翻译计划——第八章:部署Tornado

    本文内容遵从CC3.0版权协议,转载请注明:转自Pythoner

    本文链接地址:《Introduction to Tornado》中文翻译计划——第五章:异步Web服务

    展开全文
    weixin_34138255 2019-01-08 07:27:08
  • Tornado中文版手册!!!

    Tornado 是一个Python web框架和异步网络库 起初由 FriendFeed 开发. 通过使用非阻塞网络I/O, Tornado 可以支持上万级的连接,处理 长连接WebSockets, 和其他 需要与每个用户保持长久连接的应用.

    下方链接已经翻译成中文版的使用文档

    https://tornado-zh.readthedocs.io/zh/latest/guide/intro.htmlicon-default.png?t=LBL2https://tornado-zh.readthedocs.io/zh/latest/guide/intro.html

     

    展开全文
    Java___orz 2022-01-04 18:28:05
  • Tornado官方文档翻译--持续更新**简介****异步和非阻塞I/O****阻塞****异步****例子****Coroutines****Python 3.5: async and await****工作原理****怎样调用协程****协程模式**回调交互(Interaction with callbacks...

    简介

    Tornado是一个Python的网络框架和异步网络库,最初由FriendFeed开发。通过使用非阻塞的网络I/O,Tornado可以吞吐数以万计的开放连接,这使它得以完美应用于长轮询和WebSockets网络协议,以及其他需要彼此进行长连接的应用。

    Tornado可以被大致分成四个主要部分:

    网络框架(包括作为基类的RequestHandler, RequestHandler用来创建网络应用,以及各种支持类)。
    执行HTTP协议(HTTPServer和AsyncHTTPClient)的客户端与服务器端。
    异步网络库,包括类IOLoop和IOStream,作为HTTP组件的构建模块,也可以被用来执行其他协议。
    协程库(tornado.gen),允许异步代码用一种比链式调用更直接的方式来写。
    

    Tornado网络框架和HTTP服务器一起提供了一个除了WSGI以外的其他选择。尽管可以在一个WSGI容器中使用Tornado网络框架,或者Tornado HTTP服务器作为其他WSGI框架的容器,但是这些组合每一个都有其局限,为了利用好Tornado,你需要将Tornado的网络框架和HTTP服务器结合起来使用。

    异步和非阻塞I/O

    实时的网络特点需要每个用户有一个长连接并且还要有足够的性能,在一个传统的异步网络服务器中,这意味着要为每一个用户分配一个线程,这样做代价是非常昂贵的。

    为了减小并发连接的代价,Tornado使用单线程的event loop。这意味着所有应用的代码都应该是异步和非阻塞的,因为一次只能有一个操作是活跃的。

    异步和非阻塞两个术语是紧密关联的,它们在使用的时候经常不加区分,但是它们并不是同一个概念。

    阻塞

    如果程序在return之前需要等待另一个事件发生,那么它就会阻塞。程序阻塞的原因很多:网络I/O,磁盘I/O, 锁等等。事实上,每个函数当它在运行和使用CPU的时候都会阻塞,至少也是一小部分(举一个极端的例子来论证为什么CPU阻塞必须和其他类型的阻塞一样要严肃对待,想一想密码哈希函数比如bcrypt,它故意占用几百毫秒的CPU事件,远超过一个典型的网络或磁盘访问事件)

    一个函数可能在一些方面是阻塞的而其他方面是非阻塞的。例如:tornado.httpclient在默认配置下会在DNS解析上阻塞,但是其他网络请求中不会阻塞(可以使用有正确配置libcurl的ThreadedResolver或tornado.curl_httpclient模块减轻这个影响)。

    异步

    异步函数在它结束之前return,并且在应用中,通常会在触发一些后续操作之前导致某些后台工作发生(这是相对于正常的异步函数来说,正常的异步函数会在return之前做完它们要做的所有事情)。有很多种不同的异步接口:

    参数回调(Callback argument)
    返回占位符(Future,Promise,Deferred)
    送入队列
    注册回调(Callback registry)
    

    不管使用何种类型的接口,从定义上来说这些异步函数和他们的调用者之间的交互是不同的;没有任何一种方式可以使一个异步函数用一种相对它的调用者来说透明的方式来实现异步(像gevent这样的系统使用轻量级的线程来提供和异步系统相近的性能,但是它们实际上并没有实现异步)。

    例子

    下面是一个异步函数的例子

    from tornado.httpclient import HTTPClient
    
    def synchronous_fetch(url):
    	http_client = HTTPClient()
    	response = http_client.fetch(url)
    	return response.body
    

    这里用参数调用方式重写这个函数变成异步:

    from tornado.httpclient import AsyncHTTPClient
    def asyncchronous_fetch(url, callback):
    	http_client = AsyncHTTPClient()
    	def handle_response(response):
    		callback(response.body)
    	http_client.fetch(url, callback=handle_response)
    

    再用Future改写:

    from tornado.concurrent import Future
    
    def async_fetch_future(url):
    	http_client = AsyncHTTPClient()
    	my_future = Future()
    	fetch_future = http_client.fetch(url)
    	fetch_future.add_done_callback(
    		lambda f: my_future.set_result(f.result())
    	)
    	return my_future
    

    原生的Future版本更加复杂,但是尽管如此仍然推荐在Tornado中进行练习Futures,因为它们有两个主要优势。错误处理更加连贯,因为Future.result方法可以简单抛出一个exception(相对于callback-oriented接口中普遍的ad-hoc错误处理来说),并且Futures非常适合用协程。多线程会再此手册的下一部分深入讨论。下面是我们的样例函数的协程版,和最初的异步版本非常相似。

    from tornado import gen
    
    @gen.coroutine
    def fetch_coroutine(url):
    	http_client = AsyncHTTPClient()
    	response = yield http_client.fetch(url)
    	raise gen.Return(response.body)
    

    声明raise gen.Return(response.body)是python2的一个神器,在这个里面生成器是不允许返回值的。为了克服这个问题,Tornado线程抛出一种特殊的异常称作Return。协程会捕捉到这个异常然后把它当成一个返回值来对待。在python3.3以及更高的版本中,return response.body也能获得同样的结果。

    Coroutines

    在Tornado中推荐使用协程的方式来写异步代码。协程使用Python的yield关键字来暂停和恢复执行而不是链式回调(类似gevent一样的框架中的合作轻量级线程有时候也被称为协程,但是在Tornado中所有的协程都使用明确的上下文转换,称为异步函数)

    协程几乎和异步代码一样简单,但是没有线程的代价。它们还通过减少上下文切换可能发生的位置的数量来使并发更容易推理。

    例子:

    from tornado import gen
    
    @gen.coroutine
    def fetch_coroutine(url):
    	http_client = AsyncHTTPClient()
    	response = yield http_client.fetch(url)
    	# In Python versions prior to 3.3,returing a value from
    	# a generator is not allowed and you must use 
    	# raise gen.Return(response.body)
    	#  instead
    	return response.body
    

    Python 3.5: async and await

    Python3.5引进了async和await关键字(使用这些关键字的函数也被称为原生协程(native coroutines))。从Tornado4.3开始,你可以使用它们来代替基于yield的协程(查看接下来的段落中它的局限)。很简单地使用async def foo()的函数定义来代替@gen.coroutine装饰器,然后await代替yield。为了兼容低版本的Python此文档的剩余部分仍然使用yield的方式,但是在能够使用的情况下async和await运行速度更快:

    async def fetch_coroutine(url):
    	http_client = AsyncHTTPClient()
    	response = await http_client.fetch(url)
    	return response.body
    

    await关键字没有yield功能多。例如,在一个基于yield的协程中,你可以yield一个Futures对象的列表,而在原生协程中则必须用tornado.gen.multi来打包这个列表。这也消除了与concurrent.futures的整合。你可以使用tornado.gen.convert_yielded把任何能用yield起作用的代码转换成await方式:

    async def f():
    	executor = concurrent.futures.ThreadPoolExecutor()
    	await tornado.gen.convert_yielded(executor.submit(g))
    
    	---g是任务函数(译者注)
    

    尽管原生协程并没有很明显的和某个特定框架绑定在一起(即:它们没有使用像tornado.gen.coroutine或asyncio.coroutine一样的装饰器),不是所有的协程都彼此兼容。有一个协程运行程序是被第一个调用的协程选中的,然后被所有通过await调用的协程所共享。Tornado的协程运行程序被设计成多用途,并且接受任何框架的可await的对象;其他协程可能可能会更加局限(例如,asyncio协程运行程序不接受来自其他框架的协程)。因此,在需要结合多个框架的的应用中推荐使用Tornado的协程运行程序。如果想要在一个已经使用asyncio运行程序的协程中调用Tornado的协程运行程序,使用tornado.platform.asyncio.to_asyncio_future适配器即可。

    工作原理

    包含yield的函数就是一个生成器。所有的生成器都是异步的;当被调用的时候它们会返回一个生成器对象,而不是运行结束。@gen.coroutine装饰器通过yield表达式和生成器沟通,通过返回一个Future对象来和协程调用者沟通。

    下面是一个简单版的协程装饰器内部循环:

    # Simplified inner loop of tornado.gen.Runner
    def run(self):
    	future = self.gen.send(self.next)
    	def callback(f):
    		self.next = f.result()
    		self.run()
    	future.add_done_callback(callback)
    

    装饰器从生成器中接受一个Future对象,等待(非阻塞)Future结束,然后解包Future对象,并且把结果返回给生成器作为yield表达式的结果。除了把异步函数返回的Future对象立即传递给yield表达式以外,大多数异步代码从不会直接触碰到Future类。

    怎样调用协程

    协程不会用正常的方式来抛出异常:它们抛出的任何异常在它被yield之前都会被困在FUture对象中。这意味着用正确的方式调用协程是非常重要的,否则你可能会忽略一些错误:

    @gen.coroutine
    def divide(x, y):
    	return x / y
    
    def bad_call():
    	# This should raise a ZeroDivisionError, but it won't bacause the coroutine is called incorrectly.
    	divide(1, 0)
    

    在几乎所有情形下,任何一个调用协程的函数本身必须也是协程,并且在调用中使用yield关键字。当你重写父类中定义的方法时,最好查阅文档看是否允许协程(文档应该说明此方法“是一个协程”或“返回Future对象”):

    @gen.coroutine
    def good_call():
    	# yield will unwrap the Future returned by divide() and raise
    	# the exception
    	yield divide(1, 0)
    

    有事你可能想不需要等待结果直接“Fire and forget” 一个协程。这种情况下推荐使用IOLoop.spawn_callback,这可以使IOLoop对调用负责。如果失败IOLoop会记录堆栈路径:

    # The IOLoop will catch the exception and print a stack trace in 
    # the logs.Note that this doesn't look like a normal call ,since
    # we pass the function object to be called by the IOLoop.
    IOLoop.current().spawn_callback(divide, 1, 0)
    

    在使用@gen.coroutine的函数中推荐用这种方式使用IOLoop.spawn_callback,但是它需要函数使用async def(否则线程运行程序不会开始)。

    最后,在项目的顶层,如果IOLoop还没有开始运行,你可以开始IOLoop,运行协程,然后用IOLoop.run_sync方法停止IOLoop。这经常被用来开始面向批量(batch-oriented)的项目的主函数:

    # run_sync() doesn't take arguments, so we must wrap the
    # call in a lambda.
    IOLoop.current().run_sync(lambda: divide(1, 0))
    

    协程模式

    回调交互(Interaction with callbacks)

    为了和使用回调而不是Future的异步代码交互,要在Task中打包此调用。这将会为你增加回调参数,并返回一个你可以yield的Future对象:

    @gen.coroutine
    def call_task():
    	# Note that there are no paterns on some_funxtion.
    	# This will be translated by Task into
    	#   some_function(other_args, callback=callback)
    	yield gen.Task(some_function, other_args)
    

    调用阻塞函数(Calling blocking functions)

    从协程中调用一个阻塞函数的最简单的方法就是使用ThreadPoolExecutor,返回一个其他协程兼容的Futures对象:

    thread_pool = ThreadPoolExecutor()
    
    @gen.coroutine
    def call_blocking():
    	yield thread_pool.submit(blocking_func, args)
    

    并行(Parallelism)

    协程装饰器能够识别列表和字典,因为它们的值是Futures对象,并且并行等待所有的这些Futures:

    @gen.coroutine
    def parallel_fetch(url1, url2):
    	resp1, resp2 = yield [http_client.fetch(url1),
    						  http_client.frtch(url2)]
    
    @gen.coroutine
    def parallel_fetch_many(urls):
    	reponse = yield [http_client.fetch(url) for url in urls]
    	# response is a list of HTTPResponse in the same order
    
    @gen.coroutine
    def parallel_fetch_dict(urls):
    	response = yield {url: http_client.fetch(url) for url in urls}
    	# response is a dict {url: HTTPResponse}
    

    交叉存取

    有时保存一个Future对象比立即yield它会更有用,以便你可以在等待之前开始开始另一个操作:

    @gen.coroutine
    def get(self):
        fetch_future = self.fetch_next_chunk()
        while True:
            chunk = yield fetch_future
            if chunk is None: break
            self.write(chunk)
            fetch_future = self.fetch_next_chunk()
            yield self.flush()
    

    这种模式用@gen.coroutine是最有用的。如果fetch_next_chunk()使用async def,那么为了开启后台进程,它一定会被当作fetch_future = tornado.gen.convert_yielded(self.fetch_next_chunk())来调用。

    循环

    对协程来说循环是很复杂的,原因是python中在for或者while循环中对每一次迭代都yield并且捕捉到yield的结果是不可能的。相反,你需要根据不同的结果区分循环条件,如下面的例子:

    import motor
    db = motor.MotorClient().test
    
    @gen.coroutine
    def loop_example(collection):
    	cursor = db.collection.find()
    	while (yield cursor.fetch_next):
    	doc = cursor.next_object()
    

    后台运行

    使用协程后PeriodicCallback便不能正常使用。相反,协程可以包含一个while True循环,然后使用tornado.gen.sleep:

    @gen.coroutine
    def minute_loop():
    	while True:
    		yield do_something()
    		yield gen.sleep(60)
    
    # Coroutines that loop forever are generally started with
    # spawn_callback().
    IOLoop.current().spawn_callback(minute_loop)
    

    有时一个更加复杂的循环可能是明智的。例如,之前的循环每60+N秒循环一次,N是do_something()的运行时间。为了精确地没60秒运行一次,使用上面的交叉存储方法:

    @gen.coroutine
    def minute_loop2():
    	while True:
    		nxt = gen.sleep(60)  # start the clock.
    		yield do_something() # Run while the clock is ticking.
    		yield nxt            # Wait for the timer to run out.
    

    Queue例子 - 并发网络爬虫

    Tornado的Tornado.queues模块为协程补充了一个生产者和消费者模式,类似Python标准库中的queue模块为线程补充的模型。

    一个yield Queue.get的协程直到队列中有元素之前都会暂停。如果队列设置了最大容量,一个yield Queue.put的协程在队列有空间之前都会暂停。

    队列中包含了许多未结束的任务,并且队列是从0开始的。put增加数量;task_done减少。

    在这个网络爬虫的例子中,队列开始只包含一个base_url。当worker获取到一个页面后,它会分析连接,然后把新的连接放进队列中,然后调用一次task_done来减少数量。最终,worker获取到一个页面,这个页面的URL在之前的页面中已经全部用过了,然后队列中就没有work剩余了。因此worker对task_done的调用会把计数器减少至0.正在等待join的主要协程会取消暂停然后结束。

    # !/usr/bin/env python
    
    import time
    from datetime import timedelta
    
    try:
    	from HTMLParser import HTMLParser
    	from urlparser import urljoin, urldefrag
    except ImportError:
    	from html.parser import HTMLParser
    	from urllib.parse import urljoin, urldefrag
    
    from tornado import httpclient, gen, ioloop, queues
    
    base_url = 'http://www.tornadoweb.org/en/stable/'
    concurrency = 10
    
    @gen.coroutine
    def get_links_from_url(url):
    	"""Download the page at `url` and parse it for links.
    
    	Returned links have had the fragment after `#` removed, and have been made
    	absolute so, e.g. the URL 'gen.html#tornado.gen.coroutine' becomes
    	'http://www.tornadoweb.org/en/stable/gen.html'.
    	"""
    	try:
    		response = yield httpclient.AsyncHTTPClient().fetch(url)
    		print('fetched %s' %url)
    
    		html = response.body if isinstance(response.body, str) \
    		    else response.body.decode()
    		urls = [urljoin(url, remove_fragment(new_url))
    				for new_url in get_links(html)]
    	except Exception as e:
    		print("Exception: %s %s" % (e, url))
    		raise gen.Return([])
    
    	raise gen.Return(urls)
    
    
    def remove_fragment(url):
    	pure_url, frag = urldefrag(url)
    	return pure_url
    
    
    def get_links(html):
    	class URLSeeker(HTMLParser):
    		def __init__(self):
    			HTMLPrser.__init__(self)
    			self.urls = []
    
    		def handle_starttag(self, tag, attrs):
    			href = dict(attrs).get("href")
    			if href and tag == 'a':
    				self.urls.append(href)
    
    		url_seeker = URLSeeker()
    		url_seeker.feed(html)
    		return url_seeker.urls
    
    
    @gen.coroutine
    def main():
    	q = queues.Queue()
    	start = time.time()
    	fetching, fetched = set(), set()
    
    	@gen.coroutine
    	def fetch_url():
    		current_url = yield q.get()
    		try:
    			if current_url in fetching:
    			return
    
    			print('fetching %s' % current_url)
    			fetched.add(current_url)
    
    			for new_url in urls:
    				# Only follow links beneath the base URL
    				if new_url.startwith(base_url):
    					yield q.put(new_url)
    
    		finally:
    			q.task_done()
    
    	@gen.coroutine
    	def worker():
    		while True:
    			yield fetch_url()
    
    	q.put(base_url)
    
    	# Start workers, then wait for the worker queue to be empty.
    	for _ in rage(concurrency):
    		worker()
    	yield q.join(timeout=timedalta(seconds=300))
    	assert fetching == fetched
    	print('Done in %d seconds, fetched %s URLs.' %(
    	time.time() - start, len(fetched)))
    
    
    if __name__ == "__main__":
    	import logging
    	logging.basicConfig()
    	io_loop = ioloop.IOLoop.current()
    	io_loop.run_sync(main)
    

    一个Tornado网络应用的结构

    一个Tornado网络应用通常是由一个或多个RequestHandler的子类组成的,Application将接收到的请求传递给处理器(handler),然后main()函数开启服务。

    下面是一个最简单的"hello world"例子:

    import tornado.ioloop
    improt tornado.web
    
    class MainHandler(tornado.web.RequestHandler):
    	def get(self):
    		self.write("Hello world")
    
    def make_app():
    	return tornado.web.Application([
    		(r"/", MainHandler),
    	])
    
    if __name__ == "__main__":
    	app = make_app()
    	app.listen(8888)
    	tornado.ioloop.IOLoop.current().start()
    

    Application对象

    Application对象负责全局配置,包括映射请求到处理器的路径表。

    路径表是一个URLSpec对象的列表(或元组),每一个都包含(至少)一个正则表达式和一个处理器类。顺序很重要;第一个匹配到的会起作用。如果正则表达式包含匹配分组,这些分组会作为路径参数传递给处理器的HTTP方法。如果一个字典作为URLSpec的第三方元素被传递进来,它支持initialization参数,这个参数会传递给RequestHandler.initializa方法。最后,URLSpec可能还会有一个名字,这将使它能够被RequestHandler.reverse_url方法使用。

    例如,在下面的代码段中,根路径/映射到MainHandler,/story/后跟一个数字类的URL会映射到StoryHandler。数字会作为字符串传递给StoryHandler.get方法。

    class MainHandler(RequestHandler):
    	def get(self):
    		self.write('<a href="%s">link to story 1</a>' %
    		self.reverse_url("story", "1"))
    
    class StoryHandler(RequestHandler):
    	def initialize(self, db):
    		self.db = db
    
    	def get(self, story_id):
    		self.write("this is story %s" % story_id)
    
    app = Application([
    	url(r"/", MainHandler),
    	url(r"/story/([0-9]+)", StoryHandler, dict(db=db), name="story")
    	])
    

    Application构造器接受许多能用来指定应用行为的关键字参数,并且允许可选特性;完整列表请参看Application.settings。

    基类化RequestHandler(Subclassing RequestHandler)

    Tornado网络应用的绝大部分工作都是在RequestHandler的子类中完成的。处理器子类的主要入口是HTTP方法:get(), post() 等等。每一个处理器可能会定义一个或多个这些方法来处理不同的HTTP动作。正如上面所描述的,这些方法将会加上路径中匹配到的参数来被调用。

    在一个处理器中,调用一些例如RequestHandler.render或者RequestHandler.write的方法来产生响应。render()方法通过名字加载了Template类,然后加上参数进行提交。write()用来在非模板环境下使用;它接受strings,bytes和dictionaries(字典必须被编码成json格式)。

    在RequestHandler中的很多方法都被设计为可在子类中重写,并且可以在整个应用中使用。定义一个BaseHandler,并重写一些方法例如write_error和get_current_user,然后把你的BaseHandler代替RequestHandler作为所有处理器的基类,这种做法是非常常见的。

    处理请求输入(Handling request input)

    请求处理器可以通过self.request来获取代表当前请求的对象。查看HTTPServerRequest的定义来获取完整的属性列表

    method, uri, path, query(The query portion of uri), 
    version(HTTP version specified in request,e.g. "HATTP/1.1"), headers, body, remote_ip, protocol, host等等,详细介绍请点击属性列表。
    
    ---译者注
    

    HTML表单提交的请求数据将会为你分析好,并且可以在一些方法像get_query_argument和get_body_argument中可用。

    class MyFormHandler(tornado.web.RequestHandler):
    	def get(self):
    		self.write('<html><body><form action="/myform" method="POST">'
                       '<input type="text" name="message">'
                       '<input type="submit" value="Submit">'
                       '</form></body></html>')
    					)
    
    	def post(self):
    		self.set_header("Content-Type", "text/plain")
    		self.write("You wrote " + self.get_body_argument("message"))
    

    因为HTML表单编码对于一个参数是单值还是列表是模糊不清的,RequestHandler对方法进行了区分,使得应用能够表明是否希望获得列表,对列表来说,使用get_query_arguments和get_body_arguments代替单值的方法。

    通过表单上传文件可以通过self.request.files来获得,映射名字(input的name属性)到文件列表。每个文件都是{“filename”:…, “content_type”:…, “body”:…}格式的字典。files对象文件仅在使用表单包装器上传文件时才存在(例如multipart/form-data Content-Type);如果没有使用这种格式,原生的上传数据会存放在self.request.boby中。默认情况下上传的文件是全部缓存在内存中的;如果你需要处理一些很大的文件,不方便放在内存中,查看stream_request_body装饰器。

    在demos文件夹中(tornado源码中有一个demos文件夹,存放了几个小的例子),file_reciver.py展示了这两种方法的来接收上传文件。

    由于HTML表单编码的问题(单值和多值的模糊性),Tornado并不打算用其他类型的输入来统一表单参数。尤其我们不会去分析JSON请求体。希望使用JSON来代替form-encoding的应用可能会重写prepare方法来解析它们的请求:

    def prepare(self):
    	if self.request.headers["Content-Type"].startwith("application/json"):
    		self.json_args = json.loads(self.request.body)
    	else:
    		self.json_args = None
    

    重写RequestHandler中的方法

    除了get和post方法外,在RequestHandler还有一些其他方法在必要时也可以被子类重写。在每个请求中,丢回发生以下一系列的调用:

    1.每个请求中都会新建一个RequestHandler对象
    2.如果有从Application配置中获得初始参数的话initialize()函数会被调用,initialize函数应该只保存传递给成员变量的参数;它可能不会产生任何输出或调用类似send_error一样的方法。
    3.调用prepare方法。这在一个基类中是最有用的,基类由你的处理器子类所共享,因为不论使用哪种HTTP方法prepare函数都会被调用。prepare可能会产生输出;如果它调用了finish(或者redirect方法等),进程就会在此结束。
    4.某一个HTTP方法被调用:get,post,put等等。如果URL正则表达式包含了捕捉分组参数(capturing groups),这些参数也会传递到此方法中。
    5.当请求结束时,on_finish会被调用,对异步处理器来说,这一步在get(或其他方法)return后就会立即发生;对异步处理器来说,它发生在调用finish之后。
    

    正如在RequestHandler文档中注释的一样,所有方法都是可以重写的。最常用扽一些可以被重写的方法包括:
    write_error -输出html的错误页面。
    on_connection_close -当客户端断开连接的时候调用;应用可能会选择检测这种情况然后停止更深层的处理,注意,不能保证一个已关闭的连接也能被及时检测到。
    get_current_user -查看User authentication
    get_user_locale 为当前用户返回一个locale对象
    set_default_headers -可能会被用来在响应中设置另外的响应头(例如一个custom Server响应头)

    错误处理

    如果处理器抛出一个异常,tornado会掉用RequestHandler.write_error来生成一个错误页面。tornado.web.HTTPError可以被用来产生一个特定的状态码;其他所有异常都返回500状态码。

    默认的错误页面包括一个堆栈路径(debug模式下)另外还有一个错误的线上描述(move brand_model.txt to project).为了生成一个自己的错误页面,重写RequestHandler.write_error(可能是在一个由你的所有处理器所共享的基类中)这个方法可以用过write和render等方法产生正常的输出。如果错误是由一个异常导致的,一个exc_info会作为一个关键字参数被传递进来(注意,此异常不保证是sys.exc_info中当前的异常,因此write_error必须使用traceback.format_exception来代替traceback.format_exc)。

    通过调用set_status,写一个响应或return等方法从常规的处理器方法(而不是write_error)中产生一个错误页面也是可能的。tornado.web.Finish这个特殊的异常可能会被抛出以终止处理器,在简单地返回不方便的情况下并不调用write_error方法。

    对于404错误来说,使用default_handler_class Application setting。这个处理器用改重写prepare方法,而不是更详细的如get方法,以便在任何HTTP方法中都能使用。它应该产生一个如上所述的错误页面:或者通过抛出一个HTTPError(404)并重写为write_error,或者调用self.set_status(404并在prepare()中直接产生响应。

    重定向

    在Tornado中有两种方式可以重定一个请求:RequestHandler.redirect和RedirectHandler。

    你可以在一个RequestHandler中使用self.redirect()。有一个可选的参数permanent,你可以使用它来表明该重定向永久的。permanent的默认值是False,可以生成一个302 Found的HTTP响应码,它适合于类似成功post请求后重定向用户这类情况。如果permanent是true,301 Moved PermanentlyHTTP响应码会被使用,在下面这中情况下是有用的:重定向到一个权威的URL来采用一种相对搜索引擎友好的方式获取页面。

    RedirectHandler可以让你在你的Application路由表中直接配置重定向。例如,配置一个静态重定向:

    app = tornado.web.Application([
    	url(r"/app", tornado.web.RedirectHandler,
    		dict(url="http://itunes.apple.com/my-app-id")),
    	
    	])
    

    下面的规则把所有以/pictures/开头的请求重定向到前缀是/photos/

    app = tornado.web.Application([
    	url(r"/photos/(.*)", MyPhotoHandler),
    	url(r"/pictures/(.*)", tornado.web.RedirectHandler, 
    		dict(url=r"/photos/{0}")),
    	])
    

    不像RequestHandler.redirect,RedirectHandler默认使用永久重定向。这是因为路由表在运行过程中不会改变并且是永久的,尽管在处理器中的重定向很可能是其他可能回改变的逻辑所导致的。如果想发起一个临时重定向,只需要把permanent=False参数加到RedirectHandler的初始化参数中。

    异步处理器

    Tornado的处理器默认是同步的:当get()/post()方法返回值的时候,请求会被问为结束,然后响应会被返回。因为在一个处理器运行的时候其他所有请求都会阻塞,所以任何长时间运行的处理器都应该做成异步方式以便用一种非阻塞的方式调用它的后续操作。这个话题在 Asynchronous and non-Blocking I/O章节中会重新讲解;这部分是关于异步技术在RequestHandler中的细节的。

    展开全文
    JunFeng666 2018-09-18 12:00:14
  • lin06051180 2017-06-20 10:12:04
  • qq_40965177 2019-08-25 22:21:27
  • lly1122334 2019-12-03 14:30:03
  • qq_37049781 2018-02-27 13:12:33
  • qq_33855133 2017-06-15 16:12:11
  • weixin_34080571 2016-03-28 20:15:47
  • weixin_33790053 2015-12-13 00:44:43
  • weixin_34104341 2016-03-28 14:41:10
  • weixin_34150830 2015-11-25 04:27:39
  • weixin_34291004 2016-01-16 08:48:29
  • weixin_28676289 2021-01-17 17:02:27
  • weixin_33982670 2016-03-28 10:03:54
  • weixin_33785972 2013-05-03 16:47:00
  • weixin_34319640 2018-02-28 10:28:00
  • weixin_33967071 2016-01-03 17:28:33
  • weixin_34419321 2016-01-16 23:02:43
  • brandon2015 2015-12-06 01:30:13
  • weixin_33807284 2016-01-27 23:41:26
  • qq_21127151 2020-03-25 08:38:45
  • monkeysheep1234 2019-04-19 18:23:55
  • sinat_15272875 2016-05-17 22:57:12
  • weixin_34112030 2016-01-03 02:31:10

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,023
精华内容 409
关键字:

tornado翻译中文