天天看點

Tornado——入門篇

第一天(hello tornado)

環境

習慣用python2,是以安裝6.0以下版本的tornado(6.0以上最低3.5)

pip install tornado==5.1.1

demo跑起來

Tornado——入門篇

執行

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高性能的基石。

    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()
           
Tornado——入門篇
  • 通過内置方法擷取
# _*_ 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)
# 差別在于如果有多個同名參數,第一個方法傳回最後一個值,未傳參數的情況且未設定預設值将會抛出異常。第二個方法傳回一個清單,未傳參數将傳回一個空清單。
           
Tornado——入門篇
Tornado——入門篇
  • 從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——入門篇

關于模版方面,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()
           
Tornado——入門篇

壓力測試:

siege 'http://localhost:8000/ POST search=af' -c10 -t10s

  • 同步
    Tornado——入門篇
  • 異步
    Tornado——入門篇

    結果:

    在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協定,無法自動穿越防火牆。

執行個體:網絡互動遊戲。

繼續閱讀