7点关于RESTful规范的API接口设计的想法

在项目中,须要为APP撰写API。刚开始接触的时候,并无考虑太多,就想提供URL,APP端经过该URL进行查询、建立、更新等操做便可。但再对相关规范进行了解后,才发现,API的设计并无那么简单,远远不是URL的问题,而是一个通讯协议的总体架构。所以,我写这篇文章,用来记录本身的一些心得,并不断完善。并提供关于RESTful API的一些参考文献。javascript

1. 使用SSL(https)来提供URL

首先,使用https能够在数据包被抓取时多一层加密。咱们如今的APP使用环境大部分都是在路由器WIFI环境下,一旦路由器被入侵,那么黑客能够很是容易的抓取到用户经过路由器传输的数据,若是使用http未经加密,那么黑客能够很轻松的获取用户的信息,甚至是帐户信息。php

其次,即便使用https,也要在API数据传输设计时,正确的采用加密。例如直接将token信息放在URL中的作法,即便你使用了https,黑客抓不到你具体传输的数据,可是能够抓到你请求的URL啊!(查了资料了,https用GET方式请求,也仅能抓到域名字符部分,不能抓到请求的数据,可是URL能够在浏览器或特殊客户端工具中直接看到。多谢下面的朋友指正错误)所以,使用https进行请求时,要采用POST、PUT或者HEAD的方式传输必要的数据。java

2. 使用GET、POST、PUT、DELETE这几种请求模式

请求模式也能够说是动做、数据传输方式,一般咱们在web中的form有GET、POST两种,而在HTTP中,存在下发这几种。web

GET (选择):从服务器上获取一个具体的资源或者一个资源列表。
POST (建立): 在服务器上建立一个新的资源。
PUT(更新):以总体的方式更新服务器上的一个资源。
PATCH (更新):只更新服务器上一个资源的一个属性。
DELETE(删除):删除服务器上的一个资源。
HEAD : 获取一个资源的元数据,如数据的哈希值或最后的更新时间。
OPTIONS:获取客户端能对资源作什么操做的信息。ajax

3. 在URI中体现资源,而非动做

阅读RESTful架构的参考文献以后,你会了解什么是资源的概念,以及REST的确切含义。再构建API的URL的时候,URI中应该仅包含资源(对象),而不要加入动做。好比 /user/1/update ,其中update就是一个动做,虽然咱们但愿经过这个URI来实现用户ID为1的用户进行信息更新,可是按照RESTful的规范,做为动做,应该用上面的PUT来表示,因此请求更新用户信息,应该使用 PUT /user/1 来表示更新用户ID为1的用户信息。json

若是去对应上面的请求模式,GET表示显示、列出、展现,POST表示提交、建立,PUT表示更新,DELETE表示删除。api

<?php
    $ch = curl_init();
    $url = 'http://api.xxx.com/user';
    $data = "name=姓名&email=xxx@xxx.com";
    // 添加参数
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    // 执行HTTP请求
    curl_setopt($ch , CURLOPT_URL , $url);
    $res = curl_exec($ch);

    var_dump(json_decode($res));
?>

上面这段代码中$url仅仅是提供到了user,而并无提供add,服务端经过识别POST请求来肯定,这是一个建立用户的操做。可是还有一些数据并无用以处理数据,而是用以验证的,好比下文的鉴权,能够将这些信息经过header进行传输,下方详细展现。浏览器

4. 版本

API的开发直接关系了APP是否能够正常使用,若是本来运行正常的API,忽然改动,那么以前使用这个API的APP可能没法正常运行。APP是不可能强迫用户主动升级的,所以,经过API版原本解决这个问题。也就是说,API的多个版本是同时运行的,并且都要保证能够正常使用。服务器

按照RESTful的规范,不一样的版本也应该用相同的API URL,经过header信息来判断版本,再调用不一样版本的程序进行处理。可是这明显会给开发带来巨大的成本。解决办法有两种:1.新版本兼容旧版本,全部旧版本的动做、字段、操做,都在新版本中能够被实现,但明显这样的维护成本很大;2.不一样的版本,用不一样的URL来提供服务,好比再URL中经过v一、v2来区分版本号,我则更喜欢采用子域名的方式,好比v2.api.xxx.com/user的方式。架构

5. HTTP响应码

在用户发出请求,服务端对请求进行响应时,给予正确的HTTP响应状态码,有利于让客户端正确区分遇到的状况。

200 OK - [GET]:服务器成功返回用户请求的数据,该操做是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操做,该操做是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户获得受权(与401错误相对),可是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操做,该操做是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(好比用户请求JSON格式,可是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再获得的。
422 Unprocesable entity - [POST/PUT/PATCH] 当建立一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将没法判断发出的请求是否成功。

6. 返回值结构

在完成了上面的URL部署以后,接下来咱们来看看返回结果应该怎么样来肯定。我看到大部分文献中指出,最好使用JSON进行返回,而非xml。我认为缘由可能有两点:1. JSON能够很好的被不少程序支持,javascript的ajax能够直接将JSON转换为对象。2. JSON的格式在容量上比xml小不少,能够减低宽带占用,提升传输效率。

那么,返回值应该怎么去部署呢?

首先,字段的合理返回,数据的包裹。由于返回值中,咱们经常要对数据进行区分分组,或者按照从属关系打包,因此,咱们再返回时,最好有包裹的思想,把数据存放在不一样的包裹中进行返回。

{
  'error_code' : 0,
  'data' : {
                   'user_id' : 1,
                   'username' : 'xiaomin'
              },
  'server_time': 14939939
}

上面返回的JSON中,使用data来做为数据包,将全部数据统一以这个字段进行包裹。除了data,也能够用list等其余形式的包裹,命名都是本身来根据本身的须要肯定的。

{
  'error_code' : 0,
  'list' : [
              {'user_id':1,'username':'xiaoming'},
              {'user_id':2,'username':'goudan'}
            ]
  'server_time': 14939939
}

总之,不要不分包,直接把全部数据和一些你想返回的全局数据混在一块儿进行返回。

其次,错误码。错误码的做用是方便查找错误缘由,一般状况下,我喜欢用error_code来表示,当error_code=0时,表示没有发生错误,当error_code>0时,发生了错误,而且提供较为详细的文档,告诉客户端对应的error_code值所产生的错误的缘由和位置。

最后,空白压缩和字符转换。也就是返回的JSON结果不要换行和空格,用一行返回结果,使整个结果文本容量最小。同时,中文等字符或结果中有引号,都进行字符转换,防止结果没法被正确识别。

7. 鉴权

其实也就是客户端的权限控制。通常而言,我会采用给客户端分发一个token来肯定该客户端的惟一身份。客户端在请求时,经过这个token,判断发出请求的客户端所对应的用户,及其相关信息和权限。

前文已经提到了,token信息不是用来进行处理的数据,虽然能够经过POST、PUT等进行数据提交或传输,可是从RESTful规范来说,它不属于操做数据,在服务端进行处理时,仅是利用token进行鉴权处理,因此,个人建议是经过header来发送token。

<?php
    $ch = curl_init();
    $url = 'http://api.xx.com/user';
    $header = array(
        'token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
        'X-HTTP-Method-Override: PUT'        
    );
    $data = array(
         'user_name' => 'xiaoming',
         'user_email' => 'xx@sfa.com'
    );
    // 添加apikey到header
    curl_setopt($ch, CURLOPT_HTTPHEADER  , $header);
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    // 执行HTTP请求
    curl_setopt($ch , CURLOPT_URL , $url);
    $res = curl_exec($ch);

    var_dump(json_decode($res));
?>

上面的代码中,经过将CURLOPT_CUSTOMREQUEST设置为PUT,就能够发出PUT请求,发出的PUT请求,仍然须要经过CURLOPT_POSTFIELDS来传输数据。服务端接受PUT请求时,首先要对发出请求的客户端进行token验证,经过对token的处理,查找到拥有该token的实际用户,从而肯定了将对哪个用户进行信息更新操做。

国内大部分API对PUT、DELETE请求进行了阉割,更不用提HEAD、PACTH、OPTIONS请求。实际上,国内大部分开放API仅支持GET和POST两种,部分API支持将app key信息经过header进行发送。在面对这种状况下,咱们不得不抛弃标准的RESTful规范,在url中加入get、add、update、delete等动做词汇,以补充因为请求支持不完善带来的动做区分问题。若是仅支持GET和POST,那么全部须要保密的数据,绝对不能够用GET来进行请求,而必须用POST。

参考文献

《理解RESTful架构》《RESTful API 设计指南》
《好RESTful API的设计原则》
《我所理解的RESTful Web API [设计篇]》
《https工做原理》

个人我的博客 www.tangshuang.net 偶尔写一些学习中的感想和经验,但愿有相同兴趣的朋友到博客来交流。

相关文章
相关标签/搜索