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