一、tornado介绍
-
轻量级web框架
-
异步非阻塞IO处理方式
-
出色的抗负载能力
-
优异的处理性能,不依赖多进程/多线程,一定程度上解决GIL问题
-
WSGI全栈替代产品,推荐同时使用其web框架和HTTP服务器
-
socket连接建立过程
-
WSGI把应用(Application)和服务器(Server)结合起来,Tornado既可以是WSGI应用也可以是WSGI服务。
既是WebServer也是WebFramework.
-
tornado生命周期
二、初步简单使用
-
httpserver形式
from tornado import web # tornado的基础web框架模块 from tornado import ioloop # tornado的核心IO循环模块,封装了linux的epoll和BSD的kqueue from tornado import httpserver # 相当于Django中的视图 # 一个业务处理类 class IndexHandler(web.RequestHandler): """处理get请求""" def get(self, *args, **kwargs): self.write("hello world") # 做出响应数据 if __name__ == '__main__': app = web.Application([ (r"/", IndexHandler) ]) # 实例化http服务对象 http_server = httpserver.HTTPServer(app) # 绑定IP和端口 http_server.listen(8000) ioloop.IOLoop.current().start()
-
多进程形式
from tornado import web # tornado的基础web框架模块 from tornado import ioloop # tornado的核心IO循环模块,封装了linux的epoll和BSD的kqueue from tornado import httpserver # 相当于Django中的视图 # 一个业务处理类 class IndexHandler(web.RequestHandler): """处理get请求""" def get(self, *args, **kwargs): self.write("hello world") # 做出响应数据 if __name__ == '__main__': app = web.Application([ (r"/", IndexHandler) ]) # 实例化http服务对象 http_server = httpserver.HTTPServer(app) # 启动多个进程, 将服务器绑定到指定的端口 http_server.bind(8000) http_server.start(5) # 开启5个进程,不写默认启动一个进程 """ 多进程存在问题: 1.每个子进程都会从父进程中复制一份IOLoop实例,如果在创建子进程前修改了IOLoop,会影响所有子进程 2.所有的进程都是由一个命令启动的,无法做到在不停止服务的情况下修改代码 3.所有进程共享一个端口,想要分别监控很困难 """ ioloop.IOLoop.current().start()
-
常用形式
import tornado.web # tornado的基础web框架模块 import tornado.ioloop # tornado的核心IO循环模块,封装了linux的epoll和BSD的kqueue # 相当于Django中的视图 # 一个业务处理类 class IndexHandler(tornado.web.RequestHandler): """处理get请求""" def get(self, *args, **kwargs): self.write("hello world") # 做出响应数据 if __name__ == '__main__': """实例化一个app对象 Application:是tornado web框架的核心应用类,是与服务器对应的接口。 里面保存了路由映射表,有一个listen方法用来创建一个http服务的实例,并绑定了端口""" app = tornado.web.Application([ (r"/", IndexHandler) ]) app.listen(8000) """ IOLoop.current():返回当前线程IOLoop实例 IOLoop.start():起动IOLoop实例的I/O循环,同时开启了监听 """ tornado.ioloop.IOLoop.current().start()
-
带options使用形式(三种使用方式)
server_options.py
from tornado import web # tornado的基础web框架模块 from tornado import ioloop # tornado的核心IO循环模块,封装了linux的epoll和BSD的kqueue from tornado import httpserver from tornado import options import config """ 定义变量 options.define( name="port", # 变量名,唯一性 default=8000, # 设置默认值 type=int, # 数据类型 help=None, # 选项变量的提示信息 metavar=None, # multiple=False, # 设置选项变量是否可以为多个值 group=None, callback=None ) options.options: 全局的options对象,所有定义的选项变量都会作为改对象的属性 """ # options.define(name="port", type=int, default=8000) # (第一、二种方式) # options.define(name="list", type=str, default=[], multiple=True) # (第一、二种方式) # 相当于Django中的视图 # 一个业务处理类 class IndexHandler(web.RequestHandler): """处理get请求""" def get(self, *args, **kwargs): self.write("hello world") # 做出响应数据 if __name__ == '__main__': # options.options.logging = None # 关闭日志 # options.parse_command_line() # 转换命令行参数,并保存到options.options(第一种方式) # options.parse_config_file("config") # 去配置文件中取参数(第二种方式.txt文档) # print(options.options.list) #(第二种方式) print(config.options.get("list")) # 直接去py文件中取,(第三种方式) app = web.Application([ (r"/", IndexHandler) ]) http_server = httpserver.HTTPServer(app) # 绑定IP和端口 # http_server.listen(options.options.port) # (第一种方式) # http_server.bind(options.options.port) # 第二种方式 # http_server.start(1) # 第二种方式 http_server.bind(config.options.get("port")) # 第三种方式 http_server.start(1) # 第三种方式 ioloop.IOLoop.current().start() """ 第一种方式必须在终端启动: 例如:python server_options.py --port=9000 --list=good,nice,cool """
config.txt(方式二)
port = 7000 list = ["good", "nice", "well"]
config.py(方式三)
options = { "port": 7000, "list": ["good", "nice", "well"] }
三、异步IO
-
协程实现异步IO
import time import threading # 版本一 gen = None def longIo(): def run(): global gen print("开始延时操作。。。") time.sleep(5) try: gen.send("我发送的数据") except StopIteration as e: pass print("结束延时操作。。。") threading.Thread(target=run).start() def genCoroutine(func): def wrapper(*args, **kwargs): global gen gen = func(*args, **kwargs) next(gen) return wrapper @genCoroutine def reaA(): print("开始执行reaA") res = yield longIo() print("接收的数据", res) print("结束执行reaA") def reaB(): print("开始执行reaB") time.sleep(2) print("结束执行reaA") def main(): reaA() reaB() main() # 版本二 def longIo(): print("开始延时操作。。。") time.sleep(5) print("结束延时操作。。。") yield "我发送的数据" def genCoroutine(func): def wrapper(*args, **kwargs): gen1 = func(*args, **kwargs) # reqA生成器 gen2 = next(gen1) # longIo的生成器 def run(g): res = next(g) # 拿到longIo的返回数据 try: gen1.send(res) # 返回给reqA数据 except Exception as e: pass threading.Thread(target=run, args=(gen2,)).start() return wrapper @genCoroutine def reaA(): print("开始执行reaA") res = yield longIo() print("接收的数据", res) print("结束执行reaA") def reaB(): print("开始执行reaB") time.sleep(2) print("结束执行reaA") def main(): reaA() reaB() main()
输出结果:
开始执行reaA
开始延时操作。。。
开始执行reaB
结束执行reaA
结束延时操作。。。
接收的数据 我发送的数据
结束执行reaA -
异步IO加回调函数
import time import threading def longIo(callback): def run(cb): print("开始延时操作。。。") time.sleep(5) print("结束延时操作。。。") cb("回调函数接收值") threading.Thread(target=run, args=(callback, )).start() def finsh(data): """ 回调函数 :param data: :return: """ print("接收到longIo的值", data) def reaA(): print("开始执行reaA") longIo(finsh) print("结束执行reaA") def reaB(): print("开始执行reaB") longIo(finsh) print("结束执行reaB") def reaC(): print("开始执行reaC") longIo(finsh) print("结束执行reaC") reaA() reaB() reaC()
输出结果:
开始执行reaA
开始延时操作。。。
结束执行reaA
开始执行reaB
开始延时操作。。。
结束执行reaB
开始执行reaC
开始延时操作。。。
结束执行reaC
结束延时操作。。。
结束延时操作。。。
接收到longIo的值 回调函数接收值
结束延时操作。。。
接收到longIo的值 回调函数接收值
接收到longIo的值 回调函数接收值
四、tornado基本框架结构
mysql_db.py
import pymysql
# 单例模式的装饰器
# def singleton(cls, *args, **kwargs):
# instance = {}
#
# def _singleton():
# if cls not in instance:
# instance[cls] = cls(*args, **kwargs)
# return instance[cls]
# return _singleton
class LongMySQl(object):
def __init__(self, host, user, passwd, dbName):
self.host = host
self.user = user
self.passwd = passwd
self.dbName = dbName
def connect(self):
self.db = pymysql.connect(self.host, self.user, self.passwd, self.dbName)
self.cursor = self.db.cursor(cursor=pymysql.cursors.DictCursor)
def close(self):
self.cursor.close()
self.db.close()
def get_one(self, sql):
res = None
try:
self.connect()
self.cursor.execute(sql)
res = self.cursor.fetchone()
self.close()
except Exception:
print("查询失败")
return res
def get_all(self, sql):
res = ()
try:
self.connect()
self.cursor.execute(sql)
res = self.cursor.fetchall()
self.close()
except Exception:
print("查询失败")
return res
#
# def get_all_obj(self, sql, tableName, *args):
# resList = []
# filedsList = []
# if (len(args) > 0):
# for item in args:
# filedsList.append(item)
# else:
# filedsSql = "select COLUMN_NAME from information_schema.COLUMNS where table_name = '%s' and table_schema = '%s'"%(
# tableName,
# self.dbName
# )
# fields = self.get_all(filedsSql)
# for item in fields:
# filedsList.append(item[0])
#
# res = self.get_all(sql)
# for item in res:
# obj = {}
# count = 0
# for x in item:
# obj[filedsList[count]] = x
# count += 1
# resList.append(obj)
# return resList
def insert(self, sql):
return self.__edit(sql)
def update(self, sql):
return self.__edit(sql)
def delete(self, sql):
return self.__edit(sql)
def __edit(self, sql):
count = 0
try:
self.connect()
self.cursor.execute(sql)
self.db.commit()
count = self.cursor.lastrowid
self.close()
except Exception:
print("事务提交失败")
self.db.rollback()
return count
orm.py(只有增加功能)
from application import Application
class ORM(Application):
def save(self):
tableName = (self.__class__.__name__).lower()
fieldsStr = valuesStr = "("
for field in self.__dict__: # {'name': 1, 'gender': 2}
fieldsStr += (field + ",")
if isinstance(self.__dict__[field], str):
valuesStr += ("'" + self.__dict__[field] + "',")
else:
valuesStr += (str(self.__dict__[field]) + ",")
fieldsStr = fieldsStr[:len(fieldsStr) - 1] + ")"
valuesStr = valuesStr[:len(valuesStr) - 1] + ")"
sql = "insert into " + tableName + " " + fieldsStr + " values" + valuesStr
self.db.insert(sql)
def delete(self):
pass
def update(self):
pass
@classmethod
def all(self):
print("all")
def filter(self):
pass
models.py
from db.orm import ORM
class Student(ORM):
def __init__(self, sname, gender):
self.name = sname
self.gender = gender
server.py
from tornado import ioloop # tornado的核心IO循环模块,封装了linux的epoll和BSD的kqueue
from tornado import httpserver
import application
import config
if __name__ == '__main__':
app = application.Application()
http_server = httpserver.HTTPServer(app)
http_server.bind(config.options.get("port")) # 第三种方式
http_server.start(1) # 第三种方式
ioloop.IOLoop.current().start()
config.py
import os
BASE_DIRS = os.path.dirname(__file__)
# 参数
options = {
"port": 8000
}
# 数据库配置
mysql = {
"host": "127.0.0.1",
"user": "root",
"passwd": "",
"dbName": "db1"
}
# 配置
settings = {
"static_path": os.path.join(BASE_DIRS, "static"),
"template_path": os.path.join(BASE_DIRS, "templates"),
# """
# 加密cookie
# """
"cookie_secret": "G0SAhaKwQ6eU/lqXoW+b3g/at/cy1EKDluw/JcFlYtA=",
# """
# 设置tornado是否在调试模式下
# 为Ture时:
# 1.在Ture下可以自动重启(有改动时)
# 2.取消缓存编译的模板(页面缓存)
# 3.取消缓存静态文件的hash值(CSS样式改动的缓存)
# 4.提供追踪信息()
# """
"debug": False,
# """
# 为Ture时:
# 仅仅使用自动重启
# """
"autoreload": True,
# """
# 为Fale:
# 仅仅取消缓存编译的模板
# """
"compiled_template_cache": False,
# """
# 为Fale:
# 仅仅取消缓存静态文件的hash值
# """
"static_hash_cache": False,
# """
# 为True:
# 仅仅提供追踪信息
# """
"serve_traceback": True,
"xsrf_cookie": True,
"login_url": "/login"
}
application.py
from db.mysql_db import LongMySQl
from tornado import web
from views import index
import config
import os
class Application(web.Application):
def __init__(self):
handlers = [
# (r"/", index.IndexHandler),
(r"/getone", index.GetOne, {"first": "1", "second": "2"}), # 传参
web.url(r"/parameter", index.ParameterHandler, name="parameter"), # 反向解析
(r"/ret_json", index.JsonHandler), # json
(r"/ret_json2", index.Json2Handler), # json2
(r"/header", index.HeaderHandler), # 响应头
(r"/status", index.StatusHandler), # 状态码
(r"/redirect", index.RedirectHandler), # 重定向
(r"/errostatus", index.ErrorHandler), # 抛出错误
(r"/re/(\w+)", index.ReHandler), # 正则匹配
# (r"/re/(?P<p1>\w+)", index.ReHandler), # 正则匹配
(r"/reget", index.ReGet), # get请求获取参数
(r"/studentdb", index.StudentsHandler), # 数据库操作
(r"/cookie", index.CookieHandler), # cookie
(r"/sccookie", index.ScCookieHandler), # 加密cookie
(r"/cookienum", index.CookieCountHandler), # cookie记录访问次数
(r"/xsrf", index.XsrfHandler), # xsrf
(r"/setxsrf", index.SetXsrfHandler), # xsrf
(r"/login", index.LoginHandler), # xsrf
(r"/auth", index.AuthHandler), # auth认证
# (r"/callbackasy", index.CallbackAsy), # 回调函数异步请求
(r"/genasy", index.GenvenletAsy), # 回调函数异步请求
(r"/genasy2", index.GenvenletAsy2), # 回调函数异步请求
(r"/chat", index.ChatHandler), # websocket
(r"/home", index.HomeHandler), # 聊天home页面
(r"/(.*)$", index.StaticFileHandler,
{"path": os.path.join(config.BASE_DIRS, "static/html"), "default_filename": "main.html"}), # 主页
]
super(Application, self).__init__(handlers, **config.settings) # 执行web.Application的__init__方法
self.db = LongMySQl(
config.mysql.get("host"),
config.mysql.get("user"),
config.mysql.get("passwd"),
config.mysql.get("dbName"),
)
index.py(*****)
from tornado.httpclient import AsyncHTTPClient
from tornado.web import RequestHandler
# from tornado.httputil import HTTPFile
from tornado.websocket import WebSocketHandler
from tornado import web
import config
import tornado
import json
import os
"""
视图:
request对象:
self.request.XXX
request.method
request.post
request.uri
request.host
request.path
request.query # 请求参数部分
request.version # HTTP版本
request.headers # 字典类型
request.body # 请求体数据
request.remote_ip # 客户端ip
request.files # 用户上传的文件
响应:
self.write("xxxx") # 可以写多个
self.finish() # 在finish下边就不要再写write
请求方式:
get, post, delete, put, patch,
head(类似于get请求,只不过响应中没有具体的内容,用户获取报头),
options(返回URL支持的所有HTTP方法)
请求函数:
initialize()
prepare() # 在请求方法之前执行 类似于中间件
set_default_headers()
write_error()
on_finish() # 在请求处理后执行,进行资源清理释放,或者日志处理
执行顺序:
在正常情况下:set_default_headers,initialize,prepare,(HTTP方法),on_finish
在抛出错误时:set_default_headers,initialize,prepare,(HTTP方法),set_default_headers,write_error,on_finish
"""
"""
模板:
1.配置模板路径
2.渲染并返回:self.render(),self.render("index.html", num=100, dit=dict, **dict),
3.语法:
{{var}},{{a+b}}(可以是表达式)
{{dit["name"]}} # 字典取值
4.条件语句:
{% if xxx %}
{% elif xxx %}
{% else %}
{% end %}
5.循环:
{" for i in arr "}
{" end "}
6.函数:
static_url():
<link rel="stylesheet" href="{{static_url('css/my_css.css')}}">
优点: 创建了一个基于文件内容的hash值,并将其添加到URL末尾,这个hash值总能保证加载的总是最新版本
自定义函数:
def sum(a, b):
return a + b
self.render(),self.render("index.html", sum=sum)
{{sum(10,20)}}
7.转义
raw:
str = "<h1>nihao<h1\>"
{% raw str %}
autoescape:
{% autoescape None %}
{{ str }}
在配置中修改:
setting中添加:
"autoescape": None
{{escape(str)}}开启转义
8.继承:
{% extends "base.html" %}
{% block main %}
{% end %}d
9.静态文件
static_path
StaticFileHandler: 是tornado预制的用来提供静态资源的handler
可以通过tornado.web.StaticFileHandler
(r"/(.*)$", StaticFileHandler, {
"path": os.path.join(config.BASE_DIRS, "static/html"),
"default_filename": "index.html"
}), # 指定访问静态文件路径,和默认文件(写在所有路由下面)
10.数据库:
tornado未带ORM
安全cookie:
setting中设置:"cookie_secret" = "xxxxxxxxxxx" # 用base64与uuid生成
ret = base64.b64encode(uuid.uuid4().bytes+uuid.uuid4().bytes)
异步:
from tornado.httpclient import AsyncHTTPClient
from tornado.httpclient import HTTPResponse, HTTPRequest
因为epoll主要用来解决网络IO的并发问题,所以Tornado的异步也是主要体现在网络的IO异步上,即异步web请求
AsyncHTTPClient: 提供异步web请求客户端,用来进行异步web请求
fetch(request, callback = None) 用于执行一个web请求,并异步响应返回一个HTTPResponse
request可以是一个url, 也可以是一个HTTPRequest对象
"""
class IndexHandler(RequestHandler):
"""处理get请求"""
def get(self, *args, **kwargs):
url = self.reverse_url("parameter")
self.write("hello world <a href='{}'>到反向解析界面</a>".format(url)) # 做出响应数据
class GetOne(RequestHandler):
"""
接收参数,写死的参数
"""
def initialize(self, first, second):
"""
接收参数(在执行get之前执行)
:return:
"""
self.first = first
self.second = second
def get(self, *args, **kwargs):
print(self.first, self.second)
self.write("hello world")
class ParameterHandler(RequestHandler):
"""
获取用户传参,反向解析url
"""
def get(self, *args, **kwargs):
page = self.get_query_argument("page") # 获取用户传参
self.write("我是反向解析url过来的")
class JsonHandler(RequestHandler):
"""
返回json数据
"""
def get(self, *args, **kwargs):
per = {
"code": 1001,
"msg": ""
}
per_json = json.dumps(per)
self.set_header("Content-Type", "application/json;charset=UTF-8") # 设置响应头
"""返回的Content-Type为text/html"""
self.write(per_json)
class Json2Handler(RequestHandler):
"""
返回json数据
"""
def get(self, *args, **kwargs):
per = {
"code": 1001,
"msg": ""
}
"""write方法可以帮你转成json字符串,返回的Content-Type类型为application/json类型"""
self.write(per)
class HeaderHandler(RequestHandler):
"""
设置响应头
"""
def set_default_headers(self):
"""
在get之前调用
:return:
"""
self.set_header("Content-Type", "text/html;charset=UTF-8")
class StatusHandler(RequestHandler):
"""
设置状态码
"""
def get(self, *args, **kwargs):
"""若reason值为None,则状态码为正常值(如404,500....)"""
self.set_status(status_code=1000, reason="描述状态码")
self.write("xxxxxxx")
class RedirectHandler(RequestHandler):
"""
重定向
"""
def get(self, *args, **kwargs):
self.redirect("/")
class ErrorHandler(RequestHandler):
"""
抛出错误信息
"""
def write_error(self, status_code, **kwargs):
code = 200
if status_code == 500:
code = 500
self.write("服务器内部出错") # 返回500界面
elif status_code == 404:
code = 400
self.write("资源不存在") # 返回404界面
self.set_status(code)
def get(self, *args, **kwargs):
flag = int(self.get_query_argument("flag"))
if flag == 0:
self.send_error(500)
self.write("你是对的")
class ReHandler(RequestHandler):
def get(self, p1, *args, **kwargs):
print(p1)
self.write("re...")
class ReGet(RequestHandler):
"""
获取请求数据:
无论get请求还是post请求都可以用:
self.get_argument()s
self.get_argument()
"""
def get(self, *args, **kwargs):
"""
获取get请求数据
self.get_query_argument(name="", default=ARG_DEFAULT, strip=True)
如果出现同名参数,则返回最后一个值的结果;default:若没有name则返回默认值
若default也未设置,则报错
strip:去除左右空格
"""
# page_list = self.get_query_arguments() # 这样拿到的是一个传参列表
# page = self.get_query_argument("page") # 拿到单个数据
self.render("login.html")
def post(self, *args, **kwargs):
"""
获取post请求数据
self.get_body_argument(name="", default=ARG_DEFAULT, strip=True)
:param args:
:param kwargs:
:return:
"""
username = self.get_body_argument("username", strip=True)
password = self.get_body_argument("password", strip=True)
"""
file数据格式:
{
'img': [
{'filename': 'mmexport1526622518750.jpg', 'body':b"xxxxxx", 'content_type': 'image/jpeg'}
{'filename': 'mmexport1526622518750.jpg', 'body':b"xxxxxx", 'content_type': 'image/jpeg'}
]
"file":[
{'filename': 'mmexport1526622518750.jpg', 'body':b"xxxxxx", 'content_type': 'image/jpeg'}
]
}
"""
files = self.request.files
for file in files:
filearr = files[file]
for fileobj in filearr:
# 存储
file_path = os.path.join(config.BASE_DIRS, "upload/" + fileobj.filename)
with open(file_path, "wb") as f:
f.write(fileobj.body)
hobby = self.get_body_arguments("hobby", strip=True)
print(username, password, hobby, files) # 李龙飞 123 ['pain', 'read', 'run']
self.write("okk")
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/reget" method="post" enctype="multipart/form-data">
姓名:<input type="text" name="username">
<hr>
密码:<input type="password" name="password">
<hr>
文件:<input type="file" name="file">
<hr>
爱好:
<input type="checkbox" value="pain" name="hobby">画画
<input type="checkbox" value="read" name="hobby">读书
<input type="checkbox" value="run" name="hobby">跑步
<input type="submit" name="登录"><span>第{{count}}次登录</span>
</form>
</body>
</html>
…
class StudentsHandler(RequestHandler):
"""
数据库操作
"""
def get(self, *args, **kwargs):
sql = "select sname, gender from student"
student_list = self.application.db.get_all(sql)
print(student_list)
self.render("student_show.html", student_list=student_list)
student_show.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>学生列表</title>
</head>
<body>
<ul>
<li style="color: red">姓名--------性别</li>
{% for student in student_list %}
<li>{{ student["sname"] }} ------->{{ student["gender"] }}</li>
{% end %}
</ul>
</body>
</html>
…
class CookieHandler(RequestHandler):
"""
self.set_cookie(
name="", # cookie名
value="", # cookie值
domain="", # 提交cookie时匹配的域名
expires=None, # 设置有效期,可以是时间戳,时间元组,datetime型, 为UTC时间
path="/", # 提交cookie时默认的路径
expires_days=5 # 设置有效期天数, 优先级低于expires
)
执行清除cookie后,并不是立即删除历览器的cookie,而是给cookie值设置为空,并改变其有效期限为失效,真正删除cookies是由浏览器自己去清理的
"""
def get(self, *args, **kwargs):
self.set_cookie("li", "youxiu") # 实质上是调用了self.set_header("Set-Cookie", "li=youxiu;path=/")
# self.get_cookie("li", "未登录") # 获取cookie
# self.clear_cookie("li") # 清除cookie
# self.clear_all_cookies(path="/", domain=None) # 删除同时匹配path和domain的所有cookie
self.write("cookie")
class ScCookieHandler(RequestHandler):
"""
uuid, base64
secret = base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)
在settings中设置 "cookie_secret": "G0SAhaKwQ6eU/lqXoW+b3g/at/cy1EKDluw/JcFlYtA=",
self.set_secure_cookie(name="", value="", expires_days=30, version=None, **kwargs)
self.get_secure_cookie(name="", value="", max_age_days=31, min_version=None) # max_age_days:过滤出在这个期限内的时间
"""
def get(self, *args, **kwargs):
self.set_secure_cookie("key", "123") # 有问题, 设置不上
self.write("...")
class CookieCountHandler(RequestHandler):
"""
cookie计数
"""
def get(self, *args, **kwargs):
count = self.get_cookie("count", default=None)
if count:
count = int(count)
count += 1
else:
count = 1
self.set_cookie("count", str(count), expires=1)
self.write("第{}次访问".format(str(count)))
class XsrfHandler(RequestHandler):
"""
setting中设置:"xsrf_cookie": True
模板中添加{% module xsrf_form_html() %}
也可以发ajax请求:
$.ajax({
.....
headers:{
"X-XSRFToken": "xsrf_token"
}
})
"""
def get(self, *args, **kwargs):
count = self.get_cookie("count", default=None)
if not count:
count = 1
self.render("postfilexsrf.html", count=count)
def post(self, *args, **kwargs):
count = self.get_cookie("count", default=None)
if count:
count = int(count)
count += 1
else:
count = 1
self.set_cookie("count", str(count))
self.redirect("/xsrf")
postfilexsrf.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="/xsrf" method="post" enctype="multipart/form-data">
姓名:<input type="text" name="username">
<hr>
密码:<input type="password" name="password">
<hr>
文件:<input type="file" name="file">
<hr>
爱好:
<input type="checkbox" value="pain" name="hobby">画画
<input type="checkbox" value="read" name="hobby">读书
<input type="checkbox" value="run" name="hobby">跑步
<input type="submit" name="登录"><span>第{{count}}次登录</span>
</form>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
function getCookie(name){
var $cookie = document.cookie.match("\\b" + name + "=([^;]*)\\b")
return $cookie ? $cookie[1]:undefined
}
$.ajax({
url: "",
method: "POST",
data: {
},
success:function (data) {
},
header: {
"X_XSRFToken": getCookie("_xsrf")
}
})
</script>
</body>
</html>
…
class SetXsrfHandler(RequestHandler):
def get(self, *args, **kwargs):
self.xsrf_token
self.finish()
class StaticFileHandler(web.StaticFileHandler):
"""
在主页就设置"_xsrf"
"""
def __init__(self, *args, **kwargs):
super(StaticFileHandler, self).__init__(*args, **kwargs)
self.xsrf_token
class LoginHandler(RequestHandler):
"""
登录界面
"""
def get(self, *args, **kwargs):
next = self.get_argument("next", "/")
url = "login?next=" + next
self.render("login2.html", url=url)
def post(self, *args, **kwargs):
next = self.get_argument("next", "/")
name = self.get_body_argument("username")
passwd = self.get_body_argument("password")
print(name, passwd)
if name == "long" and passwd == "123":
print(next)
self.redirect(next + "?flag=logined")
else:
self.redirect("/login?next=" + next)
login2.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<form action="{{ url }}" method="post" enctype="multipart/form-data">
{% module xsrf_form_html() %}
姓名:<input type="text" name="username">
<hr>
密码:<input type="password" name="password">
<hr>
文件:<input type="file" name="file">
<hr>
爱好:
<input type="checkbox" value="pain" name="hobby">画画
<input type="checkbox" value="read" name="hobby">读书
<input type="checkbox" value="run" name="hobby">跑步
<input type="submit" name="登录"><span></span>
</form>
</body>
</html>
…
class AuthHandler(RequestHandler):
"""
认证
"""
def get_current_user(self):
"""
若返回为Ture则验证成功
若返回值为False则重定向到settings 中login_url所指定的路由
:return:
"""
flag = self.get_argument("flag", default="")
return flag
@web.authenticated # 认证装饰器
def get(self, *args, **kwargs):
self.write("你好啊")
# class CallbackAsy(RequestHandler):
# """
# 用回调函数实现异步请求(这个有问题)
# """
# def on_response(self, response):
# if response.error:
# self.send_error(500)
# else:
# data = response.body
# self.write(data)
# self.finish()
#
# @web.asynchronous
# def get(self, *args, **kwargs):
# url = "www.baidu.com"
# client = AsyncHTTPClient() # 创建客户端
# client.fetch(url, self.on_response)
class GenvenletAsy(RequestHandler):
"""
用协程实现异步请求
"""
@web.gen.coroutine
def get(self, *args, **kwargs):
url = "https://www.baidu.com"
client = AsyncHTTPClient() # 创建客户端
res = yield client.fetch(url)
if res.error:
self.send_error(500)
else:
data = res.body
self.write(data)
class GenvenletAsy2(RequestHandler):
def get(self, *args, **kwargs):
res = yield self.getData()
self.write(res)
@web.gen.coroutine
def getData(self):
url = "www.baidu.com"
client = AsyncHTTPClient()
data = yield client.fetch(url)
if data.error:
data = {"ret": 0}
else:
data = data.body
raise web.gen.Return(data)
class HomeHandler(RequestHandler):
def get(self, *args, **kwargs):
self.render("home.html")
class ChatHandler(WebSocketHandler):
user = [] # 存储每一个人的信息
def open(self):
"""
websocket连接建立后执行
:return:
"""
self.user.append(self)
for user in self.user:
user.write_message("[{}]登录了".format(self.request.remote_ip))
def on_message(self, message):
"""
当客户端发送消息过来时调用
:param message:
:return:
"""
for user in self.user:
user.write_message("[{}]说:{}".format(self.request.remote_ip, message))
#
def on_close(self):
"""
当websocket链接关闭后调用
:return:
"""
self.user.remove(self)
for user in self.user:
user.write_message("[{}]退出了".format(self.request.remote_ip))
#
# def write_message(self, message, binary=False):
# """
# 主动向客户端发送消息
# :param message: 可以是字符串,或者字典(转成json字符串),
# :param binary:如果binary为false,则message会以UTF-8编码发送,为Ture发送二进制,字节码
# :return:
# """
# pass
def close(self, code=1001, reason="123"):
"""
关闭websocket链接, 服务器主动关闭
:param code:
:param reason:
:return:
"""
pass
def check_origin(self, origin):
"""
判断源origin, 对于符合的请求允许链接,同源策略
:param origin:
:return:
"""
return True
home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>聊天界面</title>
</head>
<body>
<div id="contents" style="width: 500px;height: 500px;overflow: auto">
<div>
<input type="text" id="message">
<button id="send">发送</button>
</div>
</div>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script>
var ws = new WebSocket("ws://127.0.0.1:8000/chat"); // 建立连接
ws.onmessage = function(e){
$("#contents").append("<p>" + e.data + "</p>")
}; // 接收服务器消息
$("#send").on("click", function () {
var $mes = $("#message");
var message = $mes.val();
ws.send(message);
$mes.val("")
}) // 向服务器发消息
</script>
</body>
</html>