什么是 RESTful?
RESTful 是一种软件设计风格,由 Roy Fielding 在他的 论文 中提出,全称为 Representational State Transfer
,直译为表现层状态转移
,或许可以解释为用 URL 定位资源,用 HTTP 动词描述操作
,不用太纠结于定义,接下来我们会详细讨论。
RESTful 风格的接口,目前来看,实现的最好的就是 Github API,经常被效仿。接下来我们通过分析 Github API 来引出我们的 API 设计原则。
为什么选择 RESTful?
我认为一套接口应该尽量满足以下几个原则:
- 安全可靠,高效,易扩展。
- 简单明了,可读性强,没有歧义。
- API 风格统一,调用规则,传入参数和返回数据有统一的标准。
我们当然可以根据自己的经验,或者参考知名公司的接口总结设计出一套满足要求的接口,但是每个人对接口的理解不同,设计出来的接口也会有所不同,接口的命名,请求参数的格式,响应的结果,错误响应的错误码,等等很多地方都会有不一样的实现。当你去寻求一种设计理念来帮助我们设计出满足要求的接口,一定会发现 RESTful。
RESTful 的设计理念基于 HTTP 协议,因为 Roy Fielding 就是 HTTP 协议(1.0 版和 1.1 版)的主要设计者。它是一种设计风格,没有规定我们一定如何实现,但是为我们提供了很好的设计理念。风格的统一,使得我们不需要过多的解释,就能让使用者明白该如何使用,同时也会有很多现成的工具来帮助我们实现 RESTful 风格的接口。
如何使用RESTful?
RESTful 设计原则
1. HTTPS
HTTPS 为接口的安全提供了保障,可以有效防止通信被窃听和篡改。而且现在部署 HTTPS 的成本也越来越低,你可以通过 cerbot 等工具,方便快速的制作免费的安全证书,所以生产环境,请务必使用 HTTPS。
另外注意,非 HTTPS 的 API 调用,不要重定向到 HTTPS,而要直接返回调用错误以禁止不安全的调用。
2. 域名
应当尽可能的将 API 与其主域名区分开,可以使用专用的域名,访问我们的 API,例如:
https://api.larabbs.com
或者可以放在主域名下,例如:
https://www.larabbs.com/api
3. 版本控制
随着业务的发展,需求的不断变化,API 的迭代是必然的,很可能当前版本正在使用,而我们就得开发甚至上线一个不兼容的新版本,为了让旧用户可以正常使用,为了保证开发的顺利进行,我们需要控制好 API 的版本。
通常情况下,有两种做法:
- 将版本号直接加入 URL 中
https://api.larabbs.com/v1 https://api.larabbs.com/v2
- 使用 HTTP 请求头的 Accept 字段进行区分 (推荐使用)
https://api.larabbs.com/ Accept: application/prs.larabbs.v1+json Accept: application/prs.larabbs.v2+json
Github Api 虽然默认使用了第一种方法,但是其实是推荐并实现了第二种方法的,我们同样也尽量使用第二种方式。
4. 用 URL 定位资源
在 RESTful 的架构中,所有的一切都表示资源,每一个 URL 都代表着一种资源,资源应当是一个名词,而且大部分情况下是名词的复数,尽量不要在 URL 中出现动词。
先来看看 github 的 例子https://developer.github.com/v3/issues/comments/GET:获取,罗列 POST:创建 PATCH:修改 PUT:设置 DELETE:删除
GET /issues 列出所有的 issue
GET /orgs/:org/issues 列出某个项目的 issue
GET /repos/:owner/:repo/issues/:number 获取某个项目的某个 issue
POST /repos/:owner/:repo/issues 为某个项目创建 issue
PATCH /repos/:owner/:repo/issues/:number 修改某个 issue
PUT /repos/:owner/:repo/issues/:number/lock 锁住某个 issue
DELETE /repos/:owner/:repo/issues/:number/lock 解锁某个 issue
例子中冒号开始的代表变量,例如 /repos/summerblue/larabbs/issues
在 github 的实现中,我们可以总结出:
- 资源的设计可以嵌套,表明资源与资源之间的关系。
- 大部分情况下我们访问的是某个
资源集合
,想得到单个资源
可以通过资源的 id 或 number 等唯一标识获取。 - 某些情况下,资源会是单数形式,例如
某个项目某个 issue 的锁
,每个 issue 只会有一把锁,所以它是单数。
错误的例子
POST https://api.larabbs.com/createTopic
GET https://api.larabbs.com/topic/show/1
POST https://api.larabbs.com/topics/1/comments/create
POST https://api.larabbs.com/topics/1/comments/100/delete
正确的例子
POST https://api.larabbs.com/topics
GET https://api.larabbs.com/topics/1
POST https://api.larabbs.com/topics/1/comments
DELETE https://api.larabbs.com/topics/1/comments/100
5. 用 HTTP 动词描述操作
HTTP 设计了很多动词,来表示不同的操作,RESTful 很好的利用的这一点,我们需要正确的使用 HTTP 动词,来表明我们要如何操作资源。
先来解释一个概念,幂等性
,指一次和多次请求某一个资源应该具有同样的副作用,也就是一次访问与多次访问,对这个资源带来的变化是相同的。
常用的动词及幂等性
动词 | 描述 | 是否幂等 |
---|---|---|
GET | 获取资源,单个或多个 | 是 |
POST | 创建资源 | 否 |
PUT | 更新资源,客户端提供完整的资源数据 | 是 |
PATCH | 更新资源,客户端提供部分的资源数据 | 否 |
DELETE | 删除资源 | 是 |
为什么 PUT 是幂等的而 PATCH 是非幂等的,因为 PUT 是根据客户端提供了完整的资源数据,客户端提交什么就替换为什么,而 PATCH 有可能是根据客户端提供的参数,动态的计算出某个值,例如每次请求后资源的某个参数减 1,所以多次调用,资源会有不同的变化。
另外需要注意的是,GET 请求对于资源来说是安全的,不允许通过 GET 请求改变(更新或创建)资源,但是真实使用中,为了方便统计类的数据,会有一些例外情况,例如帖子详情,记录访问次数,每调用一次,访问次数 +1;
6. 资源过滤
我们需要提供合理的参数供客户端过滤资源,例如
?state=closed: 不同状态的资源
?page=2&per_page=100:访问第几页数据,每页多少条。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
7. 正确使用状态码
HTTP 提供了丰富的状态码供我们使用,正确的使用状态码可以让响应数据更具可读性。
- 200 OK - 对成功的 GET、PUT、PATCH 或 DELETE 操作进行响应。也可以被用在不创建新资源的 POST 操作上
- 201 Created - 对创建新资源的 POST 操作进行响应。应该带着指向新资源地址的 Location 头
- 202 Accepted - 服务器接受了请求,但是还未处理,响应中应该包含相应的指示信息,告诉客户端该去哪里查询关于本次请求的信息
- 204 No Content - 对不会返回响应体的成功请求进行响应(比如 DELETE 请求)
- 304 Not Modified - HTTP 缓存 header 生效的时候用
- 400 Bad Request - 请求异常,比如请求中的 body 无法解析
- 401 Unauthorized - 没有进行认证或者认证非法
- 403 Forbidden - 服务器已经理解请求,但是拒绝执行它
- 404 Not Found - 请求一个不存在的资源
- 405 Method Not Allowed - 所请求的 HTTP 方法不允许当前认证用户访问
- 410 Gone - 表示当前请求的资源不再可用。当调用老版本 API 的时候很有用
- 415 Unsupported Media Type - 如果请求中的内容类型是错误的
- 422 Unprocessable Entity - 用来表示校验错误
- 429 Too Many Requests - 由于请求频次达到上限而被拒绝访问
8. 数据响应格式
考虑到响应数据的可读性及通用性,默认使用 JSON 作为数据响应格式。如果客户端有需求使用其他的响应格式,例如 XML,需要在 Accept 头中指定需要的格式。
https://api.larabbs.com/
Accept: application/prs.larabbs.v1+json
Accept: application/prs.larabbs.v1+xml
对于错误数据,默认使用如下结构:
'message' => ':message', // 错误的具体描述
'errors' => ':errors', // 参数的具体错误描述,422 等状态提供
'code' => ':code', // 自定义的异常码
'status_code' => ':status_code', // http状态码
'debug' => ':debug', // debug 信息,非生产环境提供
例如
{
"message": "422 Unprocessable Entity",
"errors": {
"name": [
"姓名 必须为字符串。"
]
},
"status_code": 422
}
{
"message": "您无权访问该订单",
"status_code": 403
}
9. 调用频率限制
为了防止服务器被攻击,减少服务器压力,需要对接口进行合适的限流控制,需要在响应头信息中加入合适的信息,告知客户端当前的限流情况
- X-RateLimit-Limit :100 最大访问次数
- X-RateLimit-Remaining :93 剩余的访问次数
- X-RateLimit-Reset :1513784506 到该时间点,访问次数会重置为
X-RateLimit-Limit
超过限流次数后,需要返回 429 Too Many Requests
错误。
10. 编写文档
为了方便用户使用,我们需要提供清晰的文档,尽可能包括以下几点
- 包括每个接口的请求参数,每个参数的类型限制,是否必填,可选的值等。
- 响应结果的例子说明,包括响应结果中,每个参数的释义。
- 对于某一类接口,需要有尽量详细的文字说明,比如针对一些特定场景,接口应该如何调用。
什么是 DingoApi?
dingo/api 是一个 Lumen 和 Laravel 都可用的 RestFul 工具包,帮助我们快速的开始构建 RestFul Api。我们的目的是教会大家如何快速的搭建并使用这个包,更多的功能,还需要你仔细阅读 DingoApi 的 文档 (Home · dingo/api Wiki · GitHub)来深入的学习和理解,这里(GitHub - liyu001989/dingo-api-wiki-zh: dingo/api 中文文档)有一份中英对照的翻译,或许能帮到你。
1. 安装
因为是 API 的课程,可以先创建一个 API 分支。
$ git checkout -b api
安装扩展包
$ composer require dingo/api:2.0.0-beta1
报错:
[Composer\Downloader\TransportException]
The "https://packagist.phpcomposer.com/p/provider-2019-01%241083636756831bc6ca0a7b15a464cb6375f4a25e56edf670c6c3e3ad02f33000.j
son" file could not be downloaded (HTTP/1.1 404 Not Found)
解决办法:composer config -g repo.packagist composer https://packagist.laravel-china.org。更换镜像源。
没看懂,继续查找
执行composer config repo.packagist composer https://packagist.phpcomposer.com
将会在Code文件夹中larabbs/composer.json的末尾自动添加镜像的配置信息(你也可以自己手工添加):
"repositories": {
"packagist": {
"type": "composer",
"url": "https://packagist.phpcomposer.com"
}
}
可以把composer.json中修改一下
"packagist": {
"type": "composer",
"url": "https://packagist.phpcomposer.com"
}
修改成
"packagist": {
"type": "composer",
"url": "https://packagist.laravel-china.org" //此处为更改成功的关键,将镜像地址更改,之前的地址下载资源有问题
}
下方要求添加两条语句,在此处直接添加
"config": {
"preferred-install": "dist",
"sort-packages": true,
"optimize-autoloader": true
},
"minimum-stability" : "dev", -->需要添加
"prefer-stable" : true -->需要添加
再次在 ~/code/larabbs $ composer require dingo/api 开始下载 当出现Package manifest generated successfully时,下载完成
dingo/api 已经发布了正式版本,目前最新版本为
2.2.3
,但是存在一个较为严重的 Bug:https://github.com/dingo/api/issues/1645 ,在 Bug 修复之前还是会使用指定的版本。
发现报错了:
Your requirements could not be resolved to an installable set of packages.
dingo 的文档中有说明,现在这个包还处在开发阶段,没有一个稳定的 release 版本,dingo/api
依赖的 dingo/blueprint
与 phpunit
都依赖了 phpdocumentor/reflection-docblock
但是依赖的版本不同,导致出现了冲突。但是我们发现 dingo/blueprint
的开发版本 dev-master
解决了冲突,可以正常安装,所以我们修改一下 composer.json
:
composer.json
.
.
.
"config": {
"preferred-install": "dist",
"sort-packages": true,
"optimize-autoloader": true
},
"minimum-stability" : "dev",
"prefer-stable" : true
}
增加了两句:
"minimum-stability" : "dev"
—— 设定的最低稳定性的版本为dev
也就是可以依赖开发版本的扩展包;"prefer-stable" : true
—— Composer 优先使用更稳定的包版本。
我们设定项目可以依赖开发版本扩展包,但是当依赖有稳定版本可以安装的时候,优先安装稳定版。
再次执行命令安装:
Package manifest generated successfully. 看到 dingo/api
已经成功安装了。
2. 配置
先将 dingo 的配置文件 publish 出来
$ php artisan vendor:publish
执行成功后,我们会在 config
目录先看到 api.php
文件,打开文件我们可以看到所有的配置都是可以再 env 中修改的,下面我们主要讲解一下我们需要用到的配置
-
API_STANDARDS_TREE 和 API_SUBTYPE
上一节我们已经讨论了 API 版本的重要性,推荐的做法是使用 Accept 头来指定我们需要访问的 API 版本。
API_STANDARDS_TREE
和API_SUBTYPE
这两个配置就和版本控制有关Accept: application/<API_STANDARDS_TREE>.<API_SUBTYPE>.v1+json
API_STANDARDS_TREE 有是三个值可选
x
本地开发的或私有环境的prs
未对外发布的,提供给公司 app,单页应用,桌面应用等vnd
对外发布的,开放给所有用户
对于我们的项目,暂时可以选择
prs
。API_STANDARDS_TREE=prs
API_SUBTYPE 一般情况下是我们项目的简称,我们的项目叫
larabbs
API_SUBTYPE=larabbs
所以我们可以通过如下方式来访问不同版本的 API
访问 v1 版本 Accept: application/prs.larabbs.v1+json 访问 v2 版本 Accept: application/prs.larabbs.v2+json
-
API_PREFIX 和 API_DOMAIN
对于一个项目,通过前缀或者子域名的方式来区分开 API 与 Web 等页面访问地址是十分有必要的。假如正式上线的项目地址为www.larabbs.com
,我们可以为 API 添加一个前缀API_PREFIX=api
通过
www.larabbs.com/api
来访问 API。
或者有可能单独配置一个子域名api.larabbs.com
API_DOMAIN=api.larabbs.com
通过 api.larabbs.com 来访问 API。
特别要注意的是:前缀和子域名,两者有且只有一个。本教程选择
API_PREFIX
的方式。 - API_VERSION
默认的 API 版本,当我们没有传Accept
头的时候,默认访问该版本的 API。一般情况下配置 v1 即可。 - API_STRICT
是否开启严格模式,如果开启,则必须使用Accept
头才可以访问 API,也就是说直接通过浏览器,访问某个 GET 调用的接口,如https://api.larabbs.com/users
,将会报错。必须使用 Postman 之类的调试工具,设置Accept
后才可访问。可以根据需求开启,默认情况下为 false。 - API_DEBUG
测试环境,打开 debug,方便我们看到错误信息,定位错误。
最后我们的配置如下
.env. . . API_STANDARDS_TREE=prs API_SUBTYPE=larabbs API_PREFIX=api API_VERSION=v1 API_DEBUG=true
注意 .env
文件是不会提交到版本库中的,所以可以将以下代码复制到 .env.example
中,提交到版本库,方便其他环境部署。
.env.example
.
.
.
# dingo config
API_STANDARDS_TREE=
API_SUBTYPE=
API_PREFIX=
API_VERSION=
API_DEBUG=
3. 版本控制
最后将修改的文件加入到版本控制中。
$ git add -A
$ git commit -m "add dingo"
1. 什么是 PostMan?
PostMan 是一款跨平台,方便我们调试 API 的工具,你可以在 PostMan 官网()Postman 下载,或者使用 百度网盘下载(postman客户端_免费高速下载|百度网盘-分享无限制)。
打开 PostMan 界面如上图所示,大体可以分为四个区域,左侧接口集合
类似文件夹的功能,我们可以把我们的接口保存在这里,右侧上中下分别是请求地址
,请求参数
和请求结果
。
随便找个接口调用一下,这个是国家气象局提供的 天气预报接口,将其填入『请求地址』处:
可以在左侧的区域,保存接口,我们新建一个 Larabbs
目录。
在左侧我们看到了新建的文件夹 Larabbs,然后保存接口。
可以给接口起个名字,填写响应的描述。
我们可以将已经调试好的接口保存下来,方便下次调试,PostMan 也为我们提供了导出导入接口的功能,方便分享接口给他人,当然 PostMan 也为付费用户提供了更多方便的功能,大家有需要的可以取官网了解,目前免费版的功能已经满足我们的需求。
2. 编写调试接口
已经安装好了 DingoApi,接下来写两个路由,尝试一下 PostMan。Laravel 5.5 已经为我们准备好了 api 的路由文件,routes/api.php
。打开这个文件,可以看到 Laravel 为我们写好的例子。
<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
Route::middleware('auth:api')->get('/user', function (Request $request) {
return $request->user();
});
由于我们使用的是 DingoApi 的路由,所以将文件替换为如下内容
<?php
use Illuminate\Http\Request;
/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/
$api = app('Dingo\Api\Routing\Router');//路由需要使用 Dingo\Api\Routing\Router 注册
$api->version('v1', function($api) {//version方法用于版本控制 参数1:版本名称,version是不同版本的路由
$api->get('version', function() {
return response('this is version v1');
});
});
$api->version('v2', function($api) {
$api->get('version', function() {
return response('this is version v2');
});
});
路由需要使用 Dingo\Api\Routing\Router
注册,写法同 Laravel 的路由,大家应该比较熟悉。DingoApi 提供了 version 方法,用来进行版本控制,第一个参数是版本名称,version 中的就是不用版本的路由。我们在 v1 和 v2 两个版本中,都注册了 verison 路由,但是响应不同,现在通过 PostMan 来试试。
还记得我们配置的是 API_PREFIX=api
,所以需要 http://larabbs.test/api/version
来访问。
我们得到了 v1 版本的结果,this is version v1
,因为配置了默认的版本是 v1 API_VERSION=v1
。但是下面却多出来了一些 html,这其实是 Larabbs 安装的 laravel-debugbar
,做接口相关的开发,不涉及网页,所以我们现在来关闭它。
打开 config/debugbar.php,可以看到一行配置 enabled
。
'enabled' => env('APP_DEBUG', false),
该配置控制 debugbar
的开启和关闭,但是它现在是随着 APP_DEBUG
的改变而改变的,我们可以把代码修改为
将
'enabled' => env('APP_DEBUG', false),
更改为
'enabled' => env('DEBUGBAR_ENABLE', false),
这样我们本地测试环境 APP_DEBUG
依然可以是 true,同时也可以关闭 laravel-debugbar
。默认 laravel-debugbar
是关闭的,我们再次通过 PostMan 访问该接口。
一切正常了,记得当我们调试网页,需要用到 laravel-debugbar
的时候,在 .env 中增加 DEBUGBAR_ENABLE=true
即可。
下面我们在Headers中增加KEY:Accept VALUE:application/prs.larabbs.v2+json
来访问 v2 版本的 version。
此时Postman出现错误:Unable to round-trip http request to upstream: lookup larabbs.test on 127.0.0.1:53: no such host
当使用网页http://larabbs.test/api/version 可以访问到this is version v1
解决方案:将蓝灯关闭即可,蓝灯影响
成功输出:this is version v2
提示:由于路由被 DingoApi 接管了,如果将来部署上线后你需要缓存路由,可以使用
php artisan api:cache
代替php artisan route:cache
,本地测试请不要执行这个命令。
3. PostMan 的环境变量功能
PostMan 为我们提供了环境变量的功能,通过切换不同的值,可以使用不同的环境,比如 host 我们就可以做成变量,这样当我们某一天切换了本地调试的域名,比如由 larabbs.app 切换为 larabbs.test 时,不用去每个接口中修改,只需要修改变量即可。
在右上角设置图标——Manager Environment——Add——Add Environment:填入环境名称——Key:用于替换域名所起的名称——Value:被替换的域名名称
如:Key:host Value:larabbs.test 在使用时,https://{host}/api/version
比如上面,我们增加了一个环境变量 host
,然后我们选择对应的环境,将域名替换为 {{host}}
,PostMan url 中,使用双括号表示变量。同样能访问得到正确的结果。
4. 代码版本控制
最后,我们将测试的路由代码恢复:
$ git checkout routes/api.php
然后将 config/debugbar.php
提交:
$ git add -A
$ git commit -m "fix laravel-debugbar"