使用CodeIgniter框架搭建RESTful API服务

简介: 使用CodeIgniter框架搭建RESTful API服务 发表于 2014-07-12   |   分类于 翻译笔记   |   6条评论 在2011年8月的时候,我写了一篇博客《使用CodeIgniter框架搭建RESTful API服务》,介绍了RESTful的设计概念,以及使用CodeIgniter框架实现RESTful API的方法。

使用CodeIgniter框架搭建RESTful API服务

在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接口应该提供访问而不是方法。
所以,这样是不可取的:

1
2
/createCustomer
/getCustomer?id=666

应该这样:

1
2
POST /customers
GET /customers/666

所有东西都应该有ID

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

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

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

1
2
3
4
5
6
7
8
<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>

而不该这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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格式的数据:

1
2
GET /customers/666
Accept: application/xml

或者vcard格式:

1
2
GET /customers/666
Accept: application/vcard

或者pdf格式:

1
2
GET /customers/666
Accept: application/pdf

或者自定义格式:

1
2
GET /customers/666
Accept: 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()函数返回,返回的内容包含对应的状态码和符合格式的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

 

则会返回如下内容:

1
2
3
4
5
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格式返回:

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

返回如下内容:

1
2
3
4
5
6
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的错误码:

1
2
3
4
5
6
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)的状态码一起返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

 

返回内容如下:

1
2
3
4
5
6
-----TESTING REST POST-----
Status: 201
Content-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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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操作处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

 

返回结果:

1
2
3
4
5
6
-----TESTING REST PUT-----
Status: 200
Content-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)状态码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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的资源已经被成功删除:

1
2
3
4
5
6
HTTP/1.1 200 OK
Status: 200
Content-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或者是本文有什么意见,或者对于其中的概念有什么困惑,请在文章后面留言。


原文地址:

目录
相关文章
|
4天前
|
安全 Java API
RESTful API设计与实现:Java后台开发指南
【4月更文挑战第15天】本文介绍了如何使用Java开发RESTful API,重点是Spring Boot框架和Spring MVC。遵循无状态、统一接口、资源标识和JSON数据格式的设计原则,通过创建控制器处理HTTP请求,如示例中的用户管理操作。此外,文章还提及数据绑定、验证、异常处理和跨域支持。最后,提出了版本控制、安全性、文档测试以及限流和缓存的最佳实践,以确保API的稳定、安全和高效。
|
7天前
|
小程序 前端开发 API
小程序全栈开发中的RESTful API设计
【4月更文挑战第12天】本文探讨了小程序全栈开发中的RESTful API设计,旨在帮助开发者理解和掌握相关技术。RESTful API基于REST架构风格,利用HTTP协议进行数据交互,遵循URI、客户端-服务器架构、无状态通信、标准HTTP方法和资源表述等原则。在小程序开发中,通过资源建模、设计API接口、定义资源表述及实现接口,实现前后端高效分离,提升开发效率和代码质量。小程序前端利用微信API与后端交互,确保数据流通。掌握这些实践将优化小程序全栈开发。
|
17天前
|
前端开发 Java API
构建RESTful API:Java中的RESTful服务开发
【4月更文挑战第3天】本文介绍了在Java环境中构建RESTful API的重要性及方法。遵循REST原则,利用HTTP方法处理资源,实现CRUD操作。在Java中,常用框架如Spring MVC简化了RESTful服务开发,包括定义资源、设计表示层、实现CRUD、考虑安全性、文档和测试。通过Spring MVC示例展示了创建RESTful服务的步骤,强调了其在现代Web服务开发中的关键角色,有助于提升互操作性和用户体验。
构建RESTful API:Java中的RESTful服务开发
|
20天前
|
XML JSON 安全
谈谈你对RESTful API设计的理解和实践。
RESTful API是基于HTTP协议的接口设计,通过URI标识资源,利用GET、POST、PUT、DELETE等方法操作资源。设计注重无状态、一致性、分层、错误处理、版本控制、文档、安全和测试,确保易用、可扩展和安全。例如,`/users/{id}`用于用户管理,使用JSON或XML交换数据,提升系统互操作性和可维护性。
14 4
|
29天前
|
缓存 前端开发 API
构建高效可扩展的RESTful API:后端开发的最佳实践
【2月更文挑战第30天】 在现代Web应用和服务端架构中,RESTful API已成为连接前端与后端、实现服务间通信的重要接口。本文将探讨构建一个高效且可扩展的RESTful API的关键步骤和最佳实践,包括设计原则、性能优化、安全性考虑以及错误处理机制。通过这些实践,开发者可以确保API的健壮性、易用性和未来的可维护性。
|
30天前
|
API 开发者 UED
深入探讨RESTful API设计原则及最佳实践
在当今互联网时代,RESTful API已成为各种软件系统之间进行通信的重要方式。本文将从资源定义、URI设计、HTTP方法选择、状态码规范等方面深入探讨RESTful API设计的原则与最佳实践,帮助开发者更好地构建高效、健壮的API。
|
3月前
|
JSON API 数据格式
RESTful API 最佳实践
RESTful API 最佳实践
118 0
|
10月前
|
XML JSON 安全
深入理解RESTful API设计原则及最佳实践
2.尽可能使用HTTP标准方法来表示对资源的操作。比如使用GET方法来获取资源,使用POST方法来创建资源,使用PUT方法来更新资源,使用DELETE方法来删除资源。
140 0
|
XML JSON API
RESTful API 最佳实践
RESTful API 最佳实践
136 0
|
网络架构 API 前端开发
REST与RESTFul API最佳实践
RESTFul API已经是现在互联网里对外开放接口的主流模式
6060 0