Python- Tornado Web服务器基础学习一

423 阅读7分钟

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)