第一天(hello tornado)
環境
習慣用python2,所以安裝6.0以下版本的tornado(6.0以上最低3.5)
pip install tornado==5.1.1
demo跑起來
執行python ./test1.py
測試一下
curl是基於URL語法在命令行方式下工作的文件傳輸工具,它支持FTP,FTPS,HTTP,HTTPS,GOPHER,TELNET,DICT,FILE及LDAP等協議。curl支持HTTPS認證,並且支持HTTP的POST,PUT等方法,FTP上傳,kerberos認證,HTTP上傳,代理服務器,cookies,用戶名/密碼認證,通過http代理服務器上傳文件到FTP服務器等等,功能十分強大。
- -A/–user-agent 設置用戶代理髮送給服務器,即告訴服務器瀏覽器爲什麼
- -basic 使用HTTP基本驗證
- –tcp-nodelay 使用TCP_NODELAY選項
- -e/–referer 來源網址,跳轉過來的網址
- –cacert 指定CA證書 (SSL)
- –compressed 要求返回是壓縮的形勢,如果文件本身爲一個壓縮文件,則可以下載至本地
- -H/–header
自定義頭信息傳遞給服務器 - -I/–head 只顯示響應報文首部信息
- –limit-rate 設置傳輸速度
- -u/–user <user[:password]>設置服務器的用戶和密碼
- -0/–http1.0 使用HTTP 1.0
curl -X PUT www.baidu.com
curl -X DELETE www.baidu.com
curl -X POST www.baidu.com -d “key=value&key1=value1”
curl -X GET www.baidu.com
-X 指定請求方式 -d 添加參數
curl localhost:8000/
Hello, Welcome to the world of tornado!
curl -X POST localhost:8000/
My name is tornado !
關於代碼
上述的一個簡單web服務主要包含了兩個模塊
-
tornado.web 這是一個tornado中的web模塊
- RequestHandler
不同於django,tornado將request與response都封裝在了requesthandler中,它封裝了對應一個請求的所有信息和方法,write方法是寫入響應信息;對於請求方式不同,將不同的邏輯寫入到與方法同名的成員方法中,當未重寫同名的成員方法時,將會返回 405 方法不被准許錯誤。 - Application
它是tornado web框架的核心應用類,是與服務器對接的接口,保存有路由信息表,其初始化接收的第一個參數就是一個路由信息映射元組的列表;其listen(端口)方法用來創建一個http服務器實例,並綁定到給定端口(注意:此時服務器並未開啓監聽)。
- RequestHandler
-
tornado.ioloop tornado的核心io循環模塊
它封裝了Linux的epoll和BSD的kqueue,tornado高性能的基石。
- IOLoop.current() 返回當前線程的IOLoop實例。
- IOLoop.start() 啓動IOLoop實例的I/O循環,同時服務器監聽被打開。
-
tornado.options 從命令行中讀取設置
它的用法如下:
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
.
.
.
tornado.options.parse_command_line()
.
http_server.listen(options.port)
.
如果一 個與 define 語句中同名的設置在命令行中被給出,那麼它將成爲全局 options 的一個 屬性。如果用戶運行程序時使用了–help 選項,程序將打印出所有你定義的選項以及 你在 define 函數的 help 參數中指定的文本。如果用戶沒有爲這個選項指定值,則使 用default的值進行代替。Tornado使用type參數進行基本的參數類型驗證,當不合
適的類型被給出時拋出一個異常。因此,我們允許一個整數的 port 參數作爲 options.port 來訪問程序。如果用戶沒有指定值,則默認爲 8000。
總結
1 創建請求處理類,繼承handler類,重寫相應方法
2 創建web應用實例對象,定義路由映射列表
3 綁定端口
4 開啓監聽服務
Tornado——第二天(關於端口綁定)
回顧
在創建完一個基礎的web應用後,我們使用 app.listen() 方法來將 web服務與端口綁定。
這個地方的listen() 方法只是一個封裝後的簡寫形式。
這個綁定過程的原始形式如下:
# app.listen(8000)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(8000)
首先使用了tornado中的http模塊爲 app 創建了一個 http服務實例。然後將這個服務與8000端口綁定
開啓多進程
我們上述都是爲tornado開啓了一個進程,如果想開啓多個線程的話,需要做以下操作:
http_server = tornado.httpserver.HTTPServer(app)
http_server.bind(8000)
http_server.start(0)
http_server.start(num_processes=1)方法指定開啓幾個進程,參數num_processes默認值爲1,即默認僅開啓一個進程;如果num_processes爲None或者<=0,則自動根據機器硬件的cpu核芯數創建同等數目的子進程;如果num_processes>0,則創建num_processes個子進程。
而以前使用的簡寫形式與listen形式則相當於
http_server.bind(8000)
http_server.start(1)
關於多進程形式,因爲子進程是從父進程中複製的ioloop實例,所以在創建子進程前如果更改了父進程的ioloop實例,那麼每一個子進程都將受影響。
Tornado——第三天(參數傳遞)
tornado獲取參數大致有三種形式:
- 路由表中正則獲取
# _*_ coding:utf-8 _*_
import tornado.web
import tornado.ioloop
import tornado.httpserver
from tornado.options import define,options
# 定義初始端口(默認8000)
define("port", default=8000,help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self,params1):
self.write("i get a params: %s"%params1)
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[
(r"/(\w*)", IndexHandler),
]
)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.current().start()
- 通過內置方法獲取
# _*_ coding:utf-8 _*_
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
class IndexHandler(tornado.web.RequestHandler):
def get(self,params1):
self.write("i get a params: %s"%params1)
params2 = self.get_query_argument("params2","None",True)
self.write("get_query_argument獲取參數是:%s"%params2)
params3 = self.get_query_arguments("params2",True)
self.write("get_query_arguments獲取參數是:%s"%params3)
# 獲取get參數
# get_query_argument(name, default=_ARG_DEFAULT, strip=True)
# get_query_arguments(name, strip=True)
# 區別在於如果有多個同名參數,第一個方法返回最後一個值,未傳參數的情況且未設置默認值將會拋出異常。第二個方法返回一個列表,未傳參數將返回一個空列表。
- 從request.body中獲取
與獲取get參數方式類似,有以下兩種方法
get_body_argument(name, default=_ARG_DEFAULT, strip=True)
get_body_arguments(name, strip=True)
** 對於請求體中的數據要求爲字符串,且格式爲表單編碼格式(與url中的請求字符串格式相同),即key1=value1&key2=value2,HTTP報文頭Header中的"Content-Type"爲application/x-www-form-urlencoded 或 multipart/form-data。對於請求體數據爲json或xml的,無法通過這兩個方法獲取。**
- 聚合方法
get_argument(name, default=_ARG_DEFAULT, strip=True)
# 相當於 get_query_argument 與 get_body_argument
get_arguments(name, strip=True)
# 相當於 get_query_arguments 與 get_body_arguments
Tornado——第四天(頁面模版與靜態文件)
#_*_coding: utf-8 _*_
import os
import tornado.web
import tornado.httpserver
import tornado.ioloop
from tornado.options import define,options
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
define("port", default=8000,help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
name = self.get_argument("name","python",True)
self.render('index1.html',name=name)
if __name__ == "__main__":
tornado.options.parse_command_line()
base_dir = os.path.dirname(__file__)
handlers = [(r"/", IndexHandler),]
settings = {
"template_path": os.path.join(base_dir, "templates"),
"static_path": os.path.join(base_dir, "static"),
"debug":True
}
app = tornado.web.Application(handlers,**settings)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.current().start()
# index1.html
<!DOCTYPE html>
<html>
<head>
<title>首頁</title>
<link rel="stylesheet" href="{{static_url("index1.css")}}">
</head>
<body>
<h1>Hello world !</h1>
<h3>hello {{name}}</h3>
</body>
</html>
# index1.css
h1 {
color:red
}
h3 {
color:green
}
關於模版方面,tornado使用的是 jinja2 , 使用起來和django是很相似的。
Tornado——第五天(文件上傳與下載)
繼續編寫昨天的代碼
class IndexHandler(tornado.web.RequestHandler):
def initialize(self, upload, download):
self.upload = upload
self.download = download
def get(self):
name = self.get_argument("name","python",True)
get_img = self.get_argument("get_img","false",True)
if get_img != "true":
self.render('index2.html',name=name)
else:
# set_header方法設置header頭
self.set_header('Content-Type','application/octet-stream')
self.set_header('Content-Disposition', 'attachment; filename="隨機圖片.png"')
img_list = os.listdir(self.download)
img_path = random.choice(img_list)
with open(os.path.join(self.download,img_path),'rb') as f:
while True:
data = f.read(4096)
if not data:
break
self.write(data)
def post(self):
print "已接收到請求"
files = self.request.files
# {"head_img":[{"filename":"..","content_type":"...","body":"..."}]}
head_img_obj = files.get('head_img')[0]
if head_img_obj:
head_img_path = os.path.join(self.upload, head_img_obj['filename'])
with open(head_img_path,'wb') as f:
f.write(head_img_obj["body"])
self.write("success !")
if __name__ == "__main__":
tornado.options.parse_command_line()
base_dir = os.path.dirname(__file__)
settings = {
"template_path": os.path.join(base_dir, "templates"),
"static_path": os.path.join(base_dir, "static"),
"debug":True
}
handlers = [
(r"/", IndexHandler, dict(
upload=os.path.join(settings["static_path"],"upload"), download=os.path.join(settings["static_path"],"download")
)),
]
Tornado——第六天(同步與異步)
此本分主要實現一個同步與異步的請求功能
# index_asyn.html
<!DOCTYPE html>
<html>
<head>
<title>異步測試頁面</title>
</head>
<body>
<input id="search" type="text" placeholder="請輸入檢索關鍵字" />
<input type="button" onclick="request()" value="檢索" />
<div id="result">
<p>檢索結果</p>
<table id="res_list"></table>
</div>
<script type="text/javascript">
function request(){
var result = document.getElementById("res_list");
var search = document.getElementById("search").value;
var ajax = new XMLHttpRequest();
ajax.open("post","/", true)
ajax.setRequestHeader("Content-Type", "application/json");
ajax.onreadystatechange = function () {
if(ajax.readyState == 4){
if(ajax.status == 200){
res_obj=JSON.parse(ajax.responseText).result;
result.innerHTML = "";
for(var i=0; i<res_obj.length;i++){
html_text = "<tr>"+
"<td>"+res_obj[i][0]+"<td/>"+
"<td>"+res_obj[i][1]+"<td/>"+
"</tr>"
result.innerHTML = result.innerHTML + html_text };
};
};
};
ajax.send(JSON.stringify({"search":search}));
};
</script>
</body>
</html>
#_*_coding: utf-8 _*_
import os
import json
import tornado.web
import tornado.httpserver
import tornado.ioloop
from tornado.httpclient import HTTPClient,AsyncHTTPClient
from tornado.options import define,options
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
define("port", default=8000,help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render('index_asyn.html')
# 同步版
# def post(self):
# if self.get_argument("search",False):
# search_text = self.get_argument("search")
# else:
# search_text = json.loads(self.request.body)["search"]
# client = HTTPClient()
# res = client.fetch("https://suggest.taobao.com/sug?code=utf-8&q=%s"%search_text)
# context = res.body.decode("utf8")
# self.write(context)
# 異步版
# @tornado.web.asynchronous
# def post(self):
# if self.get_argument("search",False):
# search_text = self.get_argument("search")
# else:
# search_text = json.loads(self.request.body)["search"]
# client = AsyncHTTPClient()
# res = client.fetch("https://suggest.taobao.com/sug?code=utf-8&q=%s"%search_text, callback=self.deal_response)
#
# def deal_response(self,response):
# content = response.body.decode("utf8")
# self.write(content)
# self.finish()
@tornado.web.asynchronous
@tornado.gen.engine
def post(self):
if self.get_argument("search",False):
search_text = self.get_argument("search")
else:
search_text = json.loads(self.request.body)["search"]
client = AsyncHTTPClient()
res = yield tornado.gen.Task(client.fetch,"https://suggest.taobao.com/sug?code=utf-8&q=%s"%search_text)
content = res.body.decode("utf8")
self.write(content)
self.finish()
if __name__ == "__main__":
tornado.options.parse_command_line()
base_dir = os.path.dirname(__file__)
handlers = [(r"/", IndexHandler),]
settings = {
"template_path": os.path.join(base_dir, "templates"),
"static_path": os.path.join(base_dir, "static"),
"debug":True
}
app = tornado.web.Application(handlers,**settings)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.current().start()
壓力測試:
siege 'http://localhost:8000/ POST search=af' -c10 -t10s
- 同步
- 異步
結果:
在10併發的情況下測試10s中
同步處理了 47 個請求 異步處理了408個請求
同步平均響應時間爲1.74s,異步平均響應時間爲0.24s
總結:
- tornado提供了一個相當成熟的異步web編程解決方案,從耗時的操作中解放出來,轉而去處理更多的請求。
- 關於self.write()
- 此方法是將數據寫入緩衝區中,並不會直接作爲相應返回。同步http對應方法會自動調用self.finish()方法。
- 此方法會自動檢測數據類型,如dict將會轉換爲json返回。並自動設置content-type header頭
Tornado——第六天(websocket)
關於輪詢 長輪詢 長鏈接
https://www.cnblogs.com/AloneSword/p/3517463.html
輪詢:客戶端定時向服務器發送Ajax請求,服務器接到請求後馬上返回響應信息並關閉連接。
優點:後端程序編寫比較容易。
缺點:請求中有大半是無用,浪費帶寬和服務器資源。
實例:適於小型應用。
長輪詢:客戶端向服務器發送Ajax請求,服務器接到請求後hold住連接,直到有新消息才返回響應信息並關閉連接,客戶端處理完響應信息後再向服務器發送新的請求。
優點:在無消息的情況下不會頻繁的請求,耗費資源小。
缺點:服務器hold連接會消耗資源,返回數據順序無保證,難於管理維護。
實例:WebQQ、Hi網頁版、Facebook IM。
長連接:在頁面裏嵌入一個隱蔵iframe,將這個隱蔵iframe的src屬性設爲對一個長連接的請求或是採用xhr請求,服務器端就能源源不斷地往客戶端輸入數據。
優點:消息即時到達,不發無用請求;管理起來也相對方便。
缺點:服務器維護一個長連接會增加開銷。
實例:Gmail聊天
Flash Socket:在頁面中內嵌入一個使用了Socket類的 Flash 程序JavaScript通過調用此Flash程序提供的Socket接口與服務器端的Socket接口進行通信,JavaScript在收到服務器端傳送的信息後控制頁面的顯示。
優點:實現真正的即時通信,而不是僞即時。
缺點:客戶端必須安裝Flash插件;非HTTP協議,無法自動穿越防火牆。
實例:網絡互動遊戲。