【Spring Boot】构建RESTful API——(二)

一、RESTful API设计规范

参考知乎上的《RESTful API最佳实践》一文,总结的RESTful API设计规范如下:

1.URI
  • 应该将API部署在专用域名之下:https://api.example.com

  • 不用大写

  • 用中杠-不用下杠_;

  • 参数列表要encode;

  • URI中不应该出现动词,动词应该使用HTTP方法表示,但是如果无法表示,也可使用动词,例如:search没有对应的HTTP方法,可以在路径中使用search,更加直观;

  • URI中的名词表示资源集合,使用复数形式;

  • 虽然/在URI中表达层级,但是避免为了追求REST导致层级过深,适当使用参数表示。

    GET /comments/tid?tid=1&page=1
    
2.Request:通过标准http方法对资源CRUD
  • GET:查询资源

    GET /comments //获取所有评论
    GET /comments/tid/1 //获取文章tid为1的所有评论
    
  • POST:创建资源

    POST /comments/tid/1 //为tid为1的文章创建评论
    
  • PUT:更新资源

    PUT /comments/cid/like/1 //为cid为1的评论点赞
    
  • DELETE:删除资源

    DELETE /comments/cid/1 //删除cid为1的评论
    
3.Response
  • 采用JSON,不要使用XML

  • 默认情况下JSON外层不需要嵌套大括号,API需要支持JSONP跨域访问或者客户端无法访问HTTP header才需要加上嵌套大括号

  • 默认情况下不要过滤API输出中的空格,并且要支持gzip

4.API版本控制
  • 在URI中存放:GET /v1/comments;
  • 客户端在Accept Header中存放:Accept: application/vnd.github.v3+json,服务器自定义Header返回当前版本信息:X-GitHub-Media-Type: github.v3; format=json(GitHub在用);
  • 以上两种方法根据情况选择,Github用的方式是REST中所要求的方式;
  • 测试API和正式API要进行区分,方式通过如上两种方式实现。
5.速度限制

为了避免请求泛滥,给API设置速度限制很重要。为此 RFC 6585 引入了HTTP状态码429(too many requests)。加入速度设置之后,应该提示用户,至于如何提示标准上没有说明,不过流行的方法是使用HTTP的返回头。
下面是几个必须的返回头(依照twitter的命名规则):

  • X-Rate-Limit-Limit :当前时间段允许的并发请求数
  • X-Rate-Limit-Remaining:当前时间段保留的请求数。
  • X-Rate-Limit-Reset:当前时间段剩余秒数

为什么使用当前时间段剩余秒数而不是时间戳?

时间戳保存的信息很多,但是也包含了很多不必要的信息,用户只需要知道还剩几秒就可以再发请求了这样也避免了clock skew问题。

6.缓存

HTTP提供了自带的缓存框架。你需要做的是在返回的时候加入一些返回头信息,在接受输入的时候加入输入验证。基本两种方法:

  • ETag:当生成请求的时候,在HTTP头里面加入ETag,其中包含请求的校验和和哈希值,这个值和在输入变化的时候也应该变化。如果输入的HTTP请求包含IF-NONE-MATCH头以及一个ETag值,那么API应该返回304 not modified状态码,而不是常规的输出结果。
  • Last-Modified:和etag一样,只是多了一个时间戳。返回头里的Last-Modified:包含了 RFC 1123 时间戳,它和IF-MODIFIED-SINCE一致。HTTP规范里面有三种date格式,服务器应该都能处理。
7.覆盖HTTP方法

一些HTTP客户端只支持GET和POST请求。为了能够加强这些客户端的访问能力,API需要能够覆盖HTTP方法。尽管这里没有任何强制的标准,但流行的做法是API会接收一个请求头X-HTTP-Method-Override,它的值可以是PUT、PATCH或者DELETE三者之一。

注意,用来覆盖HTTP方法的header只能在POST请求中被接受。GET请求永远不能修改服务器上的数据

8.过滤信息

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数:

?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件
9.错误处理

就像HTML的出错页面向访问者展示了有用的错误消息一样,API也应该用之前熟悉易读的格式来提供有用的错误消息。错误的表现形式应该跟其他资源保持一致,只是用一些自己的字段。

API应该一直返回合理的HTTP状态码。API错误一般情况下分成两类:代表客户端错误的400系列状态码和代表服务端错误的500系列状态码。API至少把所有400系列错误统一用易读的JSON格式来展示。如果可能(比如,如果负载均衡和反向代理能够创建自定义错误内容的话),500系列的状态码也这么弄。

JSON错误内容应该为开发者提供一些东西 - 有用的错误消息,唯一的错误码(通过它可以在文档中找到更多错误细节),可能的话提供错误细节描述。用JSON格式来输出错误看起来这样:

{
  "code" : 1234,
  "message" : "Something bad happened :(",
  "description" : "More details about the error here"
}

对于PUT、PATCH和POST的请求进行的校验错误需要嵌套多个字段。最佳做法是用固定的错误码来表示校验失败,然后在额外的errors字段中提供错误的细节,像这样:

{
  "code" : 1024,
  "message" : "Validation Failed",
  "errors" : [
    {
      "code" : 5432,
      "field" : "first_name",
      "message" : "First name cannot have fancy characters"
    },
    {
       "code" : 5622,
       "field" : "password",
       "message" : "Password cannot be blank"
    }
  ]
}
10.HTTP状态码

HTTP定义了很多有意义的状态码,你可以在你的API中使用。这些状态码可以帮助API消费者用来路由它们获取到的响应内容。整理了一个你肯定会用到的状态码列表:

  • 200 OK - 对成功的GET、PUT、PATCH或DELETE操作进行响应。也可以被用在不创建新资源的POST操作上
  • 201 Created - 对创建新资源的POST操作进行响应。应该带着指向新资源地址的Location header)
  • 204 No Content - 对不会返回响应体的成功请求进行响应(比如DELETE请求)
  • 304 Not Modified - HTTP缓存header生效的时候用
  • 400 Bad Request - 请求异常,比如请求中的body无法解析
  • 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 - 由于请求频次达到上限而被拒绝访问
11.认证

RESTful API应该是无状态。这意味着对请求的认证不应该基于cookie或者session。相反,每个请求应该带有一些认证凭证。

如果一直使用SSL,认证凭证可以简单的使用随机生成的access token,把其做为HTTP Basic Auth中user name字段的值传给API。这么做的好处是可以通过浏览器访问 - 如果浏览器从服务器收到401 Unauthorized状态码,它将会弹出一个对话框让人输出认证凭证。

当然,这种基于token来进行基本认证的方法只能当用户从API管理后台拷贝了一个token到自己的代码中才行。如果搞不到token,只能使用OAuth 2来把安全token传递给第三方。OAuth 2使用Bearer token,并且也是基于SSL来保证传输安全。

支持JSONP的API可能需要第三种方法来实现认证,因为JSONP的请求没法发送HTTP Basic Auth凭证或者Bearer token。这种情况下,可以使用一个额外的查询参数access_token。注意:使用查询参数来传递token存在一个固有的安全隐患,因为大多数web服务器会在服务器日志中保存查询参数。

不管怎么样,以上三种方法是用来在API之间传输token的方法。实际传输的token可以是一样的。

12.使用SSL

一定要使用SSL。没有例外。如今,你的web API可以从任何有互联网的地方(像图书馆,咖啡馆,机场等等)被访问到。这些地方并不都是安全的。很多地方根本没有对网络连接进行加密,如果认证凭证被劫持的话,这样访问者很容易被窃听或者被冒充。

一直使用SSL的另一个优势是,加密的连接简化了用户认证的工作 - 你可以使用简单的access token,而不需要对每个API请求进行签名。

需要注意的一件事是以非SSL的形式访问API的URL。不要把请求跳转到它们的SSL版本上。直接抛出一个严重错误!

13.Hypermedia API

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向http://api.example.com的根目录发出请求,会得到这样一个文档。

{"link": {
  "rel":   "collection https://www.example.com/comments",
  "href":  "https://api.example.com/comments",
  "title": "List of comments",
  "type":  "application/vnd.yourformat+json"
}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。
Hypermedia API的设计被称为HATEOAS

在进行分页查询时可以返回下一页的URI,如果没有说明服务器已经取到最后一条数据了,客户端可以减少不必要的请求以及URI的构造,建议在分页的情况下使用。

二、构建一个RESTful API

首先,在Spring Boot中我们会使用到这些注解。

  • @Controller: 修饰class,用来创建处理http请求的对象
  • @RestController:Spring4之后加入的注解,原本在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController就不需要再配置,默认返回json格式,所以一般直接使用这个
  • @RequestMapping:配置url映射

接下来,我们设计RESTful API接口如下:

请求类型 URL 功能说明
GET /girls 查询所有女生
POST /girls 创建一个女生
GET /gilrs/id 根据id查询一个女生信息
PUT /gilrs/id 根据id更新一个女生信息
DELETE /gilrs/id 根据id删除一个女生信息

新建Girl实体类:

public class Girl {
    
    private Integer id;
    
    private String name;
    
    private String cupSize;
    
    //省略setter和getter..
}

创建GirlController类

@RestController
public class GirlController {

    //创建线程安全的Map
    static Map<Integer,Girl> girls = Collections.synchronizedMap(
            new HashMap<Integer, Girl>()
    );

    @GetMapping(value = "/girls")
    public List<Girl> getGirls(){
        List<Girl> res = new ArrayList<Girl>(girls.values());
        return res;
    }

    @PostMapping(value="/girls")
    public String postUser(@ModelAttribute Girl girl) {
        girls.put(girl.getId(), girl);
        return "success";
    }

    @GetMapping(value="/girls/{id}")
    public Girl getUser(@PathVariable Integer id) {
        return girls.get(id);
    }

    @PutMapping(value="/girls/{id}")
    public String putUser(@PathVariable Integer id, @ModelAttribute Girl girl) {
        Girl u = girls.get(id);
        u.setName(girl.getName());
        u.setCupSize(girl.getCupSize());
        girls.put(id, u);
        return "success";
    }

    @DeleteMapping(value="/girls/{id}")
    public String deleteUser(@PathVariable Long id) {
        girls.remove(id);
        return "success";
    }
}

运行程序,就能够简单的实现了这个RESTful API的功能。可以在POSTMAN程序中进行简单的测试,但是我们会发现一个严重的问题,我们写的这个程序没有数据,所以接下来我们要来了解如何在Spring Boot中使用数据库。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342