友情提示:
1.文中大多是总结性的文字, 涉及很多省略的前置基础知识,可以自行搜索酌情补充;
2.另,个人水平有限,难免出错误导,请谨慎阅读
3.可以参考github上详细的代码笔记, 一定会有收获
谈谈我在学习Tornado时的一些理解:
1.应用场景和C10k问题
1.常见的两种不适合采用多线程的应用场景:
- 1.用户量大,高并发; (如, 秒杀|抢票)
- 2.大量的HTTP持久连接; (有些网页"挂机"行为)
2.C10k(略,现在早已开始探讨C10M等了)
2.Tornado 特点:
- 1…一个轻量级的Web框架,其拥有异步非阻塞IO的处理方式。类似:Python web框架Web.py
- 2.作为Web服务器,Tornado有较为出色的抗负载能力.
- 3.诞生是为了提供一种 C10K问题 的解决方案, 注重性能
- 4.Tornado框架和服务器一起组成一个WSGI的全栈替代品。单独在WSGI容器中使用tornado网络框架或者tornaod http服务器,有一定的局限性,为了最大化的利用tornado的性能,官方推荐同时使用tornaod的网络框架和HTTP服务器.(注: 生产环境亦如此建议)
- 5.目前其他两大主流python web框架逐步的往支持异步的方向发展,且python3.x的版本开始提供异步的实现,这些都开始挤压Tornado的市场;
3.关于epoll
epoll 是一种实现 系统层网络通信的 解决方案.相比之前的解决方案, epoll有两大特点:
- 1.使用事件通知来代替遍历轮询;(只通知那些活跃的socket相关信号)
- 2.通过文件描述符fd来标记socket,降低复制socket资源的开销;
- 3.是Linux 内核2.6+之后才具有的功能, 利用CPU和内存共享的那一块儿特殊内存来实现底层事件通知.
- 4.epoll主要是用来解决网络IO的并发问题,所以Tornado的异步编程也主要体现在网络IO的异步上,即异步Web请求。
4.利用HTTP协议向服务器传递参数有那几种途径?
- 1.URL路径参数;
- 2.URL查询参数;
- 3.请求头;(cookies, 一般会把cookies单独拿出来讲解)
- 4.请求体;(一般又细分为: 1.普通表单,媒体文件,原生字符串; 2.以及form表单, json,xml; 可能处理起来有细微差别, django和tornado都是)
补充: 1.多值参数取值的问题? 取单个存在后面覆盖前面的特点; 一般取多个和取单个会提供不同的方法;
5.关于传递参数中的几个小问题:
- 1.什么时候设置default,什么时候不设置default?
具体看需求. 比如,如果是对返回的列表进行分页,前端没有传递per_page或page_num等约定的参数的时候, 我们应该设置默认值.
当某些参数是前端必传的,而没有传递的时候我们可以采用不设置默认值转而抛出异常的方式,进入异常处理的页面逻辑, 比如404自定义页面;
- 2.default的默认值_ARG_DEFAULT是什么?
翻看源码发现: _ARG_DEFAULT=object() , 至于为什么这样设置呢? 主要是为了后面在方法内部进行逻辑校验等处理的时候,能更好的区分
该内容是由前端参数传递过来的呢还是该方法取到的默认值. 所以必须要 _ARG_DEFAULT 和 传过来的arg 有一定的区分度. 但是, 我们知道
我们没法控制前端通过该参数传递什么值到后端, 但是又要做到区分两者是来自哪里. 所以解决方案一般是: is 身份判断符来判断. 所以, 这里就
不能采用 不可变类型来做区分.
- 3.什么时候使用strip,亦即什么时候要截断空白字符,什么时候不需要?
当对传入参数值包含空格\t\tab等空白字符需要忽略的时候则需要截断, 处理误输入;
当对传入的参数值有严格完整性的要求时,就不截断空白字符.
6.其他请求信息(一般python web都会对一些常用的特殊的请求信息, 做一个单独的归类)
RequestHandler.request 对象存储了关于请求的相关信息,具体属性有:
- method HTTP的请求方式,如GET或POST;
- host 被请求的主机名;
- uri 请求的完整资源标示,包括路径和查询字符串;
- path 请求的路径部分;
- query 请求的查询字符串部分;
- version 使用的HTTP版本;
- headers 请求的协议头,是类字典型的对象,支持关键字索引的方式获取特定协议头信息,例如:
request.headers["Content-Type"]
- body 请求体数据;
- remote_ip 客户端的IP地址;
- files 用户上传的文件,为字典类型
{
"form_filename_1":[<tornado.httputil.HTTPFile>, <tornado.httputil.HTTPFile>],
"form_filename_2":[<tornado.httputil.HTTPFile>,],
}
tornado.httputil.HTTPFile是接收到的文件对象,它有三个属性:
- filename 文件的实际名字,与form_filename1不同,字典中的键名代表的是表单对应项的名字;
- body 文件的数据实体;
- content_type 文件的类型。 这三个对象属性可以像字典一样支持关键字索引,如
request.files["form_filename1"][0]["body"]
。
7.关于字符串文本和二进制文本在不同操作系统下处理的情况
- 1.python写入文件的操作 mode 有 wb 和 w两种.
- 2.在不同OS下有不同的方式表示换行符, Linux(\n), MacOS(\n, 备注:之前是\r), Windows(\r\n)
- 3.所以在二进制写的情况下, 不同操作系统都会把\n解析成\n本身, 这样导致
aaa\nbbb
这样的写法在windows下也是不换行的. - 4.在普通文本写的情况下, windows看到\n的时候自动解析成
\r\n
, 会有换行的效果. - 5.可以在不同OS下使用python解释器来验证上述情况
补充: python操作文件, 在调用 close
的方法的时候才将内存中的内容写入磁盘保存起来; 但是我们可以调用flush
方法,在一个文件句柄在写的时候, 另外一个文件句柄就可以同步查看了.
8.tornado的RequestHandler类封装方法的一些说明
- 1.
self.write(chunk)
方法- 1.
self.write(chunk)
该方法是默认封装的方法, 不过该方法的作用是将 chunk 数据写入 缓冲区, 并不是直接做为response,这一点要注意; - 2.基于上述1的描述, 可以推出在同一个请求的method中可以多次(连续或非连续)使用 write 方法来追加数据;
- 3.那么多次追加的时候是否有先后顺序呢? 答案显然是有, 但是需要先划分write是写入响应头还响应体.
- 3.1 对于写入响应头write(header)或响应体write(body)的数据,write的先后顺序并不会影响最终整个响应报文的顺序.
- 1.
# 1 写法一
write(header)
write(body)
# 2 写法二
write(body)
write(header)
# 注: 这两者的先后对报文最终"组装"并无影响;
- 3.2 对于写入响应头不同item的顺序, 会影响报文的顺序
# 1.写法一
write(header1)
write(header2)
# 2.写法二
write(headr2)
write(header2)
- 3.2 对于写入响应体不同item的顺序, 会影响报文的顺序
# 略
-
2.操作json数据(最好的方式, 看write源码)
- 1.可以直接给write传入dict, 会自动帮我们设置响应头的Content-Type: application/json; charset=UTF-8
- 2.如果手动转换成json数据然后传入write, 则设置响应头的Content-Type: text/html; charset=UTF-8
- 3.顺便补充一下, 平时我们更多的时候只关注Content-Type的前半部分, 但是不要忘了
charset=UTF-8
也可能是Content-Type
一部分; - 4.再补充一下, 一般说响应状态码,我们更多的是关注 code的number部分, 但是
Enum Name
,如400 BAD_REQUEST
才是完整的
-
3.
set_header(name, value)
和set_default_headers()
- 3.1 注意:在HTTP处理方法中使用set_header()方法会覆盖掉在set_default_headers()方法中设置的同名header。
-
4.
set_status(status_code, reason=None)
- 4.1 当前浏览器比较智能,在自定义不存在的状态码的时候, 浏览器会补充上
xxx Unknown
- 4.2 两个参数, 一个是 int, 一个是 str
- 4.1 当前浏览器比较智能,在自定义不存在的状态码的时候, 浏览器会补充上
-
5.
redirect
-
6.
send_error(status_code=500, **kwargs)
- 抛出HTTP错误状态码status_code,默认为500,kwargs为可变命名参数。使用send_error抛出错误后tornado会调用write_error()方法进行
处理,并返回给浏览器处理后的错误页面。
- 抛出HTTP错误状态码status_code,默认为500,kwargs为可变命名参数。使用send_error抛出错误后tornado会调用write_error()方法进行
小部分代码示例, 更多代码请点击这里:
# file: 08-response_.py
# *-* coding:utf8 *-*
"""
涉及知识点:
1.write顺序的讲解
2.操作json响应
3.设置响应头
4.状态码
5.redirect, 访问: /login/
6.重点: 异常处理
"""
import json
import tornado.web
import tornado.httpserver
import tornado.ioloop
from tornado.web import RequestHandler
from tornado.options import options
options.define("port", default=8000, type=int, help="run server on the given port")
class IndexHandler(RequestHandler):
def get(self):
print "enter into func: %s" % "get"
# 1.顺序的讲解
# self.write("response 1 </br>")
# self.write("response 2 </br>")
# self.write("response 3 </br>")
# 2.操作json
user = {
"nick_name": "laowang",
"age": 18
}
# 自己处理成json数据在传递给write(),看看有什么不同?
# 自己处理成json, 则 会帮我们设置成 , 响应头: Content-Type: text/html; charset=UTF-8
json_str = json.dumps(user)
self.write(json_str)
# write 是可以直接接收字典数据的, 会自动帮我们处理成json数据. 配置响应头: Content-Type: application/json; charset=UTF-8
# self.write(user)
# 3.设置响应头
# 会先调用 set_default_headers 方法再调用 HTTP的method方法, 如果在 HTTP的method方法中操作了 set_header 方法,且该方法和
# set_default_headers 方法都操作了 相同的 响应头的值, 那么 set_header 会覆盖 set_default_headers 的相关设置.
self.set_header("Content-Type", "application/json; charset=UTF-32") # UTF-32
# 4.状态码
# self.set_status(666)
self.set_status(200) # 自己测试了一下, 会自动帮我们补充上
def set_default_headers(self):
print "enter into func: %s" % "set_default_headers"
self.set_header("Content-Type", "application/json; charset=UTF-8") # UTF-8
class LoginHandler(RequestHandler):
def get(self):
self.write('<form method="post"><input type="submit" value="登陆"></form>')
def post(self):
self.redirect("/") # 一般不直接写path, 而是通过reverse_url来转换
class MyErrorDemoHandler(RequestHandler):
def get(self):
"""
self.write("error index")
# 注意:默认的write_error()方法不会处理send_error抛出的kwargs参数,即上面的代码中content="出现404错误"是没有意义的。
self.send_error(404, content="出现404错误")
# 注意:使用send_error()方法后就不要再向输出缓冲区写内容了!
self.write("error end") # 不会处理
"""
err_code = self.get_argument("code", u"") # 注意返回的是unicode字符串,下同
err_title = self.get_argument("title", u"")
err_content = self.get_argument("content", u"")
if err_code:
try:
err_code = int(err_code)
except Exception:
err_code = 400
self.send_error(err_code, title=err_title, content=err_content)
else:
self.write("error index")
def write_error(self, status_code, **kwargs):
self.write(u"<h1>你来到一片无人区</h1>")
self.write(u"<p>错误名:%s</p>" % kwargs["title"])
self.write(u"<p>错误详情:%s</p>" % kwargs["content"])
if __name__ == '__main__':
options.parse_command_line()
app = tornado.web.Application(
[
(r"/", IndexHandler),
(r'/login/', LoginHandler),
(r'/error/', MyErrorDemoHandler),
],
debug=True
)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.current().start()