使用CodeIgniter框架搭建RESTful API服务

使用CodeIgniter框架搭建RESTful API服务

发表于 2014-07-12   |   分类于 翻译笔记   |   6条评论

在2011年8月的时候,我写了一篇博客《使用CodeIgniter框架搭建RESTful API服务》,介绍了RESTful的设计概念,以及使用CodeIgniter框架实现RESTful API的方法。转眼两年过去了,REST在这两年里有了很大的改进。我对于前一篇博客中的某些方面不是很满意,所以希望能利用这次机会写一个更加完善的版本。

我的项目基于Phil SturgeonCodeIgniter REST Server,遵循他自己的DBAD协议。Phil的这个项目很棒,干净利落,简单实用,并且极具特色,解决了我自己项目中两个问题:在请求中查询语句的使用,以及复杂的身份验证方法。正如我前面所说,我的项目基于Phil的项目开发,并且只有一个方面不同:我给每个资源分别分配一个控制器,而不是用一个独立庞大的控制器进行统一管理。使用多个独立控制器的好处是维护起来更加简单方便。

完整的项目源码可以在awhitney42/codeigniter-restserver-resources下载。

RESTful

在深入探讨之前,我们先来回顾一下TESTful的概念。REST全名Representational State Transfer,中文可以译为:表现层状态转化,是一种网络服务的架构工具,而不仅仅是一套接口规范。

使用名词而不是动词

你的RESTful接口应该提供访问而不是方法。
所以,这样是不可取的:

12
/createCustomer/getCustomer?id=666

应该这样:

12
POST /customersGET  /customers/666

所有东西都应该有ID

REST的优点之一就是简洁,一定程度上来讲,REST只是一个URI的集合,每个资源都需要一个独一无二的ID作为一个标识。

12
GET /customers/666GET /products/4234

使用动词进行操作

在REST里,使用不同的HTTP动词进行增删改查的操作:

动词 操作
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),即“超媒体即应用状态引擎”。这意味着应该始终使用链接(超媒体)来获取资源信息,然后客户端通过这个链接就可以获取到不同的资源。

所以,相关的资源应当返回一条链接,而不是返回整个资源的内容。
也就是,该这样:

12345678
<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>

而不该这样:

1234567891011121314
<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格式的数据:

12
GET /customers/666Accept: application/xml

或者vcard格式:

12
GET /customers/666Accept: application/vcard

或者pdf格式:

12
GET /customers/666Accept: application/pdf

或者自定义格式:

12
GET /customers/666Accept: application/vnd.mycompany.customer-v1+json

使用状态码作为回复

HTTP的状态码提供了一套标准化方案,用来反馈请求的状态。

  含义 解释
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 访问和当前状态存在冲突

CodeIgniter

CodeIgniter是一个流行的MVC框架,很适合用来进行RESTful API开发。控制器(Controller)处理客户端的请求并返回内容,模型(Model)进行增删改查(CRUD)的操作,视图(View)用来处理资源的表现格式。不过在这个例子里,我们没有用模型,而是直接用控制器进行格式处理,这样整个项目更干净更简单。

代码可以直接在Github中获取:codeigniter-restserver-resources

在接下来的例子里,我们用到了REST_Controller这个库,继承自原生的CI_Controller。它可以完成绝大部分繁杂的工作:处理请求、调用模块、格式化内容、返回数据。你的每个资源控制器都应该继承自REST_Controller这个类。

下面我们来看一个例子,我们假设要提供一个Widgets资源的RESTful接口:

1
class Widgets extends REST_Controller

get()

Widgets类中的第一个函数是get(),用来响应HTTP的GET请求。这个函数调用了父类的protected函数_get(),用来获取请求中的参数ID。然后这个函数根据是否有参数ID使用widgets_model调用getWidgets()或者getWidget($id)方法,模型的返回值将会通过父类的response()函数返回,返回的内容包含对应的状态码和符合格式的数据。

1234567891011121314151617
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格式的数据:

1
$ curl -i -H "Accept: application/json" -X GET http://foo.com/index.php/api/widgets

则会返回如下内容:

12345
HTTP/1.1 200 OKStatus: 200Content-Type: application/json

{"1":{"id":1,"name":"sprocket"},"2":{"id":2,"name":"gear"}}

请求特定的资源也很简单,这次我们去请求ID为2的widget并且通过XML格式返回:

1
$ curl -i -H "Accept: application/xml" -X GET http://foo.com/index.php/api/widgets/id/2

返回如下内容:

123456
HTTP/1.1 200 OKStatus: 200Content-Type: application/xml

<?xml version="1.0" encoding="utf-8"?><xml><id>2</id><name>gear</name></xml>

如果请求一个不存在的资源就会返回404的错误码:

123456
HTTP/1.1 404 Not FoundStatus: 404Content-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)的状态码一起返回。

123456789101112131415161718192021
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);}

为了测试通过POST请求新建资源的操作,我们需要把我们的cURL包裹在PHP代码里,创建一个rest_client.php文件:

1234567891011121314151617181920
print "\n-----TESTING REST POST-----\n";test_post();function test_post() {   $data = array("name" => "bolt");   $data_string = json_encode($data);   $ch = curl_init(‘http://foo.com/index.php/api/widgets‘);   curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");   curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);   curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);   curl_setopt($ch, CURLOPT_HTTPHEADER, array(       ‘Content-Type: application/json‘,       ‘Content-Length: ‘ . strlen($data_string))   );   $result = curl_exec($ch);   $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);   $contenttype = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);   print "Status: $httpcode" . "\n";   print "Content-Type: $contenttype" . "\n";   print "\n" . $result . "\n";}

在命令行中通过PHP命令执行:

1
$ php rest_client.php

返回内容如下:

123456
-----TESTING REST POST-----Status: 201Content-Type: application/xml

<?xml version="1.0" encoding="utf-8"?><xml><id>3</id><name>bolt</name></xml>

put()

接下来的方法是put(),用来处理PUT请求,更新已经存在的资源数据。处理过程和POST的处理十分相似,最大的区别就在于使用$this->_put_args而不是$this->_post_args,以及返回200而不是201。

1234567891011121314151617181920
public function put(){    $data = $this->_put_args;    try {        //$id = $this->widgets_model->updateWidget($data);        $id = $data[‘id‘]; // test code        //throw new Exception(‘Invalid request data‘, 400); // test code    } catch (Exception $e) {        // Here the model can throw exceptions like the following:        // * For invalid input data: new Exception(‘Invalid request data‘, 400)        // * For a conflict when attempting to create, like a resubmit: new Exception(‘Widget already exists‘, 409)        $this->response(array(‘error‘ => $e->getMessage()), $e->getCode());    }    if ($id) {        $widget = array(‘id‘ => $data[‘id‘], ‘name‘ => $data[‘name‘]); // test code        //$widget = $this->widgets_model->getWidget($id);        $this->response($widget, 200); // 200 being the HTTP response code    } else        $this->response(array(‘error‘ => ‘Widget could not be found‘), 404);}

为了测试UPDATE更新资源的功能,我们依旧使用PHP进行cURL的操作,大多数的网络服务器默认没有开启PUT和DELETE,我们可以在header中使用X-HTTP-Method-Override,通过POST来发送PUT请求。这样的话,服务器会把它当做一个POST请求,而REST服务器会把它作为PUT操作处理。

12345678910111213141516171819202122
print "\n-----TESTING REST PUT-----\n";test_put();

function test_put() {   $data = array("id" => "3", "name" => "nut");   $data_string = json_encode($data);   $ch = curl_init(‘http://foo.com/index.php/api/widgets‘);   curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");   curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);   curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);   curl_setopt($ch, CURLOPT_HTTPHEADER, array(       ‘X-HTTP-Method-Override: PUT‘,       ‘Content-Type: application/json‘,       ‘Content-Length: ‘ . strlen($data_string))   );   $result = curl_exec($ch);   $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);   $contenttype = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);   print "Status: $httpcode" . "\n";   print "Content-Type: $contenttype" . "\n";   print "\n" . $result . "\n";}

同样使用PHP命令测试:

1
$ php rest_client.php

返回结果:

123456
-----TESTING REST PUT-----Status: 200Content-Type: application/xml

<?xml version="1.0" encoding="utf-8"?><xml><id>3</id><name>nut</name></xml>

delete()

最后的函数就是delete了,请求中必须包含ID的参数否则就会返回400(Bad Request)的状态码,因为我们不希望用户删除所有的资源内容。如果ID没有对应的资源则会返回404(Not Found) 错误码。如果对应的widget存在,则会尝试调用deleteWidget($id)删除对应资源。操作中的所有异常都会被捕获到并且返回。如果删除成功,返回200(Success)状态码。

1234567891011121314151617181920212223
function delete(){    $id = $this->_get(‘id‘);    if(!$id)    {        $this->response(array(‘error‘ =>                            ‘An ID must be supplied to delete a widget‘), 400);    }    //$widget = $this->widgets_model->getWidget($id);    $widget = @$widgets[$id]; // test code    if($widget) {        try {            $this->widgets_model->deleteWidget($id);        } catch (Exception $e) {            // Here the model can throw exceptions like the following:            // * Client is not authorized: new Exception(‘Forbidden‘, 403)            $this->response(array(‘error‘ => $e->getMessage()),                                    $e->getCode());        }            $this->response($widget, 200); // 200 being the HTTP response code    } else        $this->response(array(‘error‘ => ‘Widget could not be found‘), 404);}

我们可以用命令行工具cURL进行测试,参数ID像GET一样放在URL地址中,和PUT相同,我们通过X-HTTP-Method-Override使服务器把请求当做POST处理,REST服务则会把这次请求当做DELETE操作处理。

1
$ curl -i -H "Accept: application/xml" -H "X-HTTP-Method-Override: DELETE" -X POST http://foo.com/index.php/api/widgets/id/1

返回200 (Success)说明id为1的资源已经被成功删除:

123456
HTTP/1.1 200 OKStatus: 200Content-Type: application/xml

<?xml version="1.0" encoding="utf-8"?><xml><id>1</id><name>sprocket</name></xml>

如果ID非法,则会返回404(Not Found)错误码,如果没有删除的权限,则会返回403 (Forbidden) 状态码。

Authentication

正如前面提到的,Phil的REST框架和我的原来的设计相比,在用户权限认证上有了很大的改进, 提供basicdigest两种认证方式。还有很多其他特性,比如可以用LDAP字典将授权融为一体,具体可以在config/rest.php中设置。

除了传统的认证方式,你还可以使用API keys或者IP地址的白名单进行用户权限管理。

DTO

在原先的设计中,我使用DTO(Data Transfer Object)在不同格式之间传输数据,Phil使用Format类来解决这个问题,从Widget的例子中我们可以看到,使用数据是多么的方便,只需要$this->_post_args或者$this->response()就可以解决问题。当然,这并不意味着DTO不好用,但是在很多场合下它会显得十分复杂和庞大。
在我原来的项目里,我对客户端需要使用和服务器端同样的DTO库来传输数据很不满意,因为它违反了KISS的原则。

总结

REST目前已经是比较成熟的网络服务的框架模型方案,是API产品目录网站programmableweb的基石,是各式各样应用和服务器的数据传输的基础,同时对于SOA来说也是十分重要,促进网络端和移动端的接口技术日趋成熟。

正如前面所看到的,用CodeIgniter框架实现RESTful接口十分简单,可以下载我的项目源码awhitney42/codeigniter-restserver-resources,从现在就开始开发吧!

后记

REST是一种遵从传统设计模式的架构风格,在过去的几年中一直都在改进,并且会一直坚持下去。所以如果对于REST或者是本文有什么意见,或者对于其中的概念有什么困惑,请在文章后面留言。



原文地址:

时间: 2024-10-11 21:59:43

使用CodeIgniter框架搭建RESTful API服务的相关文章

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

在2011年8月的时候,我写了一篇博客<使用CodeIgniter框架搭建RESTful API服务>,介绍了RESTful的设计概念,以及使用CodeIgniter框架实现RESTful API的方法.转眼两年过去了,REST在这两年里有了很大的改进.我对于前一篇博客中的某些方面不是很满意,所以希望能利用这次机会写一个更加完善的版本.我的项目基于Phil Sturgeon的CodeIgniter REST Server,遵循他自己的DBAD协议.Phil的这个项目很棒,干净利落,简单实用,并

Go实战--通过gin-gonic框架搭建restful api服务(github.com/gin-gonic/gin)

生命不止,继续 go go go !!! 先插播一条广告,给你坚持学习golang的理由: <2017 软件开发薪酬调查:Go 和 Scala 是最赚钱的语言> 言归正传! 之前写过使用golang实现简单的restful api相关的博客: Go实战–实现简单的restful api(The way to go) 其中,使用了github.com/gorilla/mux,今天要跟大家介绍的是gin-gonic/gin. gin-gonic/gin 介绍: Gin is a HTTP web

基于gin web框架搭建RESTful API服务

这篇主要学习go项目中的项目结构.项目规范等知识,ROM采用的database/sql的写法. 1.技术框架 利用的是ginweb框架,然后ROM层选用database/sql,安装mysql驱动.安装方式如下: //使用github上的gin托管地址 $ go get -u github.com/gin-gonic/gin $ go get github.com/go-sql-driver/mysql 2.项目结构如下 项目结构分析: 1.main.go主要是存放路由,启动项目: 2.rout

【重学Node.js 第1&amp;2篇】本地搭建Node环境并起RESTful Api服务

本地搭建Node环境并起RESTful Api服务 课程介绍看这里:https://www.cnblogs.com/zhangran/p/11963616.html 项目github地址:https://github.com/hellozhangran/happy-egg-server 说明:本想分两章讲环境搭建和起api服务,但操作下来确实过于简单.但考虑到整个系列的完整性也不能不讲,那就把这两篇合二为一吧 本地搭建Node环境 开发环境主要是指Node.MongoDB.本人是用的mac电脑,

开放接口/RESTful/Api服务的设计和安全方案

总体思路 这个涉及到两个方面问题:一个是接口访问认证问题,主要解决谁可以使用接口(用户登录验证.来路验证)一个是数据数据传输安全,主要解决接口数据被监听(HTTPS安全传输.敏感内容加密.数字签名) 用户身份验证:Token与Session开放接口Api服务其实就是客户端与服务端无状态交互的一种形式,这有点类似REST(Representational State Transfer)风格.普通网站应用一般使用session进行登录用户信息的存储和验证(有状态),而开放接口服务/REST资源请求则

玩转 SpringBoot 2 快速搭建 | RESTful Api 篇

概述 RESTful 是一种架构风格,任何符合 RESTful 风格的架构,我们都可以称之为 RESTful 架构.我们常说的 RESTful Api 是符合 RESTful 原则和约束的 HTTP 协议的Web 接口,需要注意的是它和 HTTP 协议并非绑定关系.我的个人理解就是:通过HTTP协议不同请求方法(GET.POST.PUT.Patch,DELETE)来判断如何操作统一命名的资源,并且通过不同的响应码来知道执行的状态. 关于 RESTful API 具体详细介绍,我推荐阅读下面 3

maven+SpringMVC搭建RESTful后端服务框架

今天来尝试一下搭建基于SpringMVC的RESTful标准的后端服务. 首先,什么是MVC?M-model,模型,指业务数据层,具体一点就是业务逻辑与数据库的交互:V-view,视图,用来展示数据,传统SpringMVC中控制器有返回类型ModelAndView,即返回含有数据模型与页面视图的jsp文件:C-controller,控制器,通常负责处理与用户间的交互,控制从数据库取数与返回结果到用户等. 那么,什么是REST风格的服务呢?REST(Representational State T

搭建RESTful API来使用Fabric Node SDK 开篇

在Balance-Transfer中,有关于Node SDK比较完备的例子. SDK的官方文档在这里:https://fabric-sdk-node.github.io/ Balance-Transfer中的各项配置项都已经准备了,如果需要重新生成artifacts,务必在生成之后相应的修改docker-compose.yaml,network-config.yaml 启动网络 运行./runApp.sh后,得到类似的结果: Stopping peer1.org1.example.com ...

三分钟学会API接口设计 之 Compass 的Restful API 快速入门指南 -- 使用Flask框架

声明: 本博客欢迎转载,但请保留原作者信息! 作者:曾国仕 团队:华为杭州OpenStack团队 引子 大部分开源框架基本上都是使用Curl + RPC的方式构筑系统,以提供对外\对内的交互能力. 这种设计,本人认为更多地是出于层次化与模块化设计的考量,简化整个架构,使得开发轻量简单化. 本文主要介绍Compass的REST API的设计与实现. 通过本文档,读者至少能快速搭建一个属于自己的REST API 框架,并且能够基于该框架进行功能扩展以建立一个完整的系统. Compass的结构简介 图