tornado处理请求报文

tornado是一个轻量、异步的web微框架,本文记录tornado怎样处理一个http请求报文。

Hello World

tornado使用RequestHandler类来处理http请求,所以在使用tornado来处理http请求时,自定义的类需要继承RequestHandler类。HTTP的方法,如:GET、PUT、DELETE、POST、HEAD等,都一一对应RequestHandler类中的方法。在使用tornado的web框架特性时一般是从tornado这个包中引入web模块里的相应类,web这个模块的源码请点击这里
  接下来看一下tornado是怎样来处理http的请求的。首先来写一个经典的Hello World程序,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tornado.web import RequestHandler, Application
from tornado.ioloop import IOLoop


class MainHandler(RequestHandler):
def get(self):
self.write('Hello World\n')


if __name__ == '__main__':
app = Application(
[
(r'/', MainHandler)
]
)
app.listen(port=8000, address='0.0.0.0')
IOLoop.current().start()

把程序启动后会监听在8000端口,使用curl来访问得到如下:

1
2
3
neal@neal-System-Product-Name:~$ curl http://172.20.12.174:8000
Hello World
neal@neal-System-Product-Name:~$

这个Hello World事例中首先从tornado.web这个模块中引入了RequestHandlerApplication两个类,RequestHandler类用来处理HTTP的请求,Application用来处理路由,这个类实例化后返回一个可调用的tornado.web.Application类,然后调用此类的listen方法使用程序监听在本地任意地址的8000端口,最后使用IOLoop.current().start()来把程序运行在起来循环处理用户请求。Tornado为了实现高并发和高性能,使用了这个IOLoop来处理socket的读写事件,IOLoop基于epoll,可以高效的响应网络事件。这是Tornado高效的保证。

在tornado的官方文档中把Request handlers分成了Entry pointsInputOutput等多个章节,这里只对InputOutput的某些方法进行讲解,更详细资料请看官方文档

Input

get_argument方法

此方法返回用户传递参数的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tornado.web import RequestHandler, Application
from tornado.ioloop import IOLoop


class ArgumentHandler(RequestHandler):
def get(self):
self.write(self.get_argument('name'))


if __name__ == '__main__':
app = Application(
[
(r'/arg', ArgumentHandler)
]
)
app.listen(port=8000, address='0.0.0.0')
IOLoop.current().start()

ArgumentHandler类是用户自定义类,此类继承RequestHandler,接着对get方法进行重写,这就是我们在使用tornado进行web开发时经常需要做的工作。HTTP协议定义的getpostdelete等方法在tornado中对应的同名方法是直接返回HTTPError(405),即是没有实现的方法,这些方法就需要程序员自己根据业务逻辑来实现。源码请参考。运行此代码再访问返回如下:

2016-09-08-get_argument

结果是直接把传递的参数name的值zhaochj直接返回。如果给name这个变量传递多个值呢?如下:

2016-09-08-get_argument-1.png

结果是得到了最后一个值。

get_arguments

此方法同样返回用户传递参数的内容,但与get_argument不同的是它能接收同名的的多个参数,并且返回一个list,测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tornado.web import RequestHandler, Application
from tornado.ioloop import IOLoop


class ArgumentHandler(RequestHandler):
def get(self):
self.write(self.get_argument('name'))


class ArgumentsHandler(RequestHandler):
def get(self):
self.write('{0}\n'.format(str(type(self.get_arguments('name')))))
self.write('{0}'.format(', '.join(self.get_arguments('name'))))


if __name__ == '__main__':
app = Application(
[
(r'/arg', ArgumentHandler),
(r'/args', ArgumentsHandler)
]
)
app.listen(port=8000, address='0.0.0.0')
IOLoop.current().start()

运行上边代码后访问得到如下结果:

2016-09-08-get_arguments.png

args

在实际编程中可能会需要捕获一个路径下的值,使用args就可以把捕获到一个路径下的所有内容,并会把这个值存放在一个位置参数中,通过访问args[0]便可访问,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tornado.web import RequestHandler, Application
from tornado.ioloop import IOLoop


class PathArgsHandler(RequestHandler):
def get(self, *args, **kwargs):
self.write('{0}'.format(args[0]))


if __name__ == '__main__':
app = Application(
[
(r'/path/args/(.*)', PathArgsHandler)
]
)
app.listen(port=8000, address='0.0.0.0')
IOLoop.current().start()

在正规中(.*)表示匹配任意内容,所以在URL中/path/args/后的所有内容都会被捕获,访问后结果如下:

![2016-09-08 17-25-41-args.png](/images/2016-09-08 17-25-41-args.png)

kwargs

args一样,kwargs可以捕获一个路径下的K/V键值数据,并存放在字典中,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tornado.web import RequestHandler, Application
from tornado.ioloop import IOLoop


class PathKwArgsHandler(RequestHandler):
def get(self, *args, **kwargs):
self.write('{0}'.format(kwargs['name']))


if __name__ == '__main__':
app = Application(
[
(r'/path/kwargs/(?P<name>.*)', PathKwArgsHandler)
]
)
app.listen(port=8000, address='0.0.0.0')
IOLoop.current().start()

(?P<name>.*)表示命名捕获,这是re的语法,请参照这里,程序运行后访问结果如下:

2016-09-08-kwargs.png

获取请求报文的其他内容

一个请求报文中包含了非常多的内容,如:method,uri,path,query,version,body等,详细请参考这里,以下事例说明怎样获取请求端的IP地址有body内容,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import json
from tornado.web import RequestHandler, Application
from tornado.ioloop import IOLoop


class RemoteIpHander(RequestHandler):
def get(self):
self.write('{0}'.format(self.request.remote_ip))


class BodyHandler(RemoteIpHander):
def post(self):
body = json.loads(self.request.body.decode())
self.write('{0}'.format(body['name']))


if __name__ == '__main__':
app = Application(
[
(r'/ip', RemoteIpHander),
(r'/body', BodyHandler)
]
)
app.listen(port=8000, address='0.0.0.0')
IOLoop.current().start()

RemoteIpHander类中直接调用self.request.remote_ip即可打印出客户端的IP地址,而BodyHandler类定义post方法,采用json.lodads来解析上传的json数据,注意最后的decode方法,因为通过客户端发送过来的数据是bytes,而json对象只能是str,所以需要decode来解码。程序运行后测试结果如下:

2016-09-08-remoteip.png

2016-09-08-postbody.png

RequestHandler这个类还有许多方法,详细信息参考官方文档

文章目录
  1. 1. Hello World
  2. 2. Input
    1. 2.1. get_argument方法
    2. 2.2. get_arguments
    3. 2.3. args
    4. 2.4. kwargs
    5. 2.5. 获取请求报文的其他内容
|