首页 > 代码库 > Tornado web框架
Tornado web框架
本节目录
1.介绍
2.tornado web应用的结构
3、路由系统
4、模板引擎
5、静态文件与文件缓存
6、cookies与secure cookie
1、介绍
tornado是一个Python web框架和异步网络库 起初由 FriendFeed 开发. 通过使用非阻塞网络I/O, Tornado 可以支持上万级的连接,处理 长连接, WebSockets, 和其他 需要与每个用户保持长久连接的应用.
Tornado 大体上可以被分为4个主要的部分:
- web框架 (包括创建web应用的
RequestHandler
类,还有很多其他支持的类). - HTTP的客户端和服务端实现 (
HTTPServer
andAsyncHTTPClient
). - 异步网络库 (
IOLoop
andIOStream
), 为HTTP组件提供构建模块,也可以用来实现其他协议. -
- ioloop.py 主要的是将底层的epoll或者说是其他的IO多路复用封装作异步事件来处理。
- iostream.py主要是对于下层的异步事件的进一步封装,为其封装了更上一层的buffer(IO)事件。
- 协程库 (
tornado.gen
) 允许异步代码写的更直接而不用链式回调的方式.
Tornado web 框架和HTTP server 一起为 WSGI 提供了一个全栈式的选择. 在WSGI容器 (WSGIAdapter
) 中使用Tornado web框架或者使用Tornado HTTP server 作为一个其他WSGI框架(WSGIContainer
)的容器,这样的组合方式都是有局限性的. 为了充分利用Tornado的特性,你需要一起使用Tornado的web框架和HTTP server.
2、Tornado web应用的结构
通常一个Tornado web应用包括一个或者多个 RequestHandler
子类, 一个可以将收到的请求路由到对应handler的 Application
对象,和 一个启动服务的 main()
函数.
一个简单的tornado应用:
import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): # self.write("Hello, world") 被用于非模板基础的输 出; 它接受字符串, 字节, 和字典(字典会被编码成JSON). # self.render(‘s1.html‘) #渲染模板文件,默认在当前目录下找 # self.render(‘template/s1.html‘)#html文件集中放在template文件夹中,让其在该文件夹下找 self.render(‘s1.html‘) #如果我们不想每次都写template的话,我们可以配置一下它,让其默认就是在该路径下查找
self.get_argument(‘xxx‘,None) # 获取用户提交的数据
#配置 settings={ ‘template_path‘:‘template‘ ,#模板路径的配置 ‘static_path‘:‘static‘ #静态文件必须要给它配置一下,不然加载是找不到的 } #路由映射,路由系统 application = tornado.web.Application([ (r"/index", MainHandler), ] ,**settings)#应用到tornado中,使其生效 # Application对象是负责全局配置的, 包括映射请求转发给处理程序的路由表. if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
Application对象
Application对象是负责全局配置的, 包括映射请求转发给处理程序的路由表.
路由表是 URLSpec
对象(或元组)的列表, 其中每个都包含(至少)一个正则 表达式和一个处理类. 顺序问题; 第一个匹配的规则会被使用. 如果正则表达 式包含捕获组, 这些组会被作为 路径参数 传递给处理函数的HTTP方法. 如果一个字典作为 URLSpec
的第三个参数被传递, 它会作为 初始参数 传递给RequestHandler.initialize
. 最后 URLSpec
可能有一个名字 , 这将允许它被RequestHandler.reverse_url
使用.
例如, 在这个片段中根URL /
映射到了 MainHandler
, 像 /story/
后跟着一个数字这种形式的URL被映射到了 StoryHandler
. 这个数字被传递(作为字符串)给 StoryHandler.get
.
class MainHandler(RequestHandler): def get(self): self.write(‘<a href="http://www.mamicode.com/%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
.
3、路由系统
在web框架中,路由表中的任意一项是一个元组,每个元组包含pattern(模式)和handler(处理器)。当httpserver接收到一个http请求,server从接收到的请求中解析出url path(http协议start line中),然后顺序遍历路由表,如果发现url path可以匹配某个pattern,则将此http request交给web应用中对应的handler去处理。
路由系统其实就是 url 和 类 的对应关系,这里不同于其他框架,其他很多框架均是 url 对应 函数,Tornado中每个url对应的是一个类。路由表,卸耦了http server层和web application层。
路由表具有非常重要的作用,卸耦了http server层和web application层。由于有了url路由机制,web应用开发者不必和复杂的http server层代码打交道,只需要写好web应用层的逻辑(handler)即可。
Application类位于web.py,一个Application类的实例相当于一个web应用。
application = tornado.web.Application([ (r"/index", MainHandler), ]) #把一个路由表(一个列表)作为参数,传递给Application类的构造函数,创建了一个实例,然后再把这个实例传递给http_server。那么当客户端发起”get /”请求的时候,http server接收到这个请求,在理由表中匹配url pattern,最后交给MainHandler去处理。
路由表
tornado原生支持RESTful比如我给用户提供了查询书、购买书、取消购买书的功能,那按理说我需要给用户三个url,查询书比如说是:http://www.book.com:8888/search,购买书是:http://www.book.com:8888/buy,取消购买书是:http://www.book.com:8888/delete。用户访问不同的url进行他需要的操作。上面仅仅是三个基本的功能,一个网站提供的功能肯定特别多,那岂不是要给用户很多个url?那有没有更简便的方式呢,客户端和服务端进行一个约定,都只需要维护一个url就行了,比如下面这种:
# 客户端和服务端都只维护下面这一个url 书本url:http://www.book.com:8888/index # 客户端和服务端通过不同的method来执行不同的操作 method:get method:post method:delete method:put
上面的这种约定是一种软件架构方式:RESTful,双方约定好怎么获取服务和提供服务,然后只维护一个url、通过改变请求的method来通信。这也是“面向资源编程”的概念,将网络中的所有东西都视为资源。Tornado原生支持RESTful,这也是其优势之一。
import tornado.ioloop import tornado.web user_info = [] class MainHandler(tornado.web.RequestHandler): # 用户以get方式访问,就执行get方法 def get(self): # self.write("Hello, world") self.render(‘index.html‘) # 用户以post方式访问,就执行post方法 def post(self, *args, **kwargs): self.write(‘post‘) # 用户以delete方式访问,就执行delete方法 def delete(self): self.write("delete") settings = { ‘template_path‘: ‘template‘, ‘static_path‘: ‘static‘, } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8888) # epoll + socket tornado.ioloop.IOLoop.instance().start()
tornado原生支持二级域名
域名的划分:
我们可以使用二级域名用来区分不同的业务,tornado在url划分的时候,比django更加友好;django是在主域名后面接着2级域名,配置使用include 进行业务分类url。
class MainHandler(tornado.web.RequestHandler): def get(self): self.write("www") class CmdbMainHandler(tornado.web.RequestHandler): def get(self): self.write("cmdb") settings = { ‘template_path‘: ‘template‘, ‘static_path‘: ‘static‘, } # 默认二级域名是www;用户输入www.freely.com:8888/index时,执行MainHandler里的方法。 application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) # 用户输入的域名是cmdb.freely.com:8888/index时,执行CmdbMainHandler里的方法。 application.add_handlers("cmdb.freely.com",#重新设置url使用add_handlers方法,第一个参数是2级域名名称,后面是url列表。 [ (r"/index", CmdbMainHandler), ], **settings) # django里就是www.freely.com:8000/index、www.freely.com:8000/cmdb这样。
4、模板引擎
Tornado中的模板语言和django中类似,模板引擎将模板文件载入内存,然后将数据嵌入其中,最终获取到一个完整的字符串,再将字符串返回给请求者。
Tornado =的模板支持“控制语句”和“表达语句”,控制语句是使用 {%
和 %}
包起来的 例如 {% if len(items) > 2 %}
。表达语句是使用 {{
和 }}
包起来的,例如 {{ items[0] }}
。
控制语句和对应的 Python 语句的格式基本完全相同。我们支持 if
、for
、while
和 try
,这些语句逻辑结束的位置需要用 {% end %}
做标记。还通过 extends
和 block
语句实现了模板继承。这些在 template
模块 的代码文档中有着详细的描述。
tornado模 板语言,无论for或者if,结尾都是end,不像django的endfor、endif;另外,tornado模板语言,取数据时跟python一模一样,如下面的取字典里的数据,可以直接dict[‘key‘],也可以dict.get(‘key‘,‘default‘);不像django里的item.1。
注:在使用模板前需要在setting中设置模板路径:"template_path" : "tpl"
1.基本使用
import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.render("index.html", list_info = [11,22,33],title=‘Mytitle‘) application = tornado.web.Application([ (r"/index", MainHandler), ]) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
<html> <head> <title>{{ title }}</title> </head> <body> <ul> {% for item in list_info %} <li>{{item)}}</li> {% end %} </ul> </body> </html>
表达式可以是任意的Python表达式, 包括函数调用. 模板代码会在包含以下对象 和函数的命名空间中执行 (注意这个列表适用于使用 RequestHandler.render
和 render_string
渲染模板的情况. 如果你直接在 RequestHandler
之外使用 tornado.template
模块, 下面这些很多都不存 在)
在模板中默认提供了一些函数、字段、类以供模板使用: escape: tornado.escape.xhtml_escape 的別名 xhtml_escape: tornado.escape.xhtml_escape 的別名 url_escape: tornado.escape.url_escape 的別名 json_encode: tornado.escape.json_encode 的別名 squeeze: tornado.escape.squeeze 的別名 linkify: tornado.escape.linkify 的別名 datetime: Python 的 datetime 模组 handler: 当前的 RequestHandler 对象 request: handler.request 的別名 current_user: handler.current_user 的別名 locale: handler.locale 的別名 _: handler.locale.translate 的別名 static_url: for handler.static_url 的別名 xsrf_form_html: handler.xsrf_form_html 的別名 其他方法
2.继承
<html> <body> <header> {% block header %}{% end %} </header> <content> {% block body %}{% end %} </content> <footer> {% block footer %}{% end %} </footer> </body> </html>
当我们扩展父模板layout.html时,可以在子模板index.html中引用这些块。
{% extends "layout.html" %} {% extends "filename.html" %} {% block header %} <h1>{{ header_text }}</h1> {% end %} {% block body %} <p>Hello from the child template!</p> {% end %} {% block footer %} <p>{{ footer_text }}</p> {% end %}
3、导入(include)
<div> <ul> <li>1024</li> <li>42区</li> </ul> </div>
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>title</title> <link href=http://www.mamicode.com/"{{static_url("css/common.css")}}" rel="stylesheet" /> </head> <body> <div class="pg-header"> {% include ‘header.html‘ %} </div> <script src=http://www.mamicode.com/"{{static_url("js/jquery-1.8.2.min.js")}}"></script> </body> </html>
4、自定义UIMethod以UIModule
a. 定义
# uimethods.py def tab(self): return ‘UIMethod‘
from tornado.web import UIModule from tornado import escape class custom(UIModule): def render(self, *args, **kwargs): return escape.xhtml_escape(‘<h1>hello world</h1>‘)
b. 注册
import tornado.ioloop import tornado.web from tornado.escape import linkify import uimodules as md import uimethods as mt class MainHandler(tornado.web.RequestHandler): def get(self): self.render(‘index.html‘) settings = { ‘template_path‘: ‘template‘, ‘static_path‘: ‘static‘, ‘static_url_prefix‘: ‘/static/‘, ‘ui_methods‘: mt, ‘ui_modules‘: md, } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8009) tornado.ioloop.IOLoop.instance().start()
c. 使用
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <link href=http://www.mamicode.com/"{{static_url("commons.css")}}" rel="stylesheet" /> </head> <body> <h1>hello</h1> {% module custom(123) %} {{ tab() }} </body>
5、模板的自动转义问题
Tornado默认会自动转义模板中的内容,把标签转换为相应的HTML实体。这样可以防止后端为数据库的网站被恶意脚本攻击。比如,你的网站中有一个评论部分,用户可以在这里添加任何他们想说的文字进行讨论。虽然一些HTML标签在标记和样式冲突时不构成重大威胁(如评论中没有闭<h1>标签),但<script>标签会允许攻击者加载其他的JavaScript文件,打开通向跨站脚本攻击、XSS或漏洞之门。
所有模板输出默认都会使用 tornado.escape.xhtml_escape
函数转义. 这个行为可以通过传递 autoescape=None
给 Application
或者 tornado.template.Loader
构造器来全局改变, 对于一个模板文件可以使 用 {% autoescape None %}
指令, 对于一个单一表达式可以使用 {% raw ...%}
来代替 {{ ... }}
. 此外, 在每个地方一个可选的 转义函数名可以被用来代替 None
.
方法一:是在Application构造函数中传递autoescape=None,另一种方法是在每页的基础上修改自动转义行为,如下所示:
{% autoescape None %}
{{ mailLink }}
这些autoescape块不需要结束标签,并且可以设置xhtml_escape来开启自动转义(默认行为),或None来关闭。
然而,在理想的情况下,你希望保持自动转义开启以便继续防护你的网站。因此,你可以使用{% raw %}指令来输出不转义的内容。
{% raw mailLink %}
5、静态文件和文件缓存
1、在应用配置 settings 中指定 static_path
选项来提供静态文件服务;
2、在应用配置 settings 中指定 static_url_prefix
选项来提供静态文件前缀服务;
3、在导入静态文件时用 {{static_url(‘XX.css‘)}}
方式实现主动缓存静态文件
Tornado中, 你可以通过在应用程序中指定特殊的 static_path
来提供静态文 件服务:
settings = { "static_path": os.path.join(os.path.dirname(__file__), "static"), "cookie_secret": "__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE_HERE__", "login_url": "/login", "xsrf_cookies": True, } application = tornado.web.Application([ (r"/", MainHandler), (r"/login", LoginHandler), (r"/(apple-touch-icon\.png)", tornado.web.StaticFileHandler, dict(path=settings[‘static_path‘])), ], **settings)
为了改善性能, 通常情况下, 让浏览器主动缓存静态资源是个好主意, 这样浏览器 就不会发送不必要的可能在渲染页面时阻塞的 If-Modified-Since
或 Etag
请求了. Tornado使用 静态内容版本(static content versioning) 来支持此项功能.
关于缓存的内容:http://www.cnblogs.com/_franky/archive/2012/07/05/2577141.html
为了使用这些功能, 在你的模板中使用 static_url
方法 而不是直接在你的HTML中输入静态文件的URL:
<html> <head> <title>FriendFeed - {{ _("Home") }}</title> </head> <body> <div><img src=http://www.mamicode.com/"{{ static_url("images/logo.png") }}"/></div> </body> </html>
因为参数 v
是基于文件内容的, 如果你更新一个文件并重启服务, 它将发送 一个新的 v
值, 所以用户的浏览器将会自动的拉去新的文件. 如果文件的内 容没有改变, 浏览器将会继续使用本地缓存的副本, 而不会从服务器检查更新, 显著的提高了渲染性能.
在生产中, 你可能想提供静态文件通过一个更优的静态服务器, 比如 nginx . 你可以配置任何web服务器识别通过 static_url()
提供的版本标签并相应的设置缓存头. 下面是我们在 FriendFeed 使用的nginx相关配置的一部分:
location /static/ { root /var/friendfeed/static; if ($query_string) { expires max; } }
6、Cookies 和 secure cookies
Cookie是由服务器端生成,发送给User-Agent(一般是浏览器),浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器(前提是浏览器设置为启用cookie)。Cookie名称和值可以由服务器端开发自己定义,这样服务器可以知道该用户是否是合法用户以及是否需要重新登录等,服务器可以设置或读取Cookies中包含信息,借此维护用户跟服务器会话中的状态。
cookie的作用
服务器可以利用Cookies包含信息的任意性来筛选并经常性维护这些信息,以判断在HTTP传输中的状态。Cookies最典型的应用是判定注册用户是否已经登录网站,用户可能会得到提示,是否在下一次进入此网站时保留用户信息以便简化登录手续,这些都是Cookies的功用。另一个重要应用场合是“购物车”之类处理。用户可能会在一段时间内在同一家网站的不同页面中选择不同的商品,这些信息都会写入Cookies,以便在最后付款时提取信息。
1.基本操作
Cookie的操作
简单来说,Cookie
是保存在浏览器的一个键值对,每次的HTTP请求都会携带 Cookie
。
获取所有的Cookies
self.cookies
设置Cookie
self.set_cookie(self, name, value, domain=None, expires=None, path="/", expires_days=None, **kwargs)
get_cookie, set_cookie普通的设置cookie, clear_cookie, clear_all_cookies是删除cookie。
可接受的参数描述:
参数 | 描述 |
---|---|
name |
Cookie的Key |
value |
Cookie的value |
domain |
生效的域名 |
expires |
以秒为过期时间,默认从 1970-01-01T00:00:10.000Z |
path |
生效路径 |
expires_days |
以天数过期时间,如果设置为 None 则关闭浏览器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!")
2、加密cookie(签名)
Cookie 很容易被恶意的客户端伪造。加入你想在 cookie 中保存当前登陆用户的 id 之类的信息,你需要对 cookie 作签名以防止伪造。Tornado 通过 set_secure_cookie 和 get_secure_cookie 方法直接支持了这种功能。 要使用这些方法,你需要在创建应用时提供一个密钥,名字为 cookie_secret。 你可以把它作为一个关键词参数传入应用的设置中:
设置一个加密Cookie所需要的参数:
self.set_secure_cookie(self, name, value, expires_days=30, version=None, **kwargs):
实例:
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!") application = tornado.web.Application([ (r"/", MainHandler), ], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
def _create_signature_v1(secret, *parts): hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) for part in parts: hash.update(utf8(part)) return utf8(hash.hexdigest()) # 加密 def _create_signature_v2(secret, s): hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) hash.update(utf8(s)) return utf8(hash.hexdigest()) def create_signed_value(secret, name, value, version=None, clock=None, key_version=None): if version is None: version = DEFAULT_SIGNED_VALUE_VERSION if clock is None: clock = time.time timestamp = utf8(str(int(clock()))) value = base64.b64encode(utf8(value)) if version == 1: signature = _create_signature_v1(secret, name, value, timestamp) value = b"|".join([value, timestamp, signature]) return value elif version == 2: # The v2 format consists of a version number and a series of # length-prefixed fields "%d:%s", the last of which is a # signature, all separated by pipes. All numbers are in # decimal format with no leading zeros. The signature is an # HMAC-SHA256 of the whole string up to that point, including # the final pipe. # # The fields are: # - format version (i.e. 2; no length prefix) # - key version (integer, default is 0) # - timestamp (integer seconds since epoch) # - name (not encoded; assumed to be ~alphanumeric) # - value (base64-encoded) # - signature (hex-encoded; no length prefix) def format_field(s): return utf8("%d:" % len(s)) + utf8(s) to_sign = b"|".join([ b"2", format_field(str(key_version or 0)), format_field(timestamp), format_field(name), format_field(value), b‘‘]) if isinstance(secret, dict): assert key_version is not None, ‘Key version must be set when sign key dict is used‘ assert version >= 2, ‘Version must be at least 2 for key version support‘ secret = secret[key_version] signature = _create_signature_v2(secret, to_sign) return to_sign + signature else: raise ValueError("Unsupported version %d" % version) # 解密 def _decode_signed_value_v1(secret, name, value, max_age_days, clock): parts = utf8(value).split(b"|") if len(parts) != 3: return None signature = _create_signature_v1(secret, name, parts[0], parts[1]) if not _time_independent_equals(parts[2], signature): gen_log.warning("Invalid cookie signature %r", value) return None timestamp = int(parts[1]) if timestamp < clock() - max_age_days * 86400: gen_log.warning("Expired cookie %r", value) return None if timestamp > clock() + 31 * 86400: # _cookie_signature does not hash a delimiter between the # parts of the cookie, so an attacker could transfer trailing # digits from the payload to the timestamp without altering the # signature. For backwards compatibility, sanity-check timestamp # here instead of modifying _cookie_signature. gen_log.warning("Cookie timestamp in future; possible tampering %r", value) return None if parts[1].startswith(b"0"): gen_log.warning("Tampered cookie %r", value) return None try: return base64.b64decode(parts[0]) except Exception: return None def _decode_fields_v2(value): def _consume_field(s): length, _, rest = s.partition(b‘:‘) n = int(length) field_value = rest[:n] # In python 3, indexing bytes returns small integers; we must # use a slice to get a byte string as in python 2. if rest[n:n + 1] != b‘|‘: raise ValueError("malformed v2 signed value field") rest = rest[n + 1:] return field_value, rest rest = value[2:] # remove version number key_version, rest = _consume_field(rest) timestamp, rest = _consume_field(rest) name_field, rest = _consume_field(rest) value_field, passed_sig = _consume_field(rest) return int(key_version), timestamp, name_field, value_field, passed_sig def _decode_signed_value_v2(secret, name, value, max_age_days, clock): try: key_version, timestamp, name_field, value_field, passed_sig = _decode_fields_v2(value) except ValueError: return None signed_string = value[:-len(passed_sig)] if isinstance(secret, dict): try: secret = secret[key_version] except KeyError: return None expected_sig = _create_signature_v2(secret, signed_string) if not _time_independent_equals(passed_sig, expected_sig): return None if name_field != utf8(name): return None timestamp = int(timestamp) if timestamp < clock() - max_age_days * 86400: # The signature has expired. return None try: return base64.b64decode(value_field) except Exception: return None def get_signature_key_version(value): value = utf8(value) version = _get_version(value) if version < 2: return None try: key_version, _, _, _, _ = _decode_fields_v2(value) except ValueError: return None return key_version 内部算法
签名Cookie的本质是:
写cookie过程:
- 将值进行base64加密
- 对除值以外的内容进行签名,哈希算法(无法逆向解析)
- 拼接 签名 + 加密值
读cookie过程:
- 读取 签名 + 加密值
- 对签名进行验证
- base64解密,获取值内容
签名后的cookie除了时间戳和一个 HMAC 签名还包含编码 后的cookie值. 如果cookie过期或者签名不匹配, get_secure_cookie
将返回 None
就像没有设置cookie一样
注:许多API验证机制和安全cookie的实现机制相同。
3、用户认证
当前已经通过认证的用户在每个请求处理函数中都可以通过 self.current_user
得到, 在每个模板中 可以使用 current_user
获得. 默认情况下, current_user
是 None
.
为了在你的应用程序中实现用户认证, 你需要在你的请求处理函数中复写 get_current_user()
方法来判断当前用户, 比如可以基于cookie的值. 这里有一个例子, 这个例子允许用户简单的通过一个保存在cookie中的特殊昵称 登录到应用程序中:
你可以使用 Python 装饰器(decorator) tornado.web.authenticated
要求用户登录. 如果请求方法带有这个装饰器 并且用户没有登录, 用户将会被重定向到 login_url
(另一个应用设置)
如果你使用 authenticated
装饰 post()
方法并且用户没有登录, 服务将返回一个 403
响应. @authenticated
装饰器是 if not self.current_user: self.redirect()
的简写. 可能不适合 非基于浏览器的登录方案.
import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): login_user = self.get_secure_cookie("login_user", None) if login_user: self.write(login_user) else: self.redirect(‘/login‘) class LoginHandler(tornado.web.RequestHandler): def get(self): self.current_user() self.render(‘login.html‘, **{‘status‘: ‘‘}) def post(self, *args, **kwargs): username = self.get_argument(‘name‘) password = self.get_argument(‘pwd‘) if username == ‘wupeiqi‘ and password == ‘123‘: self.set_secure_cookie(‘login_user‘, ‘Li‘) self.redirect(‘/‘) else: self.render(‘login.html‘, **{‘status‘: ‘用户名或密码错误‘}) settings = { ‘template_path‘: ‘template‘, ‘static_path‘: ‘static‘, ‘static_url_prefix‘: ‘/static/‘, ‘cookie_secret‘: ‘aiuasdhflashjdfoiuashdfiuh‘ } application = tornado.web.Application([ (r"/index", MainHandler), (r"/login", LoginHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
import tornado.ioloop import tornado.web class BaseHandler(tornado.web.RequestHandler): def get_current_user(self): return self.get_secure_cookie("login_user") class MainHandler(BaseHandler): @tornado.web.authenticated def get(self): login_user = self.current_user self.write(login_user) class LoginHandler(tornado.web.RequestHandler): def get(self): self.current_user() self.render(‘login.html‘, **{‘status‘: ‘‘}) def post(self, *args, **kwargs): username = self.get_argument(‘name‘) password = self.get_argument(‘pwd‘) if username == ‘wupeiqi‘ and password == ‘123‘: self.set_secure_cookie(‘login_user‘, ‘Li‘) self.redirect(‘/‘) else: self.render(‘login.html‘, **{‘status‘: ‘用户名或密码错误‘}) settings = { ‘template_path‘: ‘template‘, ‘static_path‘: ‘static‘, ‘static_url_prefix‘: ‘/static/‘, ‘cookie_secret‘: ‘aiuasdhflashjdfoiuashdfiuh‘, ‘login_url‘: ‘/login‘ } application = tornado.web.Application([ (r"/index", MainHandler), (r"/login", LoginHandler), ], **settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()
tornado源码分析:http://www.nowamagic.net/academy/detail/13321013
Tornado web框架