Tornado是什么?
Tornado 是使用Python开发的全栈式(full-stack)Web框架和异步网络库。通过非阻塞IO,Tornado可以处理数以万计的开放,是long polling、WebSockets和其他需要为用户维护长连接应用的理想选择。
Tornado跟其他主流的Web服务器框架(主要是Python框架)不同是采用epoll非阻塞IO,响应快速,可处理数千并发连接,特别适用于实时的Web服务。
Tornado主要分为四个部分:
- Web框架(包括RequestHandler,用于创建Web程序的基类,以及各种支持类)
- 实现HTTP的客户端和服务器端(HTTPServer和AsynHTTPClient)
- 一个异步网络库(IPLoop和IOStream)
- 一个协程库(tornado.gen),使得异步调用代码能够以更直接的方式书写,取代回调代码。
简单的Web服务
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
# 从命令行中读取命令
from tornado.options import define, options
define("port", default = 8000, help = "run on the given port", type= int)
# 请求处理函数类。当处理一个请求时,将这个类实例化,并调用与HTTP请求方法所对应的方法。
class IndexHandler(tornado.web.RequestHandler):
def get(self):
# RequestHandler内建方法get_argument从一个查询字符串中取得参数greeting的值;如果这个参数没有出现在查询字符串中,Tornado将使用get_argument的第二个参数作为默认值
greeting = self.get_argument('greeting', 'hello')
# RequestHandler的另一个有用的方法是write,它以一个字符串作为函数的参数,并将其写入到HTTP响应中。
self.write(greeting + ', friendly user!')
if __name__ == "__main__":
# 使用Tornado的options模块来解析命令行
tornado.options.parse_command_line()
# 创建了一个Tornado的Application类的实例。传递给Application类__init__方法的最重要的参数是handlers。它告诉Tornado应该用哪个类来响应请求
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
# 从这里开始的代码将会被反复使用:一旦Application对象被创建,我们可以将其传递给Tornado的HTTPServer对象,然后使用我们在命令行指定的端口进行监听(通过options对象取出。)最后,在程序准备好接收HTTP请求后,我们创建一个Tornado的IOLoop的实例
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
你可以在命令行里尝试运行这个程序以测试输出:
$ python hello.py --port=8000
现在你可以在浏览器中打开http://localhost:8000,或者打开另一个终端窗口使用curl测试我们的应用:
$ curl http://localhost:8000/
Hello, friendly user!
$ curl http://localhost:8000/?greeting=Salutations
Salutations, friendly user!
下面来逐步分析下:
1. tornado.options的工作流程:
# 从命令行中读取命令
from tornado.options import define, options
define("port", default = 8000, help = "run on the given port", type= int)
- 如果一个与define语句中同名的设置在命令行中被给出,那么它将成为全局options的一个属性。
python3 hello.py --port=8001
- 如果用户运行程序时使用了--help选项,程序将打印出所有你定义的选项以及你在define函数的help参数中指定的文本
(base) liuyandeMacBook-Air:python扩展学习 liuyan$ python3 hello.py --port=8000 --help
Usage: hello.py [OPTIONS]
Options:
--help show this help information
/Users/liuyan/Library/Python/3.8/lib/python/site-packages/tornado/log.py options:
--log-file-max-size max size of log files before rollover
(default 100000000)
--log-file-num-backups number of log files to keep (default 10)
--log-file-prefix=PATH Path prefix for log files. Note that if you
are running multiple tornado processes,
log_file_prefix must be different for each
of them (e.g. include the port number)
--log-rotate-interval The interval value of timed rotating
(default 1)
--log-rotate-mode The mode of rotating files(time or size)
(default size)
--log-rotate-when specify the type of TimedRotatingFileHandler
interval other options:('S', 'M', 'H', 'D',
'W0'-'W6') (default midnight)
--log-to-stderr Send log output to stderr (colorized if
possible). By default use stderr if
--log_file_prefix is not set and no other
logging is configured.
--logging=debug|info|warning|error|none
Set the Python log level. If 'none', tornado
won't touch the logging configuration.
(default info)
hello.py options:
--port run on the given port (default 8000)
- 如果用户没有为这个选项指定值,则使用default的值进行代替。端口默认8000。
- Tornado使用type参数进行基本的参数类型验证,当不合适的类型被给出时抛出一个异常。因此,我们允许一个整数的port参数作为options.port来访问程序。
(base) liuyandeMacBook-Air:python扩展学习 liuyan$ python3 hello.py --port=adv
Traceback (most recent call last):
File "hello.py", line 15, in <module>
tornado.options.parse_command_line()
File "/Users/liuyan/Library/Python/3.8/lib/python/site-packages/tornado/options.py", line 720, in parse_command_line
return options.parse_command_line(args, final=final)
File "/Users/liuyan/Library/Python/3.8/lib/python/site-packages/tornado/options.py", line 362, in parse_command_line
option.parse(value)
File "/Users/liuyan/Library/Python/3.8/lib/python/site-packages/tornado/options.py", line 586, in parse
self._value = _parse(value)
ValueError: invalid literal for int() with base 10: 'adv'
2. class IndexHandler,是Tornado的请求处理函数类。
# 请求处理函数类。当处理一个请求时,将这个类实例化,并调用与HTTP请求方法所对应的方法。
class IndexHandler(tornado.web.RequestHandler):
def get(self):
# RequestHandler内建方法get_argument从一个查询字符串中取得参数greeting的值;如果这个参数没有出现在查询字符串中,Tornado将使用get_argument的第二个参数作为默认值
greeting = self.get_argument('greeting', 'hello')
# RequestHandler的另一个有用的方法是write,它以一个字符串作为函数的参数,并将其写入到HTTP响应中。
self.write(greeting + ', friendly user!')
也可以定义post方法:
class WrapHandler(tornado.web.RequestHandler):
def post(self):
text = self.get_argument('text')
width = self.get_argument('width', 40)
self.write(textwrap.fill(text,int(width)))
请求时标识为post请求,并带上参数即可
curl http://localhost:8000/wrap -d text=Lorem+ipsum+dolor+sit+amet,+consectetuer+adipiscing+elit.
同理HTTP的所有方法都是可以定义的。
其中RequestHandler的常用方法有:
- 获取参数
RequestHandler.get_argument(name, strip=True) :从请求体和查询字符串中返回指定参数name的值,如果出现多个同名参数,则返回最后一个的值。
RequestHandler.get_arguments(name, strip=True) :从请求体和查询字符串中返回指定参数name的值,注意返回的是list列表(即使对应name参数只有一个值)。若未找到name参数,则返回空列表[]。
- 获取请求的其他信息
RequestHandler.request对象存储了关于请求的相关信息:
- method HTTP的请求方式,如GET或POST;
- host 被请求的主机名;
- uri 请求的完整资源标示,包括路径和查询字符串;
- path 请求的路径部分;
- query 请求的查询字符串部分;
- version 使用的HTTP版本;
- headers 请求的协议头,是类字典型的对象,支持关键字索引的方式获取特定协议头信息,例如:request.headers["Content-Type"]
- body 请求体数据;
- remote_ip 客户端的IP地址;
- files 用户上传的文件,为字典类型,型如:
{
"form_filename1":[<tornado.httputil.HTTPFile>, <tornado.httputil.HTTPFile>],
"form_filename2":[<tornado.httputil.HTTPFile>,],
...
}
tornado.httputil.HTTPFile是接收到的文件对象,它有三个属性:
filename 文件的实际名字,与form_filename1不同,字典中的键名代表的是表单对应项的名字; body 文件的数据实体; content_type 文件的类型。 这三个对象属性可以像字典一样支持关键字索引,如request.files["form_filename1"][0]["body"]。
3、Application对象中的参数handlers
# 使用Tornado的options模块来解析命令行
tornado.options.parse_command_line()
# 创建了一个Tornado的Application类的实例。传递给Application类__init__方法的最重要的参数是handlers。它告诉Tornado应该用哪个类来响应请求
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
handlers应该是一个元组组成的列表,其中每个元组的第一个元素是一个用于匹配的正则表达式,第二个元素是一个RequestHanlder类。Tornado在元组中使用正则表达式来匹配HTTP请求的路径。
定义如下get方法并在handlers中使用正则匹配
class ReverseHandler(tornado.web.RequestHandler):
def get(self,input):
self.write(input[::-1])
app = tornado.web.Application(
# 多个请求路径
handlers=[
(r"/reverse/(\w+)", ReverseHandler),
(r"/wrap", WrapHandler)
]
)
下列请求连接均可正确返回
curl http://localhost:8000/reverse/stressed
desserts
curl http://localhost:8000/reverse/slipup
pupils
4、HTTP返回的状态码
如果没有额外设置状态码的话,请求后会返回系统状态码。
- 404 Not Found
Tornado会在HTTP请求的路径无法匹配任何RequestHandler类相对应的模式时返回404(Not Found)响应码。
- 400 Bad Request
如果你调用了一个没有默认值的get_argument函数,并且没有发现给定名称的参数,Tornado将自动返回一个400(Bad Request)响应码。
- 405 Method Not Allowed
如果传入的请求使用了RequestHandler中没有定义的HTTP方法(比如,一个POST请求,但是处理函数中只有定义了get方法),Tornado将返回一个405(Methos Not Allowed)响应码。
- 500 Internal Server Error
当程序遇到任何不能让其退出的错误时,Tornado将返回500(Internal Server Error)响应码。你代码中任何没有捕获的异常也会导致500响应码。
- 200 OK
如果响应成功,并且没有其他返回码被设置,Tornado将默认返回一个200(OK)响应码。
例如:
[I 230203 18:32:59 web:2271] 200 GET /reverse/stressed (::1) 1.20ms
[I 230203 18:33:26 web:2271] 200 GET /reverse/stressed (::1) 0.44ms
[I 230203 18:33:31 web:2271] 200 GET /reverse/stressed (::1) 0.45ms
[I 230203 18:34:05 web:2271] 200 POST /wrap (::1) 0.58ms
[I 230203 18:40:33 web:2271] 200 GET /reverse/stressed (::1) 0.91ms
[I 230203 18:44:55 web:2271] 200 GET /reverse/111 (::1) 0.46ms
[W 230203 18:45:11 web:2271] 404 GET /re/111 (::1) 7.53ms
但是如果你想使用自己的方法代替默认的错误响应,你可以重写write_error方法在你的RequestHandler类中。
class IndexHandler(tornado.web.RequestHandler):
def get(self):
greeting =self.get_argument('greeting', 'hello')
self.write(greeting + ', friendly user!')
def write_error(self, status_code, **kwargs):
self.write("Gosh darnit, user! You caused a %d error." % status_code)
此时使用post方法访问,则会报出定义的错误。
curl -d foo=bar http://localhost:8000/
Gosh darnit, user! You caused a 405 error.(base)