Chinaunix首页 | 论坛 | 博客
  • 博客访问: 547276
  • 博文数量: 142
  • 博客积分: 2966
  • 博客等级: 少校
  • 技术积分: 1477
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-07 22:37
文章分类

全部博文(142)

文章存档

2013年(3)

2012年(21)

2011年(53)

2010年(33)

2009年(32)

分类: Python/Ruby

2011-05-05 11:59:36


一、下载安装

>>自动安装:

TornadoPypI上,可以通过pip或者easy_install自动安装,前提是服务器上已经安装了libcurl库,当用pip或者easy_install安装的时候,不会自动安装例子程序
>>
手工安装:
原始下载地址(本站也提供1.2.1的下载,原始地址要翻墙):

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

T的源代码托管在GitHub上,如果开发环境是py2.6+,也可以简单的把tornado目录添加到py的环境变量PYTHONPATH 中,这样就不需要编译setup.py(也就是不用安装的意思),既然标准库已经包含了epoll的支持(since the standard library includes epoll support.)

>>
安装条件:

T
py2.5 2.6 2.7上进行过测试,如果要使用t的全部功能,必须安装PycURL(版本7.18.2 或者以上),对py2.5必须安装JSON支持组件py2.6+以上的JSON支持已经在标准库内实现。下面是Mac OS XUbuntu的安装例子:
Mac OS X 10.6 (Python 2.6+)

sudo easy_install setuptools pycurl

Ubuntu Linux (Python 2.6+)

sudo apt-get install python-pycurl

Ubuntu Linux (Python 2.5)

sudo apt-get install python-dev python-pycurl python-simplejson

二、模块列表和简介:
最重要的模块是web模块,他作为web框架包含了Tornado 包内大部分的有用的东西(meat of Tornado ,这里暂且不知道是使用了大部分T的功能,还是说T的大部分功能在web中实现),其他的工具和模块让web变的更有用

主模块

web – 用来创建FriendFeedweb框架,他集成(混合)了大部分T的重要功能特征
escape – XHTML, JSON, and URL
编解码方法
database -
MySQLdb的简单封装,让mysql的使用更方便
template –
基于python的模板渲染语言
httpclient –
一个非阻塞http客户端,设计它用来和webhttpserver协同工作
auth –
第三方认证和认证方案的实现 (Google OpenID/OAuth, Facebook Platform, Yahoo BBAuth, FriendFeed OpenID/OAuth, Twitter OAuth)
locale –
本地化和翻译支持

options –
命令行和配置文件解析工具,为了webserver环境做了优化

底层模块

httpserver – A very simple HTTP server built on which web is built(这个没法翻译,容易翻译错,除非很了解)
iostream – A simple wrapper around non-blocking sockets to aide common reading and writing patterns
ioloop – Core I/O loop
(也不敢翻译,怕误解)

随机模块:

s3server – 一个实现了Amazon S3接口的web服务器,基于本地文件存储

三、Tornado使用概览
一个T应用会把URL或者URL模式映射到tornado.web.RequestHandler的子类,这些子类定义getset方法来处理对应URLGETPOST请求。
瞎蒙的代码把根URL /映射到MainHandler,把用正则表达式表达的URL/story/([0-9]+映射到StoryHandler,正则表达式的查找分组会作为StoryHandler方法的参数穿进去

class MainHandler(tornado.web.RequestHandler):    def get(self):        self.write("You requested the main page") class StoryHandler(tornado.web.RequestHandler):    def get(self, story_id):        self.write("You requested the story " + story_id) application = tornado.web.Application([    (r"/", MainHandler),    (r"/story/([0-9]+)", StoryHandler),])

你可以使用get_argument方法来获取query参数或者接卸post主体:

class MainHandler(tornado.web.RequestHandler):    def get(self):        self.write('
'                   ''                   ''                   '
 ')     def post(self):        self.set_header("Content-Type", "text/plain")        self.write("You wrote " + self.get_argument("message"))

上传文件
上传文件在 self.request.files,他会把htmlinput 标签的名字映射到一组文件上,每个文件是一个字典,形式如:

{"filename":..., "content_type":..., "body":...}

如果你想给客户端发送一个错误消息,比如403未认证错误,你只需要抛出一个如下的异常:

if not self.user_is_logged_in():    raise tornado.web.HTTPError(403)

处理函数可以用过self.request来访问当前请求相关的对象,HTTPRequest对象包含了一些有用的属性:
arguments –
所有GET and POST 参数
files –
所有上传过来的文件 (via multipart/form-data POST requests)
path –
请求的路径,?之前的所有内容

headers –
请求的头

HTTPRequest完整的定义信息在httpserver

重载RequestHandler方法
除了get()/post()/etcRequestHandler 其他一些特定的方法设计就是用来被子类在必要的时候覆盖掉,
当一个请求到来以后,下面是完整的流程步骤:
1
,每个请求会创建一个RequestHandler 对象
2
,调用initialize(),并且把Application 配置作为关键参数传进去,该函数一般仅仅是把传进来的参数保存
成成员变量,并不会产生任何输出,比如错误信息:send_error
3
,调用prepare().这是基类中对所有子类可以分享的最有用的部分,以为他不关心当前请求的具体方法,该函数可能
产生输出,如果他调用finish 或者send_error,那么处理到此结束
4
,如下的一种HTTP方法会被调用:getpostput等等,如果有URL正则表达式查询分组,则作为参数传进去
下面是一个initialize方法示例:

class ProfileHandler(RequestHandler):

    def initialize(self, database):

        self.database = database

 

    def get(self, username):

        ...

 

app = Application([

    (r'/user/(.*)', ProfileHandler, dict(database=database)),

    ])

其他被设计可以覆盖的方法包括:
get_error_html(self, status_code, exception=None, **kwargs) – returns HTML (as a string) for use on error pages.
get_current_user(self) – see User Authentication below
get_user_locale(self) – returns locale object to use for the current user
get_login_url(self) – returns login url to be used by the @authenticated decorator (default is in Application settings)
get_template_path(self) – returns location of template files (default is in Application settings)

模板
你可以使用任何基于python的模板语言,但是T ships with 他自己的模板语言,这个模板语言相对其他的模板系统他非常的快而且更灵活,具体产看模板部分的文档以便得到更详细的的完整描述
一个T模板就是一个HTML或者其他的基于文本的文件,其中会用一些特定标签包住Python控制语法和
表达式:

      {{ title }}

           {% for item in items %}

           

  • {{ escape(item) }}
  • {% end %}

假如你把上面的模板保存为template.html,然后放在和python脚本同一个目录下,你就可以用如下的方式
把该模板渲染输出:

class MainHandler(tornado.web.RequestHandler):

    def get(self):

        items = ["Item 1", "Item 2", "Item 3"]

        self.render("template.html", title="My title", items=items)

T模板支持控制语法和表达式,前者用{%%}包围,后者用{{}}包围,比如:

{% if len(items) > 2 %}
{{ items[0] }}

控制语句或多或少的的精确对应了python的语法,我们支持if for while try,所有的语法都用
{% end %}
结束。我们同时使用extendsblock语法支持模板的继承,在模板模块的文档中有详细介绍。

表达式可以是任何py表达式,包括函数调用,模板代码的执行环境的名字空间包含如下的对象和函数(
注意,这个列表只是针对RequestHandler.render render_string这两种模板渲染方法,如果你使用
模板模块在RequestHandler 之外进行渲染,所有的条目都不复有效)

escape: alias for tornado.escape.xhtml_escape
xhtml_escape: alias for tornado.escape.xhtml_escape
url_escape: alias for tornado.escape.url_escape
json_encode: alias for tornado.escape.json_encode
squeeze: alias for tornado.escape.squeeze
linkify: alias for tornado.escape.linkify
datetime: the Python datetime module
handler: the current RequestHandler object
request: alias for handler.request
current_user: alias for handler.current_user
locale: alias for handler.locale
_: alias for handler.locale.translate
static_url: alias for handler.static_url
xsrf_form_html: alias for handler.xsrf_form_html
reverse_url: alias for Application.reverse_url
All entries from the ui_methods and ui_modules Application settings
Any keyword arguments passed to render or render_string

如果你正在创建一个实际的应用程序,你可能会使用所有的T模板特征,尤其是模板的继承。具体请阅读
模板模块的文档(部分特征在UIModules中,他在web模块中实现)

表面之下,T模板会被直接翻译成Python。模板的所有表达式会拷贝到一个代表你模板的函数中,我们不会
试图妨碍模块语言中的任何事情。我们精确明白的创造他,以便提供更灵活的模板渲染系统,这也是很多
其他模板系统所做不到的。因此,如果你在模板的表达式中写一些随机(意:乱的)的东西,你会得到相应的python语法
错误

Cookie和安全cookie
你可以通过set_cookie 方法在用户的浏览器端设置cookie

class MainHandler(tornado.web.RequestHandler):

    def get(self):

        if not self.get_cookie("mycookie"):

            self.set_cookie("mycookie", "myvalue")

            self.write("Your cookie was not set yet!")

        else:

            self.write("Your cookie was set!")

cookie很容易被恶意客户端伪造,假如你想保存当前登录用户的IDcookie中,你必须给cookie签名以便
防止伪造。T通过set_secure_cookie get_secure_cookie 来支持。为了使用这两个方法,你必须在创建应用
程序的时候给变量cookie_secret 赋一个有效的值,你可以通过关键字参数的方式给应用程序传递过去:

application = tornado.web.Application([

    (r"/", MainHandler),

], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")

签名cookie包含了加密后的cookie值和时间戳以及一个HMAC 签名,如果cookie已经过期或者HMAC不匹配
get_secure_cookie
会简单的返回None,就当cookie根本不存在,上面cookie代码的安全版本示例:

class MainHandler(tornado.web.RequestHandler):

    def get(self):

        if not self.get_secure_cookie("mycookie"):

            self.set_secure_cookie("mycookie", "myvalue")

            self.write("Your cookie was not set yet!")

        else:

            self.write("Your cookie was set!")

用户认证
当前经过认证的用户在每个请求处理函数内都是有效的:self.current_user,并且在每个模板内current_user都是
有效的,在默认的情况下current_user 是一个None

为了在你的应用程序中实现用户认证,你必须在你的请求处理产生中覆盖get_current_user() 方法以便决定
当前用户有效与否的根据,比如是通过cookie来确认。下面是一个例子:让用户通过昵称登录应用程序,然后保存
cookie(下面这段代码因为包含原生的html语法,所以插入的话会有问题,如果通过可见即可得的方式插入,换行全部没有了,如果通过html编辑简单插入,又会导致自动生成inut控件,form等,所以使用这种的方法,但是大于号小于号我也没工夫进行转义,那段代码的大概意义就是创建一个forminput,用来输入昵称,然后登录)

class BaseHandler(tornado.web.RequestHandler):   

           def get_current_user(self):       

              return self.get_secure_cookie("user")

class MainHandler(BaseHandler):   

          def get(self):       

                if not self.current_user:           

                     self.redirect("/login")  return       

                name = tornado.escape.xhtml_escape(self.current_user)       

                self.write("Hello, " + name)

class LoginHandler(BaseHandler):   

          def get(self):       

              self.write('<html><body><form action="/login"

                           method="post">'                   

                            'Name: <input type="text" name="name">'                 

                            '<input type="submit" value="Sign in">'                 

                             '</form></body></html>')

           def post(self):       

               self.set_secure_cookie("user", self.get_argument("name"))       

               self.redirect("/")

 

application = tornado.web.Application([    (r"/", MainHandler),   

 (r"/login", LoginHandler),],

 cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")

你可以使用tornado.web.authenticated修饰函数来强制坚持用户登录。如果一个请求到了被该修饰器修饰的方法,
并且用户并没有登录,那么用户会直接被导航到login_url (这也是一项应用程序的设置),基于斯,上面的
代码可以重写如下:

class MainHandler(BaseHandler):

    @tornado.web.authenticated

    def get(self):

        name = tornado.escape.xhtml_escape(self.current_user)

        self.write("Hello, " + name)

 

settings = {

    "cookie_secret": "61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",

    "login_url": "/login",

}

application = tornado.web.Application([

    (r"/", MainHandler),

    (r"/login", LoginHandler),

], **settings)

如果你用修饰器修饰post函数,并且用户没有登录,服务器会发送一个403响应
T
内建了第三方的认证方案,比如google OAuth,具体查看auth模块。可以check out T博客例子程序来看一看
完整的用户认证的例子

跨站伪造请求保护
跨站请求伪造或者叫XSRF,对于个性化的web应用来说是一个普遍问题,可以查看文章了解xsrf的工作机制。

一般可以接受的组织XSRF的方案是使用无法预知(猜测)的值来cookie每个用户,并且使用该值作为你的
整个站点的每个form请 求的额外参数。如果cookieform中的这个值不匹配,说明请求是伪造的

T内建了XSRF保护,为了在你的站点内包含他,要设置应用程序的xsrf_cookies选项:

settings = {
“cookie_secret”: “61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=”,
“login_url”: “/login”,
“xsrf_cookies”: True,
}
application = tornado.web.Application([
(r"/", MainHandler),
(r"/login", LoginHandler),
], **settings)

如果xsrf_cookies 设置了,T应用程序会为所有的用户设置_xsrf ,并且拒绝所有不匹配_xsrf 值的putpost
delete请求。如果你使用这项设置,你必须把你网站的所有post-form表单修改成如下的形式(添加xsrf_form_html
,该函数在所有的模板内都有效)

窗体顶端

{{ xsrf_form_html() }}
< input type=”text” name=”message”/>
< input type=”submit” value=”Post”/>

窗体底端

如果你发送ajax post请求,你必须在你的每个请求中让你的js代码包含 _xsrf 值,下面是我们在FriendFeed
使用的自动添加该值的jquery代码:

function getCookie(name) {

    var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");

    return r ? r[1] : undefined;

}

 

jQuery.postJSON = function(url, args, callback) {

    args._xsrf = getCookie("_xsrf");

    $.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",

        success: function(response) {

        callback(eval("(" + response + ")"));

    }});

};

头的方式传递过来:

putdelete请求(和没有使用form-encoded参数的post一样),XSRF 必须通过HTTP 头的方式传递过来:
头部变量名是X-XSRFToken

静态文件和文件缓存(Static files and aggressive file caching
你可以通过在应用程序中设置static_path 选择来让Tornado 提供静态文件服务。
settings = {
“static_path”: os.path.join(os.path.dirname(__file__), “static”),
“cookie_secret”: “61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=”,
“login_url”: “/login”,
“xsrf_cookies”: True,
}
application = tornado.web.Application([
(r"/", MainHandler),
(r"/login", LoginHandler),
], **settings)

该设置会自动的让所有的一/static开始的请求从那个指定的静态目录获取,例如
请求会从指定的目录下获取foo.png。我们还会自动的在static目录下提供/robots.txt/favicon.ico(即便
他们不是以/static开始)

为了提高性能,一般来说,浏览器(aggressively )缓存静态资源是一个好主意,这样浏览器就就不需要
发送不需要的If-Modified-Since 或者 Etag请求,这些请求可能会阻塞页面的渲染。T通过static content versioning
来支持这项功能

为了使用这想功能,在模板中使用static_url() 方法,而不是直接把文件的没名字打在HTML文件中

< html>
< headl>
< titlel>FriendFeed – {{ _(“Home”) }}
< /headl>
< bodyl>
< divl>< img src=”{{ static_url(“images/logo.png”) }}”/l>
< /bodyl>
< /htmll>

static_url函数会把这个相对路径转成一个类似 /static/images/logo.png?v=aae54这样的URL,其中的v
logo.png的散列值,他的出现会让T服务器发送cache头部给用户的浏览器,这样就会然浏览器无限期(indefinitely
的缓存内容

因为v值基于文件的内容,所以如果你更新了文件并且重启了服务器,就会发送新的v值,这样浏览器就会
自动获取新文件,如果文件的内容并未改变,浏览器还是会继续使用本地缓存的副本,而不是不断的检查服务器上的
更新情况,这样就可以显著的提高渲染性能

你可以可以使用nginx这样的服务器来更高校的提供静态文件的输出,你同样可以配置大多数web服务器来支持
这些缓存机制,下面是ngnix的配置(FriendFeed中使用):

location /static/ {
root /var/friendfeed/static;
if ($query_string) {
expires max;
}
}

本地化
当前用户的地域(登录或者不登陆)通过 self.locale 一直有效,可以在请求处理中使用,对应的模板变量是locale ,
本地的名称比如en_US变量是locale.name,同时你可以使用locale.translate 来翻译字符串,模板还有一个全局

函数call _()用来作字符串翻译,翻译函数有两种使用形式:
_(“Translate this string”)
上面的语句直接基于当前的locale来翻译字符串

_(“A person liked this”, “%(num)d people liked this”, len(people)) % {“num”: len(people)}

这条语句会根据第三个参数来决定翻译的字符串的单复数。根据len(people)的返回值,如果是1返回前一个字符串,
否则返回第二条字符串

最通用的模式是使用python占位符来代替变量,因为占位符can move around on translation
下面是一个可能的本地化的模板:



FriendFeed – {{ _(“Sign in”) }} </head><BR><body><BR><form action=”{{ request.path }}” method=”post”><BR><div>{{ _(“Username”) }} <input type=”text” name=”username”/></div><BR><div>{{ _(“Password”) }} <input type=”password” name=”password”/></div><BR><div><input type=”submit” value=”{{ _(“Sign in”) }}”/></div><BR>{{ xsrf_form_html() }}<BR></form><BR><script type="text/javascript" src="/js/jquery.qqFace.js"></script> </body><BR></html></FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>默认的,我们会使用用户浏览器发送你的<SPAN lang=EN-US>Accept-Language </SPAN>头来检测用户的<SPAN lang=EN-US>locale</SPAN>,如果我们没有发现合适的</FONT><FONT size=3><SPAN lang=EN-US>Accept-Language<BR></SPAN>值,我们会选择<SPAN lang=EN-US>en_US</SPAN>。如果你让用户设置它们的<SPAN lang=EN-US>locale </SPAN>,你可以在你的请求处理函数中重载<SPAN lang=EN-US>get_user_locale </SPAN>来</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>覆盖掉默认的<SPAN lang=EN-US>locale</SPAN>选择:</FONT></P><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">class</SPAN></B><SPAN lang=EN-US> BaseHandler<SPAN style="COLOR: black">(</SPAN>tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">RequestHandler)</SPAN>:</SPAN></FONT></FONT><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> get_current_user<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN>user_id = <SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">get_secure_cookie(</SPAN><SPAN style="COLOR: darkslateblue">"user"</SPAN><SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><B><SPAN style="COLOR: #ff7700">if</SPAN></B> <B><SPAN style="COLOR: #ff7700">not</SPAN></B> user_id: <B><SPAN style="COLOR: #ff7700">return</SPAN></B> <SPAN style="COLOR: green">None</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><B><SPAN style="COLOR: #ff7700">return</SPAN></B> <SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">backend</SPAN>.<SPAN style="COLOR: black">get_user_by_id(</SPAN>user_id<SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT face=宋体 size=3> </FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> get_user_locale<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><B><SPAN style="COLOR: #ff7700">if</SPAN></B> <SPAN style="COLOR: darkslateblue">"locale"</SPAN> <B><SPAN style="COLOR: #ff7700">not</SPAN></B> <B><SPAN style="COLOR: #ff7700">in</SPAN></B> <SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">current_user</SPAN>.<SPAN style="COLOR: black">prefs</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">            </SPAN><I><SPAN style="COLOR: gray"># Use the Accept-Language header</SPAN></I></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">            </SPAN><B><SPAN style="COLOR: #ff7700">return</SPAN></B> <SPAN style="COLOR: green">None</SPAN></FONT></FONT></SPAN><FONT size=3><FONT face=宋体><SPAN lang=EN-US><SPAN style="mso-spacerun: yes">        </SPAN><B><SPAN style="COLOR: #ff7700">return</SPAN></B> <SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">current_user</SPAN>.<SPAN style="COLOR: black">prefs[</SPAN><SPAN style="COLOR: darkslateblue">"locale"</SPAN><SPAN style="COLOR: black">]</SPAN></SPAN>)</FONT></FONT> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>如果<SPAN lang=EN-US>get_user_locale </SPAN>返回<SPAN lang=EN-US>None</SPAN>,我们会重新回到<SPAN lang=EN-US>Accept-Language </SPAN>头的处理上(<SPAN lang=EN-US>fall back on</SPAN>)</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>你可以使用<SPAN lang=EN-US>tornado.locale.load_translations </SPAN>方法为你的应用程序加载翻译,他接受一个包含<SPAN lang=EN-US>csv</SPAN>文件的的目录,</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>文件名的格式是<SPAN lang=EN-US> local.csv</SPAN>比如<SPAN lang=EN-US>es_GT.csv.</SPAN>该方法会从这些<SPAN lang=EN-US>csv</SPAN>中加载所有的翻译,并且根据出现的每个<SPAN lang=EN-US>csv</SPAN>文件</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>推断可以支持的<SPAN lang=EN-US>locale</SPAN>,你只需要在你的服务器端的<SPAN lang=EN-US>main</SPAN>中调用一次该方法即可。</FONT></P><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">  </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> main<SPAN style="COLOR: black">()</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN>tornado.<SPAN style="COLOR: crimson">locale</SPAN>.<SPAN style="COLOR: black">load_translations(</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><SPAN style="COLOR: crimson">os</SPAN>.<SPAN style="COLOR: black">path</SPAN>.<SPAN style="COLOR: black">join(</SPAN><SPAN style="COLOR: crimson">os</SPAN>.<SPAN style="COLOR: black">path</SPAN>.<SPAN style="COLOR: black">dirname(</SPAN>__file__<SPAN style="COLOR: black">)</SPAN>, <SPAN style="COLOR: darkslateblue">"translations"</SPAN><SPAN style="COLOR: black">))</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN>start_server<SPAN style="COLOR: black">()</SPAN></FONT></FONT></SPAN> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>你通过<SPAN lang=EN-US>tornado.locale.get_supported_locales() </SPAN>得到你的的应用程序中所有可用的<SPAN lang=EN-US>local</SPAN>的列表用户的<SPAN lang=EN-US>locale</SPAN>会在</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>所有支持的<SPAN lang=EN-US>local</SPAN>中选择一个最接近的。例如,如果用户的<SPAN lang=EN-US>locale</SPAN>是<SPAN lang=EN-US>es_GT</SPAN>,并且<SPAN lang=EN-US>es locale</SPAN>可以支持,</FONT><FONT size=3><SPAN lang=EN-US> self.locale<BR></SPAN>会为那个<SPAN lang=EN-US>request</SPAN>选择<SPAN lang=EN-US>es</SPAN>。如果没有匹配的,就选择<SPAN lang=EN-US>en_US</SPAN></FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3><SPAN lang=EN-US>locale</SPAN>模块有详细的说明。<STRONG><SPAN lang=EN-US style="FONT-WEIGHT: normal; FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体"></SPAN></STRONG></FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3><STRONG><SPAN lang=EN-US style="FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体">UI </SPAN></STRONG><STRONG><SPAN style="FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体">模块</SPAN></STRONG></FONT><SPAN lang=EN-US><BR><FONT size=3>T</FONT></SPAN><FONT size=3>的<SPAN lang=EN-US>UI</SPAN>模块可以让你的应用程序很容易的支持支持标准的,可重用的<SPAN lang=EN-US>UI</SPAN>窗口,<SPAN lang=EN-US>UI</SPAN>模块像特殊的函数调用一样用来渲染</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>你的页面的组件,而且他们可以把自己使用的<SPAN lang=EN-US>CSS</SPAN>和<SPAN lang=EN-US>JS</SPAN>脚本打包成包。</FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>例如,你如果正在创建一个博客,你想让博客的条目出现在首页和每个文章的页面,你可以创建一个<SPAN lang=EN-US>Entry </SPAN>模块来</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>在每个页面渲染他们。首先,为<SPAN lang=EN-US>UI</SPAN>模块创建一个<SPAN lang=EN-US>python</SPAN>模块,比如<SPAN lang=EN-US>uimodules.py</SPAN></FONT></P><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">class</SPAN></B><SPAN lang=EN-US> Entry<SPAN style="COLOR: black">(</SPAN>tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">UIModule)</SPAN>:</SPAN></FONT></FONT><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> render<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN>, entry, show_comments=<SPAN style="COLOR: green">False</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><B><SPAN style="COLOR: #ff7700">return</SPAN></B> <SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">render_string(</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">            </SPAN><SPAN style="COLOR: darkslateblue">"module-entry.html"</SPAN>, show_comments=show_comments<SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>告诉<SPAN lang=EN-US>Tornado</SPAN>使用<SPAN lang=EN-US>uimodules.py</SPAN>来设置应用程序:</FONT></P><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">class</SPAN></B><SPAN lang=EN-US> HomeHandler<SPAN style="COLOR: black">(</SPAN>tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">RequestHandler)</SPAN>:</SPAN></FONT></FONT><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> get<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN>entries = <SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">db</SPAN>.<SPAN style="COLOR: black">query(</SPAN><SPAN style="COLOR: darkslateblue">"SELECT * FROM entries ORDER BY date DESC"</SPAN><SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">render(</SPAN><SPAN style="COLOR: darkslateblue">"home.html"</SPAN>, entries=entries<SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT face=宋体 size=3> </FONT></SPAN><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">class</SPAN></B><SPAN lang=EN-US> EntryHandler<SPAN style="COLOR: black">(</SPAN>tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">RequestHandler)</SPAN>:</SPAN></FONT></FONT><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> get<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN>, entry_id<SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN>entry = <SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">db</SPAN>.<SPAN style="COLOR: black">get(</SPAN><SPAN style="COLOR: darkslateblue">"SELECT * FROM entries WHERE id = %s"</SPAN>, entry_id<SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><B><SPAN style="COLOR: #ff7700">if</SPAN></B> <B><SPAN style="COLOR: #ff7700">not</SPAN></B> entry: <B><SPAN style="COLOR: #ff7700">raise</SPAN></B> tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">HTTPError(</SPAN><SPAN style="COLOR: orangered">404</SPAN><SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">render(</SPAN><SPAN style="COLOR: darkslateblue">"entry.html"</SPAN>, entry=entry<SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT face=宋体 size=3> </FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体>settings = <SPAN style="COLOR: black">{</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><SPAN style="COLOR: darkslateblue">"ui_modules"</SPAN>: uimodules,</FONT></FONT></SPAN><SPAN lang=EN-US style="COLOR: black"><FONT face=宋体 size=3>}</FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体>application = tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">Application([</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><SPAN style="COLOR: black">(</SPAN>r<SPAN style="COLOR: darkslateblue">"/"</SPAN>, HomeHandler<SPAN style="COLOR: black">)</SPAN>,</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><SPAN style="COLOR: black">(</SPAN>r<SPAN style="COLOR: darkslateblue">"/entry/([0-9]+)"</SPAN>, EntryHandler<SPAN style="COLOR: black">)</SPAN>,</FONT></FONT></SPAN><FONT size=3><FONT face=宋体><SPAN lang=EN-US style="COLOR: black">]</SPAN><SPAN lang=EN-US>, <SPAN style="COLOR: #66cc66">**</SPAN>settings<SPAN style="COLOR: black">)</SPAN></SPAN></FONT></FONT> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>在<SPAN lang=EN-US>entry.html</SPAN>内,你通过<SPAN lang=EN-US>show_comments </SPAN>参数来引用<SPAN lang=EN-US>Entry </SPAN>模块,从而展开这个条目:</FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>{{ modules.Entry(entry, show_comments=True) }}</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>模块可以包含自定义的<SPAN lang=EN-US>css</SPAN>和<SPAN lang=EN-US>js</SPAN>函数,者通过覆盖如下的方法实现:</FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>embedded_css, embedded_javascript, javascript_files, css_files methods:</FONT></SPAN></P><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">class</SPAN></B><SPAN lang=EN-US> Entry<SPAN style="COLOR: black">(</SPAN>tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">UIModule)</SPAN>:</SPAN></FONT></FONT><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> embedded_css<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><B><SPAN style="COLOR: #ff7700">return</SPAN></B> <SPAN style="COLOR: darkslateblue">".entry { margin-bottom: 1em; }"</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT face=宋体 size=3> </FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> render<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN>, entry, show_comments=<SPAN style="COLOR: green">False</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><B><SPAN style="COLOR: #ff7700">return</SPAN></B> <SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">render_string(</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">   </SPAN><SPAN style="mso-spacerun: yes">         </SPAN><SPAN style="COLOR: darkslateblue">"module-entry.html"</SPAN>, show_comments=show_comments<SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>无论模块在一个页面内被使用多少次,模块<SPAN lang=EN-US>css</SPAN>和<SPAN lang=EN-US>js</SPAN>只会包含一次,<SPAN lang=EN-US>css</SPAN>总是在页面的<SPAN lang=EN-US>head</SPAN>部分包含,<SPAN lang=EN-US>js</SPAN>总是在页面的尾部</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>标签的前面包含</FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><STRONG><SPAN style="FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体"><FONT size=3>非阻塞、异步请求</FONT></SPAN></STRONG><SPAN lang=EN-US><BR></SPAN><FONT size=3>当一个请求处理函数在执行完毕只会,当前的请求自动终止。既然<SPAN lang=EN-US>T</SPAN>使用了一个非阻塞<SPAN lang=EN-US>IO</SPAN>的模式,你可以</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>覆盖掉这种默认的行为,如果你想让主要的请求处理函数返回之后当前<SPAN lang=EN-US>requst</SPAN>依然有效。这可以通过修饰器:</FONT><SPAN lang=EN-US><BR><FONT size=3>tornado.web.asynchronous</FONT></SPAN><FONT size=3>来做到</FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>当你使用这个装饰器的时候,你必须有义务来调用<SPAN lang=EN-US>self.finish() </SPAN>来终止<SPAN lang=EN-US>http</SPAN>请求,否则用户浏览器会</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>简单的挂住:</FONT></P><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">class</SPAN></B><SPAN lang=EN-US> MainHandler<SPAN style="COLOR: black">(</SPAN>tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">RequestHandler)</SPAN>:</SPAN></FONT></FONT><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN>@tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">asynchronous</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> get<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">write(</SPAN><SPAN style="COLOR: darkslateblue">"Hello, world"</SPAN><SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">finish()</SPAN></FONT></FONT></SPAN> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>下面是一个真实的例子,他使用了<SPAN lang=EN-US>Tornado</SPAN>内建的异步<SPAN lang=EN-US>HTTP client</SPAN>来调用<SPAN lang=EN-US>FriendFeed</SPAN>的<SPAN lang=EN-US>API</SPAN>:</FONT></P><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">class</SPAN></B><SPAN lang=EN-US> MainHandler<SPAN style="COLOR: black">(</SPAN>tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">RequestHandler)</SPAN>:</SPAN></FONT></FONT><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN>@tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">asynchronous</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> get<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN>http = tornado.<SPAN style="COLOR: black">httpclient</SPAN>.<SPAN style="COLOR: black">AsyncHTTPClient()</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN>http.<SPAN style="COLOR: black">fetch(</SPAN><SPAN style="COLOR: darkslateblue">""</SPAN>,</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">                   </SPAN>callback=<SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">on_response)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT face=宋体 size=3> </FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> on_response<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN>, response<SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><B><SPAN style="COLOR: #ff7700">if</SPAN></B> response.<SPAN style="COLOR: black">error</SPAN>: <B><SPAN style="COLOR: #ff7700">raise</SPAN></B> tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">HTTPError(</SPAN><SPAN style="COLOR: orangered">500</SPAN><SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN>json = tornado.<SPAN style="COLOR: black">escape</SPAN>.<SPAN style="COLOR: black">json_decode(</SPAN>response.<SPAN style="COLOR: black">body)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">write(</SPAN><SPAN style="COLOR: darkslateblue">"Fetched "</SPAN> + <SPAN style="COLOR: green">str</SPAN><SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">len</SPAN><SPAN style="COLOR: black">(</SPAN>json<SPAN style="COLOR: black">[</SPAN><SPAN style="COLOR: darkslateblue">"entries"</SPAN><SPAN style="COLOR: black">]))</SPAN> + <SPAN style="COLOR: darkslateblue">" entries "</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><SPAN style="mso-spacerun: yes">               </SPAN><SPAN style="COLOR: darkslateblue">"from the FriendFeed API"</SPAN><SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">finish()</SPAN></FONT></FONT></SPAN> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>当<SPAN lang=EN-US>get()</SPAN>返回的时候,<SPAN lang=EN-US>request</SPAN>并没有结束,当<SPAN lang=EN-US>HTTp client</SPAN>周期性的调用<SPAN lang=EN-US>on_response</SPAN>,这个<SPAN lang=EN-US>req</SPAN>依然</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>处在打开的状态,通过调用<SPAN lang=EN-US>self.finish()</SPAN>,<SPAN lang=EN-US>response </SPAN>最终刷<SPAN lang=EN-US>(Flush to)</SPAN>到<SPAN lang=EN-US>client</SPAN></FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>作为一个更高级的异步例子,聊天应用程序是不错的例子,他通过<SPAN lang=EN-US>ajax long polling</SPAN>实现了聊天室。</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>使用了<SPAN lang=EN-US>long polling</SPAN>的用户可能想覆盖<SPAN lang=EN-US>on_connection_close() </SPAN>函数来做一些清理工作,当<SPAN lang=EN-US>client</SPAN>关闭</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>连接以后(<SPAN lang=EN-US>but see that method’s docstring for caveats</SPAN>)</FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><STRONG><SPAN style="FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体"><FONT size=3>第三方登录和认证</FONT></SPAN></STRONG><SPAN lang=EN-US><BR><FONT size=3>Tornado</FONT></SPAN><FONT size=3>的认证模块实现了很多热门网站的认证和认证协议,包括<SPAN lang=EN-US>google/gmail,twitter</SPAN>,<SPAN lang=EN-US>yahoo</SPAN>,和</FONT><SPAN lang=EN-US><BR><FONT size=3>FriendFeed. </FONT></SPAN><FONT size=3>该模块包含了让用户通过这些网站进行登录的方法,同时在应用角度,也提供了对这些</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>网站用户信息的授权访问,基于次,你就可以用来下载用户的地址簿,或者以用户的角度发布一条</FONT><SPAN lang=EN-US><BR><FONT size=3>twitter</FONT></SPAN><FONT size=3>消息。</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>下面是一个例子处理程序,他使用了<SPAN lang=EN-US>google</SPAN>作为认证机制,在<SPAN lang=EN-US>cooker</SPAN>中保存<SPAN lang=EN-US>google</SPAN>身份证据以便</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>后面可以使用。</FONT></P><SPAN lang=EN-US><FONT face=宋体 size=3>lass GoogleHandler<SPAN style="COLOR: black">(</SPAN>tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">RequestHandler</SPAN>, tornado.<SPAN style="COLOR: black">auth</SPAN>.<SPAN style="COLOR: black">GoogleMixin)</SPAN>:</FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN>@tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">asynchronous</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> get<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><B><SPAN style="COLOR: #ff7700">if</SPAN></B> <SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">get_argument(</SPAN><SPAN style="COLOR: darkslateblue">"openid.mode"</SPAN>, <SPAN style="COLOR: green">None</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">     </SPAN><SPAN style="mso-spacerun: yes">       </SPAN><SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">get_authenticated_user(</SPAN><SPAN style="COLOR: green">self</SPAN>._on_auth<SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">            </SPAN><B><SPAN style="COLOR: #ff7700">return</SPAN></B></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">authenticate_redirect()</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT face=宋体 size=3> </FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> _on_auth<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN>, <SPAN style="COLOR: crimson">user</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><B><SPAN style="COLOR: #ff7700">if</SPAN></B> <B><SPAN style="COLOR: #ff7700">not</SPAN></B> <SPAN style="COLOR: crimson">user</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">            </SPAN><SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">authenticate_redirect()</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">            </SPAN><B><SPAN style="COLOR: #ff7700">return</SPAN></B></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><I><SPAN style="COLOR: gray"># Save the user with, e.g., set_secure_cookie()</SPAN></I></FONT></FONT></SPAN> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>更详细内容查看<SPAN lang=EN-US>auth</SPAN>模块</FONT><SPAN lang=EN-US><BR><BR></SPAN><STRONG><SPAN style="FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体"><FONT size=3>性能考虑</FONT></SPAN></STRONG><SPAN lang=EN-US><BR><FONT size=3>web</FONT></SPAN><FONT size=3>应用程序的性能一半受制于框架和架构,而不是前段的性能,也就是说,<SPAN lang=EN-US>Tornado</SPAN>相对于流行的</FONT><FONT size=3><SPAN lang=EN-US>python<BR>web</SPAN>框架来说,他是非常快的(</FONT><FONT size=3><SPAN lang=EN-US>pretty fast).<BR></SPAN>我们使用各种流行的<SPAN lang=EN-US>py</SPAN>框架(<SPAN lang=EN-US>Djiango</SPAN>,<SPAN lang=EN-US>web.py</SPAN>,<SPAN lang=EN-US>CheeryPy)</SPAN>运行一个简单的<SPAN lang=EN-US>Hello Word</SPAN>应用的加载<SPAN lang=EN-US>,</SPAN>以此</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>来获得这些框架针对<SPAN lang=EN-US>Tornado</SPAN>的性能表现。我们使用<SPAN lang=EN-US>Apache/mod_wsgi </SPAN>来运行<SPAN lang=EN-US>Djiango</SPAN>和<SPAN lang=EN-US>web.py</SPAN>,以</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>独立的服务器来运行<SPAN lang=EN-US>CherryPy </SPAN>,这些运行方式和环境是我们对这些框架印象深刻的认识。我们使用</FONT><FONT size=3><SPAN lang=EN-US>ngnix<BR></SPAN>的反向代理技术,在后面运行<SPAN lang=EN-US>4</SPAN>个独立线程的前端,这事我们推荐的运行<SPAN lang=EN-US>Tornado</SPAN>的方式(测试机器有</FONT><SPAN lang=EN-US><BR><FONT size=3>4</FONT></SPAN><FONT size=3>核心,推荐<SPAN lang=EN-US>1</SPAN>核心一个前端)</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>我们使用<SPAN lang=EN-US>Apache Benchmark (ab)</SPAN>命令在每个独立的机器上加载测试:</FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>ab -n 100000 -c 25 </FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>在<SPAN lang=EN-US>4</SPAN>核心<SPAN lang=EN-US>2.4G AMD</SPAN>皓龙处理器上的测试结果如下(每秒的<SPAN lang=EN-US>request</SPAN>数):</FONT></P> <P style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: center" align=center><SPAN lang=EN-US></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>在我们的测试中,<SPAN lang=EN-US>Tornado</SPAN>一贯的保持对第二名有<SPAN lang=EN-US>4</SPAN>倍的效率。而且即便是一个独立的<SPAN lang=EN-US>T</SPAN>前段也能<SPAN lang=EN-US>33%</SPAN>的性能提升,</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>虽然这种情况下他只使用了我们<SPAN lang=EN-US>4</SPAN>核处理器的<SPAN lang=EN-US>1</SPAN>核。</FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>在产品线上使用</FONT><FONT size=3><SPAN lang=EN-US>Tornado<BR></SPAN>在<SPAN lang=EN-US>FriendFeed</SPAN>,我们使用<SPAN lang=EN-US>nginx</SPAN>作为负载均衡和静态文件服务器(<SPAN lang=EN-US>static fil e</SPAN>),同时在多台前段的</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>机器上运行<SPAN lang=EN-US>Tornado</SPAN>服务器。我们一般一个<SPAN lang=EN-US>cpu</SPAN>核心运行一个<SPAN lang=EN-US>Tornado</SPAN>前端(根据具体情况,有的时候会多一些)</FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>当在一个负载均衡服务器后面运行的时候,推荐把<SPAN lang=EN-US>xheaders=True to</SPAN>传给<SPAN lang=EN-US>HTTPServer</SPAN>构造函数。这会让</FONT><SPAN lang=EN-US><BR><FONT size=3>Tornado</FONT></SPAN><FONT size=3>使用类似<SPAN lang=EN-US> X-Real-IP</SPAN>这样的<SPAN lang=EN-US>header</SPAN>来获取用户的<SPAN lang=EN-US>ip</SPAN>地址而不是把所有的流量都归于负载器的<SPAN lang=EN-US>IP</SPAN>地址(这句话</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>有点没理解,感觉应该是<SPAN lang=EN-US>tornado</SPAN>告诉浏览器他当前正在访问的地址是什么,避免浏览器下次再</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>访问负载均衡服务器)</FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>下面是我们在<SPAN lang=EN-US>FriendFeed</SPAN>中使用的简化的<SPAN lang=EN-US>ngnix</SPAN>的配置文件的例子。他假设<SPAN lang=EN-US>nginx</SPAN>和<SPAN lang=EN-US>Tornado</SPAN>服务器运行在</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>同一个机器上,<SPAN lang=EN-US>4</SPAN>个<SPAN lang=EN-US>Tornado</SPAN>服务器运行在端口<SPAN lang=EN-US>8000~8003</SPAN>上:</FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>user nginx;<BR>worker_processes 1;</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>error_log /var/log/nginx/error.log;<BR>pid /var/run/nginx.pid;</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>events {<BR>worker_connections 1024;<BR>use epoll;<BR>}</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>http {<BR># Enumerate all the Tornado servers here<BR>upstream frontends {<BR>server 127.0.0.1:8000;<BR>server 127.0.0.1:8001;<BR>server 127.0.0.1:8002;<BR>server 127.0.0.1:8003;<BR>}</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>include /etc/nginx/mime.types;<BR>default_type application/octet-stream;</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>access_log /var/log/nginx/access.log;</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>keepalive_timeout 65;<BR>proxy_read_timeout 200;<BR>sendfile on;<BR>tcp_nopush on;<BR>tcp_nodelay on;<BR>gzip on;<BR>gzip_min_length 1000;<BR>gzip_proxied any;<BR>gzip_types text/plain text/html text/css text/xml<BR>application/x-javascript application/xml<BR>application/atom+xml text/javascript;</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3># Only retry if there was a communication error, not a timeout<BR># on the Tornado server (to avoid propagating “queries of death”<BR># to all frontends)<BR>proxy_next_upstream error;</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>server {<BR>listen 80;</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3># Allow file uploads<BR>client_max_body_size 50M;</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>location ^~ /static/ {<BR>root /var/www;<BR>if ($query_string) {<BR>expires max;<BR>}<BR>}<BR>location = /favicon.ico {<BR>rewrite (.*) /static/favicon.ico;<BR>}<BR>location = /robots.txt {<BR>rewrite (.*) /static/robots.txt;<BR>}</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT size=3>location / {<BR>proxy_pass_header Server;<BR>proxy_set_header Host $http_host;<BR>proxy_redirect false;<BR>proxy_set_header X-Real-IP $remote_addr;<BR>proxy_set_header X-Scheme $scheme;<BR>proxy_pass <BR>}<BR>}<BR>}</FONT></SPAN></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3><STRONG><SPAN lang=EN-US style="FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体">WSGI </SPAN></STRONG><STRONG><SPAN style="FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体">和<SPAN lang=EN-US>Google AppEngine</SPAN></SPAN></STRONG></FONT><SPAN lang=EN-US><BR><FONT size=3>Tornado</FONT></SPAN><FONT size=3>对<SPAN lang=EN-US>WSGI</SPAN>的支持有限,然而,既然<SPAN lang=EN-US>WSGI</SPAN>不支持非阻塞的<SPAN lang=EN-US>request</SPAN>,如果你选择使用<SPAN lang=EN-US>WSGI</SPAN>服务器来</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>代替<SPAN lang=EN-US>Tornado HTTP</SPAN>服务器的话,你的应用程序不能使用任何异步的或者非阻塞的<SPAN lang=EN-US>Tornado</SPAN>特性。在</FONT><SPAN lang=EN-US><BR><FONT size=3>WSGI</FONT></SPAN><FONT size=3>应用中,如下的特性肯定没法支持:</FONT><SPAN lang=EN-US><BR><FONT size=3>@tornado.web.asynchronous, httpclient </FONT></SPAN><FONT size=3>模块<SPAN lang=EN-US>, the auth </SPAN>模块<SPAN lang=EN-US>.</SPAN></FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3>你可以在<SPAN lang=EN-US>request</SPAN>处理函数中使用<SPAN lang=EN-US>wsgi</SPAN>模块中的<SPAN lang=EN-US>WSGIApplication </SPAN>创建一个有效的<SPAN lang=EN-US>WSGI</SPAN>应用,而不是使用</FONT><SPAN lang=EN-US><BR><FONT size=3>tornado.web.Application.</FONT></SPAN><FONT size=3>下面是一个使用内建的<SPAN lang=EN-US>WSGI CGIHandler</SPAN>来创建有效的<SPAN lang=EN-US>Google AppEngine</SPAN>应用</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>的例子:</FONT></P><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">import</SPAN></B><SPAN lang=EN-US> tornado.<SPAN style="COLOR: black">web</SPAN></SPAN></FONT></FONT><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">import</SPAN></B><SPAN lang=EN-US> tornado.<SPAN style="COLOR: black">wsgi</SPAN></SPAN></FONT></FONT><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">import</SPAN></B><SPAN lang=EN-US> wsgiref.<SPAN style="COLOR: black">handlers</SPAN></SPAN></FONT></FONT><SPAN lang=EN-US><FONT face=宋体 size=3> </FONT></SPAN><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">class</SPAN></B><SPAN lang=EN-US> MainHandler<SPAN style="COLOR: black">(</SPAN>tornado.<SPAN style="COLOR: black">web</SPAN>.<SPAN style="COLOR: black">RequestHandler)</SPAN>:</SPAN></FONT></FONT><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><B><SPAN style="COLOR: #ff7700">def</SPAN></B> get<SPAN style="COLOR: black">(</SPAN><SPAN style="COLOR: green">self</SPAN><SPAN style="COLOR: black">)</SPAN>:</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><SPAN style="COLOR: green">self</SPAN>.<SPAN style="COLOR: black">write(</SPAN><SPAN style="COLOR: darkslateblue">"Hello, world"</SPAN><SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT face=宋体 size=3> </FONT></SPAN><FONT size=3><FONT face=宋体><B><SPAN lang=EN-US style="COLOR: #ff7700">if</SPAN></B><SPAN lang=EN-US> __name__ == <SPAN style="COLOR: darkslateblue">"__main__"</SPAN>:</SPAN></FONT></FONT><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN>application = tornado.<SPAN style="COLOR: black">wsgi</SPAN>.<SPAN style="COLOR: black">WSGIApplication([</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">        </SPAN><SPAN style="COLOR: black">(</SPAN>r<SPAN style="COLOR: darkslateblue">"/"</SPAN>, MainHandler<SPAN style="COLOR: black">)</SPAN>,</FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN><SPAN style="COLOR: black">])</SPAN></FONT></FONT></SPAN><SPAN lang=EN-US><FONT size=3><FONT face=宋体><SPAN style="mso-spacerun: yes">    </SPAN>wsgiref.<SPAN style="COLOR: black">handlers</SPAN>.<SPAN style="COLOR: black">CGIHandler()</SPAN>.<SPAN style="COLOR: black">run(</SPAN>application<SPAN style="COLOR: black">)</SPAN></FONT></FONT></SPAN> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3><SPAN lang=EN-US>appEngine</SPAN>详细的例子参照:<SPAN lang=EN-US>appengine</SPAN></FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><STRONG><SPAN style="FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体"><FONT size=3>支持和注意事项</FONT></SPAN></STRONG><SPAN lang=EN-US><BR></SPAN><FONT size=3>因为<SPAN lang=EN-US>FriendFeed</SPAN>和其他的大量<SPAN lang=EN-US>Tornado</SPAN>用户都是在<SPAN lang=EN-US>ngnix</SPAN>或者<SPAN lang=EN-US>apache</SPAN>代理的后面运行,<SPAN lang=EN-US>tornado</SPAN>的<SPAN lang=EN-US>HTTP</SPAN>服务器当前</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>不准备支持<SPAN lang=EN-US>multi-line headers</SPAN>的处理,以及其他一些混乱的输入(<SPAN lang=EN-US>malformed input</SPAN>)的处理</FONT><SPAN lang=EN-US><BR></SPAN><FONT size=3>讨论和提交<SPAN lang=EN-US>bug</SPAN>:<SPAN lang=EN-US></SPAN></FONT></P> <P style="MARGIN: 0cm 0cm 0pt"><FONT size=3><SPAN lang=EN-US>Tornado</SPAN>是<SPAN lang=EN-US><a href="http://developers.facebook.com/opensource.php" target="_blank">Facebook<SPAN lang=EN-US><SPAN lang=EN-US>开源技术</SPAN></SPAN></A></SPAN>之一,采用第二版的<SPAN lang=EN-US>Apache Licence</SPAN></FONT></P> <P class=MsoNormal style="MARGIN: 0cm 0cm 0pt"><SPAN lang=EN-US><FONT face="Times New Roman"> </FONT></SPAN></P> <P> </P> </div> <!-- <div class="Blog_con3_1">管理员在2009年8月13日编辑了该文章文章。</div> --> <div class="Blog_con2_1 Blog_con3_2"> <div> <!--<img src="/image/default/tu_8.png">--> <!-- JiaThis Button BEGIN --> <div class="bdsharebuttonbox"><A class=bds_more href="#" data-cmd="more"></A><A class=bds_qzone title=分享到QQ空间 href="#" data-cmd="qzone"></A><A class=bds_tsina title=分享到新浪微博 href="#" data-cmd="tsina"></A><A class=bds_tqq title=分享到腾讯微博 href="#" data-cmd="tqq"></A><A class=bds_renren title=分享到人人网 href="#" data-cmd="renren"></A><A class=bds_weixin title=分享到微信 href="#" data-cmd="weixin"></A></div> <script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script> <!-- JiaThis Button END --> </div> 阅读(10363) | 评论(0) | 转发(0) | <div class="HT_line3"></div> </div> <div class="Blog_con3_3"> <div><span id='digg_num'>0</span><a href="javascript:void(0)" id='digg' bid='291023' url='/blog/digg.html' ></a></div> <p>上一篇:<a href="/uid-20544356-id-290882.html">Python PycURL 网络编程</a></p> <p>下一篇:<a href="/uid-20544356-id-302339.html">url重定向</a></p> </div> </div> <!-- <div class="Blog_con3_4 Blog_con3_5"> <div class="Blog_tit2 Blog_tit7">热门推荐</div> <ul> <li><a href="" title="" target='blank' ></a></li> </ul> </div> --> </div> </div> <div class="Blog_right1_7" id='replyList'> <div class="Blog_tit3">给主人留下些什么吧!~~</div> <!--暂无内容--> <!-- 评论分页--> <div class="Blog_right1_6 Blog_right1_12"> </div> <!-- 评论分页--> <div class="Blog_right1_10" style="display:none"> <div class="Blog_tit3">评论热议</div> <!--未登录 --> <div class="Blog_right1_8"> <div class="nologin_con1"> 请登录后评论。 <p><a href="http://account.chinaunix.net/login" onclick="link(this)">登录</a> <a href="http://account.chinaunix.net/register?url=http%3a%2f%2fblog.chinaunix.net">注册</a></p> </div> </div> </div> <div style="text-align:center;margin-top:10px;"> <script type="text/javascript" smua="d=p&s=b&u=u3118759&w=960&h=90" src="//www.nkscdn.com/smu0/o.js"></script> </div> </div> </div> </div> <input type='hidden' id='report_url' value='/blog/ViewReport.html' /> <script type="text/javascript"> //测试字符串的长度 一个汉字算2个字节 function mb_strlen(str) { var len=str.length; var totalCount=0; for(var i=0;i<len;i++) { var c = str.charCodeAt(i); if ((c >= 0x0001 && c <= 0x007e) || (0xff60<=c && c<=0xff9f)) { totalCount++; }else{ totalCount+=2; } } return totalCount; } /* var Util = {}; Util.calWbText = function (text, max){ if(max === undefined) max = 500; var cLen=0; var matcher = text.match(/[^\x00-\xff]/g), wlen = (matcher && matcher.length) || 0; //匹配url链接正则 http://*** var pattern = /http:\/\/([\w-]+\.)+[\w-]+(\/*[\w-\.\/\?%&=][^\s^\u4e00-\u9fa5]*)?/gi; //匹配的数据存入数组 var arrPt = new Array(); var i = 0; while((result = pattern.exec(text)) != null){ arrPt[i] = result[0]; i++; } //替换掉原文本中的链接 for(var j = 0;j<arrPt.length;j++){ text = text.replace(arrPt[j],""); } //减掉链接所占的长度 return Math.floor((max*2 - text.length - wlen)/2 - 12*i); }; */ var allowComment = '0'; //举报弹出层 function showJuBao(url, cid){ $.cover(false); asyncbox.open({ id : 'report_thickbox', url : url, title : '举报违规', width : 378, height : 240, scroll : 'no', data : { 'cid' : cid, 'idtype' : 2 , 'blogurl' : window.location.href }, callback : function(action){ if(action == 'close'){ $.cover(false); } } }); } $(function(){ //创建管理员删除的弹出层 $('#admin_article_del').click(function(){ $.cover(false); asyncbox.open({ id : 'class_thickbox', html : '<div class="HT_layer3_1"><ul><li class="HT_li1">操作原因:<select class="HT_sel7" id="del_type" name="del_type"><option value="广告文章">广告文章</option><option value="违规内容">违规内容</option><option value="标题不明">标题不明</option><option value="文不对题">文不对题</option></select></li><li class="HT_li1" ><input class="HT_btn4" id="admin_delete" type="button" /></li></ul></div>', title : '选择类型', width : 300, height : 150, scroll : 'no', callback : function(action){ if(action == 'close'){ $.cover(false); } } }); }); $('#admin_delete').live('click' , function(){ ///blog/logicdel/id/3480184/url/%252Fblog%252Findex.html.html var type = $('#del_type').val(); var url = '/blog/logicdel/id/291023/url/%252Fuid%252F20544356.html.html'; window.location.href= url + '?type=' + type; }); //顶 js中暂未添加&过滤 $('#digg').live('click' , function(){ if(isOnLine == '' ) { //showErrorMsg('登录之后才能进行此操作' , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); return false; } var bid = $('#digg').attr('bid'); var url = $('#digg').attr('url'); var digg_str = $.cookie('digg_id'); if(digg_str != null) { var arr= new Array(); //定义一数组 arr = digg_str.split(","); //字符分割 for( i=0 ; i < arr.length ; i++ ) { if(bid == arr[i]) { showErrorMsg('已经赞过该文章', '消息提示'); return false; } } } $.ajax({ type:"POST", url:url, data: { 'bid' : bid }, dataType: 'json', success:function(msg){ if(msg.error == 0) { var num = parseInt($('#digg_num').html(),10); num += 1; $('#digg_num').html(num); $('#digg').die(); if(digg_str == null) { $.cookie('digg_id', bid, {expires: 30 , path: '/'}); } else { $.cookie('digg_id', digg_str + ',' + bid, {expires: 30 , path: '/'}); } showSucceedMsg('谢谢' , '消息提示'); } else if(msg.error == 1) { //showErrorMsg(msg.error_content , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); } else if(msg.error == 2) { showErrorMsg(msg.error_content , '消息提示'); } } }); }); //举报弹出层 /*$('.box_report').live('click' , function(){ if(isOnLine == '' ) { //showErrorMsg('登录之后才能进行此操作' , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); return false; } var url = $('#report_url').val(); var cid = $(this).attr('cid'); $.cover(false); asyncbox.open({ id : 'report_thickbox', url : url, title : '举报违规', width : 378, height : 240, scroll : 'no', data : { 'cid' : cid, 'idtype' : 2 }, callback : function(action){ if(action == 'close'){ $.cover(false); } } }); });*/ //评论相关代码 //点击回复显示评论框 $('.Blog_a10').live('click' , function(){ if(isOnLine == '' ) { //showErrorMsg('登录之后才能进行此操作' , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); return false; } if(allowComment == 1) { showErrorMsg('该博文不允许评论' , '消息提示'); return false; } var tid = $(this).attr('toid');//留言作者id var bid = $(this).attr('bid');//blogid var cid = $(this).attr('cid');//留言id var tname = $(this).attr('tname'); var tpl = '<div class="Blog_right1_9">'; tpl += '<div class="div2">'; tpl += '<textarea name="" cols="" rows="" class="Blog_ar1_1" id="rmsg">文明上网,理性发言...</textarea>'; tpl += '</div>'; tpl += '<div class="div3">'; tpl += '<div class="div3_2"><a href="javascript:void(0);" class="Blog_a11" id="quota_sbumit" url="/Comment/PostComment.html" tid="'+tid+'" bid="'+bid+'" cid="'+cid+'" tname="'+tname+'" ></a><a href="javascript:void(0)" id="qx_comment" class="Blog_a12"></a></div>'; tpl += '<div class="div3_1"><a href="javascript:void(0);" id="mface"><span></span>表情</a></div>'; tpl += '<div class="clear"></div>'; tpl += '</div>'; tpl += '</div>'; $('.z_move_comment').html(''); $(this).parents('.Blog_right1_8').find('.z_move_comment').html(tpl).show(); }); //引用的评论提交 $('#quota_sbumit').live('click' , function(){ if(isOnLine == '' ) { //showErrorMsg('登录之后才能进行此操作' , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); return false; } var bid = $(this).attr('bid'); var tid = $(this).attr('tid');//被引用人的id var qid = $(this).attr('cid');//引用的id var url = $(this).attr('url'); var text = $('#rmsg').val(); var tname = $(this).attr('tname'); if(text == '' || text=='文明上网,理性发言...') { showErrorMsg('评论内容不能为空!' , '消息提示'); return false; } else { if(mb_strlen(text) > 1000){ showErrorMsg('评论内容不能超过500个汉字' , '消息提示'); return false; } } $.ajax({ type: "post", url: url , data: {'bid': bid , 'to' : tid , 'qid' : qid , 'text': text , 'tname' : tname }, dataType: 'json', success: function(data){ if(data.code == 1){ var tpl = '<div class="Blog_right1_8">'; tpl+= '<div class="Blog_right_img1"><a href="' +data.info.url+ '" >' + data.info.header + '</a></div>'; tpl+= '<div class="Blog_right_font1">'; tpl+= '<p class="Blog_p5"><span><a href="' +data.info.url+ '" >' + data.info.username + '</a></span>' + data.info.dateline + '</p>'; tpl+= '<p class="Blog_p7"><a href="' + data.info.quota.url + '">' + data.info.quota.username + '</a>:'+ data.info.quota.content + '</p>'; tpl+= '<p class="Blog_p8">' + data.info.content + '</p><span class="span_text1"><a href="javascript:void(0);" class="Blog_a10" toid=' + data.info.fuid + ' bid=' + data.info.bid + ' cid=' + data.info.cid + ' tname = ' + data.info.username + ' >回复</a> |  <a class="comment_del_mark" style="cursor:pointer" url="' + data.info.delurl + '" >删除</a> |  <a href="javascript:showJuBao(\'/blog/ViewReport.html\','+data.info.cid+')" class="box_report" cid="' + data.info.cid + '" >举报</a></span></div>'; tpl+= '<div class="z_move_comment" style="display:none"></div>'; tpl+= '<div class="Blog_line1"></div></div>'; $('#replyList .Blog_right1_8:first').before(tpl); $('.z_move_comment').html('').hide(); } else if(data.code == -1){ //showErrorMsg(data.info , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); } }, error: function(){//请求出错处理 } }); }); //底部发表评论 $('#submitmsg').click(function(){ if(allowComment == 1) { showErrorMsg('该博文不允许评论' , '消息提示'); return false; } var bid = $(this).attr('bid'); var toid = $(this).attr('toid'); var text = $('#reply').val(); var url = $(this).attr('url'); if(text == '' || text=='文明上网,理性发言...') { showErrorMsg('评论内容不能为空!' , '消息提示'); return false; } else { if(mb_strlen(text) > 1000){ showErrorMsg('评论内容不能超过500个汉字' , '消息提示'); return false; } } $.ajax({ type: "post", url: url , data: {'bid': bid , 'to' : toid ,'text': text}, dataType: 'json', success: function(data){ if(data.code == 1) { var tpl = '<div class="Blog_right1_8">'; tpl += '<div class="Blog_right_img1"><a href="' +data.info.url+ '" >' + data.info.header + '</a></div>'; tpl += '<div class="Blog_right_font1">'; tpl += '<p class="Blog_p5"><span><a href="' +data.info.url+ '" >' + data.info.username + '</a></span>' + data.info.dateline + '</p>'; tpl += '<p class="Blog_p6">' + data.info.content + '</p>'; tpl += '<div class="div1"><a href="javascript:void(0);" class="Blog_a10" toid=' + data.info.fuid + ' bid=' + data.info.bid + ' cid=' + data.info.cid + '>回复</a> |  <a class="comment_del_mark" style="cursor:pointer" url="' + data.info.delurl + '">删除</a> |  <a href="javascript:showJuBao(\'/blog/ViewReport.html\','+data.info.cid+')" class="box_report" cid="' + data.info.cid + '">举报</a></div>'; tpl += '<div class="z_move_comment" style="display:none"></div>'; tpl += '</div><div class="Blog_line1"></div></div>'; $('.Blog_tit3:first').after(tpl); $('#reply').val('文明上网,理性发言...'); } else if(data.code == -1) { showErrorMsg(data.info , '消息提示'); } }, error: function(){//请求出错处理 } }); }); //底部评论重置 $('#reset_comment').click(function(){ $('#reply').val('文明上网,理性发言...'); }); //取消回复 $('#qx_comment').live('click' , function(){ $('.z_move_comment').html('').hide(); }); $('#rmsg, #reply').live({ focus:function(){ if($(this).val() == '文明上网,理性发言...'){ $(this).val(''); } }, blur:function(){ if($(this).val() == ''){ $(this).val('文明上网,理性发言...'); } } }); //删除留言确认 $('.comment_del_mark').live('click' , function(){ var url = $(this).attr('url'); asyncbox.confirm('删除留言','确认', function(action){ if(action == 'ok') { location.href = url; } }); }); //删除时间确认 $('.del_article_id').click(function(){ var delurl = $(this).attr('delurl'); asyncbox.confirm('删除文章','确认', function(action){ if(action == 'ok') { location.href = delurl; } }); }); /* //字数限制 $('#rmsg, #reply').live('keyup', function(){ var id = $(this).attr('id'); var left = Util.calWbText($(this).val(), 500); var eid = '#errmsg'; if(id == 'reply') eid = '#rerrmsg'; if (left >= 0) $(eid).html('您还可以输入<span>' + left + '</span>字'); else $(eid).html('<font color="red">您已超出<span>' + Math.abs(left) + '</span>字 </font>'); }); */ //加载表情 $('#face').qqFace({id : 'facebox1', assign : 'reply', path : '/image/qqface/'}); $('#mface').qqFace({id : 'facebox', assign : 'rmsg', path:'/image/qqface/'}); /* $('#class_one_id').change(function(){ alert(123213); var id = parseInt($(this).val() , 10); if(id == 0) return false; $('.hidden_son_class span').each(function( index , dom ){ if( dom.attr('cid') == id ) { } }); }); */ //转载文章 var turn_url = "/blog/viewClassPart.html"; $('#repost_bar').click(function(){ if(isOnLine == '' ) { //showErrorMsg('登录之后才能进行此操作' , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); return false; } var id = $(this).attr('bid'); asyncbox.open({ id : 'turn_class_thickbox', url : turn_url, title : '转载文章', width : 330, height : 131, scroll : 'no', data : { 'id' : id }, callback : function(action){ if(action == 'close'){ $.cover(false); } } }); }); /* //转发文章 $('#repost_bar').live('click' , function(){ if(isOnLine == '' ) { //showErrorMsg('登录之后才能进行此操作' , '消息提示'); showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); return false; } var bid = $(this).attr('bid'); var url = $(this).attr('url'); asyncbox.confirm('转载文章','确认', function(action){ if(action == 'ok'){ $.ajax({ type:"POST", url:url, data: { 'bid' : bid }, dataType: 'json', success:function(msg){ if(msg.error == 0){ showSucceedMsg('转发成功!', '消息提示'); }else if(msg.error == 1){ //location.href = '/index.php?r=site/login'; showErrorMsg('操作失败,您需要先登录!', '消息提示', 'http://account.chinaunix.net/login'); }else{ showErrorMsg(msg.error_content, '消息提示'); } } }); } }); }); */ }); </script> <!--该部分应该放在输出代码块的后面才起作用 --> <script type="text/javascript"> SyntaxHighlighter.autoloader( 'applescript /highlight/scripts/shBrushAppleScript.js', 'actionscript3 as3 /highlight/scripts/shBrushAS3.js', 'bash shell /highlight/scripts/shBrushBash.js', 'coldfusion cf /highlight/scripts/shBrushColdFusion.js', 'cpp c /highlight/scripts/shBrushCpp.js', 'c# c-sharp csharp /highlight/scripts/shBrushCSharp.js', 'css /highlight/scripts/shBrushCss.js', 'delphi pascal /highlight/scripts/shBrushDelphi.js', 'diff patch pas /highlight/scripts/shBrushDiff.js', 'erl erlang /highlight/scripts/shBrushErlang.js', 'groovy /highlight/scripts/shBrushGroovy.js', 'java /highlight/scripts/shBrushJava.js', 'jfx javafx /highlight/scripts/shBrushJavaFX.js', 'js jscript javascript /highlight/scripts/shBrushJScript.js', 'perl pl /highlight/scripts/shBrushPerl.js', 'php /highlight/scripts/shBrushPhp.js', 'text plain /highlight/scripts/shBrushPlain.js', 'py python /highlight/scripts/shBrushPython.js', 'ruby rails ror rb /highlight/scripts/shBrushRuby.js', 'scala /highlight/scripts/shBrushScala.js', 'sql /highlight/scripts/shBrushSql.js', 'vb vbnet /highlight/scripts/shBrushVb.js', 'xml xhtml xslt html /highlight/scripts/shBrushXml.js' ); SyntaxHighlighter.all(); function code_hide(id){ var code = document.getElementById(id).style.display; if(code == 'none'){ document.getElementById(id).style.display = 'block'; }else{ document.getElementById(id).style.display = 'none'; } } </script> <!--回顶部js2011.12.30--> <script language="javascript"> lastScrollY=0; function heartBeat(){ var diffY; if (document.documentElement && document.documentElement.scrollTop) diffY = document.documentElement.scrollTop; else if (document.body) diffY = document.body.scrollTop else {/*Netscape stuff*/} percent=.1*(diffY-lastScrollY); if(percent>0)percent=Math.ceil(percent); else percent=Math.floor(percent); document.getElementById("full").style.top=parseInt(document.getElementById("full").style.top)+percent+"px"; lastScrollY=lastScrollY+percent; if(lastScrollY<200) { document.getElementById("full").style.display="none"; } else { document.getElementById("full").style.display="block"; } } var gkuan=document.body.clientWidth; var ks=(gkuan-960)/2-30; suspendcode="<div id=\"full\" style='right:-30px;POSITION:absolute;TOP:500px;z-index:100;width:26px; height:86px;cursor:pointer;'><a href=\"javascript:void(0)\" onclick=\"window.scrollTo(0,0);\"><img src=\"\/image\/top.png\" /></a></div>" document.write(suspendcode); window.setInterval("heartBeat()",1); </script> <!-- footer --> <div class="Blog_footer" style='clear:both'> <div><a href="http://www.chinaunix.net/about/index.shtml" target="_blank" rel="nofollow">关于我们</a> | <a href="http://www.it168.com/bottomfile/it168.shtml" target="_blank" rel="nofollow">关于IT168</a> | <a href="http://www.chinaunix.net/about/connect.html" target="_blank" rel="nofollow">联系方式</a> | <a href="http://www.chinaunix.net/about/service.html" target="_blank" rel="nofollow">广告合作</a> | <a href="http://www.it168.com//bottomfile/flgw/fl.htm" target="_blank" rel="nofollow">法律声明</a> | <a href="http://account.chinaunix.net/register?url=http%3a%2f%2fblog.chinaunix.net" target="_blank" rel="nofollow">免费注册</a> <p>Copyright 2001-2010 ChinaUnix.net All Rights Reserved 北京皓辰网域网络信息技术有限公司. 版权所有 </p> <div>感谢所有关心和支持过ChinaUnix的朋友们 <p><a href="http://beian.miit.gov.cn/">16024965号-6 </a></p> </div> </div> </div> </div> <script language="javascript"> //全局错误提示弹出框 function showErrorMsg(content, title, url){ var url = url || ''; var title = title || '消息提示'; var html = ''; html += '<div class="HT_layer3_1 HT_layer3_2"><ul><li><p><span class="login_span1"></span>' + content + '</p></li>'; if(url == '' || url.length == 0){ html += '<li class="HT_li1"><input type="button" class="HT_btn2" onclick=\'close_windows("error_msg")\'></li>'; } else { html += '<li class="HT_li1"><input type="button" class="login_btn1" onclick="location.href=\'' + url + '\'"></li>'; } html += '</ul></div>'; $.cover(true); asyncbox.open({ id: 'error_msg', title : title, html : html, 'callback' : function(action){ if(action == 'close'){ $.cover(false); } } }); } //全局正确提示 function showSucceedMsg(content, title , url ){ var url = url || ''; var title = title || '消息提示'; var html = ''; html += '<div class="HT_layer3_1 HT_layer3_2"><ul><li><p><span class="login_span2"></span>' + content + '</p></li>'; if(url == '' || url.length == 0){ html += '<li class="HT_li1"><input type="button" class="HT_btn2" onclick=\'close_windows("error_msg")\'></li>'; } else { html += '<li class="HT_li1"><input type="button" class="HT_btn2" onclick="location.href=\'' + url + '\'"></li>'; } html += '</ul></div>'; $.cover(true); asyncbox.open({ id: 'error_msg', title : title, html : html, 'callback' : function(action){ if(action == 'close'){ $.cover(false); } } }); } //关闭指定id的窗口 function close_windows(id){ $.cover(false); $.close(id); } //公告 var tID; var tn; // 高度 var nStopTime = 5000; // 不同行间滚动时间隔的时间,值越小,移动越快 var nSpeed = 50; // 滚动时,向上移动一像素间隔的时间,值越小,移动越快 var isMove = true; var nHeight = 25; var nS = 0; var nNewsCount = 3; /** * n 用于表示是否为第一次运行 **/ function moveT(n) { clearTimeout(tID) var noticev2 = document.getElementById("noticev2") nS = nSpeed; // 只在第一次调用时运行,初始化环境(有没有参数) if (n) { // 设置行高 noticev2.style.lineHeight = nHeight + "px"; // 初始化显示位置 tn = 0; // 刚进入时在第一行停止时间 nS = nStopTime; } // 判断鼠标是否指向层 if (isMove) { // 向上移动一像素 tn--; // 如果移动到最下面一行了,则移到顶行 if (Math.abs(tn) == nNewsCount * nHeight) { tn = 0; } // 设置位置 noticev2.style.marginTop = tn + "px"; // 完整显示一行时,停止一段时间 if (tn % nHeight == 0) { nS = nStopTime; } } tID = setTimeout("moveT()", nS); } moveT(1); // 此处可以传入任何参数 </script> <script type="text/javascript"> // var _gaq = _gaq || []; // _gaq.push(['_setAccount', 'UA-20237423-2']); // _gaq.push(['_setDomainName', '.chinaunix.net']); // _gaq.push(['_trackPageview']); // // (function() { // var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; // ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; // var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); // })(); </script> <script type="text/javascript"> var _bdhmProtocol = (("https:" == document.location.protocol) ? " https://" : " http://"); document.write(unescape("%3Cscript src='" + _bdhmProtocol + "hm.baidu.com/h.js%3F0ee5e8cdc4d43389b3d1bfd76e83216b' type='text/javascript'%3E%3C/script%3E")); function link(t){ var href= $(t).attr('href'); href+="?url="+encodeURIComponent(location.href); $(t).attr('href',href); //setCookie("returnOutUrl", location.href, 60, "/"); } </script> </body> </html>