1. 程式人生 > >11.2.5、搭建RESTful API 之 實現WSGI服務的URL對映

11.2.5、搭建RESTful API 之 實現WSGI服務的URL對映

問題引出:對於一個稍具規模的網站來說,實現的功能不可能通過一條URL來完成。如何定義多條URL,也是RESTful API考慮的問題。

需求:

本小節將考慮這樣一個虛擬機器管理的WSGI服務。使用者可以通過傳送HTTP請求,來實現對虛擬機器的管理(包括建立、查詢、更新以及刪除虛擬機器等操作)。這個WSGI服務不會真正的在物理機上建立虛擬機器,只是在服務中儲存相應的虛擬機器記錄而已。

(nova已經實現類似的功能。當用戶向 Nova 傳送建立虛擬機器的請求時,Nova 首先會在資料庫中新增相應的記錄,同時在物理機上建立相應的虛擬機器。

Nova必須保證資料庫中儲存的虛擬機器資訊和實際建立的虛擬機器資訊一致,我們直接將記錄儲存在記憶體中)

開始今天的行程:

一、基礎知識

(1)、RESTful API提供一套URL的規則。在RESTful API中,每條URL都是與資源相對應的。 一個資源可能是集合,也可能是一個個體。集合通常用集合名來標誌。

例如:本小節例項中,使用instances表示虛擬機器的集合,而個體通常使用統一的ID標誌,使用UUID標誌虛擬機器

(2)、對於集合的操作通常是新增和查詢; 對於個體的操作是虛擬機器的查詢、刪除和更新。對應的URL如下圖示:

{instance_id}是虛擬機器的UUID。將資源的ID放在URL中,是RESTful API的一大特點。

(3)、在WSGI中,要實現URL對映,主要是依賴Mapper和Controller兩個類。

Mapper類用於實現URL的對映。當用戶傳送請求時,Mapper類會根據使用者請求的URL及其方法來確定處理放入方法。

Controller類,則是實現處理HTTP請求的各種方法。

二、程式碼實現

1、配置檔案:configure.ini

[pipeline:main]
pipeline = auth instance
[app:instance]
paste.app_factory = routers:app_factory
[filter:auth]
paste.filter_factory = auth:filter_factory

WSGI服務共使用了auth過濾器和instance應用程式兩個部件。auth與上一節的相同,不同的是新定義了instance應用程式。instance應用程式對應的工

廠方法是routers包的app_factory方法。

2、URL對映的實現

WSGI服務使用了auth過濾器和instance應用程式兩個部件。其中auth過濾器是用於HTTP頭認證的,比較簡單。核心功能都在instance應用程式中實現。

from webob.dec import *
from webob import Request,Response
import webob.exc

from routes import Mapper,middleware
import controllers

class Router(object):
    def __init__(self):
        self.mapper = Mapper()
        self.add_routes()
        self.router = middleware.RoutesMiddleware(self._dispatch, self.mapper)

    @wsgify
    def __call__(self, req):
        return self.router

    def add_routes(self):
        controller = controllers.Controller()

        self.mapper.connect('/instances', controller = controller,action = 'create', conditions = {'method' : ['POST']})
        self.mapper.connect('/instances', controller = controller,action = 'index', conditions = {'method' : ['GET']})
        self.mapper.connect('/instances/{instance_id}', controller = controller,action = 'show', conditions = {'method' : ['GET']})
        self.mapper.connect('/instances/{instance_id}', controller = controller,action = 'update', conditions = {'method' : ['PUT']})
        self.mapper.connect('/instances/{instance_id}', controller = controller,action = 'delete', conditions = {'method':['DELETE']})

    @staticmethod
    @wsgify
    def _dispatch(req):
        match = req.environ['wsgiorg.routing_args'][1]  #獲取URL解析結果
        if not match:   #如果解析結果為空,則輸出錯誤資訊
            return webob.exc.HTTPNotFound()

        app = match['controller']   #獲取URL對應的controller例項
        return app

def app_factory(global_config, **local_config):
    return Router()

(1)instance應用程式對應的工廠方法為routers包的app_factory方法。app_factory方法返回一個Router物件。工廠方法必須返回一個函式的例項。

Router類中必須實現__call__方法。  Router類的__call__方法的定義。返回router變數,其實也是一個可呼叫的例項。

__call__是在伺服器收到HTTP請求時被呼叫。當伺服器呼叫__call__方法時,會轉而呼叫router方法。

(2)Router類的初始化方法(兩件事情)

  • 初始化mapper成員變數。mapper成員變數是routes包的Mapper標準類,主要功能是 建立Mapper物件,用於URL解析。 然後,呼叫自身的add_routes方法往Mapper物件中註冊URL對映
  • 初始化router成員變數。 (路由匹配,修改環境變數,實現分發) router成員變數的功能將HTTP請求分發給相應的方法進行處理。router成員變數是一個 RoutesMiddleware物件。初始化需要提供兩個引數-----dispatch方法和mapper變數。dispatch方法是Router類的靜態方法,功能是實現HTTP請求的分發。

RoutesMiddleware物件(可呼叫的例項)執行過程:首先通過Mapper物件解析資訊(匹配路由,修改環境變數),講解析結果傳遞給dispatch方法。

(3)addroutes方法

addroutes方法共添加了5條URL路由,各條URL對映對應的功能參見上表。在新增URL對映時,呼叫mapper變數的connect方法。

Mapper類的connect方法用法:


<URL>:請求的URL

<controller>:處理HTTP請求的controller物件,這個物件必須是可呼叫的(實現了__call__方法)

<action>:指的是處理HTTP請求的方法名。所有定義的<action>對應的方法都在controller類中

<method-list>:是HTTP請求的方法列表。在RESTful API中,定義了GET、POST、PUT、DEAD和DELETE等方法,不同的方法對應資源的某項操作

action是一個字串,指定的是方法名,而不是方法的例項。

(4)dispatch方法

  首先獲取URL的解析結果(通過mapper類物件進行匹配路由,修改環境變數)。如果結果為空,說明相應的URL沒有在mapper物件中註冊,輸出錯誤信

息。如果不為空,返回URL對應的controller物件。由於controller物件是可呼叫的,最終WSGI服務會呼叫controller物件的__call__方法處理HTTP請

求。  (dispatch方法只是將URL請求進行分發給相應的controller,並沒有具體到方法)

3、controller類的實現

import uuid
from webob import Request,Response
import simplejson
from webob.dec import wsgify

class Controller(object):
    def __init__(self):
        self.instances = {}
        for i in range(3):
            inst_id = str(uuid.uuid4())
            self.instances[inst_id] = {'id' : inst_id, 'name' : 'inst-' + str(i)}
        print self.instances

    @wsgify
    def create(self, req):
        print req.params
        name = req.params['name']
        if name:
            inst_id = str(uuid.uuid4())

            inst = {'id' : inst_id, 'name' : name}
            self.instances[inst_id] = inst
            return {'instance' : inst}

    @wsgify
    def show(self, req, instance_id):
        inst = self.instances.get(instance_id)
        return {'instance' : inst}

    @wsgify
    def index(self, req):
        return {'instances': self.instances.values()}

    @wsgify
    def delete(self, req, instance_id):
        if self.instances.get(instance_id):
                self.instances.pop(instance_id)
    @wsgify
    def update(self, req, instance_id):
        inst = self.instances.get(instance_id)
        name = req.params['name']
        if inst and name:
            inst['name'] = name
            return {'instance': inst}

    @wsgify
    def __call__(self, req):
        arg_dict = req.environ['wsgiorg.routing_args'][1] #獲取URL解析結果
        action = arg_dict.pop('action')  #獲取處理的方法
        del arg_dict['controller']   #刪除 controller項,剩下的都是引數列表

        method = getattr(self, action)  #搜尋controller類中定義的方法
        result = method(req, **arg_dict)  #呼叫方法,處理HTTP請求

        if result is None:   #無返回值
            return Response(body='',status='204 Not Found',headerlist=[('Content-Type','application/json')])
        else:  #有返回值
            if not isinstance(result, str):
                result = simplejson.dumps(result) #將返回值轉化為字串
            return result
(1)準備引數   (2)查詢並執行controller中相應的方法  (3)返回結果

定義的 /instances/{instance_id}這樣的URL。當Router的mapper物件解析這樣的URL時,會把{instance_id}解析成一個引數。

例如:對於/instances/123 這條url,解析完畢後,會在以上程式碼的arg_dict字典中產生 {'instance_id':123}。

create方法:

功能是建立一條虛擬機器記錄,其對應的URL是 POST/instances。在傳送請求時,必須提供name欄位來指定虛擬機器名。 

def create(self, req):
        print req.params
        name = req.params['name']   #獲取虛擬機器名
        if name:
            inst_id = str(uuid.uuid4())  #自動生成UUID

            inst = {'id' : inst_id, 'name' : name}  #構造虛擬機器資訊元祖
            self.instances[inst_id] = inst   #新增虛擬機器記錄
            return {'instance' : inst}
可使用req.param來獲取客戶端提交的資料。

程式碼測試:

執行python WSGIService.py,便可啟動WSGI服務。WSGI服務啟動後,可在另一個終端進行測試。對所有的URL進行測試:

(1)、GET /instances

 

WSGI服務啟動時,會自動生成3條虛擬機器記錄。輸出的結果,便是生成的3條虛擬機器記錄資訊。

(2)、POST /instances

 

以上命令建立名為new-inst的虛擬機器記錄。

(3)、GET /instances/{instance_id}

  curl -H 'X-Auth-Token:open-sesame' -X GET > 127.0.0.1:8000/instances/c81e83fe-ae90-44a3-89e7-20918dfa9aef

{instance_id}是要查詢的虛擬機器的id,需要根據自己的實際情況修改。可以通過GET /instances來檢視所有虛擬機器記錄的id

(4)、PUT /instances/{instance_id} 

curl -H 'X-Auth-Token:open-sesame' -X PUT --data 'name=new-inst2' > 127.0.0.1:8000/instances/c81e83fe-ae90-44a3-89e7-20918dfa9aef
 更名虛擬機器

(5)、DELETE /instances/{instance_id}

curl -H 'X-Auth-Token:open-sesame' -X DELETE  > 127.0.0.1:8000/instances/c81e83fe-ae90-44a3-89e7-20918dfa9aef

 刪除虛擬機器

RESTful API

1、使用PasteDeploy配置WSGI服務

(1)、RESTful API底層是HTTP協議。傳統HTTP協議基礎上,明確定義各種HTTP方法的意義。 RESTful API定義的標準方法。


(2)、RESTful API使用PasteDeploy定製WSGI服務。WSGI服務的功能可以通過配置檔案配置。在配置檔案中可以定義app、filter、pipeline和composite等部件,

   

(3)、app和filter都須對應一個工廠方法。工廠方法通常有如下引數:

def factory(global_config, **local_config)

global_config引數儲存從客戶端傳入的引數,例如HTTP訊息體、客戶上下文資訊等。

local_config引數儲存了在配置檔案中設定的引數。

每個工廠方法最終會返回一個處理對應的app和filter HTTP請求的方法例項。處理app請求的方法例項通常有如下引數:  def app(request): request引數儲存HTTP請求的上下文資訊。

處理filter請求的方法例項通常有如下引數:def filter(request, app):其中request引數儲存HTTP請求的上下文,app引數指定從當前過濾器的下一個過濾器(或應用程式)開始,到最後一個應用程式位置所形成的的子pipeline的例項。

2、Mapper.connect方法的呼叫

一個完整的WSGI服務,通常需要解析和處理多條URL。routes標準包中,定義了Mapper類來實現URL對映的管理。可以通過呼叫Mapper物件的connect方法來向WSGI服務註冊URL對映。Mapper物件的connect方法一個呼叫示例。

mapper.connect(url,controller=controller, action=action, conditions = condition)

(1)、url引數指定的是請求的URL。兩種形式,/<resources>型別的URL對應的是集合的操作,/<resources>/<resource-id>型別的URL對應的是成員操作。<resources>是資源的集合名,<resource-id>是資源的UUID。

(2)、controller指定的是處理HTTP請求的controller物件

(3)、action引數指定controller物件中處理HTTP請求的方法

(4)、condition引數指定伺服器接收和處理HTTP請求的條件。通常在condition中指定HTTP請求的方法。例如:conditions = dict(method=[‘POST’]),指定只接受HTTP POST請求,conditions =dict(method=[‘POST’,‘PUT’])指定只接收HTTP POST和PUT請求。


RESTful API告一段落!!!