socket
服務端再加上業務邏輯處理, 比如像是Tornado
這樣的框架. 有一些框架則只包含業務邏輯處理, 例如Django
, bottle
, flask
這些框架, 它們的使用需要依賴包含socket
的第三方模塊(即 wsgiref
)來運行
在python中常見的web框架構建模式有以下兩種:
MVC框架:
Models 數據相關操作
Views 模板html文件
Controllers 業務邏輯
mvc類似於抽象工廠設計模式,確認了架構以后才考慮應用設計模式, 三層架構大於MVC模式
三層架構將整個項目划分為:表現層(UI)、業務邏輯層(BLL)、數據訪問層(DAL)
MTV框架:
Models 數據相關操作
Templates 模板html文件
Views 業務邏輯
相當於文件夾的歸類, 只是命名不同, 所遵循的的思想也只是大同小異
flask, django, bottle 框架中使用模板引擎Jinja2:一個模板系統為Flask提供模板支持,其靈活,快速和安全等優點被廣泛使用。
web框架本質是socket, 通過socket發送的就是字符串,
第一塊:協議和方式
第二塊:請求頭
第三塊:發送內容
第一塊:協議和狀態
第二塊:響應頭
第三塊:響應內容
python web 框架分類:
自己的socket ====> Tornado
第三方:wsgi +框架 ===>Django, flash, bottl都是基於wsgi
Tornado:
1.
a.導入tornado模塊
b.寫類xxxxxHandler, 必須繼承tornado的模塊
c.路由系統,就是url和類的關系
d.程序運行
e.模板路勁配置和靜態文件配置
2.
寫表單<input type="text" name="use" / >提交
后台.self.get_argument('use)
后台返回請求:
self.render("index.html") 找到文件, 渲染, 得到字符串
self.write("字符串") 字符串返回給用戶
self.redirect("/manager") url跳轉
3.
模板語言:
a.{%%}if else 等代碼塊
b.{{}}
c.自定義 UIMethod, UIModule
模板語言本質:
字符串形式的函數 "def execute(): xxx" compile exec
cookie:
Tornado:
self.set_cookie()
self.get_cookie()
Ajax:
本質就是瀏覽器在用戶未感知的情況下發請求
XMLHttpRequest ==>發請求
jquery內部本質還是調用這個發送XMLHttpRequest
XSS是通過網頁使用JavaScript跨站腳本的攻擊是代碼注入的一種。
- 通常是通過利用網頁開發時留下的漏洞,通過巧妙的方法注入惡意指令代碼到網頁,使用戶加載並執行攻擊者惡意制造的網頁程序。
- 攻擊成功后,攻擊者可能得到更高的權限(如執行一些操作)、私密網頁內容、會話和cookie等各種內容。
import os import time from jinja2 import Template 模板語言配合jinja2使用 css, js, 靜態文件需要引用

from wsgiref.simple_server import make_server #首次得下載wsgiref模塊, #建立動態響應函數 def application(environ,start_response):#要實現的是做一個web應用 # 響應首行和響應頭,響應頭可以是空,響應首行必須有內容 start_response('200 OK', [('Content-Type', 'text/html')]) # environ, start_response 這兩個參數是兩個形參,所以叫什么名字都無所謂, # 關鍵是實參誰來調用它 # application的調用取決於make_server在調用serve_forever的時候要跑application # 相當於實參是什么取決於模塊給他放什么參數 # 這個wsgiref模塊里面有個make_server類 # application(a,b)里面有兩個參數,a就是打包好的數據也就是上面的environ # 換句話來說environ里面放的就是所有的數據,,按着http協議解析數據:environ # start_response:確定響應頭的信息,,,按者http協議組裝數據:start_response print("environ",environ) path=environ.get("PATH_INFO")# 當前的請求路徑 if path=="/login": with open("template/login.html","rb")as f: data=f.read() return [data] elif path=="/auth": return [b"<h1>hello</h1>"] #類似socket創建的socket對象,綁定IP端口,開啟監聽3步 s=make_server("127.0.0.1",8084,application) print("servering") s.serve_forever()#類似socket建立連接,等待接收用戶請求 import os def new(): f = open(os.path.join("文件夾","文件夾下級文件","r")) data = f.read() f.close() return data

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> //靜態文件引入寫法 <link rel="stylesheet" href="{{start_url('commons.css')}}" /> </head> <body> <h1>提交內容:</h1> <form method="post" action="/index"> <input type="text" name="xxx" /> <input type="submit" value="提交" /> </form> <h1>展示內容:</h1> //模板語言:1.支持普通方式 2.支持代碼塊方式, 3.支持自定義方式(uimethod,uimodule) <ul> //模板語言固定寫法(普通方式), 使用for循環展示數據 {% for item in xxoo %} <li>{{item}}</li> {% end %} </ul> //轉換后的新字符串格式 <ul> <li>[11]</li> <li>[22]</li> </ul> //靜態文件引入寫法 <script src='{{static_url("oldboy.js")}}'></script> </body> </html>

import tornado.ioloop import tornado.web INPUTS_LIST = [] # 創建MainHandler類繼承tornado.web.RequestHandler class MainHandler(tornado.web.RequestHandler): def get(self): self.write("hello world") #1.打開s1.html文件,讀取內容(包含特殊語法) #2.xxoo = [11,22] && 讀取內容(包含特殊語法),結合起來 #3.得到新的字符串 #4.返回給用戶 self.render("s1.html", xxoo = INPUTS_LIST)#默認去當前目錄下找文件,渲染成html文件 def post(self, *args, **kwargs): name = self.get_argument('xxx') #get_argument獲取前台提交的數據,get&post都可以獲取,get在url中可以傳輸,post只能以提交方式傳輸,兩者區別還有長度安全性 INPUTS_LIST.append(name) print("post") # self.write("hello world") self.render("s1.html", xxoo = INPUTS_LIST) settings = { "tempalte_path": "tpl",#模板路徑配置, 相當於配了全局的變量,以后所有使用html的頁面都可以使用tempalte找到 "static_path": "static",#靜態文件配置 "static_url_prefix": "/sss/", "ui_methods" : mt, "ui_modules": md, } # 匹配用戶URL,和類, 叫:路由映射,路由系統 application = tornado.web.Application([ (r"/index",MainHandler) ], **settings) #配置文件settings生效 if __name__ == "__main": #socket運行起來 application.listen("127.0.0.1",8888) tornado.ioloop.IOLoop.instance().start()

import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): # self.write("Hello, world") self.render('js2.html') settings = { 'template_path':'template',#路徑配置,就是自動取到該路徑下尋找文件 'static_path':'static',#靜態文件配置,需要特殊處理 } #路由映射,根據不同url對應到不同的類里面 application = tornado.web.Application([ (r"/index", MainHandler), ],**settings) #第二個接收配置文件 if __name__ == "__main__": application.listen(8888)#監聽端口 tornado.ioloop.IOLoop.instance().start()

import tornado.ioloop import tornado.web input_list=[]#用來接收用戶的數據 class MainHandler(tornado.web.RequestHandler): def get(self): # self.write("Hello, world") self.render('js2.html',xxxooo = input_list) def post(self, *args, **kwargs): name = self.get_argument('jjj')#根據value提取用戶輸入的名字 input_list.append(name) print(name) #1、打開js2.html文件,讀取內容(包含特殊語法) #2、xxxooo = [11,22,333,44] 讀取內容(包含特殊語法) #3、得到新的字符串 #4、將得到新的字符串返回給用戶 self.render('js2.html',xxxooo = input_list) settings = { 'template_path':'template',#路徑配置,就是自動取到該路徑下尋找文件 'static_path':'static',#靜態文件配置,需要特殊處理 'static_url_prefix':'/sss/',#標記文件開始的名字 } #路由映射,根據不同url對應到不同的類里面 application = tornado.web.Application([ (r"/index", MainHandler), ],**settings) #第二個接收配置文件 if __name__ == "__main__": application.listen(8888)#監聽端口 tornado.ioloop.IOLoop.instance().start()

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>你好</title> <link rel="stylesheet" href="/sss/commons.css"> </head> <body> <P style="font-size: 50px"> js3</P> <h1>提交內容</h1> <form method="post" action="/index"> <input type="text" name="jjj"> <input type="submit" value="提交"> </form> <h1>顯示內容</h1> <ul> {% for item in xxxooo %} <li>{{item}}</li> {% end %} </ul> <!--<script src="sss/jayzhou.js"></script>--> </body> </html>
對於模板語言,主要有三類
模板語言分為三類:
{{}}表達式
{% if %}{% end %}
例如:
<ul>
{% for item in xxxooo %}
<li>{{item}}</li>
{% end %}
</ul> #模板語言里通過for循環展示數據
自定義:
uimethod/uimodle

然后在上面的s1里面配置好設置文件
settings = { 'template_path':'template',#路徑配置,就是自動取到該路徑下尋找文件 'static_path':'static',#靜態文件配置,需要特殊處理 'static_url_prefix':'/sss/',#標記文件開始的名字 'ui_methods':uimethod#這個是我們導入的文件 }
在js2.html文件里面的模板語言
<ul> {% for item in xxxooo %} <li>{{item}}</li> <h2>{{func(item)}}</h2> {% end %} <h2>{{func(amp)}}</h2>
js2.html文件里面要這樣寫
<ul> {% for item in xxxooo %} <li>{{item}}</li> {% end %} <h2>{{func(amp)}}</h2> <h3>{% module custom() %}</h3> <!--調用自定義的custom模塊--> </ul>

# wsgiref在py2中運行正常, 在py3中會報錯 # 當我們將執行的index()和news()功能函數放進Controllers業務邏輯處理模塊, \ # 將返回結果ret改為文件讀寫后的內容, 並將該文件放置到Views或者Template模塊中, \ # 就形成了最基礎版本的MVC和MTV框架 from wsgiref.simple_server import make_server def index(): return "This is index " def news(): return "welcome to news " URLS = { '/index': index, '/news': news, } def RunServer(rq, rp):#RunServer(rq, rp) 該方法中rq封裝了請求信息, rp封裝了響應信息 rp('200 OK', [('Content-Type', 'text/html')]) url = rq['PATH_INFO']#獲取請求的url連接地址 if url in URLS.keys(): ret = URLS[url]()#根據請求的url執行對應的函數 else: ret = '404' return ret if __name__ == '__main__': http = make_server('', 8000, RunServer)#這里創建socket服務端, 並傳入業務邏輯功能函數RunServer(rq, rp) http.serve_forever()#啟動服務端, 阻塞進程等待客戶端訪問, 一旦有訪問則執行RunServer(rq, rp)方法

.body { margin: 0; background-color: cornflowerblue; }

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>S1</title> <link rel="stylesheet" href="../static/commons.css"> </head> <body> <form method="post"> <input type="text" name="name"> <input type="submit" value="提交"> </form> <h1>內容展示</h1> <ul> {% for item in contents %} <li>{{item}}</li> {% end %} </ul> </body> </html>

import tornado.web, tornado.ioloop # 客戶端第一次訪問調用的是`MyHandle`類中的`get(self, *args, **kwargs)`方法, 服務端向客戶端返回`index.html`文件 # - 客戶端瀏覽器接受到`index.html`文件之后, 在輸入框中輸入內容並提交之后會調用`post(self, *args, **kwargs)`, 並將輸入的內容追加到 # - `self.get_argument('name')` 獲取指定參數的內容 # `CONTENTS_LIST`中, 服務端返回`index.html`, 返回過程中`Toranado` # 會將`CONTENTS_LIST` 的內容渲染到`index.html`之后才會發給客戶端瀏覽器 class MyHandle(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.render("index.html", contents=CONTENTS_LIST) def post(self, *args, **kwargs): CONTENTS_LIST.append(self.get_argument('name')) self.render('index.html', contents=CONTENTS_LIST) if __name__ == '__main__': CONTENTS_LIST = []#為存放的是輸入框輸入的內容 settings = { #字典表示的是配置文件 'template_path': 'template',#模板文件的存放位置 'static_path': 'static', #靜態文件的存放位置, 靜態文件必須聲明, 否則瀏覽器無法找到靜態文件 'static_url_prefix': 'static/', #靜態文件前綴, 減少每個文件引入都要加前綴的麻煩 } application = tornado.web.Application([ (r"/index", MyHandle) ], **settings) application.listen(800)#設置服務端的監聽端口 tornado.ioloop.IOLoop.instance().start()#阻塞服務端進程, 等待客戶端的訪問
模板引擎的使用:

def test_uimethod(self):
return "uimethod"

from tornado.web import UIModule
class MyClass(UIModule):
def render(self, *args, **kwargs):
return "uimodule"

import tornado.web, tornado.ioloop import uimethod as ut import uimodule as ud class MyHandle(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.render("index.html", ag="this is ag", contents=CONTENTS_LIST) def post(self, *args, **kwargs): CONTENTS_LIST.append(self.get_argument('name')) self.render('index.html', contents=CONTENTS_LIST) if __name__ == '__main__': CONTENTS_LIST = [] settings = { 'template_path': 'template', 'static_path': 'static', 'static_url_prefix': 'static/', 'ui_methods': ut, 'ui_modules': ud } application = tornado.web.Application([ (r"/index", MyHandle) ], **settings) application.listen(80) tornado.ioloop.IOLoop.instance().start()

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>S1</title> <link rel="stylesheet" href='{{static_url("commons.css")}}'> </head> <body> <h1>{{ag}}</h1> <h1>{{test_uimethod()}}</h1> <h1>{%module MyClass()%}</h1> <form method="post"> <input type="text" name="name"> <input type="submit" value="提交"> </form> <h1>內容展示</h1> <ul> {% for item in contents %} <li>{{item}}</li> {% end %} </ul> <hr> </body> </html>
- 模板引擎中的
{{key}}
表示取key
對應的值, 當key
為函數時候執行該函數並取該函數結果. 例如index.html
文件中的<h1>{{ag}}</h1>
實際上取得是index.py
的self.render("index.html", ag="this is ag", contents=CONTENTS_LIST)
中的參數ag
的值 <h1>{{test_uimethod()}}</h1>
這里執行的是自定義函數, 我們將這個自定義函數寫在uimethod.py
文件中, 並且在index.py
文件中導入, 然后將index.py
文件中的settings
配置增加一行'ui_methods': ut
, 該行內容表示模板引擎可執行自定義函數- 模板引擎中的
{%%}
可用於循環語句和條件語言以及自定義類的執行,{% for item in contents %}
此處正是用於循環遍歷contents
中的內容 <h1>{%module MyClass()%}</h1>
此處表示模板引擎執行自定義類, 該類的文件對應的是uimodule.py
文件, 我們需要在index.py
的settings
中增加一行'ui_modules': ud
, 改行表示模板引擎可使用自定義類- 注意, 我們將
index.html
文件引入css
的方式改為了<link rel="stylesheet" href='{{static_url("commons.css")}}'>
,static_url()
是模板引擎內置的自定義函數, 用該函數引入css
文件時候, 僅當css
文件內容發生變化時候, 瀏覽器才會重新緩存該css
文件
以下tornado_cookie:

import tornado.ioloop import tornado.web import time class IndexHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.render("index.html") class ManagerHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): co = self.get_cookie("auth") if co == "1": self.render("manager.html") else: self.redirect("/login") class LoginHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.render("login.html",status_text="") def post(self, *args, **kwargs): username = self.get_argument("username",None) pwd = self.get_argument("password",None) check = self.get_argument("auth",None) if username == "alex" and pwd == "sb": if check: # self.get_secure_cookie()#原生cookie self.set_cookie("username",username,expires_days=7) self.set_cookie("auth","1",expires_days=7) else: r = time.time() + 10#當前時間加10秒 self.set_cookie("auth","1",expires=r) self.set_cookie("username",username,expires=r) self.redirect("/manager") else: self.render("login.html",status_text="登錄失敗") class LogoutHandler(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.set_cookie("auth","1",expires=time.time()) self.redirect("/login") settings = { "template_path": "views", } application = tornado.web.Application([ (r"/index", IndexHandler), (r"/login",LoginHandler), (r"/manager",ManagerHandler), (r"/logout",LogoutHandler), ],**settings) if __name__ == "__main__": application.listen(8888) tornado.ioloop.IOLoop.instance().start()

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/login" method="post"> <input type="text" name="username" /> <input type="password" name="password" /> <input type="checkbox" name="auth" value="1">7天免登錄 <input type="submit" value="登錄"> <span style="color:red;">{{status_text}}</span> </form> </body> </html>

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <a href="/logout">退出</a> <h1>你的當前銀行卡余額:-1000</h1> </body> </html>

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首頁</h1> </body> </html>

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <input type="text" name="username" /> <input type="password" name="password" /> <input type="checkbox" name="auth" value="1">7天免登錄 <input type="button" value="登錄"> <script src="{{static-url('jquery-3.3.1')}}"></script> <script> function SubmitForm(){ $.post('/login',{'username':$('#user').val(), 'password':$('#pwd').val()},function(callback){ console.log(callback); }) } </script>

start.py文件: from controllers import home import tornado.web import tornado.ioloop settings = { 'template_path': 'views',#模板路勁配置 'static_path':'statics',#靜態文件 } application = tornado.web.Application([ # (r'/index',home.IndexHandler), (r'/index/(?P<num>\d*)/(?P<nid>\d*)',home.IndexHandler), ],**settings) if __name__ == '__main__': application.listen(8880) tornado.ioloop.IOLoop.instance().start() home.html文件: import tornado.web class IndexHandler(tornado.web.RequestHandler): def get(self, nid,num): print(nid,num) self.write("ok")
路由系統:
在Tornado
中支持兩種路由系統, 正則路由系統以及二級域名路由系統
# 默認路由系統, 根據url的不容調用不同的類 application = tornado.web.Application([ (r"/index/(?P<page>\d*)", home.IndexHandle), ], **settings) #二級路由匹配 application.add_handlers("test.ming.com",[ (r"/index/(?P<page>\d*)", home.IndexHandle) ])
(r"/index/(?P<page>\d*)", home.IndexHandle)
這里我們訪問時候需要以類似http://127.0.0.1/index/2
的方式訪問, 在get
或者post
接受處理請求的函數應有page
參數來接受訪問地址最后的整數(我們稍后將根據這個做一個分頁的demo
)- 當我們加入二級域名時候, 默認訪問網站執行第一個默認路由系統, 僅當我們訪問設置的二級域名才會調用對應的處理器(當前代碼指的是
test.ming.com
的域名)
接下來我們使用基於正則的路由系統來實現網頁分頁功能.

#利用全局變量模擬數據庫所有內容 USER_LIST= [ {'username': 'test', 'email': 'test@163.com'} ] #利用循環生成多條數據來模擬大量數據依次便於實現分頁效果 for i in range(300): tmp = {'username': "test - " + str(i), 'email': str(i) + "@vip.com"} USER_LIST.append(tmp)

# 單獨用於實現分頁功能的類 class Page: # current_page表示當前頁數, all_item表示總的數據條目 # 初始化時候將當前頁數current_page與總頁數all_page加入到對象中 def __init__(self, current_page, all_item): # all_page表示總頁數, 每頁顯示5條數據, more表示余數, 如果大於0則表示應多加一頁才能顯示完所有的數據 all_page, more = divmod(all_item, 5) if more > 0: all_page += 1 self.all_page = all_page # 捕捉異常, 防止傳入非法字符冒充頁數, 一旦發生異常則直接將當前頁current_page設置為1, 表示默認顯示第一頁 try: current_page = int(current_page) except Exception as e: current_page = 1 # 如果前傳入的頁數小於1, 則直接默認為第一頁 if current_page < 1: current_page = 1 self.current_page = current_page # 根據當前頁在每個頁面顯示11個頁碼, 此處是開始頁碼 @property def start_page(self): return (self.current_page - 1) * 5 # 結束頁碼 @property def end_page(self): return self.current_page * 5 # 顯示的頁碼對應的html字符串, base_url表示可定制的url跳轉路徑 def page_str(self, base_url): # 定義list_page列表用來暫時存儲所有的頁碼字符串 list_page = [] # 如果總頁數小於11頁, 則直接顯示所有頁數 if self.all_page < 11: s = 0 e = self.all_page # 總頁數大於11頁時候 else: # 當前頁數小於6則直接顯示1到11頁 if self.current_page <= 6: s = 1 e = 11 # 當前頁數大於6頁時候 else: # 當前頁加上5也之后就大於總頁數則直接顯示倒數11頁 if self.current_page + 5 > self.all_page: s = self.all_page - 10 e = self.all_page # 當前頁數大於6頁並且加上5頁並不超過總頁數時候, 顯示當前頁前后5頁以及當前頁 else: s = self.current_page - 5 e = self.current_page + 5 # 首頁設置 first_page = '<a href="/%s/1">首頁</a>' % (base_url) list_page.append(first_page) # 上一頁設置 # 當前頁小於等於第一頁時候, 點擊上一頁不做任何操作(這里理論上是不會有小於的情況) if self.current_page <= 1: pre_page = '<a href="javascript:void(0);">上一頁</a>' # 當前頁大於第一頁, 點擊上一頁則直接跳轉到上一頁 else: pre_page = '<a href="/%s/%s">上一頁</a>' % (base_url, self.current_page - 1) list_page.append(pre_page) # 根據上邊條件過濾后的頁碼起始位置s以及頁碼終止位置e來生成對應的11條頁碼對應的html字符串 for p in range(s, e + 1): if p == self.current_page: tmp = '<a class="active" href="/index/%s">%s</a>' % (p, p) else: tmp = '<a href="/%s/%s">%s</a>' % (base_url, p, p) list_page.append(tmp) # 下一頁設置 # 下一頁要大於或者等於最大頁數時候, 不做任何操作 if self.current_page >= self.all_page: next_page = '<a href="javascript:void(0);">下一頁</a>' else: next_page = '<a href="/%s/%s">下一頁</a>' % (base_url, self.current_page + 1) list_page.append(next_page) # 尾頁設置 last_page = '<a href="/%s/%s">尾頁</a>' % (base_url, self.all_page) list_page.append(last_page) # 頁面跳轉 jump_page = """<input type="text" /><a onclick='JumpTo("%s",this)'>GO</a>""" % base_url # 頁面跳轉的js代碼, 本質就是location.href的使用 jspt = """<script> function JumpTo(base_url,th){ var val=th.previousElementSibling.value; if(val.trim().length>0){ location.href="/"+base_url+"/"+val } } </script>""" list_page.append(jump_page) list_page.append(jspt) return "".join(list_page)

import tornado.web from commons.all import USER_LIST from commons.pager import Page class IndexHandle(tornado.web.RequestHandler): def get(self, c_page): # c_page表示url傳遞過來的當前頁數, len(USER_LIST)表示總共有多少條數據 pg = Page(c_page, len(USER_LIST)) #開始頁數 start = pg.start_page #尾頁 end = pg.end_page #當前顯示的數據片段 current_list = USER_LIST[start:end] #傳入index並返回對應的下面分頁部分的html代碼 str_page = pg.page_str('index') #將當前顯示的數據片段, 當前頁數以及分頁的html代碼返回給瀏覽器 self.render('index.html', list_info=current_list, current_page=pg.current_page, str_page=str_page) def post(self, c_page): #獲取傳入的username的值 username = self.get_argument('username', None) #獲取傳入的email的值 email = self.get_argument("email", None) #生成臨時的字典對象, 表示一個完整的數據片段 tmp = {'username': username, 'email': email} #將該數據片段加入的全局變量USER_LIST, 這里用全局變量模擬數據庫獲取的數據 USER_LIST.append(tmp) self.redirect('/index/' + c_page)

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .pager a{ display: inline-block; padding: 5px; margin: 3px; background-color: aquamarine; } .pager a.active { background-color: crimson; color: aliceblue; } </style> </head> <body> <h1>提交數據</h1> <form method="post" action="/index/{{current_page}}"> <input name="username" type="text"> <input name="email" type="text"> <input type="submit" value="提交"> </form> <h1>顯示數據</h1> <table border="1"> <thead> <tr> <th>用戶名</th> <th>郵箱</th> </tr> </thead> <tbody> {%for tmp in list_info%} <tr> <td>{{tmp['username']}}</td> <td>{{tmp['email']}}</td> </tr> {%end%} </tbody> </table> <div class="pager"> <!--加raw 以此來直接執行原始的字符串, 不做轉義--> {%raw str_page%} </div> </body> </html>

import tornado.web,tornado.ioloop from controllers import home if __name__ == '__main__': settings = { # 模板路徑配置 'template_path': 'views', } application = tornado.web.Application([ (r"/index/(?P<c_page>\d*)", home.IndexHandle), ], **settings) application.listen(80) tornado.ioloop.IOLoop.instance().start()
模板引擎:
基本使用
繼承,extends 頁面整體布局用繼承
導入,include 如果是小組件等重復的那么就用導入
在Tornado
框架中, 模板引擎能帶給我們很多方便, 它是便捷展現頁面的極佳方式. 在上一節中我們介紹了模板引擎對於{{}}
以及對於 {%%}
的用法. 我們簡單回顧一下:
**{{}}
使用: **
- 直接取服務端在
render()
函數中傳遞參數的值, 例如服務端中有self.render('index.html', contents=CONTENTS_LIST)
, 在html
文件中有{{contents}}
則表示在html
中取服務端的CONTENTS_LIST
的內容 - 我們通過配置
'ui_methods': 需要執行的自定義python模塊,
之后, 我們可以在html
文件中通過{{自定義python模塊中的函數名()}}
來執行對應的函數取得該函數的返回結果以此來顯示
**{%%}
的使用: **
{%for tmp in iterable%}
用於循環語句, 注意要加上{%end%}
結束語句{%if condition%}
用於條件判斷, 同樣同上需要結束語句- 通過配置
ui_modules : 需要執行的python模塊
之后, 我們可以在文件中通過{%module python模塊名()%}
來直接執行該模塊中對應的方法, 注意該模塊需要繼承tornado.web.UIModule
以上有不懂的請參照上一篇博客(Tornado框架01-入門總概)中的具體實例實現后再對應解釋來理解
接下來我們老規矩, 先使用一下模板引擎的繼承之后, 再詳細介紹


import tornado.web class IndexHandle(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.render("extend/index.html") class AccountHandle(tornado.web.RequestHandler): def get(self, *args, **kwargs): self.render("extend/account.html")

{%extends "../template/master.html"%} <!--自定義css具體內容--> {%block tm_css%} <style type="text/css"> .page-content{ background-color: green; } </style> {%end%} <!--#自定義的文本內容--> {%block tm_content%} <h1>This is Account</h1> {%end%} <!--#自定義的js文件--> {%block tm_js%} <script type="text/javascript"> console.log('This is Account') </script> {%end%}

{%extends "../template/master.html"%} <!--對應的自定義css的具體內容--> {%block tm_css%} <style type="text/css"> .page-content{ background-color: yellow; } </style> {%end%} <!--自定義的文本內容--> {%block tm_content%} <h1>This is Index</h1> {%end%} <!--自定義的js文件--> {%block tm_js%} <script type="text/javascript"> console.log('This is Index') </script> {%end%}

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Master</title> <style type="text/css"> * { margin: 0px; padding: 0px; } .page-header{ height: 50px; background-color: red; } .page-content { height: 200px; background-color: azure; } .page-footer{ height: 50px; background-color: black; } </style> <!--為后邊自定義的css命名並占位置--> {%block tm_css%}{%end%} </head> <body> <div class="page-header"></div> <div class="page-content"> <!--自定義的內容, 命名並占位--> {%block tm_content%}{%end%} </div> <div class="page-footer"></div> <!--自定義的js文件位置--> {%block tm_js%}{%end%} </body> </html>

import tornado.web, tornado.ioloop
from controllers import home
if __name__ == '__main__':
CONTENTS_LIST = []
settings = {
'template_path': 'views',
}
application = tornado.web.Application([
(r"/index", home.IndexHandle),
(r"/account", home.AccountHandle),
], **settings)
application.listen(80)
tornado.ioloop.IOLoop.instance().start()
- 從運行結果來看, 兩個網頁的主體結構相同, 只是里邊包含的
css
具體樣式, 具體內容以及js
文件不同 - 要繼承模板文件來使用我們要在當前文件首行寫上
{%extends "../template/master.html"%}
, 這里表示當前文件以master.html
來進行渲染
{%block tm_css%}
<style type="text/css">
.page-content{
background-color: yellow;
}
</style>
{%end%}```
在index.html
的這部分其實就是master.html
中tm_css
的具體內容
- 在
master.html
文件中{%block tm_css%}{%end%}
相當與為后面具體要寫入的內容做一個占位符, 並且起名為tm_css
.
使用模板的繼承可以重復使用相同結構的模板, 可以大大減少代碼量. 但是有時候並不是所有的網頁結構都是我需要的, 我們會想要單獨包含所有網頁都有的相同的一小部分內容. 此時就需要模板文件的包含來實現
<form> <input type="text"> <input type="submit" value="提交"> </form>

{%extends "../template/master.html"%} <!--對應的自定義css的具體內容--> {%block tm_css%} <style type="text/css"> .page-content{ background-color: yellow; } </style> {%end%} <!--自定義的文本內容--> {%block tm_content%} <h1>This is Index</h1> {%include "../include/form.html"%} {%include "../include/form.html"%} {%include "../include/form.html"%} {%end%} <!--自定義的js文件--> {%block tm_js%} <script type="text/javascript"> console.log('This is Index') </script> {%end%}
cookie:
cookie:在瀏覽器端保存鍵值對, 特性:每次http請求都會附加在請求中並發送給服務器端

import tornado.web class IndexHandle(tornado.web.RequestHandler): def get(self): username = self.get_argument('u', None) if not username: # 設置未加密的cookie, 鍵為'name', 值為test self.set_cookie('name', 'test') #設置加密cookie, 鍵為'user', 值為test. # 設置加密cookie我們需要在配置中添加自定義的加密串(俗稱對加密結果加鹽)"cookie_secret": 'test-secret,' self.set_secure_cookie('user', 'test') self.redirect('/admin') def post(self): pass class AdminHandle(tornado.web.RequestHandler): def get(self, *args, **kwargs): #獲取指定key未加密的cookie的值 name = self.get_cookie('name', None) #獲取指定key的加密后的cookie的值 user = self.get_cookie('user', None) print('name: ', name, "\nuser: ", user) # 對於set_cookie()和set_secure_cookie()都用以下常見參數 # name 表示傳入cookie的鍵 # value 表示傳入cookie的name對應的值 # domain=None 表示域名 # expires=None 設置過期時間, 這里單位為秒 # path="/" 表示當前的cookie在那些路徑下有效, /表示當前域名下所有的路徑均有效 # expires_days=None 設置過期時間, 單位為天

import tornado.web, tornado.ioloop from controllers import home if __name__ == '__main__': settings = { # 模板路徑配置 'template_path': 'views', "cookie_secret": 'test-secret,' } application = tornado.web.Application([ (r"/index", home.IndexHandle), (r"/admin", home.AdminHandle), ], **settings) application.listen(803) tornado.ioloop.IOLoop.instance().start()
加密cookie
的加密和解密原理:
- 解密時候將加密
cookie
中的base64(test)
也就是加密后的值和時間戳再加上cookie_secret
生成新的加密串和加密cookie
中的加密串比較, 若相同則合法驗證通過, 然后再通過反解加密base64(test)
取其本來的值
JavaScript操作Cookie
由於Cookie保存在瀏覽器端,所以在瀏覽器端也可以使用JavaScript來操作Cookie。

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <h1>adasd</h1> <script> /* 設置cookie,指定秒數過期 */ function setCookie(name,value,expires){ var temp = []; var current_date = new Date(); current_date.setSeconds(current_date.getSeconds() + 5); document.cookie = name + "= "+ value +";expires=" + current_date.toUTCString(); } </script> </body> </html> 然后在瀏覽器中訪問 setCookie("k22=11",5) 設置cookie,超時時間為5秒 undefined document.cookie 查看cookie "k1=999; k22=11= 5" document.cookie 5秒后查看 "k1=999" <!--toUTCString() 方法可根據世界時 (UTC) 把 Date 對象轉換為字符串,並返回結果--> <!--設置cookie,指定秒數過期--> <!--current_date.getSeconds() 獲取當前秒--> <!--current_date.setSeconds 設置秒--> <!--data.setDate(data.getDate()+7),表示獲取超過現在7天的時間--> <!--current_date 當前時間+5秒--> <!--toUTCString() 當前統一時間-->
對於參數
- domain 指定域名下的cookie
- path 域名下指定url中的cookie
- secure https使用
jquery中設置cookie
要用需要下載 這里
1、 導入jquery
2、 導入jQuery Cookie Plugin v1.4.1
注意點:
如果用jquery導入的時候expires這里如果為數字的時候,表示天數
如果不想用天數,那么要用,這里的超時時間必須要用toUTCString()統一時間
current_date.setSeconds(current_date.getSeconds() + 5); 用天數,然后用字符串拼接的方式";expires="+ current_date.toUTCString()
等來設置時間,js數組的.join方法是吧數組變成字符串
$.cookie(“k1”,”22”,{“path”:””,”domin”:””,expires=1})
上面的cookie中的數組,在內部用了join方法分割成了字符串
tornado支持兩種方式
- 一、簡單的方式
- 二、簽名的方式
首先服務端讓瀏覽器端生成cookie的時候會經過base64加密,首先生成加密串,
注意這里的當前時間是
>>> import time
>>> time.time()
1491471613.5271676 --->生成的這個值就是當前時間
>>>
加密串 =v1(value)+當前時間+內部自定義字符串
之后生成的這個cookie就是k1(key)=v1|加密串|當前時間
如何驗證這個cookie有沒有被篡改:
客戶端向瀏覽器端發送請求:會把v1和加密串和當前時間發送給瀏覽器,瀏覽器內部會經過md5生成一個新的加密串
自定義字符串+發送過來的時間+v1等於新的加密串,然后加密串進行對比,如果一致就能通過

import tornado.ioloop import tornado.web class IndexHandler(tornado.web.RequestHandler): #這里判斷判斷用戶登錄 def get(self): if self.get_argument("u",None) in ["aa","eric"]: self.set_cookie("name",self.get_argument("u")) # self.set_secure_cookie("name",self.get_argument("u")) else: self.write("請登錄") class ManagerHandler(tornado.web.RequestHandler): #如果有cookie的時候就登錄 def get(self): if self.get_cookie("name",None) in ["aa","eric"]: self.write("歡迎登錄:"+self.get_cookie("name")) else: self.redirect("/index") settings={ "template_path":"views", "static_path":"statics" } application=tornado.web.Application([ (r"/index",IndexHandler), (r"/manager",ManagerHandler) ],**settings) if __name__=="__main__": application.listen(8000) tornado.ioloop.IOLoop.instance().start() # 上面就是用一種簡單的模式登錄,登錄的時候 # 在瀏覽器中輸入 # http://127.0.0.1:8000/index?u=aa # 之后就會執行IndexHandler方法中的get方法首先存入用戶輸入的cookie,對比后台,然后訪問manager網站的時候,判斷,如果有對應的cookie那么就會出現歡迎登錄

import tornado.ioloop import tornado.web class IndexHandler(tornado.web.RequestHandler): #這里判斷判斷用戶登錄 def get(self): if self.get_argument("u",None) in ["alex","eric"]: # 這里設置加密的cookie self.set_secure_cookie("user",self.get_argument("u")) else: self.write("請登錄") class ManagerHandler(tornado.web.RequestHandler): #如果有cookie的時候就登錄 def get(self): # 獲取加密的cookie if str(self.get_secure_cookie("user",None),encoding="utf-8") in ["alex","eric"]: self.write("歡迎登錄:"+str(self.get_secure_cookie("user"))) else: self.redirect("/index") settings={ "template_path":"views", "static_path":"statics", # 這必須設置配置 "cookie_secret":"hello", } application=tornado.web.Application([ (r"/index",IndexHandler), (r"/manager",ManagerHandler) ],**settings) if __name__=="__main__": application.listen(8000) tornado.ioloop.IOLoop.instance().start() # 設置加密的cookie用set_secure_cookie()方法,如果獲取cookie的時候用get_secure_cookie() # 注意這里獲取加密cookie # 注意:這里獲取的cookie是byte類型,所以必須要轉換一下類型
Cookie 很容易被惡意的客戶端偽造。加入你想在 cookie 中保存當前登陸用戶的 id 之類的信息, 你需要對 cookie 作簽名以防止偽造。Tornado 通過 set_secure_cookie 和 get_secure_cookie 方法直接支持了這種功能。 要使用這些方法,你需要在創建應用時提供一個密鑰,名字為 cookie_secret。 你可以把它作為一個關鍵詞參數傳入應用的設置中 簽名Cookie的本質是: 寫cookie過程: 將值進行base64加密 對除值以外的內容進行簽名,哈希算法(無法逆向解析) 拼接 簽名 + 加密值 讀cookie過程: 讀取 簽名 + 加密值 對簽名進行驗證 base64解密,獲取值內容 注:許多API驗證機制和安全cookie的實現機制相同
session:
cookie
中勢必會造成瀏覽器端的臃腫, 此時便需要在服務端保存原本在瀏覽器端的那些鍵值對. 在瀏覽器端只需存儲一個表示身份的隨機加密字符串, 當瀏覽器端訪問服務端時候攜帶該字符串, 經過比較, 驗證合法之后便可以取該用戶在服務端存儲的相應信息. 但是在Tornado
中並沒有session
的模塊, 我們需要自定義來實現.
優化后代碼:
在Tornado框架中,默認執行Handler的get/post等方法之前默認會執行 initialize方法,所以可以通過自定義的方式使得所有請求在處理前執行操作
這里的initialize就是鈎子函數

#定義tornado中的鈎子函數和反射函數來優化下面的類 class BaseHandler(tornado.web.RequestHandler): def initialize(self): self.session=Session(self) class IndexHandler(BaseHandler): #這里判斷判斷用戶登錄,get方法是被反射調用的getattr def get(self): if self.get_argument("u",None) in ["aa","eric"]: self.session.set_value("is_login",True) self.session.set_value("name",self.get_argument("u",None)) else: self.write("請登錄") class ManagerHandler(BaseHandler): def get(self): val=self.session.get_value("is_login") if val: self.write(self.session.get_value("name")) else: self.write("請重新登錄")

#!/usr/bin/env python # -*- coding:utf-8 -*- import tornado.ioloop import tornado.web #這個字典必須定制成為全局變量用來保存用戶的信息,如果是局部變量, # 那么http請求斷開下次用戶登錄這個用戶信息就會消失 container={} #把Sesson封裝起來 class Session: def __init__(self,handler): self.handler=handler self.random_str=None #用戶連接初始化隨機數 def __genarate_random_str(self): #創建隨機字符串 import hashlib import time #首先通過md5生成隨機數據,電腦中的數據都是16進制保存的 obj=hashlib.md5() ## 加入自定義參數來更新md5對象 obj.update(bytes(str(time.time()),encoding="utf-8")) # 得到加嚴后的十六進制隨機字符串來作為用戶的索引 random_str=obj.hexdigest() return random_str def __setitem__(self,key,value): #這里判斷如果服務端沒有隨機數 if not self.random_str: #用戶連接,首先服務端沒有隨機數,那么去客戶端拿隨機數 random_str=self.handler.get_cookie("__kakaka__") #去客戶端中拿隨機數 if not random_str: #如果客戶端也沒有隨機數,那么服務端就自己創建隨機數 random_str=self.__genarate_random_str() #創建隨機數 container[random_str]={} #清空隨機數字典中的內容 else: if random_str in container.keys(): #如果客戶端有隨機數,並且為真那么就直接登錄成功 pass else: #如果客戶端到的隨機數是偽造的,那么服務端就自己創建隨機數 random_str=self.__genarate_random_str() container[random_str]={} self.random_str=random_str #最后把上面判斷出來的隨機數傳遞給類 container[self.random_str][key]=value #這里是為寫超時時間做准備 self.handler.set_cookie("__kakaka__",self.random_str) #設置cookie給瀏覽器,這里可以設置超時時間 def __getitem__(self,key): #獲取值, 注意加密方式返回的cookie的值是bytes類型的 random_str=self.handler.get_cookie("__kakaka__") # 如果客戶端沒有隨機字符串(索引值為空表示當前用戶是新用戶,直接返回空,),就結束,程序到此終止 if not random_str: return None user_info_dict=container.get(random_str,None)#客戶端有隨機字符串,但是內容服務器不匹配,就退出 if not user_info_dict: return None value=user_info_dict.get(key,None) #前面如果都滿足,有值就拿值,沒有就為None return value #定義tornado中的鈎子函數和反射函數來優化下面的類 class BaseHandler(tornado.web.RequestHandler): def initialize(self): self.session=Session(self) class IndexHandler(BaseHandler): #這里判斷判斷用戶登錄,get方法是被反射調用的getattr def get(self): if self.get_argument("u",None) in ["aa","eric"]: self.session["is_login"]=True self.session["name"]=self.get_argument("u",None) # self.session.set_value("is_login",True) # self.session.set_value("name",self.get_argument("u",None)) else: self.write("請登錄") class ManagerHandler(BaseHandler): def get(self): val=self.session["is_login"] if val: self.write(self.session["name"]) else: self.write("請重新登錄") settings={ "template_path":"views", "static_path":"statics", "cookie_secret":"hello", } application=tornado.web.Application([ (r"/index",IndexHandler), (r"/manager",ManagerHandler) ],**settings) if __name__=="__main__": application.listen(8003) tornado.ioloop.IOLoop.instance().start()

用戶如果直接連接manager會提示必須登錄,主要原因是瀏覽器cookie中沒有登錄信息 1、 用戶訪問index網頁的時候就是訪問IndexHandler這個類,用戶連接,服務器內部就會初始化隨機數 2、 服務器就會執行set_value方法,並且傳入參數is_login參數,首先為了判斷用戶是否第一次登陸,所以用if not self.random_str,沒有就用get_cookie()方法去客戶端中拿隨機數,這里需要判斷,如果客戶端也沒有隨機數,那么服務端就要自己創建隨機數,並且把這個隨機數傳遞給服務器這個類;如果客戶端有隨機數,要判斷這個隨機數是否是偽造的,如果是偽造的,服務器需要自己創建隨機數,並且把這個隨機數傳遞給服務器這個類;之后把is_login參數替代key傳遞給session這個字典求出來value這個值,並且設置一下這個cookie傳遞給瀏覽器;然后設置key為name的cookie 3、 用戶訪問manager這個網站,會執行get方法,並且獲取瀏覽器隨機數,如果瀏覽器中沒有隨機數或者瀏覽器的隨機數是偽造的,那么就會退出,如果經過了2這個步驟,那么就能登錄成功並且得到設置cookie中key為name的值
驗證碼:
驗證碼原理在於后台自動創建一張帶有隨機內容的圖片,然后將內容通過img標簽輸出到頁面。
這個驗證碼是放在tornado的session里面的
驗證碼機制:服務器首先創建驗證碼,並且把驗證碼放入到隨機數這個字典里面,用戶通過get方法接收到驗證碼,然后用戶輸入驗證碼和賬戶信息發送給服務器,服務器通過對比用戶發來的驗證碼和自己產生的驗證碼,(這里要創建不分辨大小寫,可以讓用戶輸入的和自己產生的轉成全部大寫或者全部小寫)對比,如果一樣那么就顯示登錄成功,如果沒有一樣,那么就顯示輸入的驗證碼錯誤。並且在前端添加一個點擊事件,只要用戶一點擊那么驗證碼就會刷新
pip3 install pillow 安裝圖像處理模塊

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> .aa{ cursor: pointer; } </style> </head> <body> <h1>請輸入登錄信息</h1> <form action="/login" method="post"> <p><input name="user" type="text" placeholder="用戶名" /></p> <p><input name="pwd" type="password" placeholder="密碼" /></p> <p> <input name='code' type="text" placeholder="驗證碼" /> <img class="aa" src="/check_code" onclick='ChangeCode();' id='imgCode'> </p> <input type="submit" value="提交"/><span style="color: red">{{status}}</span> </form> <script type="text/javascript"> function ChangeCode() { var code = document.getElementById('imgCode'); //url后面只能添加問號,添加問號就是改變地址 code.src += '?'; } </script> </body> </html>

#/usr/bin/env python #-*- coding:utf-8-*- import tornado.ioloop import tornado.web import tornado.httpserver import tornado.ioloop import tornado.process import tornado.web # #這個字典必須定制成為全局變量用來保存用戶的信息,如果是局部變量, # 那么http請求斷開下次用戶登錄這個用戶信息就會消失 container={} #把Sesson封裝起來 class Session: def __init__(self,handler): self.handler=handler self.random_str=None #用戶連接初始化隨機數 def __genarate_random_str(self): #創建隨機字符串 import hashlib import time #首先通過md5生成隨機數據,電腦中的數據都是16進制保存的 obj=hashlib.md5() obj.update(bytes(str(time.time()),encoding="utf-8")) random_str=obj.hexdigest() return random_str def __setitem__(self,key,value): #這里判斷如果服務端沒有隨機數 if not self.random_str: #用戶連接,首先服務端沒有隨機數,那么去客戶端拿隨機數 random_str=self.handler.get_cookie("__kakaka__") #去客戶端中拿隨機數 if not random_str: #如果客戶端也沒有隨機數,那么服務端就自己創建隨機數 random_str=self.__genarate_random_str() #創建隨機數 container[random_str]={} #清空隨機數字典中的內容 else: if random_str in container.keys(): #如果客戶端有隨機數,並且為真那么就直接登錄成功 pass else: #如果客戶端到的隨機數是偽造的,那么服務端就自己創建隨機數 random_str=self.__genarate_random_str() container[random_str]={} self.random_str=random_str #最后把上面判斷出來的隨機數傳遞給類 container[self.random_str][key]=value #這里是為寫超時時間做准備 self.handler.set_cookie("__kakaka__",self.random_str) #設置cookie給瀏覽器,這里可以設置超時時間 def __getitem__(self,key): #獲取值 random_str=self.handler.get_cookie("__kakaka__") if not random_str:#如果客戶端沒有隨機字符串,就結束 return None user_info_dict=container.get(random_str,None)#客戶端有隨機字符串,但是內容服務器不匹配,就退出 if not user_info_dict: return None value=user_info_dict.get(key,None) #前面如果都滿足,有值就拿值,沒有就為None return value #定義tornado中的鈎子函數和反射函數來優化下面的類 class BaseHandler(tornado.web.RequestHandler): def initialize(self): self.session=Session(self) class IndexHandler(BaseHandler): #這里判斷判斷用戶登錄,get方法是被反射調用的getattr def get(self): if self.get_argument("u",None) in ["aa","eric"]: self.session["is_login"]=True self.session["name"]=self.get_argument("u",None) # self.session.set_value("is_login",True) # self.session.set_value("name",self.get_argument("u",None)) else: self.write("請登錄") class ManagerHandler(BaseHandler): def get(self): val=self.session["is_login"] if val: self.write(self.session["name"]) else: self.write("請重新登錄") # class CheckCodeHandler(BaseHandler): # def get(self): # import io # import check_code # # mstream = io.BytesIO() # img, code = check_code.create_validate_code() # img.save(mstream, "GIF") # # self.session["CheckCode"] = code # self.write(mstream.getvalue()) class MainHandler(BaseHandler): def get(self): self.render('login.html',status="") def post(self, *args, **kwargs): user=self.get_argument("user",None) pwd=self.get_argument("pwd",None) code=self.get_argument("code",None) #比較用戶輸入的驗證碼和服務器給出的驗證碼的值 check_code=self.session["CheckCode"] if code.upper()==check_code.upper(): self.write("驗證碼正確") else: # self.redirect("/login") self.render("login.html",status="驗證碼錯誤") class CheckCodeHandler(BaseHandler): def get(self, *args, **kwargs): #生成圖片並且返回 import io import check_code #建立內存級別文件,相當於一個容器 mstream = io.BytesIO() #創建圖片並且寫入驗證碼 img, code = check_code.create_validate_code() #將圖片內容寫入到IO中mstream img.save(mstream, "GIF") #為每個用戶保存其對應的驗證碼 self.session["CheckCode"] = code self.write(mstream.getvalue()) settings={ 'template_path': 'views', 'static_path': 'static', "static_url_prefix":"/statics/", "cookie_secret":"hello", # "xsrf_cookies":True, } application=tornado.web.Application([ (r"/index",IndexHandler), (r"/manager",ManagerHandler), # (r"/login",LoginHandler), (r"/login",MainHandler), (r"/check_code",CheckCodeHandler), ],**settings) if __name__=="__main__": application.listen(8000) tornado.ioloop.IOLoop.instance().start()
下載下面源碼之后,需要把check_code.py和Monaco.ttf導入到這個代碼目錄中(僅僅限制與python3.5)
CSRF:
會集群要會:分布式哈希haxi redis
CSRF限制post請求的
用戶訪問是先請求服務器調用get請求,然后發送post請求,之后服務器會給用戶一個隨機字符串,當用戶離開后,下次再訪問會帶着這個隨機字符串訪問服務器,如果用戶沒有這個隨機字符串,那么CSRF會阻止這個用戶請求,這樣可以使服務器免遭受惡意攻擊造成服務器宕機
要加上CSRF:
1、在配置文件中加上配置文件”xsrf_cookies”:True
2、在前台代碼中加上{% raw xsrf__form_html %}

class CsrfHandler(BaseHandler): def get(self, *args, **kwargs): self.render("csrf.html") def post(self, *args, **kwargs): self.write("csrf.post") settings={ 'template_path': 'views', 'static_path': 'static', "static_url_prefix":"/statics/", "cookie_secret":"hello", "xsrf_cookies":True, 這里加上配置文件 } application=tornado.web.Application([ (r"/index",IndexHandler), (r"/manager",ManagerHandler), # (r"/login",LoginHandler), (r"/login",MainHandler), (r"/check_code",CheckCodeHandler), (r"/csrf",CsrfHandler) ],**settings)

<form action="/csrf" method="post"> {% raw xsrf_form_html() %} <p><input type="text" placeholder="用戶"/></p> <p><input type="text" placeholder="密碼"/></p> <p> <input name="code" type="text" placeholder="驗證碼"/> <!--<img src="/check_code">--> </p> <input type="submit" value="Submit"/>
提交的是AJAX的post請求
如果你提交的是 AJAX 的 POST
請求,你還是需要在每一個請求中通過腳本添加上 _xsrf
這個值。下面是在 FriendFeed 中的 AJAX 的 POST
請求,使用了 jQuery 函數來為所有請求組添加 _xsrf
值:
function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b");
return r ? r[1] : undefined;
}
jQuery.postJSON = function(url, args, callback) {
args._xsrf = getCookie("_xsrf");
$.ajax({url: url, data: $.param(args), dataType: "text", type: "POST",
success: function(response) {
callback(eval("(" + response + ")"));
}});
};
對於 PUT
和 DELETE
請求(以及不使用將 form 內容作為參數的 POST
請求) 來說,你也可以在 HTTP 頭中以 X-XSRFToken
這個參數傳遞 XSRF token。
如果你需要針對每一個請求處理器定制 XSRF 行為,你可以重寫 RequestHandler.check_xsrf_cookie()
。例如你需要使用一個不支持 cookie 的 API, 你可以通過將 check_xsrf_cookie()
函數設空來禁用 XSRF 保護機制。然而如果 你需要同時支持 cookie 和非 cookie 認證方式,那么只要當前請求是通過 cookie 進行認證的,你就應該對其使用 XSRF 保護機制,這一點至關重要。
1、cookie和session的區別
1)cookie是保存在客戶端的,session是保存在服務端的,因為服務器端,表示可能在內存中,可能在數據庫端,可能在緩存中統稱為服務器端
2、session和cookie有什么聯系?:
答:有。session是通過cookie人為構建起來的,在web開發里面本身沒有session這個東西的。在服務器端可以高層一個數據庫,可以在內存中搞成一個字典,每一次用戶來訪問的時候,給用戶發一對token,下一次,用戶訪問再帶着這一對token來,服務器端就知道你是不是上一次的你。如果再問就來畫一張圖
3、分頁 XSS 跨站腳本攻擊
4、CSRF/別名XSRF工作方式:
答:跨站請求偽造, 理解為:攻擊者盜用了你的身份,以你的名義發送惡意請求
驗證:第一次請求的時候是get方式請求,防止沒有經過驗證就來post請求,造成大並發機器宕機
5、 Ajax
為什么要有Ajax
答:防止頁面批量刷新
利用:
iframe 忽略
XMLHttpRequest
自己寫
xhr
xhr.open()
xhr.onreadystatechange
xhr.send()
jQuery
會用下面的就會jquery,ajax
$.ajax({
url:
type
data
dataType
success
error
})
6、 驗證碼、
7、 上傳文件
form標簽
form標簽 enctype=““form標簽里面必須要有這個才能進行上傳文件
通過Ajax上傳文件
利用formDate()
XMLHttpRequest
jQuery
iframe+form標簽為了兼容性設計,ifram就相當於設置一個通道,form把數據提交到這個通道,然后不刷頁面上傳文件
tornado 結合前端進行文件上傳:
在表單中我們獲取用戶提交的數據,使用的是get_argument,復選框使用的是get_arguments,但是文件的不一樣,文件的使用request.files。
form文件上傳

<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> <title>上傳文件</title> </head> <body> <form id="my_form" name="form" action="/index" method="POST" enctype="multipart/form-data" > <input name="fff" id="my_file" type="file" /> <input type="submit" value="提交" /> </form> </body> </html>
注意:
form文件上傳,一定要在form表單上設置enctype的參數。enctype="multipart/form-data"。不然上傳無法成功。

import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get(self): self.render('index.html') def post(self, *args, **kwargs): file_metas = self.request.files["fff"] # print(file_metas) for meta in file_metas: file_name = meta['filename'] with open(file_name, 'wb') as up: up.write(meta['body']) settings = { 'template_path': 'template', } application = tornado.web.Application([ (r"/index", MainHandler), ], **settings) if __name__ == "__main__": application.listen(8003) tornado.ioloop.IOLoop.instance().start()
說明: 1、代碼中self.request封裝了所有發送過來請求的內容。 2、self.request.files:可以獲取上傳文件的所有信息。此方法獲取的是一個生成器,內部是由yield實現的,因此我們在利用此方法返回的對象的時候,不能通過下標獲取里面的對象,只能通過迭代的方法。 3、迭代出來的對象的filename:就表示上傳文件的文件名。 4、迭代出來的對象的body:表示上傳文件的內容。獲取的文件內容是字節形式的。
ajax上傳文件
- 原生ajax
- jquery
原生ajax上傳文件

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <input type="file" id="img" /> <input type="button" onclick="UploadFile();" /> <script> function UploadFile(){ var fileObj = document.getElementById("img").files[0]; var form = new FormData(); form.append("k1", "v1"); form.append("fff", fileObj); var xhr = new XMLHttpRequest(); xhr.open("post", '/index', true); xhr.send(form); } </script> </body> </html>
說明:
代碼中利用原生的ajax進行文件上傳。
關鍵點:
1、獲取文件對象,通過files[0],獲取當前上傳的文件對象。
2、通過FormData(),實例化一個對象form對象。
3、然后將要傳遞的參數,文件以鍵和值以逗號分隔的形式append到form對象中去。
4、然后將整個form對象發送到服務端。
注意:
后台代碼和上面的代碼一樣,不變。注意接收的文件名要同步。
jquery文件上傳:

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <input type="file" id="img" /> <input type="button" onclick="UploadFile();" /> <script> function UploadFile(){ var fileObj = $("#img")[0].files[0]; var form = new FormData(); form.append("k1", "v1"); form.append("fff", fileObj); $.ajax({ type:'POST', url: '/index', data: form, processData: false, // tell jQuery not to process the data contentType: false, // tell jQuery not to set contentType success: function(arg){ console.log(arg); } }) } </script> </body> </html>
說明:
1、和原生的一樣,都是顯得獲取當前上傳文件的對象。files[0];然后實例化form對象,將要傳遞的內容append到實例化的對象form中。
2、后台代碼同前,注意字段名對應。
關鍵點:
processData:false和contentType:false。這2個是關鍵。
默認的jquery會將我們上傳的數據做部分處理。上面兩段代碼,就是告訴jquery不要處理我們的文件,不然會將我們的文件處理得不完整。
iframe文件上傳
原生的ajax和jquery上傳的時候,我們都是通過實例化一個form對象來進行文件的上傳。但是實例化這個form的對象並不是所有的瀏覽器都存在,比如低版本的IE就可能沒有合格FormData對象,那上面的方法就存在兼容性,沒有form對象就不能發送。因此的使用一個兼容性更好的來進行操作,iframe。

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <form id="my_form" name="form" action="/index" method="POST" enctype="multipart/form-data" > <div id="main"> <input name="fff" id="my_file" type="file" /> <input type="button" name="action" value="Upload" onclick="redirect()"/> <iframe id='my_iframe' name='my_iframe' src="" class="hide"></iframe> </div> </form> <script> function redirect(){ document.getElementById('my_iframe').onload = Testt; document.getElementById('my_form').target = 'my_iframe'; document.getElementById('my_form').submit(); } function Testt(ths){ var t = $("#my_iframe").contents().find("body").text(); console.log(t); } </script> </body> </html>
關鍵點:
1、document.getElementById('my_form').target = 'my_iframe':這段代碼就是獲取iframe標簽。
target就是目標,只要給form設置了target的話,form提交的時候,就會提交到這個target指定的目標上。所以上面的代碼表示只要form提交,就會提交到iframe上去。
2、當iframe操作完后會執行Testt方法,Testt方法就是獲取后台返回的信息,並打印。
Jsonp實現ajax跨域請求
同源策略
瀏覽器有一個很重要的概念——同源策略(Same-Origin Policy)。所謂同源是指,域名,協議,端口相同。不同源的客戶端腳本(javascript、ActionScript)在沒明確授權的情況下,不能讀寫對方的資源。比較特別的是:由於同源策略是瀏覽器的限制,所以請求的響應和發送是可以進行的,只不過瀏覽器不支持罷了.
- 限制:XmlHttpRequest, AJax
- 不限制:img iframe script塊等等具有src屬性的標簽
JSONP(JSON with Padding)
是JSON的一種”使用模式”,可用於解決主流瀏覽器的跨域數據訪問的問題。由於同源策略,一般來說位於 server1.example.com 的網頁無法與不是 server1.example.com的服務器溝通,而 HTML 的script
元素是一個例外。利用 <script>
元素的這個開放策略,網頁可以得到從其他來源動態產生的 JSON 資料,而這種使用模式就是所謂的 JSONP。用 JSONP 抓到的資料並不是 JSON,而是任意的JavaScript,用 JavaScript 直譯器執行而不是用 JSON 解析器解析
基本思路
利用script標簽,src導入目標域名的接口,在文檔數的head標簽中添加一行script標簽,得到內容后將scprit標簽刪除,返回的解析后的參數即為得到的數據.

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <h1>Index</h1> <input type="button" onclick="Ajax();" value="普通AJax"/> <input type="button" onclick="Ajax2();" value="跨域普通AJax"/> <input type="button" onclick="Ajax3();" value="跨域牛逼AJax"/> <input type="button" onclick="Ajax4();" value="江西TV"/> <script src="/static/jquery-2.1.4.min.js"></script> <script> // 原生ajax,測試無效 function Ajax(){ $.ajax({ url: '/get_data/', type: 'POST', data: {'k1': 'v1'}, success: function (arg) { alert(arg); } }) } // 使用ajax跨域請求,測試無效 function Ajax2(){ $.ajax({ url: 'http://wupeiqi.com:8001/api/',//需修改 type: 'GET', data: {'k1': 'v1'}, success: function (arg) { alert(arg); } }) } // 利用script標簽,得到數據 function Ajax3(){ // script // alert(api) var tag = document.createElement('script'); tag.src = 'http://wupeiqi.com:8001/api/';//需修改 document.head.appendChild(tag); document.head.removeChild(tag); } function fafafa(arg){ console.log(arg); } // 例子,獲取江西衛視的節目單 function Ajax4(){ // script // alert(api) var tag = document.createElement('script'); tag.src = 'http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403'; document.head.appendChild(tag); document.head.removeChild(tag); } function list(arg){ console.log(arg); } </script> </body> </html>
JSONP實現ajax跨域
以上的代碼其實也是jsonp的基本思路

$.ajax({
url:..
type: 'GET',
dataType: 'jsonp',
//傳遞給請求處理程序或頁面的,用以獲得jsonp回調函數名的參數名(一般默認為:callback)
jsonp: 'callback',
//自定義的jsonp回調函數名稱,默認為jQuery自動生成的隨機函數名,也可以寫"?",jQuery會自動為你處理數據
jsonpCallback: 'list'
})
function list(arg){
}
jsonp: callback #發送給請求處理程序的,被請求端通過request.GET.get("callback"),獲得jsonp回調函數的參數
jsonpCallback: 'list' #定義回調函數的名稱,然后后面通過list(...)來處理獲取數據

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="button" onclick="Jsonp1();" value='提交'/> </p> <p> <input type="button" onclick="Jsonp2();" value='提交'/> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function Jsonp1(){ var tag = document.createElement('script'); tag.src = "http://c2.com:8000/test/"; document.head.appendChild(tag); document.head.removeChild(tag); } function Jsonp2(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'GET', dataType: 'JSONP', success: function(data, statusText, xmlHttpRequest){ console.log(data); } }) } </script> </body> </html>
JSONP不能發送POST請求
究其根源,通過script標簽的src屬性進行跨域請求,<script src='http://www.jxntv.cn/data/jmd-jxtv2.html?callback=qwerqweqwe&_=1454376870403'>
最后全部都會轉換成GET請求,哪怕是你把type改為POST
.

其他ajax跨站請求方式
需要順帶提一句的是,跨站請求還有另一種方式:cors
,跨站資源共享,但此中方式對瀏覽器版本有要求,IE8以下的均不支持.
CORS與JSONP相比,無疑更為先進、方便和可靠。
1、 JSONP只能實現GET請求,而CORS支持所有類型的HTTP請求。
2、 使用CORS,開發者可以使用普通的XMLHttpRequest發起請求和獲得數據,比起JSONP有更好的錯誤處理。
3、 JSONP主要被老的瀏覽器支持,它們往往不支持CORS,而絕大多數現代瀏覽器都已經支持了CORS(這部分會在后文瀏覽器支持部分介紹)。
CORS(跨域資源共享)
背后的原理是:使用自定的HTTP頭部與服務器進行溝通,從而由服務器決定響應是否成功。
它允許瀏覽器向跨源(協議 + 域名 + 端口)服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。
瀏覽器將CORS請求分成兩類:簡單請求(simple request)和非簡單請求(not-so-simple request)。
只要同時滿足以下兩大條件,就屬於簡單請求。
1) 請求方法是以下三種方法之一:
HEAD
GET
POST
2)HTTP的頭信息不超出以下幾種字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限於三個值application/x-www-form-urlencoded、multipart/form-data、text/plain
凡是不同時滿足上面兩個條件,就屬於非簡單請求。
瀏覽器對這兩種請求的處理,是不一樣的。
簡單請求和非簡單請求的區別?
簡單請求:一次請求
非簡單請求:兩次請求,在發送數據之前會先發一次請求用於做“預檢”,只有“預檢”通過后才再發送一次請求用於數據傳輸。
預檢:
請求方式:OPTIONS
- “預檢”其實做檢查,檢查如果通過則允許傳輸數據,檢查不通過則不再發送真正想要發送的消息
- 如何“預檢”
=> 如果復雜請求是PUT等請求,則服務端需要設置允許某請求,否則“預檢”不通過
Access-Control-Request-Method
=> 如果復雜請求設置了請求頭,則服務端需要設置允許某請求頭,否則“預檢”不通過
Access-Control-Request-Headers
基於cors實現AJAX請求:
a、支持跨域,簡單請求
服務器設置響應頭:Access-Control-Allow-Origin = '域名' 或 '*'

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="submit" onclick="XmlSendRequest();" /> </p> <p> <input type="submit" onclick="JqSendRequest();" /> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function XmlSendRequest(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { var result = xhr.responseText; console.log(result); } }; xhr.open('GET', "http://c2.com:8000/test/", true); xhr.send(); } function JqSendRequest(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'GET', dataType: 'text', success: function(data, statusText, xmlHttpRequest){ console.log(data); } }) } </script> </body> </html> HTML

class MainHandler(tornado.web.RequestHandler): def get(self): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.write('{"status": true, "data": "seven"}')
b、支持跨域,復雜請求
由於復雜請求時,首先會發送“預檢”請求,如果“預檢”成功,則發送真實數據。
- “預檢”請求時,允許請求方式則需服務器設置響應頭:Access-Control-Request-Method
- “預檢”請求時,允許請求頭則需服務器設置響應頭:Access-Control-Request-Headers
- “預檢”緩存時間,服務器設置響應頭:Access-Control-Max-Age
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="submit" onclick="XmlSendRequest();" /> </p> <p> <input type="submit" onclick="JqSendRequest();" /> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function XmlSendRequest(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { var result = xhr.responseText; console.log(result); } }; xhr.open('PUT', "http://c2.com:8000/test/", true); xhr.setRequestHeader('k1', 'v1'); xhr.send(); } function JqSendRequest(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'PUT', dataType: 'text', headers: {'k1': 'v1'}, success: function(data, statusText, xmlHttpRequest){ console.log(data); } }) } </script> </body> </html>
class MainHandler(tornado.web.RequestHandler): def put(self): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.write('{"status": true, "data": "seven"}') def options(self, *args, **kwargs): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('Access-Control-Allow-Headers', "k1,k2") self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") self.set_header('Access-Control-Max-Age', 10)
c 、跨域獲取響應頭
默認獲取到的所有響應頭只有基本信息,如果想要獲取自定義的響應頭,則需要再服務器端設置Access-Control-Expose-Headers。

<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="submit" onclick="XmlSendRequest();" /> </p> <p> <input type="submit" onclick="JqSendRequest();" /> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function XmlSendRequest(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { var result = xhr.responseText; console.log(result); // 獲取響應頭 console.log(xhr.getAllResponseHeaders()); } }; xhr.open('PUT', "http://c2.com:8000/test/", true); xhr.setRequestHeader('k1', 'v1'); xhr.send(); } function JqSendRequest(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'PUT', dataType: 'text', headers: {'k1': 'v1'}, success: function(data, statusText, xmlHttpRequest){ console.log(data); // 獲取響應頭 console.log(xmlHttpRequest.getAllResponseHeaders()); } }) } </script> </body> </html>

class MainHandler(tornado.web.RequestHandler): def put(self): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('xxoo', "seven") self.set_header('bili', "daobidao") self.set_header('Access-Control-Expose-Headers', "xxoo,bili") self.write('{"status": true, "data": "seven"}') def options(self, *args, **kwargs): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('Access-Control-Allow-Headers', "k1,k2") self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") self.set_header('Access-Control-Max-Age', 10)
d、跨域傳輸cookie
在跨域請求中,默認情況下,HTTP Authentication信息,Cookie頭以及用戶的SSL證書無論在預檢請求中或是在實際請求都是不會被發送。
如果想要發送:
- 瀏覽器端:XMLHttpRequest的withCredentials為true
- 服務器端:Access-Control-Allow-Credentials為true
- 注意:服務器端響應的 Access-Control-Allow-Origin 不能是通配符 *
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <p> <input type="submit" onclick="XmlSendRequest();" /> </p> <p> <input type="submit" onclick="JqSendRequest();" /> </p> <script type="text/javascript" src="jquery-1.12.4.js"></script> <script> function XmlSendRequest(){ var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4) { var result = xhr.responseText; console.log(result); } }; xhr.withCredentials = true; xhr.open('PUT', "http://c2.com:8000/test/", true); xhr.setRequestHeader('k1', 'v1'); xhr.send(); } function JqSendRequest(){ $.ajax({ url: "http://c2.com:8000/test/", type: 'PUT', dataType: 'text', headers: {'k1': 'v1'}, xhrFields:{withCredentials: true}, success: function(data, statusText, xmlHttpRequest){ console.log(data); } }) } </script> </body> </html>
class MainHandler(tornado.web.RequestHandler): def put(self): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('Access-Control-Allow-Credentials', "true") self.set_header('xxoo', "seven") self.set_header('bili', "daobidao") self.set_header('Access-Control-Expose-Headers', "xxoo,bili") self.set_cookie('kkkkk', 'vvvvv'); self.write('{"status": true, "data": "seven"}') def options(self, *args, **kwargs): self.set_header('Access-Control-Allow-Origin', "http://www.xxx.com") self.set_header('Access-Control-Allow-Headers', "k1,k2") self.set_header('Access-Control-Allow-Methods', "PUT,DELETE") self.set_header('Access-Control-Max-Age', 10)