1. 程式人生 > >[CI] 使用CodeIgniter框架搭建RESTful API服務

[CI] 使用CodeIgniter框架搭建RESTful API服務

解釋 valid 源碼 stat target 面向服務 prot 多次 1.0

在2011年8月的時候,我寫了一篇博客《使用CodeIgniter框架搭建RESTful API服務》,介紹了RESTful的設計概念,以及使用CodeIgniter框架實現RESTful API的方法。轉眼兩年過去了,REST在這兩年裏有了很大的改進。我對於前一篇博客中的某些方面不是很滿意,所以希望能利用這次機會寫一個更加完善的版本。
我的項目基於Phil Sturgeon的CodeIgniter REST Server,遵循他自己的DBAD協議。Phil的這個項目很棒,幹凈利落,簡單實用,並且極具特色,解決了我自己項目中兩個問題:在請求中查詢語句的使用,以及復雜的身份驗證方法。正如我前面所說,我的項目基於Phil的項目開發,並且只有一個方面不同:我給每個資源分別分配一個控制器,而不是用一個獨立龐大的控制器進行統一管理。使用多個獨立控制器的好處是維護起來更加簡單方便。
完整的項目源碼可以在awhitney42/codeigniter-restserver-resources下載。
RESTful在深入探討之前,我們先來回顧一下TESTful的概念。REST全名Representational State Transfer,中文可以譯為:表現層狀態轉化,是一種網絡服務的架構工具,而不僅僅是一套接口規範。
使用名詞而不是動詞你的RESTful接口應該提供訪問而不是方法。
所以,這樣是不可取的:
/createCustomer
/getCustomer?id=666
應該這樣:
POST /customers
GET /customers/666
所有東西都應該有IDREST的優點之一就是簡潔,一定程度上來講,REST只是一個URI的集合,每個資源都需要一個獨一無二的ID作為一個標識。
GET /customers/666
GET /products/4234
使用動詞進行操作在REST裏,使用不同的HTTP動詞進行增刪改查的操作:
[td]
動詞 操作
POST 新建一個資源
GET 讀取一個資源
PUT 更新一個資源
DELETE 刪除一個資源
使用這個REST服務的映射表來設計接口,在開發客戶端的時候可以輕松上手,不用深入理解接口的含義。這套標準十分適用於REST,符合面向服務架構(SOA)的核心設計思想:服務抽離、松耦合、可復用、可發現、健全性。
通過這個一致的映射關系,客戶端也知道哪個動詞是冪等(Idempotent)的。冪等是指這個操作可以重復多次,並且每次都會得到相同的結果。參照HTTP規範,GET、PUT和DELETE操作是冪等的,POST操作不是冪等的。這也就是為什麽在REST中使用POST來進行添加操作,而使用PUT來進行更新操作。
把東西鏈接起來REST中的一個核心概念是HATEOAS(Hypermedia As The Engine Of Application State),即“超媒體即應用狀態引擎”。這意味著應該始終使用鏈接(超媒體)來獲取資源信息,然後客戶端通過這個鏈接就可以獲取到不同的資源。
所以,相關的資源應當返回一條鏈接,而不是返回整個資源的內容。
也就是,該這樣:

<officer id="1">
   <name>James T. Kirk</name>
   <rank>Captain</rank>
   <subordinates>
      <link ref="http://ncc1701/api/officers/2">
      <link ref="http://ncc1701/api/officers/3">
   </subordinates>
</officer>
而不該這樣:
<officer id="1">
   <name>James T. Kirk</name>
   <rank>Captain</rank>
   <subordinates>
      <officer id="2">
         <name>Spock</name>
         <rank>Commander</rank>
      </officer>
      <officer id="3">
         <name>Doctor Leonard McCoy</name>
         <rank>Commander</rank>
      </officer>
   </subordinates>
</officer>

  返回資源的鏈接應該用完整的URI地址而不是相對路徑,這要求客戶端請求這些資源的當前狀態,維持HATEOAS原則。使用完整的URI地址的好處是客戶端不需要額外的了解API相關的內容,只需要簡單的訪問這些鏈接就可以了。
提供多種資源表現方式REST中的R代表Representational,即表現層,這意味著REST服務應該提供不同的表現方式,以全面支持不同的客戶端請求。在一次HTTP請求中,服務器可以通過指定的表現方式返回資源,REST服務應該使用標準的表現方式,以便客戶端之間的信息互通。
比如可以通過如下請求XML格式的數據:
GET /customers/666
Accept: application/xml
或者vcard格式:
GET /customers/666
Accept: application/vcard
或者pdf格式:
GET /customers/666
Accept: application/pdf
或者自定義格式:
GET /customers/666
Accept: application/vnd.mycompany.customer-v1+json
使用狀態碼作為回復HTTP的狀態碼提供了一套標準化方案,用來反饋請求的狀態。
[td]

含義 解釋
200 OK 確認GET、PUT和DELETE操作成功
201 Created 確認POST操作成功
304 Not Modified 用於條件GET訪問,告訴客戶端資源沒有被修改
400 Bad Request 通常用於POST或者PUT請求,表明請求的內容是非法的
401 Unauthorized 需要授權
403 Forbidden 沒有訪問權限
404 Not Found 服務器上沒有資源
405 Method Not Allowed 請求方法不能被用於請求相應的資源
409 Conflict 訪問和當前狀態存在沖突
CodeIgniterCodeIgniter是一個流行的MVC框架,很適合用來進行RESTful API開發。控制器(Controller)處理客戶端的請求並返回內容,模型(Model)進行增刪改查(CRUD)的操作,視圖(View)用來處理資源的表現格式。不過在這個例子裏,我們沒有用模型,而是直接用控制器進行格式處理,這樣整個項目更幹凈更簡單。
代碼可以直接在Github中獲取:codeigniter-restserver-resources。
在接下來的例子裏,我們用到了REST_Controller這個庫,繼承自原生的CI_Controller。它可以完成絕大部分繁雜的工作:處理請求、調用模塊、格式化內容、返回數據。你的每個資源控制器都應該繼承自REST_Controller這個類。
下面我們來看一個例子,我們假設要提供一個Widgets資源的RESTful接口:

class Widgets extends REST_Controller
get()Widgets類中的第一個函數是get(),用來響應HTTP的GET請求。這個函數調用了父類的protected函數_get(),用來獲取請求中的參數ID。然後這個函數根據是否有參數ID使用widgets_model調用getWidgets()或者getWidget($id)方法,模型的返回值將會通過父類的response()函數返回,返回的內容包含對應的狀態碼和符合格式的數據。

[mw_shl_code=php,true]function get()
{    
    $id = $this->_get(‘id‘);
    if(!$id)
    {
        $widgets = $this->widgets_model->getWidgets();                        
        if($widgets)
            $this->response($widgets, 200); // 200 being the HTTP response code
        else
            $this->response(array(‘error‘ => ‘Couldn\‘t find any widgets!‘), 404);
    }
    $widget = $this->widgets_model->getWidget($id);
    if($widget)
        $this->response($widget, 200); // 200 being the HTTP response code
    else
        $this->response(array(‘error‘ => ‘Widget could not be found‘), 404);
}

  cURL是一個很好的命令行工具,我們可以用來測試REST服務,返回一個JSON格式的數據:

則會返回如下內容:
HTTP/1.1 200 OK
Status: 200
Content-Type: application/json
{"1":{"id":1,"name":"sprocket"},"2":{"id":2,"name":"gear"}}
請求特定的資源也很簡單,這次我們去請求ID為2的widget並且通過XML格式返回:
$ curl -i -H "Accept: application/xml" -X GET http://foo.com/index.php/api/widgets/id/2
返回如下內容:

HTTP/1.1 200 OK
Status: 200
Content-Type: application/xml
<?xml version="1.0" encoding="utf-8"?>
<xml><id>2</id><name>gear</name></xml>
如果請求一個不存在的資源就會返回404的錯誤碼:

HTTP/1.1 404 Not Found
Status: 404
Content-Type: application/xml
<?xml version="1.0" encoding="utf-8"?>
<xml><error>Widget could not be found</error></xml>
post()接下來的函數是post(),用來處理創建widget的POST請求。可以通過$this->_post_args獲取請求的數據。父類通過Format.php對請求的數據進行處理並把它們放到了$this->_post_args裏。接下來post()方法使用widgets_model模型調用createWidgets($data)函數,如果數據非法或者請求沖突,widgets_model模型會拋出異常並且返回異常的內容。如果調用成功,則會調用getWidget($id)函數獲取最新的widget,在返回的時候會將返回值和201 (Created)的狀態碼一起返回。

function post()
{
    $data = $this->_post_args;
    try {
        $id = $this->widgets_model->createWidget($data);
    } catch (Exception $e) {
        // Here the model can throw exceptions like the following:
        // * Invalid input data:
                    //   throw new Exception(‘Invalid request data‘, 400);
        // * Conflict when attempting to create, like a resubmit:
                    //   throw new Exception(‘Widget already exists‘, 409)
        $this->response(array(‘error‘ => $e->getMessage()),
                                    $e->getCode());
    }
    if ($id) {
        $widget = $this->widgets_model->getWidget($id);
        $this->response($widget, 201); // 201 is the HTTP response code
    } else
        $this->response(array(‘error‘ => ‘Widget could not be created‘),
                                    404);
}

  

[CI] 使用CodeIgniter框架搭建RESTful API服務