基于ThinkPHP开发RESTful Web API

转载地址:基于ThinkPHP开发RESTful Web API


REST是一种风格,而不是标准。因为既没有REST RFC,也没有REST协议规范或者类似的规定。REST架构是Roy Fielding(他也是HTTP和URI规范的主要作者之一)在一篇论文中描述的。REST并没有创造新的技术,组件或服务,隐藏在RESTful Web API背后的理念是使用Web的现有特征和能力。RESTful Web API定义了如何更好地使用现有Web标准中的一些准则和约束。本文首先介绍RESTful相关知识,然后以Atromic为例,介绍如何在ThinkPHP中开发具有鉴权功能的RESTful API。

基于ThinkPHP开发RESTful Web API - 第1张  | Eureka

RESTful基础知识

资源

资源是REST中最关键的抽象概念,它们是能够被远程访问的应用程序对象。一个资源就是一个标识单位,它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在,任何可以被访问或被远程操纵的东西都可能是一个资源。资源可以是静态的,也就是该资源的状态永远不会改变。相反,某些资源的状态可能随着时间推移呈现很大的可变性。这两种类型的资源都是有效的。可以用一个URI(统一资源定位符)指向资源,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。URI提供了Web通用的识别机制,它包含了客户端直接与被引用的资源进行交互时需要的所有信息。作为资源标识的URI最好具有“可读性”,因为具有可读性的URI更容易被使用,使用者一看就知道被标识的是何种资源,比如如下一些URI就具有很好的可读性。

1
2
3
http://www.eurekao.com/employees/c001(编号C001的员工)
http://www.eurekao.com/sales/2015/12/31(2015年12月31日的销售额)
http://www.eurekao.com/orders/2015/q4(2015年第4季度签订的订单)

使用“链接”关联相关的资源

在绝大多数情况下,资源并不会孤立地存在,必然与其它资源具有某种关联。既然我们推荐资源采用具有可寻址性的URI来标识,那么我们就可以利用它来将相关的资源关联起来。超链接常被客户端用于资源导航。

1
2
http://www.eurekao.com/hotel/656bcee2-28d2-404b-891b/Room/4
http://www.eurekao.com/hotel/656bcee2-28d2-404b-891b/Room/4/Reservation/15

我们可以通过URI获取指定的Room,也可以获取Room下的Reservation。

统一资源接口

为了简化整体系统架构,REST架构风格包含了统一接口的概念。统一接口包含一组受限的操作,由它们进行资源的访问和操作。不论什么资源,都使用相同的接口。由于REST是面向资源的,所以一个Web API旨在实现针对单一资源的操作。我们在前面已经说个,针对资源的基本操作唯CRUD而已,这是使我们可以为Web API定义标准接口成可能。所谓的标准接口就是针对不同资源的Web API定义一致性的操作来操作它们,其接口可以采用类似于下面的模式。

1
2
3
4
5
6
7
class MenuController extends BaseController
{
     public function Get(){}
     public function Create(Resource resource){}
     public function Update(Resource resource){}
     public function Delete(string id){}
}

使用标准的HTTP方法

由于RESTful Web API采用了同一的接口,所以其成员体现为针对同一资源的操作。对于Web来说,针对资源的操作通过HTTP方法来体现。我们应该将两者统一起来,是Web API分别针对CRUD的操作只能接受具有对应HTTP方法的请求。关于何时以及如何使用不同的HTTP方法,在Fielding的论文中没有描述。对于大部分方法,如GET或 DELETE,通过阅读HTTP规范就能清楚其含义,而对于POST和PUT,就不那么容易了。

重要方法典型用法典型状态码安全幂等
GET获取表示

变更时获取表示(缓存)

200(OK) – 表示已在响应中发出
204(无内容) – 资源有空表示
301(Moved Permanently) – 资源的URI已被更新
303(See Other) – 其他(如,负载均衡)
304(not modified)- 资源未更改(缓存)
400 (bad request)- 指代坏请求(如,参数错误)
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求
DELETE删除资源200 (OK)- 资源已被删除
301 (Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他,如负载均衡
400 (bad request)- 指代坏请求t
404 (not found)- 资源不存在
409 (conflict)- 通用冲突
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求
PUT用客户端管理的实例号创建一个资源
通过替换的方式更新资源
如果未被修改,则更新资源(乐观锁)
200 (OK)- 如果已存在资源被更改
201 (created)- 如果新资源被创建
301(Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他(如,负载均衡)
400 (bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示/p>
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求
POST使用服务端管理的(自动产生)的实例号创建资源
创建子资源
部分更新资源
如果没有被修改,则不过更新资源(乐观锁)
200(OK)- 如果现有资源已被更改
201(created)- 如果新资源被创建
202(accepted)- 已接受处理请求但尚未完成(异步处理)
301(Moved Permanently)- 资源的URI被更新
303(See Other)- 其他(如,负载均衡)
400(bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求

HTTP 状态代码

HTTP定义了一套可以从API返回的有意义的状态代码。 这些代码能够用来帮助API使用者对不同的响应做出相应处理。常用的状态码如下:

  • 200 OK (成功) – 对一次成功的GET, PUT, PATCH 或 DELETE的响应。也能够用于一次未产生创建活动的POST
  • 201 Created (已创建) – 对一次导致创建活动的POST的响应。 同时结合使用一个位置头信息指向新资源的位置- Response to a POST that results in a creation. Should be combined with a Location header pointing to the location of the new resource
  • 204 No Content (没有内容) – 对一次没有返回主体信息(像一次DELETE请求)的请求的响应
  • 304 Not Modified (未修改) – 当使用HTTP缓存头信息时使用304
  • 400 Bad Request (错误的请求) – 请求是畸形的, 比如无法解析请求体
  • 401 Unauthorized (未授权) – 当没有提供或提供了无效认证细节时。如果从浏览器使用API,也可以用来触发弹出一次认证请求
  • 403 Forbidden (禁止访问) – 当认证成功但是认证用户无权访问该资源时
  • 404 Not Found (未找到) – 当一个不存在的资源被请求时
  • 405 Method Not Allowed (方法被禁止) – 当一个对认证用户禁止的HTTP方法被请求时
  • 410 Gone (已删除) – 表示资源在终端不再可用。当访问老版本API时,作为一个通用响应很有用
  • 415 Unsupported Media Type (不支持的媒体类型) – 如果请求中包含了不正确的内容类型
  • 422 Unprocessable Entity (无法处理的实体) – 出现验证错误时使用
  • 429 Too Many Requests (请求过多) – 当请求由于访问速率限制而被拒绝时

表示

对资源的操纵永远是通过其表示实现的。资源可能永远不会在网络中传输,相反,传输的是资源的表示。资源的表示包括数据和描述数据的元数据,例如,HTTP头“Content-Type” 就是这样一个元数据属性。

通常使用XML,JSON或Atom来表征资源。通过设置“Accept”请求头,客户端就可以请求指定的表示编码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
REQUEST:
GET /hotel/656bcee2-28d2-404b-891b HTTP/1.1
Host: localhost
User-Agent: xLightweb/2.6
Accept: application/json
RESPONSE:
HTTP/1.1 200 OK
Server: xLightweb/2.6
Content-Length: 263
Content-Type: application/json; charset=utf-8
{"classification":"Comfort",
"name":"Central",
"RoomURI":["http://localhost/hotel/656bcee2-28d2-404b-891b/Room/1",
       "http://localhost/hotel/656bcee2-28d2-404b-891b/Room/2"]}

RESTful Web API服务端程序必须根据HTTP规范返回状态码。状态码的第一个数字标识返回类型,1xx表示临时响应,2xx表示成功响应 ,3xx代表转发,4xx表示客户端错误,5xx代表服务端错误。使用错误的响应码,或者总返回200响应,并在消息主体中包含特定应用程序的响应,这两种做法都是不好的实践。

ThinkPHP对RESTful的支持

TP原生是不支持RESTful的,他是通过重载Controller类,在__Call方法中实现对Restful的支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public function __call($method,$args) {
if( 0 === strcasecmp($method,ACTION_NAME.C('ACTION_SUFFIX'))) {
if(method_exists($this,$method.'_'.$this->_method.'_'.$this->_type)) { // RESTFul方法支持
$fun  =  $method.'_'.$this->_method.'_'.$this->_type;
$this->$fun();
}elseif($this->_method == $this->defaultMethod && method_exists($this,$method.'_'.$this->_type) ){
$fun  =  $method.'_'.$this->_type;
$this->$fun();
}elseif($this->_type == $this->defaultType && method_exists($this,$method.'_'.$this->_method) ){
$fun  =  $method.'_'.$this->_method;
$this->$fun();
}elseif(method_exists($this,'_empty')) {
// 如果定义了_empty操作 则调用
$this->_empty($method,$args);
}elseif(file_exists_case($this->view->parseTemplate())){
// 检查是否存在默认模版 如果有直接输出模版
$this->display();
}else{
E(L('_ERROR_ACTION_').':'.ACTION_NAME);
}
}
}

系统自动调用处理方式,就是根据当前请求类型和资源类型自动调用相关操作方法。系统的自动调用规则是:

定义规范说明
操作名_提交类型_资源后缀标准的Restful方法定义,例如 read_get_pdf
操作名_资源后缀当前提交类型和defaultMethod属性相同的时候,例如read_pdf
操作名_提交类型当前资源后缀和defaultType属性相同的时候,例如read_post

我们可以使用下面的模版来实现对资源的CRUD操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SensitiveController extends RestController{
    
    public function index(){
        $this->response("",'json',404);
    }
    public function create_post_json(){
    }
    public function read_get_json(){
    }
    public function update_put_json(){
    }
    public function delete_delete_json(){
    }
}

因为有操作名的存在,此时TP并不能完全按照RESTful的资源规则来发送请求,需要在URL中加入操作名,如对id=1的user的资源获取,其URL格式为

1
http://www.eureko.com/user/read/id/1.xml

而我们希望得到的URL是

1
http://www.eureko.com/user/1

这就需要用到REST路由定义,我们定义下面的路由

1
array('user/:id\d$',       'user/read',   '',array('method'=>'get'))

就能通过RESTful风格的URL正常获取到资源。

Atromic中具备鉴权功能的RESTful Web API实践

定义Controller

我们以用户登录、登出为例,首先定义符合RESTful规则的Controller,我们采用json数据表示资源。定义两个方法为login_post和logout_post,他们分别是登录和登出的处理方法,都只接收POST动作请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class PublicController extends RestController {
    protected $allowMethod    = array('get','post','put','delete');
    protected $defaultType      = 'json';
    public function login_post(){
        $username = I('username');
        $password = I('password',"");
        $verify = I('verify',null);
        $result = array('success'=>true);
        $rs = D('Admin')->login($username,$password);
        if($rs['status']>0) {
            $result['data'] = $rs['data'];
            $this->response($result, 'json');
        }
        else{
            $result['success'] = false;
            $result['msg'] = $rs['msg'];
            $this->response($result,'json');
        }
    }
    public function logout_post(){
        $result = array('success'=>true);
        if(is_login()){
            D('Admin')->logout();
            session('[destroy]');
            $this->response($result,'json');
        } else {
            $this->response('','json',401);
        }
    }
}

设置路由

我们要求URL的RESTful请求规则如下

1
2
POST /Admin/login  //登录
POST /Admin/logout //登出

在config.php文件中开启路由,并定义路由规则

1
2
3
4
5
6
7
8
9
10
<?php
return array(
    'URL_ROUTER_ON'   => true,
    //为rest相关操作设置路由,并设置默认路由返回404
    'URL_ROUTE_RULES'=>array(
        array('login$',             'public/login',  '',array('method'=>'post')),
        array('logout$',            'public/logout', '',array('method'=>'post')),
    )
);

配置Nginx

通过定义Rest风格的Controller和Rest路由,我们的URL变为了这样

1
http://www.eurekao.com/index.php/Admin/login

配置ThinkPHP的URL模式工作在REWRITE模式下,同时配置Nginx去掉URL地址里面的入口文件index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server {
        listen       80;
        server_name  localhost;
root    D:\workspace\PHP\Atromic;
    
location / {
            index  index.php index.html index.htm;
            if (-e $request_filename) {
    break;
}
    if (!-e $request_filename) {
   rewrite ^/(.*)$ /index.php/$1 last;
   break;
   }
        }
location ~ .+\.php($|/) {
            fastcgi_index  index.php;
            fastcgi_split_path_info ^(.+\.php)(.*)$;
            fastcgi_param   SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param   PATH_INFO               $fastcgi_path_info;
            fastcgi_param   PATH_TRANSLATED $document_root$fastcgi_path_info;
            fastcgi_pass    127.0.0.1:9010;
            include         fastcgi_params;
        }
    }

最终我们请求的URL成为符合RESTful规范的格式

1
http://www.eurekao.com/Admin/login

鉴权功能的实现

虽然我们请求的资源的URL是RESTful风格,但是在处理该请求的Controller里面,还是按照TP的action规范来编写处理函数的,如我们对 http://www.eurekao.com/User/1 的请求,最终转换到UserController中的read_get方法来处理,因此我们在对 http://www.eurekao.com/User/1 进行权限检查时,实际上就是对 http://www.eurekao.com/User/readGet进行权限检查权,因此可以按照TP提供的Auth权限认证类来进行权限检查处理。

Auth权限认证是按规则进行认证。在数据库中有规则表(think_auth_rule),用户组表(think_auth_group), 用户组用户关系表(think_auth_group_access)。在规则表中定义权限规则,在用户组表中定义每个用户组有哪些权限规则,在用户组用户关系表中定义用户所属的用户组。

添加认证规则
数据表:think_auth_rule

基于ThinkPHP开发RESTful Web API - 第2张  | Eureka

其中name是最重要的字段:模块/控制器/方法。顺序不能改变,大小写随意。Auth类中会统一转成小写。其次是type。一般情况下设定为1。(最后存入用户权限的就是type为1的规则)、

添加用户组(角色)
数据表:think_auth_group

基于ThinkPHP开发RESTful Web API - 第3张  | Eureka

其中rules字段最为重要,就是控制哪个组有哪些权限。

添加用户和组对应关系

基于ThinkPHP开发RESTful Web API - 第4张  | Eureka

此表实际上就是member和auth_group的中间表,
规定哪个用户(uid)属于哪个组(group_id)

然后继承RestController作为所以控制器的基类,在_initialize函数中进行权限检测,代码中高亮部分为权限检测的核心。如果用户未登录HTTP状态返回401,如果权限检测结果为不具备该权限HTTP状态返回403。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class BaseController extends RestController{
    protected $_CONFIG = array();
    protected $allowMethod    = array('get','post','put','delete');
    protected $defaultType      = 'json';
    protected function _initialize(){
        if(defined('UID')) return;
        define('UID',is_login());
        if(!UID){
            $this->response("",'json',401);
        }
        $this->_CONFIG = D('Setting')->fetchAll();
        // 是否是超级管理员
        define('IS_ROOT',   is_administrator());
        if(!IS_ROOT && C('ADMIN_ALLOW_IP')){
            // 检查IP地址访问
            if(!in_array(get_client_ip(),explode(',',C('ADMIN_ALLOW_IP')))){
                $this->response("",'json',403);
            }
        }
        // 检测系统权限
        if(!IS_ROOT){
            //检测访问权限
            $rule  = strtolower(MODULE_NAME.'/'.CONTROLLER_NAME.'/'.ACTION_NAME);
            if ( !$this->checkRule($rule,array('in','1,2')) ){
                $this->response("",'json',403);
            }else {
                // 检测分类及内容有关的各项动态权限
                $dynamic = $this->checkDynamic();
                if (false === $dynamic) {
                    $this->response("",'json',403);
                }
            }
        }
    }
    final protected function checkRule($rule, $type=AuthGroupModel::RULE_URL, $mode='url'){
        static $Auth    =   null;
        if (!$Auth) {
            $Auth       =   new \Think\Auth();
        }
        if(!$Auth->check($rule,UID,$type,$mode)){
            return false;
        }
        return true;
    }
    protected function checkDynamic(){}
}

 

参考文献

(1)artech 《我所理解的RESTful Web API [设计篇]》:http://www.cnblogs.com/artech/p/3506553.html

(2)Gregor Roth 《RESTful HTTP的实践》:http://www.infoq.com/cn/articles/designing-restful-http-apps-roth/

(3)ThinkPHP 《ThinkPHP3.2完全开发手册——RESTFul》:http://document.thinkphp.cn/manual_3_2.html#restful

(4)luofei614 《 比RBAC更好的权限认证方式(Auth类认证)》:http://www.thinkphp.cn/topic/4029.html

(5)haran 《跟haran一起学习Auth认证方法的使用》 :http://www.thinkphp.cn/topic/9679.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值