1. 程式人生 > >Python 學習筆記6

Python 學習筆記6

6、Python RESTful API 開發

1、RESTful API 概述

1-1、展示微博開放平臺的 RESTfulAPI

介紹微博開放平臺

open.weibo.com

在linux或mac中,$ curl 請求URL?請求引數

 pro.jsonlint.com上

通過HTTP請求,請求到JSON的過程,就是RESTful的呼叫

1-2、RESTful 設計理念

REST - Representational State Transfer 表現層狀態轉化

資源– resources – 網路上的具體資訊,如文字,圖片

URI – 統一資源識別符號,用來唯一的標識一個資源

URL – 統一資源定位器,用來定位某個特定的資源,如一個網址

表現層(Representation)- 把資源具體呈現出來的形式

如:純文字,HTML,JSON

狀態轉移 - State Transfer

 HTTP協議,是一個無狀態的協議,所有的狀態都儲存在伺服器端

  GET– 獲取資源

 POST – 新建資源

  PUT– 更新資源

 DELETE – 刪除資源

  一個RESTful請求:$ curl –X GEThttps://api.weibo.com/2/users/show.json

REST構架設計6原則

  UniformInferface

 Stateless

 Cacheable

 Client-Server

 Layered System

 Code on Demand

1-3、Python微型Web框架Flask簡介

Flasks是一個基於Werkzeug,Jinja2以及”善意”構建的Python微型Web框架,並且基於BSD 開源證書!

1-4、一個例程和總結

Debug模式:

If __name__ == ’__main__’:

           App.run(debug=True)

Debug模式下,可以看到程式碼,可以直接除錯

在瀏覽器中,使用POST方法較麻煩,但是可以在命令列中進行

$ curl –X POST 127.0.0.1:5000/index/yx

2、Python RESTful API開發工具介紹及應用

2-1 Chrome 開發者工具介紹

Network

Elements

Console

  使用console.table()方法,建立一個表格

  使用console.timeEnd()方法,統計一個操作所花費的時間

  例項:

進行移動端的開發,點選手機圖示

2-2 Python HTTP 庫 Requests 介紹

例項:

[[email protected] ~]# python2.7

Python 2.7.11 (default, Apr  6 2016, 00:07:19)

[GCC 4.1.2 20080704 (Red Hat 4.1.2-55)] onlinux2

Type "help","copyright", "credits" or "license" for moreinformation.

>>> import requests

>>> r =requests.get('https://github.com/timeline.json')  //使用requests傳送網路請求

/usr/local/lib/python2.7/site-packages/requests/packages/urllib3/util/ssl_.py:315:SNIMissingWarning: An HTTPS request has been made, but the SNI (Subject NameIndication) extension to TLS is not available on this platform. This may causethe server to present an incorrect TLS certificate, which can cause validationfailures. For more information, seehttps://urllib3.readthedocs.org/en/latest/security.html#snimissingwarning.

 SNIMissingWarning

>>> r.text

u'{"message":"Hello there,wayfaring stranger. If you\u2019re reading this then you probably didn\u2019tsee our blog post a couple of years back announcing that this API would goaway: http://git.io/17AROg Fear not, you should be able to get what you needfrom the shiny new Events API instead.","documentation_url":"https://developer.github.com/v3/activity/events/#list-public-events"}'

>>> payload = {'key1': 'value1','key2': 'value2'}  //為URL傳遞引數

>>> r = requests.get("http://httpbin.org/get",params=payload)

>>> r.text  //讀取伺服器響應的內容,unicode字符集

u'{\n "args": {\n   "key1": "value1", \n    "key2": "value2"\n  }, \n  "headers":{\n    "Accept":"*/*", \n   "Accept-Encoding": "gzip, deflate", \n    "Host": "httpbin.org",\n    "User-Agent":"python-requests/2.9.1"\n  },\n  "origin": "114.255.40.54",\n  "url":"http://httpbin.org/get?key2=value2&key1=value1"\n}\n'

>>> r.url  //訪問URL

u'http://httpbin.org/get?key2=value2&key1=value1'

>>> r.json()  //返回伺服器JSON格式的內容

{u'origin': u'114.255.40.54', u'headers':{u'Host': u'httpbin.org', u'Accept-Encoding': u'gzip, deflate', u'Accept':u'*/*', u'User-Agent': u'python-requests/2.9.1'}, u'args': {u'key2': u'value2',u'key1': u'value1'}, u'url':u'http://httpbin.org/get?key2=value2&key1=value1'}

>>> type(r.text)

<type 'unicode'>

>>> type(r.json())

<type 'dict'>

>>> 

C:\Users\Administrator>python

Python 2.7.11 (v2.7.11:6d1b6a68f775,Dec  5 2015, 20:32:19) [MSC v.1500 32 bit(

Intel)] on win32

Type "help","copyright", "credits" or "license" for moreinformation.

>>> from PIL import Image   //以請求返回的二進位制資料建立一張圖片

>>> from StringIO import StringIO

>>> import requests

>>> r =requests.get('http://requests-docs-cn.readthedocs.org/zh_CN/latest/_stat

ic/requests-sidebar.png')

>>> i =Image.open(StringIO(r.content))

>>> i.show()

>>> 

//定製請求頭 – 模擬一個瀏覽器

# requests_t.py - Browser

import requests

hdr ={"User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64)AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110Safari/537.36"}

r = requests.get('http://127.0.0.1:5000/test1', headers = hdr)

print r.text

# app.py - Server

from flask import Flask,request

app = Flask(__name__)

@app.route('/')

def index():

   print request.headers

   return 'Hello restful'

if __name__ == '__main__':

   app.run(host='0.0.0.0', port=5000, debug=True)

2-3 實踐:動手編寫一個驗證登入的程式

token是一個加密的字串,其中包含使用者名稱,過期時間,以及一些隨機資訊

 base64對token進行加密

程式碼:

# app.py

import base64
import random
import time
from flask import Flask, request


app = Flask(__name__)


users = {
    "magigo": ["123456"]
}


def gen_token(uid):
    token = base64.b64encode(':'.join([str(uid), str(random.random()), str(time.time() + 7200)]))
    users[uid].append(token)
    return token
def verify_token(token):
    _token = base64.b64decode(token)
    if not users.get(_token.split(':')[0])[-1] == token:
        return -1
    if float(_token.split(':')[-1]) >= time.time():
        return 1
    else:
        return 0


@app.route('/index', methods=['POST', 'GET'])
def index():
    print request.headers
    return 'hello'


@app.route('/login', methods=['POST', 'GET'])
def login():
    uid, pw = base64.b64decode(request.headers['Authorization'].split(' ')[-1]).split(':')
    if users.get(uid)[0] == pw:
        return gen_token(uid)
    else:
        return 'error'


@app.route('/test1', methods=['POST', 'GET'])
def test():
    token = request.args.get('token')
    if verify_token(token) == 1:
        return 'data'
    else:
        return 'error'


if __name__ == '__main__':
    app.run(debug=True)
 


#requests_r.py


import requests
# r = requests.get('http://127.0.0.1:5000/login', auth=('magigo', '123456'))
# print r.text


token ='bWFnaWdvOjAuMzE4MTUxNTA1MjQ4OjE0MjU4MzkzMjMuODk='
r = requests.get('http://127.0.0.1:5000/test1', params={'token': token})
print r.text
# bWFnaWdvOjAuMzE4MTUxNTA1MjQ4OjE0MjU4MzkzMjMuODk=

3、OAuth 2.0介紹和實現(上)

3-1 OAuth 2.0的原理介紹

OAuth – 開放授權 – 一個正式的網際網路標準協議

  三方協作的過程:使用者、網站、微博

Token – 令牌

OAuth概述 – 其中,B步驟是關鍵

OAuth四種授權模式

  授權碼模式

  簡化模式

  密碼模式

  客戶端模式

授權碼模式

3-2 實現OAuth 2.0協議中的必選方法

1)實現重定向的機制

@app.route('/client/login')

def client_login():

   uri = 'http://localhost:5000/oauth'

   return redirect(uri)

# request_t.py

r =requests.get('http://localhost:5000/client/login')

print r.text

print '======='

print r.history    //通過history屬性,取得重定向情況

# app.py

@app.route('/oauth')

def oauth():

   return 'Please login'

返回結果:

Please login

=======

[<Response [302]>]   //表示重定向

2)實現授權碼的機制

auth_code = {}

def gen_code(uri):

   code = random.randint(0,10000)

   auth_code[code] = uri

return code

3)實現token發放的機制

@app.route('/oauth')

def oauth():

   if request.args.get('code'):

       if auth_code.get(int(request.args.get('code'))) ==request.args.get('redirect_uri'):

           return gen_token(request.args.get('client_id'))

return 'Pleaselogin'

3-3 編寫OAuth授權伺服器

知識點:

清除OAuth 2.0協議的內容

掌握Flask的重定向操作

掌握OAuth 2.0協議中的授權模式

程式碼:

#app.py
import base64
import random
import time

from flask importFlask, request, redirect

app = Flask(__name__)

users = {
    "yx": ["35"]
}

redirect_uri='http://localhost:5000/client/passport'

client_id = '35'
users[client_id] = []
auth_code = {}
oauth_redirect_uri = []

# 授權碼生成器
def gen_auth_code(uri):
    code = random.randint(0,10000)
    auth_code[code] = uri
    return code

def gen_token(uid):
    token = base64.b64encode(':'.join([str(uid),str(random.random()), str(time.time() +7200)]))
    users[uid].append(token)
    return token

def verify_token(token):
    _token = base64.b64decode(token)
    if not users.get(_token.split(':')[0])[-1] == token:
        return -1
   
if float(_token.split(':')[-1]) >= time.time():
        return 1
   
else:
        return 0

@app.route('/index',methods=['POST','GET'])
def index():
    print request.headers
    return 'hello'

@app.route('/login',methods=['POST','GET'])
def login():
    uid, pw =base64.b64decode(request.headers['Authorization'].split(' ')[-1]).split(':')
    if users.get(uid)[0] == pw:
        return gen_token(uid)
    else:
        return 'error'

@app.route('/oauth',methods=['POST','GET'])
def oauth():
    # 登入
   
if request.args.get('user'):
        if users.get(request.args.get('user'))[0] == request.args.get('pw')and oauth_redirect_uri:
            uri = oauth_redirect_uri[0] + '?code=%s' % gen_auth_code(oauth_redirect_uri[0])
            return redirect(uri)
    # 驗證授權碼,發放token
   
if request.args.get('code'):
        if auth_code.get(int(request.args.get('code'))) == request.args.get('redirect_uri'):
            return gen_token(request.args.get('client_id'))
    #
   
if request.args.get('redirect_uri'):
       oauth_redirect_uri.append(request.args.get('redirect_uri'))
    return 'please login'

# 客戶端
@app.route('/client/login',methods=['POST','GET'])
def client_login():
    uri = 'http://localhost:5000/oauth?response_type=code&client_id=%s&redirect_uri=%s'% (client_id, redirect_uri)
    return redirect(uri)

@app.route('/client/passport',methods=['POST','GET'])
def client_passport():
    code = request.args.get('code')
    uri = 'http://localhost:5000/oauth?grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s'% (code, redirect_uri, client_id)
    return redirect(uri)

@app.route('/test1',methods=['POST','GET'])
def test():
    token = request.args.get('token')
    if verify_token(token) == 1:
        return 'data'
   
else
:
        return 'error'

if
__name__ == '__main__':
    app.run(debug=True)

# requests.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests

r = requests.get('http://127.0.0.1:5000/client/login')
print r.text
print '========='
print
r.history
print '========='
print
r.url
print '========='
uri_login = r.url.split('?')[0] +'?user=yx&pw=35'
r2 = requests.get(uri_login)
print r2.text
print r2.history
print '========='
r3 = requests.get('http://127.0.0.1:5000/test1',params={'token': r2.text})
print r3.text

4、OAuth 2.0介紹和實現(下)

4-1 Flask渲染頁面及Cookies

Flask中的表單

  提交表單時,使用POST方法

Flask響應物件

  ResponseHeaders – 瀏覽器的響應頭

  http://dormousehole.readthedocs.org/en/latest/quickstart.html#about-responses

Cookies簡介 – 小型文字檔案

  1)分類:Cookies總是存在客戶端中,按在客戶端中的儲存位置,可分為記憶體Cookies和硬碟Cookies

  2)用途:Cookies可以用於購物車,以及免登陸

  3)缺陷:Cookies增加了網路流量,造成安全問題,無法儲存大量資料

  http://dormousehole.readthedocs.org/en/latest/quickstart.html#id11

4-2 Token的設計以及加密方法

知識點:

  無需核對使用者名稱密碼的token驗證機制 – 無法被偽造的token

  hmac(雜湊運算訊息認證碼)簡介

Token的基本設計原則:

  Token不攜帶使用者敏感資訊

  無需查詢資料庫,Token可以進行自我驗證

hmac簡介

作用:驗證訊息是否被篡改

公式:

使用Python的hmac模組直接計算:hmac.new(‘secret123’,value).digest()

缺陷:無法抵禦重放攻擊

驗證token的有效性:真的沒過期合法請求

  驗證驗證碼是否一致

  驗證是否過期 – 生存期10分鐘左右

  驗證這個token是否屬於被請求資料的使用者

4-3 最終的編碼

總結:

  掌握Flask渲染頁面和處理響應物件

  瞭解hmac加密驗證的內容

安全建議:使用HTTPS傳輸,使用請求頭而不是引數傳遞Token

實戰:製作一個基於OAuth 2.0的驗證登入服務

在Chrome開發者工具中,resources-> Cookies->localhost中,可以看到使用者名稱和密碼

程式碼:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import base64
import random
import time
import json
import hmac
from datetime importdatetime,timedelta

from flask importFlask, request, redirect, make_response

app = Flask(__name__)

users = {
    "yx": ["35"]
}

redirect_uri='http://localhost:5000/client/passport'
client_id = '123456'
users[client_id] = []
auth_code = {}
oauth_redirect_uri = []

TIMEOUT = 3600 * 2


# 新版本的token生成器
def gen_token(data):
    '''
   
:param data: dict type
   
:return: base64 str
    '''
   
data = data.copy()
    if "salt" not in data:
        data["salt"] = unicode(random.random()).decode("ascii")
    if "expires"not in data:
        data["expires"] = time.time() + TIMEOUT
    payload = json.dumps(data).encode("utf8")
    # 生成簽名
   
sig =_get_signature(payload)
    return encode_token_bytes(payload + sig)

# 授權碼生成器
def gen_auth_code(uri, user_id):
    code = random.randint(0,10000)
    auth_code[code] = [uri, user_id]
    return code

# 新版本的token驗證
def verify_token(token):
    '''
   
:param token: base64 str
   
:return: dict type
    '''
   
decoded_token =decode_token_bytes(str(token))
    payload = decoded_token[:-16]
    sig = decoded_token[-16:]
    # 生成簽名
   
expected_sig =_get_signature(payload)
    if sig != expected_sig:
        return {}
    data = json.loads(payload.decode("utf8"))
    if data.get('expires') >=time.time():
        return data
    return 0

# 使用hmac為訊息生成簽名
def _get_signature(value):
    """Calculatethe HMAC signature for the given value."""
   
return hmac.new('secret123456', value).digest()

# 下面兩個函式將base64編碼和解碼單獨封裝
def encode_token_bytes(data):
    return base64.urlsafe_b64encode(data)

def decode_token_bytes(data):
    return base64.urlsafe_b64decode(data)


# 驗證伺服器端
@app.route('/index',methods=['POST','GET'])
def index():
    return 'hello'

@app.route('/login',methods=['POST','GET'])
def login():
    uid, pw =base64.b64decode(request.headers['Authorization'].split(' ')[-1]).split(':')
    if users.get(uid)[0] == pw:
        return gen_token(dict(user=uid,pw=pw))
    else:
        return 'error'

@app.route('/oauth',methods=['POST','GET'])
def oauth():
    # 處理表單登入, 同時設定Cookie
   
if request.method =='POST' and request.form['user']:
        u = request.form['user']
        p = request.form['pw']
        if users.get(u)[0] == pand oauth_redirect_uri:
            uri = oauth_redirect_uri[0] + '?code=%s' % gen_auth_code(oauth_redirect_uri[0], u)
            expire_date = datetime.now()+ timedelta(minutes=1)
            resp =make_response(redirect(uri))
            resp.set_cookie('login','_'.join([u, p]), expires=expire_date)
            return resp
    # 驗證授權碼,發放token
   
if request.args.get('code'):
        auth_info = auth_code.get(int(request.args.get('code')))
        if auth_info[0] ==request.args.get('redirect_uri'):
            # 可以在授權碼的auth_code中儲存使用者名稱,編進token
           
return gen_token(dict(client_id=request.args.get('client_id'),user_id=auth_info[1]))
    # 如果登入使用者有Cookie,則直接驗證成功,否則需要填寫登入表單
   
if request.args.get('redirect_uri'):
       oauth_redirect_uri.append(request.args.get('redirect_uri'))
        if request.cookies.get('login'):
            u, p = request.cookies.get('login').split('_')
            if users.get(u)[0] == p:
                uri = oauth_redirect_uri[0] + '?code=%s' % gen_auth_code(oauth_redirect_uri[0], u)
                return redirect(uri)
        return '''
            <form action=""method="post">
                <p><inputtype=text name=user>
                <p><inputtype=text name=pw>
                <p><inputtype=submit value=Login>
            </form>
        '''


# 客戶端
@app.route('/client/login',methods=['POST','GET'])
def client_login():
    uri = 'http://localhost:5000/oauth?response_type=code&client_id=%s&redirect_uri=%s'% (client_id, redirect_uri)
    return redirect(uri)

@app.route('/client/passport',methods=['POST','GET'])
def client_passport():
    code = request.args.get('code')
    uri = 'http://localhost:5000/oauth?grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s'% (code, redirect_uri, client_id)
    return redirect(uri)


# 資源伺服器端
@app.route('/test1',methods=['POST','GET'])
def test():
    token = request.args.get('token')
    ret = verify_token(token)
    if ret:
        return json.dumps(ret)
    else:
        return 'error'

if
__name__ == '__main__':
    app.run(debug=True)

5、Flask-RESTful外掛介紹及應用

5-1 Flask-RESTful外掛介紹

Flask-RESTful概述

Flask-RESTful 是一個Flask擴充套件,它添加了快速構建REST APIs的支援。它當然也是一個能夠跟你現有的ORM/庫協同工作的輕量級的擴充套件。Flask-RESTful鼓勵以最小設定的最佳實踐。如果你熟悉 Flask 的話,Flask-RESTful 應該很容易上手。

安裝:>pip install flask-restful

資源路由

Flask-restful不在使用裝飾器作為路由處理請求的部分是類,而不是flask的函式

可插拔檢視:

程式碼:

from flask import Flask
from flask.ext import restful

app = Flask(__name__)
api = restful.Api(app)

class HelloWorld(restful.Resource):
    def get(self):
        return {'hello': 'world'}

api.add_resource(HelloWorld, '/')

if __name__ == '__main__':
    app.run(debug=True)
 


引數解析


程式碼:


# 1. 關於引數解析的部分
# 一組虛擬的資料
TODOS = {
    'todo1': {'task': 1},
    'todo2': {'task': 2},
    'todo3': {'task': 3},
}


# 定義允許的引數為task,型別為int,以及錯誤時的提示
parser = reqparse.RequestParser()
parser.add_argument('task', type=int, help='Please set a int task content!')


# 真正處理請求的地方
class TodoList(Resource):
    def get(self):
        return TODOS, 200, {'Etag': 'some-opaque-string'}


    def post(self):
        args = parser.parse_args()
        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
        todo_id = 'todo%i' % todo_id
        TODOS[todo_id] = {'task': args['task']}
        return TODOS[todo_id], 201


# 實際定義路由的地方
api.add_resource(TodoList, '/todos', '/all_tasks')

Linux下測試:

% curl http://localhost:5000/todos -d “task=4” –X POST

% curl http://localhost:5000/todos

響應域

程式碼:

# 2. 關於響應域的部分


# ORM的資料模型
class TodoDao(object):
    def __init__(self, todo_id, task):
        self.todo_id = todo_id
        self.task = task


        # 這個域不會被返回
        self.status = 'active'


# marshal-蒙版
resource_fields = {
    'task':   fields.String,
    'uri':    fields.Url('todo_ep')
}


# 真正處理請求的地方
class Todo(Resource):
    # 蒙版
    @marshal_with(resource_fields)
    def get(self, todo_id):
        return TodoDao(todo_id=todo_id, task='Remember the milk'), 200


# 實際定義路由的地方
api.add_resource(Todo, '/todos/<todo_id>', endpoint='todo_ep')

Linux下測試:

% curl http://localhost:5000/todos/4

結果:

{

    "task": "Remember themilk",

    "uri": "/todos/4"

}

5-2 Flask-RESTful請求解析

知識點:

必選引數與多值引數的設定

指定獲取引數的位置

程式碼:

from flask import Flask
from flask.ext.restful importreqparse, Api,Resource


app = Flask(__name__)
api = Api(app)


USERS = {
    'row1': {'name':'jilu', 'rate': [70,65]},
    'row2': {'name':'bob', 'rate': [80,90, 68]},
    'row3': {'name':'tim', 'rate': [90,80]},
}


parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True)  //必選引數
parser.add_argument('rate', type=int, help='rate is a number',action='append')
parser.add_argument('User-Agent',type=str, location='headers') //指定獲取引數的位置






# 真正處理請求的地方
class UserInfo(Resource):
    def get(self):
        return USERS, 200


    def post(self):
        args = parser.parse_args()
        user_id = int(max(USERS.keys()).lstrip('row')) + 1
        user_id = 'row%i' % user_id
        USERS[user_id] = {'name': args['name'], 'rate': args['rate']}
        USERS[user_id]['ua'] = args.get('User-Agent')
        return USERS[user_id], 201


api.add_resource(UserInfo, '/')


if __name__ == '__main__':
    app.run(debug=True)
 


引數解析器的繼承


程式碼:


# 解析器繼承
parser_copy = parser.copy()
parser_copy.add_argument('bar', type=int)
parser_copy.replace_argument('bar', type=str, required=True) // 覆蓋原先已經設定好的引數
parser_copy.remove_argument('User-Agent') //刪除原先已有的引數
 

5-3 Flask-RESTful的響應域

Flask-RESTful提供的一種控制響應物件的方法,可以和各種ORM結合,過濾掉不想對使用者暴露的內部資料結構。

知識點:

重新命名域名和設定預設域名

定製響應域的欄位

生成複雜、巢狀結構的響應

程式碼:

import json

from datetime import datetime

from flask.ext.restful import Resource,fields, marshal_with, marshal

# 基本例子

resource_fields = {

   'name': fields.String,

   'address': fields.String,

   'date_updated': fields.DateTime(dt_format='rfc822'),

}

class UserInfo(object):

   def __init__(self, name, address, date_updated=datetime.now()):

       self.name = name

       self.address = address

       self.date_updated = date_updated

print json.dumps(marshal(UserInfo('magi', 'beijing'),resource_fields))//使用marshal()而不使用marshal_with()

# class Todo(Resource):

#    @marshal_with(resource_fields, envelope='resource')

#    def get(self, **kwargs):

#        return UserInfo('magi', 'beijing')

# 輸出域別名

resource_fields2 = {

   'open_name': fields.String(attribute='name'),

   'address': fields.String,

   'date_updated': fields.DateTime(dt_format='rfc822'),

}

print json.dumps(marshal(UserInfo('magi','beijing'), resource_fields2))

# 輸出域預設值

class UserInfo2(object):

   def __init__(self, address):

       self.address = address

resource_fields3 = {

   'open_name': fields.String(default='add_magi'),

   'address': fields.String,

   'date_updated': fields.DateTime(dt_format='rfc822', default=str(datetime.now())),

}

printjson.dumps(marshal(UserInfo2(address='beijing'), resource_fields3))

# 自定義輸出域

class UserInfo2(object):

   def __init__(self, address, flag, date_updated=datetime.now()):

       self.address = address

       self.date_updated = date_updated

       self.flags = flag

class UrgentItem(fields.Raw):

   def format(self, value):

       return "Urgent" if value & 0x01 else "Normal"

resource_fields4 = {

   'open_name': fields.String(default='add_magi'),

   'address': fields.String,

   'date_updated': fields.DateTime(dt_format='rfc822'),

   'priority': UrgentItem(attribute='flags'),

}

printjson.dumps(marshal(UserInfo2(address='beijing', flag=1), resource_fields4))

# 複雜結構

resource_fields = {'name': fields.String}

resource_fields['address'] = {}

resource_fields['address']['line 1'] =fields.String(attribute='addr1')

resource_fields['address']['line 2'] =fields.String(attribute='addr2')

resource_fields['address']['city'] =fields.String

resource_fields['address']['state'] =fields.String

resource_fields['address']['zip'] =fields.String

data = {'name': 'bob', 'addr1': '123 fakestreet', 'addr2': '', 'city': 'New York', 'state': 'NY', 'zip': '10468'}

print json.dumps(marshal(data,resource_fields))

# 巢狀域

address_fields = {}

address_fields['line 1'] =fields.String(attribute='addr1')

address_fields['line 2'] =fields.String(attribute='addr2')

address_fields['city'] =fields.String(attribute='city')

address_fields['state'] =fields.String(attribute='state')

address_fields['zip'] =fields.String(attribute='zip')

resource_fields = {}

resource_fields['name'] = fields.String

resource_fields['billing_address'] = fields.Nested(address_fields)

resource_fields['shipping_address'] =fields.Nested(address_fields)

address1 = {'addr1': '123 fake street','city': 'New York', 'state': 'NY', 'zip': '10468'}

address2 = {'addr1': '555 nowhere', 'city':'New York', 'state': 'NY', 'zip': '10468'}

data = { 'name': 'bob', 'billing_address':address1, 'shipping_address': address2}

print json.dumps(marshal(data,resource_fields))

5-4 重構程式

對資源伺服器進行:

新增資料模型

使用marshal

將flask函式替換為restful形式的類

from flask.ext import restful
from flask.ext.restful importfields,marshal_with

app = Flask(__name__)
api = restful.Api(app)

# 資源伺服器端
# 資料模型
class Test1Data(object):
    def __init__(self, client_id, expires, salt, user_id):
        self.client_id = client_id
        self.expires = expires
        self.salt = salt
        self.user_id = user_id

# marshal-蒙版
resource_fields = {
    'client_id': fields.String(default=''),
    'expires': fields.Float(default=0.0),
    #'salt':fields.Float(default=0.0),
   
'user_id': fields.String(default=''),
    #'date':fields.DateTime(default=str(datetime.now()))

}

# 新的資源伺服器
class Test1(restful.Resource):
   @marshal_with(resource_fields)
    def get(self):
        token = request.args.get('token')
        ret = verify_token(token)
        if ret:
            return ret
        else:
            return 'error'

api.add_resource(Test1, '/test1')

知識點總結:

  掌握Flask-RESTful的基本使用

  掌握Flask-RESTful的如何解析請求

  掌握Flask-RESTful的如何處理響應域


6、HTTPs以及Flask-OAuthlib外掛使用

1 HTTPs介紹及搭建 Flask HTTPs 環境

HTTPs介紹

HTTPS(全稱:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全為目標的HTTP通道,簡單講是HTTP的安全版。是超文字傳輸協議和SSL/TLS的組合,用以提供加密通訊及對網路伺服器身份的鑑定。HTTPS連線經常被用於全球資訊網上的交易支付和企業資訊系統中敏感資訊的傳輸。這個系統的最初研發由網景公司(Netscape)進行,並內置於其瀏覽器NetscapeNavigator中,提供了身份驗證與加密通訊方法。

HTTP訪問

HTTPs訪問

證書

HTTPs握手過程

在Linux或mac下,搭建Flask HTTPs開發環境

需要的軟體:

  OpenSSL

  stunnel

$ openssl req -new -x509 -days 365 -nodes-out magigo.pem -keyout magigo.pem

$ chmod 600 magigo.pem

$ vim https_st

程式碼:

1 pid =                                                                                   

  2cert = /home/yuxiang/magigo.pem

  3debug = 7                 

  4foreground = yes

  5

  6[https]                   

  7accept = 443              

  8connect = 5000

$ sudo stunnel https_st

然後啟動程式,輸入https://localhost:5000/client/login,會出現安全連線問題

Flask-OAuthlib的基本使用

安裝:

  pipinstall Flask-OAuthlib

原理:

#client.py – 客戶端(8000)

1)  將使用者重定向到oauth授權伺服器

2)  使用得到的授權碼去換取token

#app.py - 授權伺服器(5000)

使用Flask-OAuthlib搭建OAuth2 Server

程式碼:

# client.py

# coding: utf-8

from flask import Flask, url_for, session, request,jsonify
from flask_oauthlib.client import OAuth


CLIENT_ID= 'LyofOAKrBZnSW5GQlp7xcg9DtbgK8lo6p641lY8t'
CLIENT_SECRET ='uCysaCWYh4aGUPIE19zMstcom9kYVUz9oXIrNwmMuyU1Y6hKl6'


app = Flask(__name__)
app.debug = True
app.secret_key = 'secret'
oauth = OAuth(app)

remote= oauth.remote_app(
    'remote',
    consumer_key=CLIENT_ID,
    consumer_secret=CLIENT_SECRET,
    request_token_params={'scope':'email'},
   base_url='http://127.0.0.1:5000/api/',
    request_token_url=None,
   access_token_url='http://127.0.0.1:5000/oauth/token',
   authorize_url='http://127.0.0.1:5000/oauth/authorize'
)

# 相當於/client/login,用於重定向使用者登入
@app.route('/')
def index():
    if 'remote_oauth' in session:
        resp = remote.get('me')
        return jsonify(resp.data)
    next_url = request.args.get('next')or request.referrer or None
    return remote.authorize(
        callback=url_for('authorized',next=next_url, _external=True)
    )

# 相當於/client/passport,用於獲取token,並存儲在Session
@app.route('/authorized')
def authorized():
    resp = remote.authorized_response()
    if resp is None:
        return 'Access denied: reason=%serror=%s' % (
            request.args['error_reason'],
           request.args['error_description']
        )
    print resp
    session['remote_oauth'] =(resp['access_token'], '')
    returnjsonify(oauth_token=resp['access_token'])


@remote.tokengetter
def get_oauth_token():
    return session.get('remote_oauth')


if __name__ == '__main__':
    import os
    os.environ['DEBUG'] = 'true'
   os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = 'true'
    app.run(host='localhost', port=8000)

# app.py

# coding: utf-8

from datetime import datetime,timedelta

from flask importFlask
from flask importsession, request
from flask importrender_template, redirect, jsonify
from flask_sqlalchemy importSQLAlchemy
from
werkzeug.security importgen_salt
from flask_oauthlib.providerimport OAuth2Provider
from flask.ext importrestful
from flask.ext.restful importfields,marshal_with

app = Flask(__name__, template_folder='templates')
app.debug = True
app.secret_key = 'secret'
app.config.update({
    'SQLALCHEMY_DATABASE_URI': 'sqlite:///db.sqlite',
})
db = SQLAlchemy(app)
oauth = OAuth2Provider(app)# 相當於/
@app.route('/',methods=('GET','POST'))
def home():
    if request.method == 'POST':
        username = request.form.get('username')
        user = User.query.filter_by(username=username).first()
        if not user:
            user = User(username=username)
            db.session.add(user)
            db.session.commit()
        session['id'] = user.id
        return redirect('/')
    user = current_user()
    return render_template('home.html',user=user)


@app.route('/client')  // 生成client_id 和 client_secret
def client():
    '''
   
為登入使用者註冊一個新的客戶端
   
:return:
    '''
   
user =current_user()
    if not user:
        return redirect('/')
    item = Client(
        client_id=gen_salt(40),
        client_secret=gen_salt(50),
        _redirect_uris=' '.join([
            'http://localhost:8000/authorized',
            'http://127.0.0.1:8000/authorized',
            'http://127.0.1:8000/authorized',
            'http://127.1:8000/authorized',
            ]),
        _default_scopes='email',
        user_id=user.id,
    )
    db.session.add(item)
    db.session.commit()
    return jsonify(
        client_id=item.client_id,
        client_secret=item.client_secret,
    )

# 相當於oauth
@app.route('/oauth/token',methods=['GET','POST'])