第一天(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伺服器執行個體,并綁定到給定端口(注意:此時伺服器并未開啟監聽)。
-
-
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(" target="_blank" rel="external nofollow" 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協定,無法自動穿越防火牆。
執行個體:網絡互動遊戲。