REST 风格的优秀设计应该像下面这些

- GET /users 获取所有用户
- GET /users/1234 获取ID为1234的用户
- POST /users 创建一个新用户
- PUT /users/1234 更新ID为1234的用户
- PATCH /users/1234 更新ID为1234的用户的部分内容
- DELETE /users/1234 删除ID为1234的用户

如果要设计一个资源拥有另外一个资源的情况的API,例如,设计一个包含用户(users)和用户的评论(comments)的 API 可以采用这样的形式:

- GET /users/1234/comments 获取用户ID为1234的所有评论
- GET /users/1234/comments/1 获取用户ID为1234的评论ID为1的单个评论
- DELETE /users/1234/messages/1 删除用户评论ID为1,属于用户1234的单个评论
 
实际工程实践中往往会遇到并不是对一个资源简单的 CRUD 的场景,设计此类 API 有这些手法可供参考
  • 将这些操作变成一个资源的属性,比如 disable 一个 user,可以在 user 里面加一个 disabled 的属性,可以设计一个 API 使用 PATCH /users/1234 将 disabled 设置成 true 即可。
  • 将这个操作看成某个资源的附属资源(就像上面例子中的 comments 一样)来设计,比如GitHub的Star a gist API ,就是这样的,它把star操作放在这个资源的后面,看上去好像是一个附属资源:- PUT /gists/:id/star   - DELETE /gists/:id/star
  • 在不得不使用其它例外形式设计 API 时,尽量用文档写清楚输入输出和返回值等其他必要信息,避免让习惯了使用资源名的调用者感到困惑
考虑到系统迭代和兼容性,需要在 API 中引入版本规则
  一种是将版本号放在 http header 内,另一种是直接放在 URL 中。而放在 URL 中是最常见的做法
    GET https://api.twitter.com/1.1/friends
    GET "https://graph.facebook.com/v2.8/me
如果一个 API 的版本过期了,任何把该请求重定向到最新版本上。比如 user API v1 版本过期了,当有调用/api/v1.0/users/1234的时候,应该被重定向(http 30x)到最新的 /api/v2.0/users/1234 上。
优雅的设计条件过滤,排序,搜索等传入参数形式
  RESTful API 经常有对返回数据过滤和排序的要求,这些输入参数推荐采用 HTTP Query Parameter 的方式实现。
  • 比如你要设计一个API,返回所有已经登录的用户,可以这样做:GET /users?login=true
  • 获取所有的用户,返回结果按照create_at降序排序可以这样设计:GET /users?sort=-create_at
  • 组合使用过滤条件和排序,GET /users?sort=-create_at,login_at&login=true 表示返回所有已登录用户,结果按照create_at降序, login_at升序
  • 单独为 API 设计一个 Query Parameter 专门用于搜索,从 API 中传递过来的 Query Parameter 可以直接设置成这些搜索框架的输入条件GET /users?q=key&&sort=-create_at,login_at&diabled=false
  • 映射到一个新的API(相当于快捷方式)比如设计一个用于返回最近登录用户的API:GET /users/recently_login这种设计可以简化客户端的调用,否则调用者每次都要根据时间合成 Query Parameter,增加了客户端使用复杂度
  • 查询数据的部分内容GET /user?fields=id,user_name,address&diabled=false&sort=-login_at   GET /facebook/v2.8/me?fields=id,name,birthday,cover,devices,email&access_token=xxX

API中都使用了下划线(user_name)的形式来命名这些参数,使用划线(user_name)还是使用驼峰(userName)的形式?下划线分割的形式比使用驼峰的形式更容易阅读(容易20%)

合理设计返回数据的形式,格式和考虑启用压缩(gzip)

  假如有个系统提供一个 API 用于上传一张图,这张图上传之后你可以调用另外一个 API 修改这个图片的描述。如果调用上传 API 后,返回数据中没有返回这张图的唯一性 ID,你就无法接着调用其它 API 引用到这个图的资源,从而无法进行修改描述的操作,除非之前额外再次调用查询操作拉取到这张图唯一性 ID。

  通常,POST 操作成功以后,我们一般也把新创建的资源的 URL 放在 HTTP header 的 location 字段中,方便客户的拉取。例如上上树图片上传的 API 返回的 header 中可以包含location: http://api.domain.name/photos/1234

  RESTful API 一般都是返回文本数据,启用 gzip 通常可以节省60%-80%以上的带宽(这个数据很好证明,随便使用几个个 json 文件 gzip下就可以看出来,我测试几个 json 文件一般300K左右都能被压缩成50K左右),尤其是在返回的数据比较大情况下,压缩比更高。不过启用gzip 不可避免会增加 CPU 的负担,实际工程项目中需要权衡考量。

  至于到底用什么用的格式来返回数据?XML?JSON?纯文本?但从统计数据来看 JSON 格式目前是使用做多的 REST API 的输入输出格式。

根据不同的 API 操作,设置合适的 HTTP 状态码和必要的出错信息

  • 200 OK 用于返回 GET, PUT, PATCH 或 DELETE 的操作。有使用也用来返回没有创建数据的 POST 操作;
  • ** 201 Created** 用来返回 POST 操作并且成功创建了数据的情况。新创建的数据资源的链接应该放在location中返回;
  • 204 No Content 用来返回一次成功的请求,但是该请求返回的 body 为空的情况,如 DELETE 请求;
  • 304 Not Modified 表示缓存没有失效,和上次的请求相比,没有新的内容;
  • 400 Bad Request 用于返回 API 参数不正确的情况,比如传入的 JSON 格式错误无法解析等;
  • 401 Unauthorized 用于表示请求等 API 缺少身份验证信息;
  • 403 Forbidden 用于表示该资源不允许特定用户访问;
  • 404 Not Found 请求一个不存在的资源;
  • 429 Too Many Requests 请求过于频繁,可以用在客户端调用过于频繁的情况。

使用 token 机制设计鉴权和验证系统(Authorization and Authentication)

  常见的场景就是用户系统-结合 OAuth2,参考腾讯云微视频MVS API,这里给出一个实用的解决方案:

  • 用户使用户名密码或者第三方登录,最终请求一个我们设计的登录 API(这个 API 接受用户名密码,或第三方登录验证结果);
  • 服务端认证成功以后,生成一个 token,并将这个 token 和用户信息关联在一起,同时返回这个 token 给调用客户端;
  • 客户端记录并保存下这个 token;
  • 下次客户端发起和用户相关请求 API 都要在 http header 中带上这个 token;
  • 服务端通过这个 token 去区分用户是谁,判断这个用户是否已经登录和有什么样的权限;
  • 服务端也要考虑 token 的失效时间;
  • 客户端在发现 token 失效的时候重新请求新的 token

为什么要多一个步骤使用 token 呢?为什么不直接把用户名和密码放在 http header 中直接做授权和验证?原因是调用 API 一般会被频繁调用,这样用户名和密码频繁在网络上传输,增加了泄漏的危险。如果使用token,即使泄漏了也不会暴露用户的密码,何况 token 也被经常被设计成有时间限制的,超时以后当前 token 就会失效,需要客户端重新做验证获得新的 token,暴露之后的影响很快就会过去。

其实获取 token,用 token 做授权和验证和 OAuth 2 如出一辙,手法完全相同的,只是 OAuth 2 有更复杂的标准步骤去换取这个token,并且这个 token 的用途不同。OAuth 2 的 token 用来授权给第三方使用,我们自己设计的系统 token 仅限在自己系统本身 API 使用。

如何实现数据的分页返回

 
一种是使用类似 Facebook Graph API 的方法,它把分页信息和数据一起返回,调用者只需要再次请求 next 中 URL 就可以获取下一页的数据,这种方式优点是灵活和直观,可以随意添加和分页相关的其他属性,例如总记录数,总页数等等。其中cursors用来解决“流”的问题(由于数据是动态增加的,基于旧数据的页数和页码会失效,于是引入 cursors 来标记数据位置,关于这个问题,Twitter 在介绍其timeline API时有

另一种符合WEB标准的做法是使用 link header,简单来说就是在 http header 使用 link字段,提供一个和超链接一样目的 URL 地址,来实现不同资源之间的转跳。如GitHub的Api文档是这样规定分页信息的,这种做法缺点是不太直观,优点是不会干扰数据,返回内容都是数据本身,无需在数据上嵌入额外的属性来说明分页信息,简单干净

如何处理有关联资源的返回数据

  对客户端来说,最直观和容易处理的返回形式如下:

  返回数据中 avatar 和 name 是每条数据都是重复的,所以你也可以这样设计返回数据:先返回该用户的所有评论 /comments?user=1234

  再通过请求该用户 API 的相关内容 /users/1234:{user_id: "1234", avatar: "a.jpg", nickName:"Jeffrey"...}这种情况下其实可以将依赖资源嵌入返回对象中,避免了客户端需要再一次发起请求来获取这个 user 的详细信息/comments?user=1234 直接返回类似这样的信息即可:

  

考虑启用 HTTP 缓存机制

HTTP协议本身支持两种缓存机制: ETagLast-Modified

  1. ETag:HTTP 请求中在 header 中包含一个内容的 hash,如果返回结果没有变化,该请求会直接返回304 Not Modified,而不是所有数据内容本身
  2. Last-Modified: 和 Etag 工作原理差不多,只是使用时间戳作为内容是否过期的标志。

限制 API 调用频次(Rate limiting)

如果一个客户端请求 API 的频率太快,根据HTTP协议,可以返回429 Too Many Requests

如果要为客户端提供更加详细的调用频次和访问次数之类的信息,除了提供文档说明以外,还可以在 http header 用自定义字段的形式提供,比如 Twitter API 是这样做的:
X-Rate-Limit-Limit: 该请求的调用上限
X-Rate-Limit-Remaining: 15分钟内还可以调用多少次
X-Rate-Limit-Reset: 还有多少秒之后访问限制会被重置
 
尽可能的使用 HTTPS,涉及用户验证的 API 一定要强制启用 HTTPS

HTTPS 现在已经是各种网络服务的标配(比如 Xcode 默认不允许请求不安全的 HTTP 信息)

如果你的WEB Server 是 Nginx,在部署了 HTTPS 的情况下,下面两个选项务必仔细设置,因为这个两个简单的设置可以很大程度上避免一些安全问题:
  

  • ssl_prefer_server_ciphers: 表示服务端加密算法优先于客户端加密算法,主要是防止降级攻击 (downgrade attack)
  • Strict-Transport-Security(HSTS):告诉浏览器这个域名在指定的时间(max-age)内应该强制使用 HTTPS 访问。

RESTful Service API 常见问题解决方案的更多相关文章

  1. HTTP Methods 和 RESTful Service API 设计

    含义: HTTP Methods:也叫 HTTP Verbs,HTTP Methods 可以翻译成 HTTP 方法.它们是 HTTP 协议的一部分,主要规定了 HTTP 如何请求和操作服务器上的资源, ...

  2. gRPC helloworld service, RESTful JSON API gateway and swagger UI

    概述 本篇博文完整讲述了如果通过 protocol buffers 定义并启动一个 gRPC 服务,然后在 gRPC 服务上提供一个 RESTful JSON API 的反向代理 gateway,最后 ...

  3. 在IIS8.5的环境下配置WCF的Restful Service

    今天在客户的环境中(Windows Server 2012 R2 + IIS 8.5)搭建Call WCF Restful Service的功能,发现了几个环境配置的问题,记录如下: 1):此环境先安 ...

  4. JAVA格物致知基础篇:用JAX-RS和Jersey打造RESTful Service

    随着服务器的处理能力越来越强,业务需求量的不断累积,越来越多的公司开始从单一服务器,单一业务承载变成了多服务器,多业务承载的快速扩展的过程中.传统的方法很难满足和应付这种业务量的增长和部署方式的改变. ...

  5. 构建基于WCF Restful Service的服务

    前言 传统的Asmx服务,由于遵循SOAP协议,所以返回内容以xml方式组织.并且客户端需要添加服务端引用才能使用(虽然看到网络上已经提供了这方面的Dynamic Proxy,但是没有这种方式简便), ...

  6. 基于.Net FrameWork的 RestFul Service

    关于本文 这篇文章的目的就是向大家阐述如何在.net framework 4.0中创建RestFul Service并且使用它. 什么是web Services,什么是WCF 首先讲到的是web Se ...

  7. spring boot之使用springfox swagger展示restful的api doc

    摘要 springfox swagger展示restful的api doc, swagger is A POWERFUL INTERFACE TO YOUR API. 新增文件: import org ...

  8. WCF Restful Service的服务

    构建基于WCF Restful Service的服务 前言 传统的Asmx服务,由于遵循SOAP协议,所以返回内容以xml方式组织.并且客户端需要添加服务端引用才能使用(虽然看到网络上已经提供了这方面 ...

  9. WCF Restful Service Get / Post请求

    Rest 它是用于创建分布式超文本媒体的一种架构方式,我们可以通过标准的HTTP(GET,POST,PUT,DELETE)操作来构建基于面向资源的软件架构方式(Resource-Oriented Ar ...

随机推荐

  1. sscanf(),sscanf_s()的相关用法

    #include<stdio.h> 定义函数 int sscanf (const char *str,const char * format,........); 函数说明  sscanf ...

  2. acl使用示例

    declare   v_count  number;  uprinciple varchar2(20);  principle  varchar2(20);  begin uprinciple := ...

  3. mybatis*中DefaultVFS的logger乱码问题

    从网上下的Java Persistence with MyBatis 3的源码 出现这个问题的原因是logback记日志的时候乱码 ResolverUtil - Not a JAR: file:... ...

  4. maven生命周期绑定要点

    生命周期不执行任何操作,都是抱插件大腿 maven-core-3.3.9-sources.jar下META-INF/plexus/components.xml的定义了三个生命周期的插件绑定 参考:ht ...

  5. 2017-4-28/PHP实现Redis

    谈一谈Redis的数据结构,如果换做PHP,怎么实现?如果再考虑用上LFU或LRU,又该如何实现?   Redis的数据结构有String.List.Set.Sorted Set.Hash等,而PHP ...

  6. Linux五种IO模型(同步 阻塞概念)

    Linux五种IO模型 同步和异步 这两个概念与消息的通知机制有关. 同步 所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回.比如,调用readfrom系统调用时,必须等待IO操 ...

  7. nodejs sequelize 对应数据库操作符的定义

    const Op = Sequelize.Op [Op.and]: {a: } // 且 (a = 5) [Op.or]: [{a: }, {a: }] // (a = 5 或 a = 6) [Op. ...

  8. 如何将两个字段合成一个字段显示(oracle和sqlserver的区别)

    oracle中,如何将两个字段数据合并成一个字段显示,接下来看一下在sql server和pl/sql的区别 sql server中如何合并(用Cast()函数) --1.创建模拟的数据表--- cr ...

  9. Ubuntu 下matlab 查看memory函数

    %Copyright (c) 2012, Michael Hirsch%All rights reserved.%%Redistribution and use in source and binar ...

  10. laravel中的注册页面

    <?php namespace App\Http\Controllers; use App\User; use Illuminate\Http\Request; class RegisterCo ...