谈谈RESTfulAPI标准理解

June 22, 2019

4509 20 minutes and 29 seconds
谈谈RESTfulAPI标准理解

工作中总结的一些关于Restful API的经验

统一资源地址的格式

  • URL 标准格式:
    • http://[host:port]/api/[版本]/[业务组件]/...
  • 规范说明:
    • 资源地址必须合理加以利用,不得申明方法性地址( url 中不得使用动词)
    • 资源地址名称统一使用小写进行命名(大小写敏感)
    • 资源地址最后不能以 / 进行结尾,否则会造成意图不明
    • 资源地址的命名中使用的名词必须区分单复数形式(名词单数和复数形式表示不同含义)
    • 资源地址的层级不应过长,建议在版本之后的内容维护在五个之内
  • 举例说明
    • 错误示范: http://[host:port]/api/v1.0/get_animal?id=object_id
    • 正确示范: http://[host:port]/api/v1.0/animal/<object_id> Method:GET

    上述错误示范是我们最经常使用的编写接口的方式,通常情况下使用了 get_animal 作为接口名称在直觉上是可以之间看出具体的使用意图,但是在后续的使用中是不可避免的造成 url 资源地址的浪费,我们无法预估后续会添加对 animal 的动作,之后可能会继续对该资源进行 add、delete、update 等一系列的动作,而每一个动作我们都必须申明一个新的资源地址,这种对后期维护代码和接口而言是灾难性的。 作为正确的 RESTful 的接口风格我们应该是将动作意图作为请求方式,这样我们可以十分有效的缩减项目中的资源地址,并且对于后端代码编写者来说,对于相同资源属性的操作方法可以集中在一个类之中,有利于对其的维护和修改;同样前台工作者来说,操作相同资源类型的方法也可以做适当的封装,有利于解构的进行。

    • 错误示范: http://[host:port]/api/v1.0/animal/get_list
    • 正确示范: http://[host:port]/api/v1.0/animal
    • 正确示范: http://[host:port]/api/v1.0/animals

    上述错误接口中使用了在单数名词下进行列表的查询,这种查询在老式接口编写中是没有什么问题的,甚至有的接口在编写中会将希望获取的结果集是单个字典还是多个元素整合的列表,作为请求参数传递给服务端。其实本身有效的避免了多业务逻辑造成的接口过多,但是作为后续业务变更和发展新业务时,会对前台对接者造成编码上的麻烦。 在RESTful的接口编写实践中,最通常的使用标准是将单数和复数区分开来对待,通过单复数的定义,直接定义了最终返回体中的信息格式。在单数名词的接口访问中,对应着以单个因子为返回体的内容;在复数名词的接口访问中,对应着以多个因子组成的复数列表为返回体的内容。 例如:

    • 访问 http://[host:port]/api/v1.0/animal 时,返回的是个体元素,看起来就是单个字典 {"name": "elephant", "number": 8}
    • 访问 http://[host:port]/api/v1.0/animals 时,返回的是复数元素,看起来就是列表内容 [{"name": "elephant", "number": 8}, {"name": "monkey", "number": 40}]

合理的利用请求头中的属性

  • HTTP 头字段根据实际用途被分为以下 4 种类型:
    • 通用头字段(英语:General Header Fields)
    • 请求头字段(英语:Request Header Fields)
    • 响应头字段(英语:Response Header Fields)
    • 实体头字段(英语:Entity Header Fields)

参考维基百科上面对于请求头的四种分类信息

  • 规范说明:
    • 默认情况下请求头中最多不得超过100个头字段
    • 每个请求头字段中大小不应该超过8190字节
    • 每个接口必须携带参数信息应转化在请求头字段中,不得继续作为参数浪费资源
  • 举例说明
    • 错误示范: http://[host:port]/api/v1.0/animal?api-key=QWxhZGRpbjpvcGVuIHNlc2FtZQ==
    • 正确示范: HEAD: {"api-key": "QWxhZGRpbjpvcGVuIHNlc2FtZQ=="} http://[host:port]/api/v1.0/animal

    上述中的错误例子就是就是在传统接口编写中,如果面向第三方进行对接时,需要进行的API对接时,将访问授权参数作为请求体中的参数进行传递,虽然这样做通常不会造成什么问题,但是试想如果面对着成百上千的接口,每个接口都必须携带这样的请求参数,势必会对整个请求体构成不必要的资源占用。如果这个时候我们除了这个参数之外还有其他一些通用参数,那么必然会导致在请求中过多的占有有效位置,减少请求参数的传递体积。 我们应该将请求头中的信息尽可能的有效利用起来,现今越来越多的平台提供了授权管理的机制,授权机制的技术演进如何,成熟的厂家都是将授权信息维护在请求头中。这样后台和前台在进行对接接口的时候都可以很有效的进行过滤器的编写,让无效的请求还未进行服务端处理时就进行阻止,有效的避免了服务端的资源浪费。

可以使用提交的方式

  • 当前RESTful中支持的提交方式:

    • GET:(SELECT)访问服务器上一个特定的资源或者是特定的资源列表
    • POST:(CREATE)在服务器上创建一个特定的资源
    • PUT:(CREATE&UPDATE)在服务器上更新一个特定的资源<全部属性>,若没有创建一个新的资源
    • PATCH:(UPDATE)在服务器上更新一个特定的资源<部分属性>
    • DELETE:(DELETE)在服务器删除一个特点的资源
    • OPTIONS:返回当前访问url的地址支持的操作方法
    • HEAD:返回当前访问的特点资源的元信息
  • 规范说明:

    • 当访问的url中没有注明特定资源的唯一标识ID时,该url只允许POST或PUT接口
    • 默认通过 GET 方法去获取资源,但是如果参数超过了url的字符限制可以使用 POST 方法
    • PUT 和 PATCH 方法都是对于资源的更新,一个是整体更新一个是部分属性更新,同时在更细致的解释上面,PUT 的操作是幂等操作,而 PATCH 的操作是非幂等的(后续举例会进行详细说明)
    • OPTION 和 HEAD 方法是较少使用的访问方式,但是都有其特定的含义和使用场景,OPTION 通常在研发对接接口时通过调用查看可进行的操作及简明的概要说明,HEAD 通常是在批量更新之后对元素进行元信息的比对时
  • 举例说明

    • 错误示范: http://[host:port]/api/v1.0/animal Method: GET
    • 正确示范: http://[host:port]/api/v1.0/animal/<object_id> Method: GET

    对上述url进行访问的时候,此操作是非法操作,因通过请求判断当前的操作应该获取服务器上的特定资源,但是并没有携带合理的对象ID。对此url进行不携带对象ID的时候,默认只能允许 POST 或 PUT 请求,如果想对特定的资源进行查询的话,对应的查询url应该是 http://[host:port]/api/v1.0/animal/<object_id> Method: GET 获取指定的资源信息(注:RESTful在实践中的建议通常是将资源的唯一标识ID作为url的一部分添加在其中,而不作为请求参数添加在请求体中)

    • 错误示范: http://[host:port]/api/v1.0/zoo?filter={over_max_size} Method: GET
    • 正确示范: http://[host:port]/api/v1.0/zoo Method: POST Body: filter={over_max_size}

    当我们对特定资源集合进行查询时,如果需要进行资源的过滤,但是过滤的条件过多导致整体url过长超过了w3c的设置时,我们应该转换该请求方式到 POST 方式上,这里需要有效的进行请求的判定。值得注意的是,通常情况下,遵循原则这种提交方式是不被RESTful接口所接受的,但是特定情况下不得不进行此种操作,切记特殊情况下进行此转换,否则还是强烈建议遵循RESTful的本身原则(当出现这种情况,建议业务设计者重新梳理一下,通常参数越复杂意味着操作逻辑越复杂,不利于本身产品的使用)

    • 正确示范1: http://[host:port]/api/v1.0/animal?object={attrs} Method: PUT
    • 正确示范2: http://[host:port]/api/v1.0/animal Method: PUT Body: object={attrs}

    当我们需要对一个服务器进行创建资源对象的时候,可以进行方法为POST 或者 PUT 的提交,但是这个其中是有一些区别的。通常情况下我们会通过 POST 创建一个必要属性完全的对象资源,而这个资源体本身在服务器端之前一定不存在,然后返回的结果体一般是这个资源对象的唯一标识ID;而通过 PUT 的提交时,携带的属性信息一般是该资源对象的全部属性,当这个资源本身在服务器端已经存在时则更新,如果不存在时则创建,通常情况下返回的是该资源对象的全部属性值 这两种创建方式都可以存在,具体使用情况需要视接口的使用场景进行判断,默认情况下如果当前接口是支持创建对象的话,本身就应该同时具备这两种接口属性,因为其返回值的不同故其适用的场景也是多种多样的

    • 正确示范1: http://[host:port]/api/v1.0/animal/monkey/001?object={attrs} Method: PUT
    • 正确示范2: http://[host:port]/api/v1.0/animal/monkey/001?object={attrs} Method: PATCH

    上面我们已经说过了 PUT 方法本身的默认含义是进行服务端资源对象的更新,那么对于 PATCH 也是资源更新的手段,它们之间又有什么样的区别呢?首先,PUT 是针对资源对象的全部属性的更新,PATCH 是针对资源对象的部分属性的更新;其次,PUT 在操作上面是幂等的,而 PATCH 在操作上是非幂等的。如何理解呢?所谓幂等就是这个url在进行频繁请求的时候,尤其是当前一次请求还未返回结果时,后一个相同的请求接踵而至时,这个url在这种情况下返回的结果体本身没有产生任何变化,那么这个请求本身就是幂等的;而非幂等则与之相反,就是每次请求的结果会对结果体本身的内容产生影响 举例说明: http://[host:port]/api/v1.0/animal/monkey/001?age=3 Method: PUT 这个请求是将唯一标识ID为Monkey-001的猴子更新年龄为3岁,当这个请求被客户端反复请求n次之后,返回给客户端的结果体中Monkey-001猴子的年龄属性仍然是3岁;http://[host:port]/api/v1.0/animal/monkey/001?age=+1 Method: PATCH 这个请求本身基本含义与上一个请求相似,但是具体还是有细微差别的,后一个请求是告诉服务器将猴子的年龄更新为基准值加一,那么这个请求在反复进行访问时,其对应的返回体中Monkey-001的猴子的年龄属性就会因访问的次数而导致值不断的更改,所以这样的接口请求方式需要开发中进行有效的校验

接口状态码的使用

  • RESTful使用标准的 HTTP STATUS CODE 参考文章
  • 规范要求:
    • 在RESTful的接口中,我们无需在进行错误的代码编写,统一使用HTTP标准的状态码进行反馈
    • 针对统一个接口的不同访问状况时,应当返回不同的状态码,不允许统一通过200状态码进行返回
  • 常用到的状态码见下表

    200 OK 请求成功接收并处理,一般响应中都会有 body 201 Created 请求已完成,并导致了一个或者多个资源被创建,最常用在 POST 创建资源的时候 202 Accepted 请求已经接收并开始处理,但是处理还没有完成。一般用在异步处理的情况,响应 body 中应该告诉客户端去哪里查看任务的状态 204 No Content 请求已经处理完成,但是没有信息要返回,经常用在 PUT 更新资源的时候(客户端提供资源的所有属性,因此不需要服务端返回)。如果有重要的 metadata,可以放到头部返回 301 Moved Permanently 请求的资源已经永久性地移动到另外一个地方,后续所有的请求都应该直接访问新地址。服务端会把新地址写在 Location 头部字段,方便客户端使用。允许客户端把 POST 请求修改为 GET。 304 Not Modified 请求的资源和之前的版本一样,没有发生改变。用来缓存资源,和条件性请求(conditional request)一起出现 307 Temporary Redirect 目标资源暂时性地移动到新的地址,客户端需要去新地址进行操作,但是不能修改请求的方法。 308 Permanent Redirect 和 301 类似,除了客户端不能修改原请求的方法 400 Bad Request 客户端发送的请求有错误(请求语法错误,body 数据格式有误,body 缺少必须的字段等),导致服务端无法处理 401 Unauthorized 请求的资源需要认证,客户端没有提供认证信息或者认证信息不正确 403 Forbidden 服务器端接收到并理解客户端的请求,但是客户端的权限不足。比如,普通用户想操作只有管理员才有权限的资源。 404 Not Found 客户端要访问的资源不存在,链接失效或者客户端伪造 URL 的时候回遇到这个情况 405 Method Not Allowed 服务端接收到了请求,而且要访问的资源也存在,但是不支持对应的方法。服务端必须返回 Allow 头部,告诉客户端哪些方法是允许的 415 Unsupported Media Type 服务端不支持客户端请求的资源格式,一般是因为客户端在 Content-Type 或者 Content-Encoding 中申明了希望的返回格式,但是服务端没有实现。比如,客户端希望收到 xml返回,但是服务端支持 Json 429 Too Many Requests 客户端在规定的时间里发送了太多请求,在进行限流的时候会用到 500 Internal Server Error 服务器内部错误,导致无法完成请求的内容 503 Service Unavailable 服务器因为负载过高或者维护,暂时无法提供服务。服务器端应该返回 Retry-After 头部,告诉客户端过一段时间再来重试

返回的结果集格式要求

一般我个人在项目使用过程中经常使用的接口格式如下

```json
# 当接口访问成功的时候
{
     "meta": {
        "message": ""
     },                       // 这里可以携带一些其他的信息
     "data" : {
        ...                   // 这里放置返回的数据集结构
     }
}
# 当接口访问失败的时候
{
     "meta": {
        "message": "Invalid API key.",   // 前台提示用户的错误信息
        "details": [],        // 需要前台帮忙记录的后端错误信息
        "retryable": false    // 相同请求体是否允许继续尝试
     },
     "data" : {}
}
```