tornado框架&三層架構&MVC&MTV&模板語言&cookie&session


 
        
web框架的本質其實就是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
wsgiref
<!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()
tornado

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()
s1代碼

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()
利用tornado框架實現數據提交,,主要是修改上面s1的代碼
<!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>
js2的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)方法
使用wsgiref再加上自己寫的業務邏輯自定義一個web框架

 

.body {
    margin: 0;
    background-color: cornflowerblue;
}
commons.css文件內容
<!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>
index.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()#阻塞服務端進程, 等待客戶端的訪問
index.py文件內容

 

模板引擎的使用:

def test_uimethod(self):
    return "uimethod"
uimethod
from tornado.web import UIModule

class MyClass(UIModule):
    def render(self, *args, **kwargs):
        return "uimodule"
uimodule.py文件如下
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()
index.py文件如下
<!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>
index.html文件如下

  • 模板引擎中的{{key}}表示取key對應的值, 當key為函數時候執行該函數並取該函數結果. 例如index.html文件中的<h1>{{ag}}</h1> 實際上取得是index.pyself.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.pysettings中增加一行'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()
tornodo_cookie
<!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>
login
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <a href="/logout">退出</a>
    <h1>你的當前銀行卡余額:-1000</h1>
</body>
</html>
manager
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>首頁</h1>
</body>
</html>
index
<!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>
jquery發送請求

 

 

 

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)
all.py文件如下
# 單獨用於實現分頁功能的類
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)
pager.py文件如下
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)
home.py文件如下
<!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>
index.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()
start.py文件如下

 

 

 

 

 

 

模板引擎:

基本使用
    繼承,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")
home
{%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%}
acount.html
{%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%}
index.html
<!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>
master.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()
start.py

  • 從運行結果來看, 兩個網頁的主體結構相同, 只是里邊包含的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.htmltm_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%}
將上面的index.html修改如下

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 設置過期時間, 單位為天
home
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()
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()                   當前統一時間-->
HTML代碼

對於參數

  • 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那么就會出現歡迎登錄
基於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 很容易被惡意的客戶端偽造。加入你想在 cookie 中保存當前登陸用戶的 id 之類的信息,
你需要對 cookie 作簽名以防止偽造。Tornado 通過 set_secure_cookie 和 get_secure_cookie
 方法直接支持了這種功能。 要使用這些方法,你需要在創建應用時提供一個密鑰,名字為 cookie_secret。 
你可以把它作為一個關鍵詞參數傳入應用的設置中

簽名Cookie的本質是:

寫cookie過程:

將值進行base64加密
對除值以外的內容進行簽名,哈希算法(無法逆向解析)
拼接 簽名 + 加密值
讀cookie過程:

讀取 簽名 + 加密值
對簽名進行驗證
base64解密,獲取值內容
注:許多API驗證機制和安全cookie的實現機制相同

 

 

session:

我們將許多信息放在cookie中勢必會造成瀏覽器端的臃腫, 此時便需要在服務端保存原本在瀏覽器端的那些鍵值對. 在瀏覽器端只需存儲一個表示身份的隨機加密字符串, 當瀏覽器端訪問服務端時候攜帶該字符串, 經過比較, 驗證合法之后便可以取該用戶在服務端存儲的相應信息. 但是在Tornado中並沒有session的模塊, 我們需要自定義來實現.
session其實就是定義在服務器端用於保存用戶回話的容器,其必須依賴cookie才能實現。
所有的web框架都是session[key]=value的方法實現的
 

優化后代碼:

 

在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("請重新登錄")
兩個類繼承一個共同的父類,利用tornado內置的鈎子函數來優化代碼
 
#!/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()
__getitem__/__setitem__優化后
用戶如果直接連接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>
login.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()
python代碼

 下載下面源碼之后,需要把check_code.py和Monaco.ttf導入到這個代碼目錄中(僅僅限制與python3.5)

驗證碼Demo源碼下載:猛擊這里
 

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)
View Code
<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"/>
html

提交的是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>
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()
PYthon
說明:

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>
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>
HTML
說明:

1、和原生的一樣,都是顯得獲取當前上傳文件的對象。files[0];然后實例化form對象,將要傳遞的內容append到實例化的對象form中。

2、后台代碼同前,注意字段名對應。

關鍵點:

processData:false和contentType:false。這2個是關鍵。

默認的jquery會將我們上傳的數據做部分處理。上面兩段代碼,就是告訴jquery不要處理我們的文件,不然會將我們的文件處理得不完整。

 

 

iframe文件上傳

 

原生的ajaxjquery上傳的時候,我們都是通過實例化一個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>
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>
利用script標簽實現跨域代碼

JSONP實現ajax跨域

以上的代碼其實也是jsonp的基本思路

$.ajax({
    url:..
    type: 'GET',
    dataType: 'jsonp',
    //傳遞給請求處理程序或頁面的,用以獲得jsonp回調函數名的參數名(一般默認為:callback)
    jsonp: 'callback',
    //自定義的jsonp回調函數名稱,默認為jQuery自動生成的隨機函數名,也可以寫"?",jQuery會自動為你處理數據
    jsonpCallback: 'list'
})
    
function list(arg){
    
}
基本的jsonp寫法
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實現跨域Ajax - Demo

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
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"}')
tornado

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>
    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)
    Tornado

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>
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)
Tornado

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>
    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)
    Tornado

     


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM