这篇我们将学习Tornado
的web基础。
用tornado写个hello world
我们使用tornado
编写一个简单的web页面。
1 | from tornado import web |
这段简洁的代码特别想Flask
的。
tornado中为什么不能写同步的方法
这是因为tornado
核心是一个单线程,当我们在一个请求中使用了同步代码的时候,会阻塞其他请求的。
1 | import time |
上面是两个url
,我们在第一个请求中做了一个延时的io
操作,现在我们看下直接访问第二个url
的耗时。
当我们先访问第一个阻塞url
的时候,再看下耗时:
tornado中的url配置
上面我们已经配置了url
,对于包含变量的url
我们可以使用下面的方式。
1 | class PeopleInfoHandler(web.RequestHandler): |
使用URLSpec
进行一些配置,我们先看下源码:
1 | class URLSpec(Rule): |
我们可以为每个url
配置一个名字,方便内部访问。
1 | urls = [ |
比如我们可以在访问一个url
的时候直接进行跳转
1 | class MainHandler(web.RequestHandler): |
我们可以在url
请求的时候传入一些初始化参数,再处理请求之前获得这些初始化参数。
1 | class PeopleIdHandler(web.RequestHandler): |
我们在配置路由的时候传入初始化数据people_db
。
这种访问初始化的一个应用场景:针对不同的url
访问不同的数据库。
小节知识点:
- 使用
reverse_url
通过参数名获得内部url
可以进行可变参数拼接,类比flask
的url_for
。 - 使用
redirect
重定向 - 可以给
Handler
传一个初始值
关于 define
,options
,parse_comand_line
这个小节我们学习下如何在启动python
模块的时候通过tornado
的option
模块传递一些参数。
1 | from tornado.options import define, options, parse_command_line |
看下define
的作用:
定义一些可以在命令行中传递的参数以及类型。
1 | define('port', default=8008, help="run on the given port", type=int) |
完成上面配置之后,需要调用parse_command_line
:
1 | options.parse_command_line() |
将运行python
模块的命令进行解析,获得define
中定义的各个参数,以属性值的形式放到options
中。
注意:
options
是一个类,全局只有一个
直接通过属性值获得参数值
1 | if __name__ == "__main__": |
完成上面配置可以直接运行脚本,访问服务。
1 | python options_test.py --port=8002 --debug=True |
除了从命令行获取到参数值,还可以通过parse_config_file
从文件中获得:
我们新建一个文件conf.fg
,输入我们要传递的参数。
1 | port=8002 |
通过parse_config_file
指定文件名:
1 | options.parse_config_file("conf.cfg") |
这样直接运行脚本即可访问服务。
RequestHandler常用方法
我们看下官方文档已经将常用方法进行了分类:常用方法分类
我们先看下入口方法:官方文档
第一个是initialize
方法,是初始化Handler
类的时候定义的方法,用于接受定义url
传入的参数。
1 | class ProfileHandler(RequestHandler): |
通过这个初始化方法之后将一些数据保存起来,供其他方法使用。
第二个方法是prepare
,是在所有真正请求处理方法调用之前调用的方法,initialize
是在prepare
之前调用的。我们可以在prepare
方法中打印日志,打开文件等操作。
Decorate this method with
gen.coroutine
or useasync def
to make it asynchronous
第三个方法是on_finish
,这个方法和prepare
是相反的,是所有请求结束之后调用的。我们可以在方法中进行关闭句柄,清理缓存等操作。
上面两个方法可以理解为Flask
的请求钩子。
还有就是一些 HTTP方法:
1 | def get(self, *args, **kwargs): |
看下输入方法有哪些:官方文档
输入方法主要处理传入的一些参数。
先看下get_query_argument
和get_query_arguments
两个方法。
get_query_argument
:
从请求的query string
返回给定name的参数的值.
如果没有提供默认值, 这个参数将被视为必须的, 并且当找不到这个 参数的时候我们会抛出一个 MissingArgumentError
异常.
如果这个参数在url中多次出现, 我们将返回最后一次的值.
返回值永远是unicode
.
get_query_arguments
:
返回指定name的参数列表.
如果参数不存在, 将返回空列表.
返回值永远是unicode
.
这两个方法获得的都是通过
url
传参。
接着我们看下get_argument
和get_arguments
两个方法。
这两个方法和上面两个类似,不过除了能够获得通过url
传递的参数外,还能获得post
方式的传参。
get_argument
:
返回指定的name参数的值.
如果没有提供默认值, 那么这个参数将被视为是必须的, 并且当 找不到这个参数的时候我们会抛出一个 MissingArgumentError
.
如果一个参数在url上出现多次, 我们返回最后一个值.
返回值永远是unicode.
get_arguments
:
返回指定name的参数列表.
如果url
传参外还通过post
方式传参,都会将参数放到列表。
如果参数不存在, 返回一个空列表.
返回值永远是unicode.
我们可以直接获取到所有的参数通过
self.request.arguments
想要获得参数我们还可以使用get_body_argument
和get_body_arguments
:
下面我们发送一个带有json
数据的post
请求:
1 | requests.post("http://127.0.0.1:8888/?name=tornado", json={ |
我们通过url
传递的参数是在arguments
,query
中,body
存的是byte
类型而body_arguments
为空。
这时候直接通过get_body_argument
会获得:
我们看下get_body_argument
的源码:
1 | def get_body_argument(self, name, default=_ARG_DEFAULT, strip=True): |
从源码看到是从request.body_arguments
获得参数。
但是我们从上面图中看到在传递json
数据的时候并没有将数据放到body_arguments
中,而是放到body
中。
我们现在从request.body
中获得数据。
那我们怎么使用get_body_argument
方法获得数据呢?
我们需要指定传输数据的form
类型。
1 | import requests |
输出方法:官方文档
使用set_status
方法设置返回的状态码:
- status_code (int) – 响应状态码. 如果
reason
是None
, 它必须存在于httplib.responses
. - reason (string) – 用人类可读的原因短语来描述状态码. 如果是
None
, 它会由来自httplib.responses
的reason填满.
使用write
返回数据:
我们可以调用多个write
:
1 | self.write("hello") |
返回的数据将是helloworld
。这是因为RequestHandler
是一个长连接如果不关闭会一直写。每调用一次将数据写入到缓冲区,最后将数据统一发送出去。
使用finish
方法结束HTTP
请求,结束响应:
我们在调用finish
方法之后就不能再使用write
了。因为连接已经被关闭了。
我们可以在finish
中传入一个字典 ,将会自动返回一个json
数据。
1 | self.finish({ |
我们使用redirect
进行重定向:
重定向到给定的URL(可以选择相对路径).
如果指定了 status
参数, 这个值将作为HTTP状态码; 否则 将通过 permanent
参数选择301 (永久) 或者 302 (临时). 默认是 302 (临时重定向).
我们使用write_error
进行错误页面的重写:
write_error
可能调用 write
, render
, set_header
,等 来产生一般的输出.