Tornado——入門篇

第一天(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("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協議,無法自動穿越防火牆。
實例:網絡互動遊戲。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章