文章来源:https://ningyu1.github.io/site/post/01-restful-design-specifications/

一、 摘要(Abstract)

RESTful API 已经非常成熟,也得到了大家的认可。我们按照 Richardson Maturity Model 对 REST 评价的模型,规范基于 level2 来设计

二、版本(Versioning)

API的版本号放入URL。例如:

https://api.jiuyescm.com/v1/
https://api.jiuyescm.com/v1.2/

三、资源、路径(Endpoint)

路径,API的具体地址。在REST中,每个地址都代表一个具体的资源(Resource)约定如下:

  • 路径仅表示资源的路径(位置),尽量不要有actions操作(一些特殊的actions操作除外)
  • 路径以 复数(名词) 进行命名资源,不管返回单个或者多个资源。
  • 使用 小写字母、数字以及下划线(“_”) 。(下划线是为了区分多个单词,如user_name)
  • 资源的路径从父到子依次如:

    /{resource}/{resource_id}/{sub_resource}/{sub_resource_id}/{sub_resource_property}
  • 使用 ? 来进行资源的过滤、搜索以及分页等

  • 使用版本号,且版本号在资源路径之前

  • 优先使用内容协商来区分表述格式,而不是使用后缀来区分表述格式

  • 应该放在一个专用的域名下,如:http://api.jiuyescm.com

  • 使用SSL

综上,一个API路径可能会是

https://api.domain.com/v1/{resource}/{resource_id}/{sub_resource}/{sub_resource_id}/{sub_resource_property}
https://api.domain.com /v1/{resource}?page=1&page_size=10
https://api.domain.com /v1/{resource}?name=xx&sortby=name&order=asc

四、操作(HTTP Actions)

HTTP动词(方法)表示对资源的具体操作。常用的HTTP动词有:

GET(SELECT):从服务器取出资源(一项或多项)
POST(CREATE):在服务器新建一个资源
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)
PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)
DELETE(DELETE):从服务器删除资源
还有两个不常用的HTTP动词
HEAD:获取资源的元数据
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的

下面是一些例子

GET /users:列出所有用户
POST /users:新建一个用户
GET /users/{user_id}:获取某个指定用户的信息
PUT /users/{user_id}:更新某个指定用户的信息(提供该用户的全部信息)
PATCH /users/{user_id}:更新某个指定用户的信息(提供该用户的部分信息)
DELETE /users/{user_id}:删除某个用户
GET /users/{user_id}/resources:列出某个指定用户的所有权限资源
DELETE /users/{user_id}/resources/{resources_id}:删除某个指定用户的指定权限资源

五、数据(Data Format)

数据是对资源的具体描述,分为请求数据和返回数据。约定如下:

  • 查询,过滤条件使用query string,例如user?name=xxx
  • Content body 仅仅用来传输数据
  • 通过Content-Type指定请求与返回的数据格式。其中请求数据还要指定Accept。(我们暂时只使用Json)
  • 数据应该拿来就能用,不应该还要进行转换操作
  • 使用字符串(YYYY-MM-dd hh:mm:ss)格式表达时间字段,例如: 2017-02-20 16:00:00
  • 数据采用UTF-8编码
  • 返回的数据应该尽量简单,响应状态应该包含在响应头中
  • 使用 小写字母、数字以及下划线(“_”) 描述字段,不使用大写描述字段(这个由于使用了一些开源的jar所以这个不强求,比如说pageinfo我们无法修改属性名称)
  • 建议资源中的唯一标识命名为id(这个不强求,有的唯一标识名称确实比较复杂)
  • 属性和字符串值必须使用双引号””(这个json转换默认规则)
  • 建议对每个字段设置默认值(数组型可设置为[],字符串型可设置为””,数值可设置为0,对象可设置为{}),这一条是为了方便前端/客户端进行判断字段存不存在操作(这样json转换会自动转成相应的字符)
  • POST操作应该返回新建的资源;PUT/PATCH操作返回更新后的完整的资源;DELETE返回一个空文档;GET返回资源数组或当个资源
  • 为了方便以后的扩展兼容,如果返回的是数组,强烈建议用一个包含如items属性的对象进行包裹,如:
{"items":[{},{}]}

示例:

POST https://api.domain.com/v1/users
Request
headers:
Accept: application/json
Content-Type: application/json;charset=UTF-8
body:
{
"user_name": "ZhangSan",
"address": "ujfhysdfsdf",
"nick": "ZS"
} Response
status: 201 Created
headers:
Content-Type: application/json;charset=UTF-8
body:
{
"requestId": sdfsdflkjoiusdf,
"code": "",
"message": "",
"items":
{
"id":"111",
"user_name": "HingKwan",
"address": "ujfhysdfsdf",
"nick": "ZS"
}
}

六、安全(Security)

调用限制

为了避免请求泛滥,给API设置速度限制很重要。入速度设置之后,可以在HTTP返回头上对返回的信息进行说明,下面是几个必须的返回头(依照twitter的命名规则)

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

这个我们一般会在getway中实现

授权校验

RESTful API是无状态的也就是说用户请求的鉴权和cookie以及session无关,每一次请求都应该包含鉴权证明。 可以使用http请求头Authorization设置授权码; 必须使用User-Agent设置客户端信息, 无User-Agent请求头的请求应该被拒绝访问。具体的授权可以采用OAuth2,或者自己定义并实现相关的授权验证机制(基于token)。 这个我们一般会在getway中实现

错误

当API返回非2XX的HTTP响应时,应该采用统一的响应信息,格式如:

HTTP/1.1 400 Bad Request
Content-Type: application/json;charset=UTF-8
{
"code":"INVALID_ARGUMENT",
"message":"{error message}",
"request_id":"sdfsdfo8lkjsdf",
"items":[],
}
  • HTTP Header Code:符合HTTP响应的状态码。详细见以下的“状态码”节
  • code:用来表示某类错误不是具体错误,比如缺少参数等。是对HTTP Header Code的补充,开发团队可以根据自己的需要自己定义
  • message:错误信息的摘要,应该是对用户处理错误有用的信息
  • request_id:请求的id,方便开发定位发生错误的请求(可选)
  • code的定义约定:
    • 采用 大写字母命名,字母与字母之间用下划线(”_”) 隔开
    • code应该用来定义错误类别,而非定义具体的某个错误。
    • 缺少参数使用:MISSING_X
    • 无效参数使用:INVALID_X
    • 逻辑验证错误使用:VALIDATION_X
    • 不存在使用:NO_FOUND_X

七、状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

200 OK - [GET/PUT/PATCH/DELETE]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 Created - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 No Content - [DELETE]:用户删除数据成功。
304 Not Modified - HTTP缓存有效。
400 Invalid Request - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 Not Found - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
405 Method Not Allowed - [*]:该http方法不被允许。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
415 Unsupported Media Type - [*]:请求类型错误。
422 Unprocesable Entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
429 Too Many Request - [*]:请求过多。
500 Internal Server Error - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
503 Service Unavailable - [*]:服务当前无法处理请求。

八、异常规范(Exceptions)

  • Controller中try catch住service的异常,再转换为restful中需要抛出的异常
try {
Long id = userService.save(vo);
vo.setId(id);
} catch(BizException e) {
throw new UnprocesableEntityException(ErrorCode.USER_NAME_EXIST.getCode(), ErrorCode.USER_NAME_EXIST.getMessage());
}
  • Controller中抛出的异常必须使用spring-mvc-rest包中的异常类,不允许自定义异常,选择需要返回的httpStatus对应的异常
#403 [*]:表示得到授权(与401错误相对),但是访问是被禁止的。
com.jiuyescm.spring.mvc.rest.exception.ForbiddenException #401 [GET]:用户请求的资源被永久删除,且不会再得到的。
com.jiuyescm.spring.mvc.rest.exception.GoneException #400 [POST/PUT/PATCH]:用户发出的请求有错误(常用在请求必要的参数错误上),服务器没有进行新建或修改数据的操作,该操作是幂等的。
com.jiuyescm.spring.mvc.rest.exception.InvalidRequestException #406 [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)或(请求参数需要数字,用户传入字符串)
com.jiuyescm.spring.mvc.rest.exception.NotAcceptableException #404 [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
com.jiuyescm.spring.mvc.rest.exception.NotFoundException #401 [*]:表示没有权限(令牌、用户名、密码错误,或任何资源没有权限)
com.jiuyescm.spring.mvc.rest.exception.UnauthorizedException #422 [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
com.jiuyescm.spring.mvc.rest.exception.UnprocesableEntityException
  • 抛出的异常中需要传入异常编码和异常信息,异常编码定义遵循上面 《安全中错误编码规范》
"MESSING_ID", "缺少参数:id"
"MESSING_NAME", "缺少参数:name"
"MESSING_ADDRESS", "缺少参数:address"
"USER_NAME_EXIST", "用户名已存在"
"USER_NOT_FOUND", "用户名不存在"
  • 常用的错误编码、异常、httpStatus对应关系
"MESSING_ID", "缺少参数:id"、InvalidRequestException、400
"MESSING_NAME", "缺少参数:name"、InvalidRequestException、400
"MESSING_ADDRESS", "缺少参数:address"、InvalidRequestException、400
"USER_NAME_EXIST", "用户名已存在"、UnprocesableEntityException、422
"USER_NOT_FOUND", "用户名不存在"、NotFoundException、404

九、示例(Example)

采用user提供的示例代码

POST /users

Resource POST /v1/users

POST Parameters Endpoint requires:

Name Type Description
name String 用户名称
address String 用户住址

and accepts a few other parameters listed below.

Name Type Description
remark String 描述信息

Example

{
"name":"tuyir",
"address":"sdflkjsdf",
"remark":"sdfoiu"
}

Response Status-Code: 201 Created

{
"code": "",
"message": null,
"items": {
"id": 27,
"name": "tuyir",
"address": "sdflkjsdf",
"remark": "sdfoiu"
}
}
Name Type Description
code String 错误编码
message String 错误描述
Items Objec t 返回结果
id Long 唯一标识
name String 用户名称
address String 家庭住址
remark String 描述信息

Error response Status-Code: 400 Bad Request

{
"code": "MESSING_NAME",
"message": “缺少参数:name”,
"items": {}
}
Name Type Description
code String 错误编码
message String 错误描述
Items Object 返回结果

HTTP Error Codes

HTTP Status Code Description
400 MESSING_NAME 缺少参数:name
400 MESSING_ADDRESS 缺少参数:address
422 USER_NAME_EXIST 用户名已存在
500 INTERNAL_SERVER_ERROR 未知的错误

DELETE /users/{user_id}

Resource DELETE /v1/users/{user_id}

Path Parameters

Name Type Description
user_id Long 用户唯一标识

Query Parameters None

Example Request

curl –H ‘Content-Type: application/json’\
-X DELETE \
‘https://api.jiuyescm.com/v1/users/111’

Response Status-Code: 204 No Content

HTTP Error Codes

HTTP Status Code Description
400 MESSING_ID 缺少参数:id
404 USER_NOT_FOUND 用户不存在

PUT /users

Resource PUT /v1/users

PUT Body Parameters Endpoint requires:

Name Type Description
user_id Long 用户唯一标识
user_name String 用户名称
address String 用户住址

and accepts a few other parameters listed below..

Name Type Description
remark String 描述信息

Example

{
"user_id": 12,
"name":"tuyir",
"address":"sdflkjsdf",
"remark":"sdfoiu"
}

Response Status-Code: 200 OK

{
"code": "",
"message": null,
"items": {
"id": 12,
"name": "tuyir",
"address": "sdflkjsdf",
"remark": "sdfoiu"
}
}
Name Type Description
code String 错误编码
message String 错误描述
Items Object 返回结果
id Long 唯一标识
name String 用户名称
address String 家庭住址
remark String 描述信息

Error response Status-Code: 400 Bad Request

{
"code": "MESSING_NAME",
"message": “缺少参数:name”,
"items": {}
}
Name Type Description
code String 错误编码
message String 错误描述

HTTP Error Codes

HTTP Status Code Description
code String 错误编码
400 MESSING_ID 缺少参数:id
400 MESSING_NAME 缺少参数:name
400 MESSING_ADDRESS 缺少参数:address
422 USER_NAME_EXIST 用户名已存在
500 INTERNAL_SERVER_ERROR 未知的错误

GET /users/{user_id}

Resource GET /v1/users/{user_id}

Path Parameters

Name Type Description
user_id Long 用户唯一标识

Example Request

Curl –H 'Content-Type: application/json' \
'https://api.jiuyescm.com/v1/users/12'

Response Status-Code: 200 OK

{
"code": "",
"message": null,
"items": {
"id": 12,
"name": "tuyir",
"address": "sdflkjsdf",
"remark": "sdfoiu"
}
}
Name Type Description
code String 错误编码
message String 错误描述
Items Object 返回结果
id String 唯一标识
name String 用户名称
address String 家庭住址
remark String 描述信息

Error response Status-Code: 404 Bad Request

{
"code": " USER_NOT_FOUND",
"message": “用户不存在”,
"items": {}
}
Name Type Description
code String 错误编码
message String 错误描述

HTTP Error Codes

HTTP Status Code Description
400 MESSING_ID 缺少参数:id
404 USER_NOT_FOUND 用户不存在

GET /users

Resource GET /v1/users

Query Parameters

Name Type Description
name String 根据用户名称进行查询
page int 第几页,不传入默认1
page_size int 每页返回多少条结果,不传入默认20

Example Request

Curl –H 'Content-Type: application/json' \
'https://api.jiuyescm.com/v1/users?name=xxx&page=1&page_size=20'

Response Status-Code: 200 Success

{
"code": "",
"message": "",
"items": {
"pageNum": 1,
"pageSize": 20,
"size": 17,
"startRow": 1,
"endRow": 17,
"total": 17,
"pages": 1,
"list": [
{
"id": 2,
"name": "ningyu1",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 3,
"name": "ningyu2",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 4,
"name": "ningyu3",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 5,
"name": "ningyu4",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 6,
"name": "ningyu5",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 7,
"name": "8888",
"address": "8888",
"remark": "8888"
},
{
"id": 8,
"name": "444",
"address": "444",
"remark": "444"
},
{
"id": 9,
"name": "ningyu7",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 12,
"name": "ningyu9",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 13,
"name": "ningyu10",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 17,
"name": "ningyu",
"address": "sdflkjsdf",
"remark": null
},
{
"id": 20,
"name": "9999",
"address": "sdflkjsdf",
"remark": null
},
{
"id": 23,
"name": "888",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 24,
"name": "222",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 25,
"name": "222444",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 26,
"name": "222444sdf",
"address": "sdflkjsdf",
"remark": "sdfoiu"
},
{
"id": 27,
"name": "tuyir",
"address": "sdflkjsdf",
"remark": "sdfoiu"
}
],
"firstPage": 1,
"prePage": 0,
"nextPage": 0,
"lastPage": 1,
"isFirstPage": true,
"isLastPage": true,
"hasPreviousPage": false,
"hasNextPage": false,
"navigatePages": 8,
"navigatepageNums": [
1
]
}
}
Name Type Description
code String 错误编码
message String 错误描述
Items Object 返回结果
id Long 唯一标识
name String 用户名称
address String 家庭住址
remark String 描述信息

不是RESTful不好,是你姿势有问题的更多相关文章

  1. ABP Framework 为什么好上手,不好深入?探讨最佳学习姿势!

    离写上一篇经验总结 ABP Framework 研习社经验总结(6.28-7.2) ,已经过去两周. ABP Framework 研习社(QQ群:726299208) 最近一周,又迎来了很多新伙伴,成 ...

  2. Restful 介绍及SpringMVC+restful 实例讲解

    restful不是一个框架,称为一种编码更烦更贴切吧,其核心类位于spring-web.jar中,即RestTemplate.class restful是rpc通过http协议的一种实现方式,和web ...

  3. RESTful API URI 设计的一些总结

    非常赞的四篇文章: Resource Naming Best Practices for Designing a Pragmatic RESTful API 撰写合格的 REST API JSON 风 ...

  4. RESTFUL API 安全设计指南

    RESTFUL API 安全设计指南 xxlegend · 2015/10/18 15:08 0x01 REST API 简介 REST的全称是REpresentational State Trans ...

  5. Restful.Data,现招募有为骚年,群号 338570336

    光阴似箭,日月如梭,套用小学作文惯用的一句开场白来开始重新开始我的博客园生涯吧. 8年的风霜雪雨,不断的击打着我内心的哀伤,可我依旧坚挺的屹立在这里,是因为技术是我一直坚持的梦想. 追寻着先辈和高人的 ...

  6. 追踪app崩溃率、事件响应链、Run Loop、线程和进程、数据表的优化、动画库、Restful架构、SDWebImage的原理

    1.如何追踪app崩溃率,如何解决线上闪退 当 iOS设备上的App应用闪退时,操作系统会生成一个crash日志,保存在设备上.crash日志上有很多有用的信息,比如每个正在执行线程的完整堆栈 跟踪信 ...

  7. 使用CodeIgniter框架搭建RESTful API服务

    使用CodeIgniter框架搭建RESTful API服务 发表于 2014-07-12   |   分类于 翻译笔记   |   6条评论 在2011年8月的时候,我写了一篇博客<使用Cod ...

  8. FJNU 1159 Fat Brother’s new way(胖哥的新姿势)

    FJNU 1159 Fat Brother’s new way(胖哥的新姿势) Time Limit: 1000MS   Memory Limit: 257792K [Description] [题目 ...

  9. 虚拟研讨会:如何设计好的RESTful API?

    http://www.infoq.com/cn/articles/how-to-design-a-good-restful-api/ REST架构风格最初由Roy T. Fielding(HTTP/1 ...

随机推荐

  1. moviepy音视频剪辑:使用fl_time进行时间特效处理报错OSError: Error in file xxxx, Accessing time

    ☞ ░ 前往老猿Python博文目录 ░ 老猿在使用moviepy音视频剪辑的fl_time进行时间特效处理时,系统报错: OSError: Error in file F:\video\WinBas ...

  2. 第15.41节、PyQt(Python+Qt)入门学习:输入部件QComboBox组合框功能详解

    专栏:Python基础教程目录 专栏:使用PyQt开发图形界面Python应用 专栏:PyQt入门学习 老猿Python博文目录 一.概述 Designer中输入工具部件中的Combo Box组合框与 ...

  3. PyQt(Python+Qt)学习随笔:QTableWidgetItem项文本和项对齐的setText、setTextAlignment方法

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 QTableWidget部件中的QTableWidgetItem项的文本可以通过text()和set ...

  4. PyQt(Python+Qt)学习随笔:QAbstractItemView的iconSize属性

    老猿Python博文目录 老猿Python博客地址 视图的iconSize属性用于控制显示icon的项上的icon图标大小,在视图可见情况下设置该属性会导致视图上的显示项重新调整布局. 可以使用ico ...

  5. HTML5中的自定义属性data-*

    在html5中,给元素添加自定义属性需要用到data-*,比如data-name,添加完data-自定义属性之后通过元素的dataset属性来访问其值. dataset与getAttribute/se ...

  6. 手把手教你写DI_0_DI是什么?

    DI是什么? Dependency Injection 常常简称为:DI. 它是实现控制反转(Inversion of Control – IoC)的一个模式. fowler 大大大神 "几 ...

  7. CF1000F One Occurrence

    本题解用于记录一下一个优秀的东西--懒标记. 题解 可以很轻易的想到莫队的做法,但是题目让你输出的是满足条件的一个数,而不是满足条件的数的个数,似乎很难去 \(O(1)\) 转移.这个时候我们的懒标记 ...

  8. JetBrains系列产品使用记录

    1.PyCharm中from  import提示找不到定义,提示错误,但其实是没有错误的 右键项目的根路径,Mark Directory As Source Root 2.自动换行 在Editor-& ...

  9. Python零散知识点记录

    1.关于setdefaultencoding之前必须reload(sys): 要在调用setdefaultencoding时必须要先reload一次sys模块,因为这里的import语句其实并不是sy ...

  10. 【Azure Redis 缓存】Azure Redis 服务不支持指令CONFIG

    问题描述 在Azure Redis的门户页面中,通过Redis Console连接到Redis后,想通过CONFIG命令来配置Redis,但是系统提示CONFIG命令不能用. 错误消息为:(error ...