socket
服務端再加上業務邏輯處理, 好比像是Tornado
這樣的框架. 有一些框架則只包含業務邏輯處理, 例如Django
, bottle
, flask
這些框架, 它們的使用須要依賴包含socket
的第三方模塊(即 wsgiref
)來運行
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跨站腳本的攻擊是代碼注入的一種。javascript
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) #一、打開js2.html文件,讀取內容(包含特殊語法) #二、xxxooo = [11,22,333,44] 讀取內容(包含特殊語法) #三、獲得新的字符串 #四、將獲得新的字符串返回給用戶 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>
對於模板語言,主要有三類css
模板語言分爲三類:
{{}}表達式
{% if %}{% end %}
例如:
<ul>
{% for item in xxxooo %}
<li>{{item}}</li>
{% end %}
</ul> #模板語言裏經過for循環展現數據
自定義:
uimethod/uimodlehtml
而後在上面的s1裏面配置好設置文件前端
settings = { 'template_path':'template',#路徑配置,就是自動取到該路徑下尋找文件 'static_path':'static',#靜態文件配置,須要特殊處理 'static_url_prefix':'/sss/',#標記文件開始的名字 'ui_methods':uimethod#這個是咱們導入的文件 }
在js2.html文件裏面的模板語言java
<ul> {% for item in xxxooo %} <li>{{item}}</li> <h2>{{func(item)}}</h2> {% end %} <h2>{{func(amp)}}</h2>
js2.html文件裏面要這樣寫python
<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()#阻塞服務端進程, 等待客戶端的訪問
模板引擎的使用:
jquery
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:web
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
中支持兩種路由系統, 正則路由系統以及二級域名路由系統ajax
# 默認路由系統, 根據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
的域名)接下來咱們使用基於正則的路由系統來實現網頁分頁功能.redis
#利用全局變量模擬數據庫全部內容 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:在瀏覽器端保存鍵值對, 特性:每次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() 當前統一時間-->
對於參數
jquery中設置cookie
要用須要下載 這裏
一、 導入jquery
二、 導入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的實現機制相同
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這個類,用戶鏈接,服務器內部就會初始化隨機數 二、 服務器就會執行set_value方法,而且傳入參數is_login參數,首先爲了判斷用戶是否第一次登錄,因此用if not self.random_str,沒有就用get_cookie()方法去客戶端中拿隨機數,這裏須要判斷,若是客戶端也沒有隨機數,那麼服務端就要本身建立隨機數,而且把這個隨機數傳遞給服務器這個類;若是客戶端有隨機數,要判斷這個隨機數是不是僞造的,若是是僞造的,服務器須要本身建立隨機數,而且把這個隨機數傳遞給服務器這個類;以後把is_login參數替代key傳遞給session這個字典求出來value這個值,而且設置一下這個cookie傳遞給瀏覽器;而後設置key爲name的cookie 三、 用戶訪問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)
會集羣要會:分佈式哈希haxi redis
CSRF限制post請求的
用戶訪問是先請求服務器調用get請求,而後發送post請求,以後服務器會給用戶一個隨機字符串,當用戶離開後,下次再訪問會帶着這個隨機字符串訪問服務器,若是用戶沒有這個隨機字符串,那麼CSRF會阻止這個用戶請求,這樣可使服務器免遭受惡意攻擊形成服務器宕機
要加上CSRF:
一、在配置文件中加上配置文件」xsrf_cookies」:True
二、在前臺代碼中加上{% 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 保護機制,這一點相當重要。
一、cookie和session的區別
1)cookie是保存在客戶端的,session是保存在服務端的,由於服務器端,表示可能在內存中,可能在數據庫端,可能在緩存中統稱爲服務器端
二、session和cookie有什麼聯繫?:
答:有。session是經過cookie人爲構建起來的,在web開發裏面自己沒有session這個東西的。在服務器端能夠高層一個數據庫,能夠在內存中搞成一個字典,每一次用戶來訪問的時候,給用戶發一對token,下一次,用戶訪問再帶着這一對token來,服務器端就知道你是否是上一次的你。若是再問就來畫一張圖
三、分頁 XSS 跨站腳本攻擊
四、CSRF/別名XSRF工做方式:
答:跨站請求僞造, 理解爲:攻擊者盜用了你的身份,以你的名義發送惡意請求
驗證:第一次請求的時候是get方式請求,防止沒有通過驗證就來post請求,形成大併發機器宕機
五、 Ajax
爲何要有Ajax
答:防止頁面批量刷新
利用:
iframe 忽略
XMLHttpRequest
本身寫
xhr
xhr.open()
xhr.onreadystatechange
xhr.send()
jQuery
會用下面的就會jquery,ajax
$.ajax({
url:
type
data
dataType
success
error
})
六、 驗證碼、
七、 上傳文件
form標籤
form標籤 enctype=「「form標籤裏面必需要有這個才能進行上傳文件
經過Ajax上傳文件
利用formDate()
XMLHttpRequest
jQuery
iframe+form標籤爲了兼容性設計,ifram就至關於設置一個通道,form把數據提交到這個通道,而後不刷頁面上傳文件
tornado 結合前端進行文件上傳:
在表單中咱們獲取用戶提交的數據,使用的是get_argument,複選框使用的是get_arguments,可是文件的不同,文件的使用request.files。
<!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:就表示上傳文件的文件名。 四、迭代出來的對象的body:表示上傳文件的內容。獲取的文件內容是字節形式的。
<!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進行文件上傳。
關鍵點:
一、獲取文件對象,經過files[0],獲取當前上傳的文件對象。
二、經過FormData(),實例化一個對象form對象。
三、而後將要傳遞的參數,文件以鍵和值以逗號分隔的形式append到form對象中去。
四、而後將整個form對象發送到服務端。
注意:
後臺代碼和上面的代碼同樣,不變。注意接收的文件名要同步。
<!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>
說明:
一、和原生的同樣,都是顯得獲取當前上傳文件的對象。files[0];而後實例化form對象,將要傳遞的內容append到實例化的對象form中。
二、後臺代碼同前,注意字段名對應。
關鍵點:
processData:false和contentType:false。這2個是關鍵。
默認的jquery會將咱們上傳的數據作部分處理。上面兩段代碼,就是告訴jquery不要處理咱們的文件,否則會將咱們的文件處理得不完整。
原生的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>
關鍵點:
一、document.getElementById('my_form').target = 'my_iframe':這段代碼就是獲取iframe標籤。
target就是目標,只要給form設置了target的話,form提交的時候,就會提交到這個target指定的目標上。因此上面的代碼表示只要form提交,就會提交到iframe上去。
二、當iframe操做完後會執行Testt方法,Testt方法就是獲取後臺返回的信息,並打印。
Jsonp實現ajax跨域請求
瀏覽器有一個很重要的概念——同源策略(Same-Origin Policy)。所謂同源是指,域名,協議,端口相同。不一樣源的客戶端腳本(javascript、ActionScript)在沒明確受權的狀況下,不能讀寫對方的資源。比較特別的是:因爲同源策略是瀏覽器的限制,因此請求的響應和發送是能夠進行的,只不過瀏覽器不支持罷了.
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({
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>
究其根源,經過script標籤的src屬性進行跨域請求,<script src='http://www.jxntv.cn/data/jmd-jxtv2.html?callback=qwerqweqwe&_=1454376870403'>
最後所有都會轉換成GET請求,哪怕是你把type改成POST
.
須要順帶提一句的是,跨站請求還有另外一種方式:cors
,跨站資源共享,但此中方式對瀏覽器版本有要求,IE8如下的均不支持.
CORS與JSONP相比,無疑更爲先進、方便和可靠。
一、 JSONP只能實現GET請求,而CORS支持全部類型的HTTP請求。
二、 使用CORS,開發者可使用普通的XMLHttpRequest發起請求和得到數據,比起JSONP有更好的錯誤處理。
三、 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、支持跨域,複雜請求
因爲複雜請求時,首先會發送「預檢」請求,若是「預檢」成功,則發送真實數據。
<!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證書不管在預檢請求中或是在實際請求都是不會被髮送。
若是想要發送:
<!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)