python tornado实战_python-web之tornado实战篇
一、網站的基本架構
1、MVC模式
MVC模式是一個非常好的軟件架構模式,在網站開發中,也常常要求遵守這個模式。
MVC模式(Model-View-Controller)是軟件工程中的一種軟件架構模式,把軟件系統分為三個基本部分:模型(Model)、視圖(View)和控制器(Controller)。
MVC模式最早由Trygve Reenskaug在1978年提出,是施樂帕羅奧多研究中心(Xerox PARC)在20世紀80年代為程序語言Smalltalk發明的一種軟件設計模式。MVC模式的目的是實現一種動態的程式設計,使后續對程序的修改和擴展簡化,并且使程序某一部分的重復利用成為可能。除此之外,此模式通過對復雜度的簡化,使程序結構更加直觀。軟件系統通過對自身基本部分分離的同時也賦予了各個基本部分應有的功能。專業人員可以通過自身的專長分組:
(控制器 Controller)- 負責轉發請求,對請求進行處理。
(視圖 View) - 界面設計人員進行圖形界面設計。 -(模型 Model) - 程序員編寫程序應有的功能(實現算法等等)、數據庫專家進行數據管理和數據庫設計(可以實現具體的功能)。
2、前端
所謂前端就是指用瀏覽器打開之后看到的那部分,它是呈現網站傳過來的信息的界面,也是用戶和網站之間進行信息交互的界面。前端開發,一般使用HTML/CSS/JS,當然,非要用python也不是不可以,但這勢必造成以后維護困難。
前端所實現的功能主要有:
呈現內容。這些內容是根據url,由后端從數據庫中提取出來的。前端將其按照一定的樣式呈現出來。另外,有一些內容,不是后端數據庫提供的,是寫在前端的。
用戶與網站交互。現在的網站,這是必須的,比如用戶登錄。當用戶在指定的輸入框中輸入信息之后,該信息就是被前端提交給后端,后端對這個信息進行處理之后,在一般情況下都要再反饋給前端一個處理結果,然后前端呈現給用戶。
3、后端
這里所說的后端,對應著MVC中的Controller和Model的部分或者全部功能,因為在我們的圖中,“后端”是一個狹隘的概念,沒有把數據庫放在其內。
主要任務就是根據需要處理由前端發過來的各種請求,根據請求的處理結果,一方面操作數據庫(對數據庫進行增刪改查),另外一方面把請求的處理結果反饋給前端。
4、數據庫
工作比較單一,就是面對后端的python程序,任其增刪改查。
二、基于Tornado的基本框架
1、tornado中的文件目錄
/.
|
handlers
|
methods
|
statics
|
templates
|
application.py
|
server.py
|
url.py
有了這個文件架構,后面的事情就是在這個基礎上添加具體內容了。
依次說明上面的架勢中每個目錄和文件的作用:
handlers:在這個文件夾中放前面所說的后端python程序,主要處理來自前端的請求,并且操作數據庫。
methods:放一些函數或者類,比如用的最多的讀寫數據庫的函數,這些函數被handlers里面的程序使用。
statics:放一些靜態文件,比如圖片,css和javascript文件等。
templates:放模板文件,都是以html為擴展名的,它們將直接面對用戶。
url.py文件:
#!/usr/bin/env python
# coding=utf-8
import sys? ? #utf-8,兼容漢字
reload(sys)
sys.setdefaultencoding("utf-8")
from handlers.index import IndexHandler? ? #假設已經有了
url = [
(r'/', IndexHandler),
]
/*
url.py文件主要是設置網站的目錄結構。from handlers.index import IndexHandler,雖然在handlers文件夾還沒有什么東西,為了演示如何建立網站的目錄結構,假設在handlers文件夾里面已經有了一個文件index.py,它里面還有一個類IndexHandler。在url.py文件中,將其引用過來。
變量url指向一個列表,在列表中列出所有目錄和對應的處理類。比如(r'/', IndexHandler),,就是約定網站根目錄的處理類是IndexHandler,即來自這個目錄的get()或者post()請求,均有IndexHandler類中相應方法來處理。
*/
application.py文件:
#!/usr/bin/env python
# coding=utf-8
from url import url
import tornado.web
import os
settings = dict(
template_path = os.path.join(os.path.dirname(__file__), "templates"),
static_path = os.path.join(os.path.dirname(__file__), "statics")
)
application = tornado.web.Application(
handlers = url,
**settings
)
/*
從內容中可以看出,這個文件完成了對網站系統的基本配置,建立網站的請求處理集合。
from url import url是將url.py中設定的目錄引用過來。
setting引用了一個字典對象,里面約定了模板和靜態文件的路徑,即聲明已經建立的文件夾"templates"和"statics"分別為模板目錄和靜態文件目錄。
接下來的application就是一個請求處理集合對象。請注意tornado.web.Application()的參數設置:
tornado.web.Application(handlers=None, default_host='', transforms=None, **settings)
關于settings的設置,不僅僅是文件中的兩個,還有其它,比如,如果填上debug = True就表示出于調試模式。調試模式的好處就在于有利于開發調試,但是,在正式部署的時候,最好不要用調試模式。
*/
server.py文件:
這個文件的作用是將tornado服務器運行起來,并且囊括前面兩個文件中的對象屬性設置。
#!/usr/bin/env python
# coding=utf-8
import tornado.ioloop
import tornado.options
import tornado.httpserver
from application import application
from tornado.options import define, options
define("port", default = 8000, help = "run on the given port", type = int)
def main():
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(options.port)
print "Development server is running at http://127.0.0.1:%s" % options.port
print "Quit the server with Control-C"
tornado.ioloop.IOLoop.instance().start()
if __name__ == "__main__":
main()
三、連接數據庫
#!/usr/bin/env python
# coding=utf-8
import pymysql
conn = pymysql.connect(host="localhost", user="root", passwd="123123", db="qiwsirtest", port=3306, charset="utf8")? ? #連接對象
cur = conn.cursor()? ? #游標對象
四、基本功能實現流程(用戶登錄)
1 前端應實現的登陸邏輯圖
2 在實現前端登陸的基礎上,在后端進行url配置及登陸狀態處理
# 1、登陸功能的url配置:(url.py文件中)
from handlers.index import IndexHandler
url = [
(r'/', IndexHandler),
]
# 2、登陸狀態處理:(在handlers里面建立index.py文件:
#!/usr/bin/env python
# coding=utf-8
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")? ? # 去往登陸頁面
當訪問根目錄的時候,不論輸入localhost:8000,還是http://127.0.0.1:8000,或者網站域名,都會將相應的請求交給handlers目錄中的index.py文件中的IndexHandler類的get()方法來處理,它的處理結果是呈現index.html模板內容。
render()函數的功能在于向請求者反饋網頁模板,并且可以向模板中傳遞數值。
特別注意,在handlers目錄中,不要缺少了__init__.py文件,因為這里面的文件要在別處被當做模塊引用.
3 測試(多次使用)
3.1?運行服務
找到server.py文件,運行它:
$ python server.py
Development server is running at http://127.0.0.1:8000
Quit the server with Control-C
3.2 訪問
打開瀏覽器,輸入http://localhost:8000或者http://127.0.0.1:8000
四、數據傳輸
在已經建立了前端表單之后,就要實現前端和后端之間的數據傳遞。在工程中,常用到一個被稱之為ajax()的方法。
4.1 前端使用ajax()進行數據傳輸,示例如下:
$(document).ready(function(){
$("#login").click(function(){
var user = $("#username").val();? ? // 從前端捕獲到數據1
var pwd = $("#password").val();? ? // 從前端捕獲到數據2
var pd = {"username":user, "password":pwd};? ? // 數據json格式
$.ajax({
type:"post",? ? ? ? // 請求方式
url:"/",? ? ? ? ? ? // 請求地址
data:pd,? ? ? ? ? ? // 傳輸數據內容
cache:false,
success:function(data){
alert(data);
},
error:function(){
alert("error!");
},
});
});
});
type:post還是get。
url:post或者get的地址
data:傳輸的數據,包括三種:(1)html拼接的字符串;(2)json數據;(3)form表單經serialize()序列化的。本例中傳輸的就是json數據,這也是經常用到的一種方式。
cache:默認為true,如果不允許緩存,設置為false.
success:請求成功時執行回調函數。本例中,將返回的data用alert方式彈出來。讀者是否注意到,我在很多地方都用了alert()這個東西,目的在于調試,走一步看一步,看看得到的數據是否如自己所要。也是有點不自信呀。
error:如果請求失敗所執行的函數。
4.2 后端接受數據:
前端通過ajax技術,將數據以 json格式 傳給了后端,并且指明了對象目錄"/",這個目錄在url.py文件中已經做了配置,是由handlers目錄的index.py文件的IndexHandler類來處理。因為是用post方法傳的數據,那么在這個類中就要有post方法來接收數據。所以,要在IndexHandler類中增加post()方法,增加之后的完善代碼是:
#!/usr/bin/env python
# coding=utf-8
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")
def post(self):
username = self.get_argument("username")? # 獲取前端傳來的數據1
password = self.get_argument("password")? # 獲取前端傳來的數據2
self.write(username)? ? ? ? ? ? ? ? ? # 向前端返回數據
get_argument(name, default=[], strip=True)
# 它能夠獲取name的值。(name就是從前端傳到后端的那個json對象的鍵的名字)
# 如果獲取不到name的值,就返回default的值,但是這個值默認是沒有的,如果真的沒有就會拋出HTTP 400。# # 要想獲取多個值,可以使用get_arguments(name, strip=true)。
self.write(username)
# 是后端向前端返回數據。這里返回的實際上是一個字符串,也可返回json字符串。
4.3 驗證數據(用戶名和密碼)
按照流程,用戶在前端輸入了用戶名和密碼,并通過ajax提交到了后端,后端借助于get_argument()方法得到了所提交的數據(用戶名和密碼)。下面要做的事情就是驗證這個用戶名和密碼是否合法,其體現在:
數據庫中是否有這個用戶
密碼和用戶先前設定的密碼(已經保存在數據庫中)是否匹配
這個驗證工作完成之后,才能允許用戶登錄,登錄之后才能繼續做某些事情。
首先,在methods目錄中創建一個db.py文件,用于數據庫操作:
# 該方法實現從數據庫中查詢,并返回查詢結果
def select_table(table, column, condition, value ):
sql = "select " + column + " from " + table + " where " + condition + "='" + value + "'"
cur.execute(sql)
lines = cur.fetchall()
return lines
有了這段代碼之后,就進一步改寫index.py中的post()方法:
def post(self):
username = self.get_argument("username")
password = self.get_argument("password")
user_infos = mrd.select_table(
table="users",column="*",condition="username",value=username)
if user_infos:
db_pwd = user_infos[0][2]
if db_pwd == password:
self.write("welcome you: " + username)
else:
self.write("your password was not right.")
else:
self.write("There is no thi user.")
特別注意,在methods目錄中,不要缺少了__init__.py文件,因為這里面的文件要在別處被當做模塊引用,才能在index.py中實現import methods.db
4.4 重復(步驟3)測試功能
五、模板使用
模板主要針對前端頁面的html來說,因為前端頁面要顯示從后端讀取出來的數據,在前端頁面中獲取數據的位置,用變量或標簽代替,就實現了模板功能。tornado提供比較好用的前端模板(tornado.template)。通過使用模板,能夠讓前端開發者的編寫不受后端的限制。
# 模板中的變量:
1、使用變量的語法:{{ 變量名 }}
2、從變量(列表、元組、字典)中取值,用索引或鍵的方式
# 模板中的標簽:
for循環:
{% for? 變量? in? 列表 | 元組 | 字典 %}
{% endfor %}
# 允許使用 for 提供的內置變量? - forloop
1、forloop.counter? ? # 記錄當前循環的次數,從1開始
2、forloop.first? ? ? # 是否是第一次循環(第一項)
3、forloop.last? ? ? # 是否是最后一次循環(最后一項)
if條件語句:
1、
{% if? 條件 %}
滿足條件要運行的內容
{% endif %}
2、
{% if %}
滿足條件要運行的內容
{% else %}
不滿足條件要運行的內容
{% endif %}
3、
{% if? 條件1 %}
滿足條件1要運行的內容
{% elif? 條件2 %}
滿足條件2要運行的內容
{% elif? 條件3 %}
滿足條件3要運行的內容
{% else %}
不滿足條件要運行的內容
{% endif %}
# 條件中允許使用 (比較運算符>? =? ? <=? ==? !=)
#? ? ? ? ? ? ? ? (邏輯運算符 not? and? or)
# 但是:and? 和? or? 不能同時出現
示例功能:用戶正確登錄之后,跳轉到另外一個頁面,并且在那個頁面中顯示出用戶的完整信息
1、先修改url.py文件,在其中增加一些內容。完整代碼如下:
#!/usr/bin/env python
# coding=utf-8
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
from handlers.index import IndexHandler
from handlers.user import UserHandler
url = [
(r'/', IndexHandler),
(r'/user', UserHandler),? ? // 新的url地址
]
2、然后就建立handlers/user.py文件,內容如下:
#!/usr/bin/env python
# coding=utf-8
import tornado.web
import methods.readdb as mrd
class UserHandler(tornado.web.RequestHandler):
def get(self):
username = self.get_argument("user")
user_infos = mrd.select_table(
table="users",column="*",condition="username",value=username)
self.render("user.html", users = user_infos)? ? # 將查詢到的用戶信息返回給前端
注意:上述的user.py代碼為了簡單突出本將要說明的,沒有對user_infos的結果進行判斷。在實際的編程中,這要進行判斷或者使用try...except。
3、前端的user.html模板文件,注意傳入的引用對象user_infos不是一個字符串了,是一個元組。內容如下:
Learning PythonYour informations are:
{% for one in users %}? ? # 使用for循環遍歷傳入的對象
username:{{one[1]}}? # 索引獲取元素password:{{one[2]}}email:{{one[3]}}{% end %}
六、模板繼承
實際工作中有很多模板都有相同的部分內容,在tornado的模板中有一種“繼承”的機制,它的作用之一就是能夠讓代碼重用。
6.1 先建立一個文件,命名為base.html,代碼如下:
Learning Python{% block header %}
{% end %}
{% block body %}
{% end %}
{% set website = "welcome to my website" %}
{% raw website %}
6.2 接下來就以base.html為父模板,依次改寫已經有的index.html和user.html模板
index.html代碼如下:
{% extends "base.html" %}? ? ? ? ?
{% block header %}? ? ? ? ? ? ? ? ?
登錄頁面
用用戶名為:{{user}}登錄
{% end %}
{% block body %}? ? ? ? ? ? ? ? ?
UserName:
Password:
{% end %}
user.html的代碼如下:
{% extends "base.html" %}? ? ? ? ?
{% block header %}? ? ? ? ? ? ? ? ?
Your informations are:
{% end %}
{% block body %}? ? ? ? ? ? ? ? ? ?
{% for one in users %}
username:{{one[1]}}password:{{one[2]}}email:{{one[3]}}{% end %}
{% end %}
七、cookie和安全
因為HTTP協議是無狀態的,即服務器不知道用戶上一次做了什么,這嚴重阻礙了交互式Web應用程序的實現。
典型場景1:在網上購物中,用戶瀏覽了幾個頁面,買了一盒餅干和兩瓶飲料。最后結帳時,由于HTTP的無狀態性,不通過額外的手段,服務器并不知道用戶到底買了什么。 所以Cookie就是用來繞開HTTP的無狀態性的“額外手段”之一。服務器可以設置或讀取Cookies中包含信息,借此維護用戶跟服務器會話中的狀態。
在剛才的購物場景中,當用戶選購了第一項商品,服務器在向用戶發送網頁的同時,還發送了一段Cookie,記錄著那項商品的信息。當用戶訪問另一個頁面,瀏覽器會把Cookie發送給服務器,于是服務器知道他之前選購了什么。用戶繼續選購飲料,服務器就在原來那段Cookie里追加新的商品信息。結帳時,服務器讀取發送來的Cookie就行了。
典型場景2:當登錄一個網站時,網站往往會請求用戶輸入用戶名和密碼,并且用戶可以勾選“下次自動登錄”。如果勾選了,那么下次訪問同一網站時,用戶會發現沒輸入用戶名和密碼就已經登錄了。這正是因為前一次登錄時,服務器發送了包含登錄憑據(用戶名加密碼的某種加密形式)的Cookie到用戶的硬盤上。第二次登錄時,(如果該Cookie尚未到期)瀏覽器會發送該Cookie,服務器驗證憑據,于是不必輸入用戶名和密碼就讓用戶登錄了。
和任何別的事物一樣,cookie也有缺陷:
cookie會被附加在每個HTTP請求中,所以無形中增加了流量。
由于在HTTP請求中的cookie是明文傳遞的,所以安全性成問題。(除非用HTTPS)
Cookie的大小限制在4KB左右。對于復雜的存儲需求來說是不夠用的。
對于用戶來講,可以通過改變瀏覽器設置,來禁用cookie,也可以刪除歷史的cookie。但就目前而言,禁用cookie的可能不多了,因為你總要在網上買點東西吧。
Cookie最讓人擔心的還是由于它存儲了用戶的個人信息,并且最終這些信息要發給服務器,那么它就會成為某些人的目標或者工具,比如有cookie盜賊,就是搜集用戶cookie,然后利用這些信息進入用戶賬號,達到個人的某種不可告人之目的;還有被稱之為cookie投毒的說法,是利用客戶端的cookie傳給服務器的機會,修改傳回去的值。這些行為常常是通過一種被稱為“跨站指令腳本(Cross site scripting)”(或者跨站指令碼)的行為方式實現的。
跨網站腳本(Cross-site scripting,通常簡稱為XSS或跨站腳本或跨站腳本攻擊)是一種網站應用程序的安全漏洞攻擊,是代碼注入的一種。它允許惡意用戶將代碼注入到網頁上,其他用戶在觀看網頁時就會受到影響。這類攻擊通常包含了HTML以及用戶端腳本語言。
XSS攻擊通常指的是通過利用網頁開發時留下的漏洞,通過巧妙的方法注入惡意指令代碼到網頁,使用戶加載并執行攻擊者惡意制造的網頁程序。這些惡意網頁程序通常是JavaScript,但實際上也可以包括Java, VBScript, ActiveX, Flash 或者甚至是普通的HTML。攻擊成功后,攻擊者可能得到更高的權限(如執行一些操作)、私密網頁內容、會話和cookie等各種內容。
cookie是好的,被普遍使用。在tornado中,也提供對cookie的讀寫函數。
set_cookie()? ? # 寫入cookie值
get_cookie()? ? # 獲取cookie值
# 是tornado默認提供的兩個方法,但是它是明文不加密傳輸的。
在index.py文件的IndexHandler類的post()方法中,當用戶登錄,驗證用戶名和密碼后,將用戶名和密碼存入cookie,代碼如下:
def post(self):
username = self.get_argument("username")
password = self.get_argument("password")
user_infos = mrd.select_table(
table="users",column="*",condition="username",value=username)
if user_infos:
db_pwd = user_infos[0][2]
if db_pwd == password:
self.set_cookie(username,db_pwd)? ? #將用戶信息存入cookie
self.write(username)
else:
self.write("your password was not right.")
else:
self.write("There is no thi user.")
tornado提供另外一種(非絕對)安全的方法:
set_secure_cookie()? ? ? # 寫入cookie信息(加密)
get_secure_cookie()? ? ? # 獲取cookie信息(加密)
# 這種方法稱其為安全cookie,是因為它以明文加密方式傳輸。
跟set_cookie()的區別還在于, set_secure_cookie()執行后的cookie保存在磁盤中,直到它過期為止。也是因為這個原因,即使關閉瀏覽器,在失效時間之間,cookie都一直存在。
要是用set_secure_cookie()方法設置cookie,要先在application.py文件的setting中進行如下配置:
setting = dict(
template_path = os.path.join(os.path.dirname(__file__), "templates"),
static_path = os.path.join(os.path.dirname(__file__), "statics"),
cookie_secret = "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=",
)
其中cookie_secret = "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E="是新增加的,但是,它并不是真正的加密,僅僅是一個障眼法罷了。
因為tornado會將cookie值編碼為Base-64字符串,并增加一個時間戳和一個cookie內容的HMAC簽名。所以,cookie_secret的值,常常用下面的方式生成(這是一個隨機的字符串):
>>> import base64, uuid
>>> base64.b64encode(uuid.uuid4().bytes)
'w8yZud+kRHiP9uABEXaQiA=='
如果嫌棄上面的簽名短,可以用base64.b64encode(uuid.uuid4().bytes + uuid.uuid4().bytes)獲取。這里得到的是一個隨機字符串,用它作為 cookie_secret值。然后修改index.py中設置cookie那句話,變成:
self.set_secure_cookie(username, db_pwd, httponly=True, secure=True)
如果要獲取此cookie,用 self.get_secure_cookie(username) 即可。
八、XSRF 跨站請求偽造
這種對網站的攻擊方式跟上面的跨站腳本(XSS)似乎相像,但攻擊方式不一樣。XSS利用站點內的信任用戶,而XSRF則通過偽裝來自受信任用戶的請求來利用受信任的網站。與XSS攻擊相比,XSRF攻擊往往不大流行(因此對其 進行防范的資源也相當稀少)和難以防范,所以被認為比XSS更具危險性。
對于防范XSRF的方法,上面推薦閱讀的文章中有明確的描述。還有一點需要提醒讀者,就是在開發應用時需要深謀遠慮。任何會產生副作用的HTTP請求,比如點擊購買按鈕、編輯賬戶設置、改變密碼或刪除文檔,都應該使用post()方法。這是良好的RESTful做法。
在tornado中,提供了XSRF保護的方法。在application.py文件中,使用xsrf_cookies參數開啟XSRF保護。
setting = dict(
template_path = os.path.join(os.path.dirname(__file__), "templates"),
static_path = os.path.join(os.path.dirname(__file__), "statics"),
cookie_secret = "bZJc2sWbQLKos6GkHn/VB9oXwQt8S0R0kRvJ5/xJ89E=",
xsrf_cookies = True,
)
這樣設置之后,Tornado將拒絕請求參數中不包含正確的 _xsrf 值的 post/put/delete 請求。tornado會在后面悄悄地處理xsrf_cookies,所以,在表單中也要包含XSRF令牌以卻表請求合法。比如 index.html 的表單,修改如下:
{% extends "base.html" %}
{% block header %}
登錄頁面
用用戶名為:{{user}}登錄
{% end %}
{% block body %}
{% raw xsrf_form_html() %}? ? ? ?
UserName:
Password:
{% end %}
{% raw xsrf_form_html() %}? ?-- 是新增的,目的就在于實現上面所說的授權給前端以合法請求。
前端向后端發送的請求是通過ajax(),所以,在ajax請求中,需要一個_xsrf參數。以下是script.js的代碼
function getCookie(name){
var x = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return x ? x[1]:undefined;
}
$(document).ready(function(){
$("#login").click(function(){
var user = $("#username").val();
var pwd = $("#password").val();
var pd = {"username":user, "password":pwd, "_xsrf":getCookie("_xsrf")};
$.ajax({
type:"post",
url:"/",
data:pd,
cache:false,
success:function(data){
window.location.href = "/user?user="+data;
},
error:function(){
alert("error!");
},
});
});
});
函數 getCookie() 的作用是得到cookie值,然后將這個值放到向后端post的數據中。
這是tornado提供的XSRF防護方法。是不是這樣做就高枕無憂了呢?沒這么簡單。要做好一個網站,需要考慮的事情還很多。
九、session
十、同步和異步(并發訪問)
所謂同步,就是在發出一個“請求”時,在沒有得到結果之前,該“請求”就不返回。但是一旦請求返回,就得到返回值了。 換句話說,就是由“請求發起者”主動等待這個“請求”的結果。
而異步則是相反,“請求”在發出之后,這個請求就直接返回了,但沒有返回結果。換句話說,當一個異步過程請求發出后,請求者不會立刻得到結果。而是在“請求”發出后,“被請求者”通過狀態、通知來通知請求者,或通過回調函數處理這個請求。
同步典型案例:(打電話)張三給李四打電話,張三說:“是李四嗎?”。當這個信息被張三發出,提交給李四,就等待李四的響應(一般會聽到“是”,或者“不是”),只有得到了李四返回的信息之后,才能進行后續的信息傳送。
異步典型案例:(發短信)張三給李四發短信,編輯了一句話“今晚一起看老齊的零基礎學python”,發送給李四。李四或許馬上回復,或許過一段時間,這段時間多長也不定,才回復。總之,李四不管什么時候回復,張三會以聽到短信鈴聲為提示查看短信。
阻塞和非阻塞
“阻塞和非阻塞”與“同步和異步”常常被換為一談,其實它們之間還是有差別的。
阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態.
阻塞調用是指調用結果返回之前,當前線程會被掛起。調用線程只有在得到結果之后才會返回。非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程。
按照這個說明,發短信就是顯然的非阻塞,發出去一條短信之后,你利用手機還可以干別的。
10.1 tornado的同步
此前,在tornado基礎上已經完成的web,就是同步的、阻塞的。
10.2 tornado的異步
tornado提供了一套異步機制,就是異步裝飾器1:?@tornado.web.asynchronous + 回調函數
#!/usr/bin/env python
# coding=utf-8
import tornado.web
from base import BaseHandler
import time
class SleepHandler(BaseHandler):
@tornado.web.asynchronous
def get(self):
tornado.ioloop.IOLoop.instance().add_timeout(
time.sleep(17), callback=self.on_response)
def on_response(self):? ? ? ? ? ? # 回調函數
self.render("sleep.html")
self.finish()
@tornado.web.asynchronous :它的作用在于將tornado服務器本身默認的設置 _auto_fininsh 值修改為 false。如果不用這個裝飾器,客戶端訪問服務器的 get() 方法并得到返回值之后,兩只之間的連接就斷開了,但是用了 @tornado.web.asynchronous 之后,這個連接就不關閉,直到執行了 self.finish() 才關閉這個連接。
異步裝飾器2:?@tornado.gen.coroutine + 生成器
import time
class SleepHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def get(self):
yield tornado.gen.Task(
tornado.ioloop.IOLoop.instance().add_timeout, time.sleep(17))
self.render("sleep.html")
yield tornado.gen.Task(tornado.ioloop.IOLoop.instance().add_timeout, time.sleep(17)) 中的 tornado.gen.Task() 方法,其作用是“Adapts a callback-based asynchronous function for use in coroutines.”。返回后,最后使用yield得到了一個生成器,先把流程掛起,等完全完畢,再喚醒繼續執行。要提醒讀者,生成器都是異步的。
10.3 實踐中的異步
如果在tornado中按照之前的方式只用它們,以下各項同步(阻塞)的,就會削減tornado的非阻塞、異步優勢:
數據庫的所有操作,不管你的數據是SQL還是noSQL,connect、insert、update等
文件操作,打開,讀取,寫入等
time.sleep,在前面舉例中已經看到了
smtplib,發郵件的操作
一些網絡操作,比如tornado的httpclient以及pycurl等
除了以上,或許在編程實踐中還會遇到其他的同步、阻塞問題,怎么解決?聰明的大牛程序員幫我們做了擴展模塊,專門用來實現異步/非阻塞的。
在數據庫方面,由于種類繁多,不能一一說明,比如mysql,可以使用adb模塊來實現python的異步mysql庫;對于mongodb數據庫,有一個非常優秀的模塊,專門用于在tornado和mongodb上實現異步操作,它就是motor。特別貼出它的logo,我喜歡。官方網站:http://motor.readthedocs.org/en/stable/上的安裝和使用方法都很詳細。
文件操作方面也沒有替代模塊,只能盡量控制好IO,或者使用內存型(Redis)及文檔型(MongoDB)數據庫。
time.sleep() 在tornado中有替代:tornado.gen.sleep()或者tornado.ioloop.IOLoop.instance().add_timeout,這在前面代碼已經顯示了。
smtp發送郵件,推薦改為tornado-smtp-client。
對于網絡操作,要使用tornado.httpclient.AsyncHTTPClient。
其它的解決方法,只能看到問題具體說了,甚至沒有很好的解決方法。不過,這里有一個列表,列出了足夠多的庫,供使用者選擇:Async Client Libraries built on tornado.ioloop,同時這個頁面里面還有很多別的鏈接,都是很好的資源,建議讀者多看看。
————————————————
版權聲明:本文為CSDN博主「隨風奔跑之水」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_40873462/article/details/90237618
總結
以上是生活随笔為你收集整理的python tornado实战_python-web之tornado实战篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux -- 代理服务器(Squid
- 下一篇: apk 路由器劫持_各种路由器固件劫持方