webiny / rest
Webiny REST 组件
Requires
- php: ^7
- webiny/annotations: ~1.6
- webiny/cache: ~1.6
- webiny/config: ~1.6
- webiny/http: ~1.6
- webiny/router: ~1.6
- webiny/security: ~1.6
- webiny/std-lib: ~1.6
Requires (Dev)
- mybuilder/phpunit-accelerator: dev-master
- phpunit/phpunit: ~6
README
一个简单但强大的 REST 库,不会干扰。
安装组件
安装组件的最佳方式是使用 Composer。
composer require webiny/rest
要获取该软件包的额外版本,请访问 Packagist 页面。
用法
一些内置功能
- 支持 GET、POST、PUT、PATCH 和 DELETE 请求
- 资源命名(通过
@rest.url
注解) - 集成的版本管理系统
- 有效的速率控制机制
- 服务通过注解配置
- 内置缓存,使用 Webiny 框架缓存组件
- 内置 ACL,使用 Webiny 框架安全组件
- 内置路由,使用 Webiny 框架路由组件
- 良好的调试选项
- 漂亮的格式化 JSON 输出(仅在开发模式下)
- CRUD 支持
用法示例
// create REST instance for the given configuration and the API class $rest = new \Webiny\Component\Rest\Rest('InternalApi', '\MyApp\Services\TestService'); // process the request and send the output to browser $rest->processRequest()->sendOutput(); // simple as that...
配置和依赖项
这是一个示例配置
Rest: ExampleApi: CompilePath: /var/tmp Router: Class: \Foo\Bar\MyServices\{foo}\{bar} Path: /services/{test}/{foo}/{mock}/{bar} Normalize: true MiddlewareApi: CompilePath: /var/tmp Middleware: \My\Custom\Handler Router: Class: \Foo\Bar\MyServices\{foo}\{bar} Path: /services/{test}/{foo}/{mock}/{bar} Normalize: true SomeOtherApi: CompilePath: /var/www/Cache/Rest Cache: someCacheService Security: Role: ROLE_ANONYMOUS Firewall: Admin RateControl: Limit: 60 Interval: 1 # in minutes Penalty: 10 # in minutes Environment: production
如你所见,你可以有多个 REST 配置。一个配置所需的最小值只是 CompilePath
的定义。
配置参数
CompilePath
这是 REST 组件存储编译文件的文件夹的绝对路径。
了解更多:当你注册一个类或在 API 命名中注册一个“服务”时,组件将遍历该类及其所有方法和它们的注解,然后根据不同的规则进行评估,以定义服务行为。所有这些都保存在一个数组中,实际上保存在一个我们称为编译缓存文件的文件中。
路由器
这是一个可选设置,它告诉 Rest 组件如何将当前 URL 转换为获取服务类名。然而,要触发该机制,URL 必须匹配 Path
参数。括号中的变量将作为模式匹配 URL 的某些部分。这些匹配可以用来创建 Class
名称。你可以在 路由和访问 API 部分中了解更多关于路由的信息。
缓存
如前所述,该组件使用 Webiny 框架的 缓存组件。Cache
的值应指向一个定义的缓存服务。
缓存用于两种不同的操作,一种是为存储结果提供缓存层,另一种是如果希望使用速率控制机制,则缓存是必需的。
安全
安全部分为 REST API 提供了一个授权和认证层。它依赖于 安全 组件。
配置包含两个参数
Firewall
:在安全组件配置中注册的防火墙的名称。角色
:这是所有用户访问API所需默认角色。您可以在注解中覆盖所需的角色。如果您不想强制角色,您可以从配置中删除该部分,或将它设置为ROLE_ANONYMOUS
,这将允许所有用户访问,除非它被方法或类注解覆盖。
如果用户没有访问权限,将返回一个403 - 禁止访问响应。
RateControl
速率控制是一种保护机制,防止任何人以在短时间内发送过多请求的方式滥用您的REST API。
使用速率控制,您可以设置以下参数
Limit
:特定IP在间隔内可以发送多少请求Interval
:经过多少分钟后应该重置限制Penalty
:如果IP达到限制,我们将封锁IP多长时间
如果达到限制并激活惩罚,组件将返回429 - 请求过多响应,直到限制恢复。
请注意,速率控制机制要求您在REST配置中指定Cache
。
环境
环境
属性值可以是'生产'或'开发'。区别在于在开发模式下,我们不断重建编译的缓存文件,我们还输出特殊的调试响应头,JSON输出使用格式化。
中间件
如果设置了此参数,它应该指向实现MiddlewareInterface
的类。这使您能够控制REST服务方法的执行。组件执行服务之前和之后的所有操作。组件将RequestBag
传递给中间件,您可以选择如何执行方法,可能进行额外的检查或自定义业务逻辑。无论您做什么,返回值都将直接传递给REST组件,并用作服务结果。
类和方法注解
/** * @rest.role ROLE_EDITOR */ class FooService { /** * @rest.method get * @rest.default * @rest.ignore * @rest.cache.ttl 100 * @rest.header.cache.expires 3600 * @rest.header.status.success 200 * @rest.header.status.errorMessage No Author for specified id. * @rest.rateControl.ignore * @rest.url some/custom/url/{param1}/param2/{param2}/other/{param3} * * @param integer $param1 Some param. * @param string $param2 Other param. * @param string $param3 Other param. */ function fooMethod($param1, $param2 = "default", $param3 = "p3def") { ... } }
注解是描述对象某些属性的一种方式。通过注解,您可以配置服务的行为。所有REST组件注解都有一个rest
命名空间。
所有注解都可以在类级别上定义,使其默认为方法,并在方法级别上可以覆盖它们。没有必需的注解。
当一个类继承另一个类时,如果子类实际上是您的REST API,父类将自动将其类和方法的注解传递给子类,因此,在覆盖方法时,请确保如果需要,也覆盖注解。
以下是可以使用的注解
@rest.method
/** * @rest.method get */
定义服务可以通过哪些HTTP方法访问。如果没有定义,默认设置为get
。支持请求类型有GET、POST、PUT、PATCH和DELETE。它们不区分大小写。
@rest.default
/** * @rest.default */
定义此方法是定义的@rest.method
请求类型的默认方法。例如,如果您将@rest.method
设置为post
,并且该方法带有@rest.default
标记,并且您只对服务名称进行POST请求,而不带方法,则将触发定义的POST请求的默认方法。
@rest.ignore
/** * @rest.ignore */
此标志告诉组件忽略该方法,并且它不是服务的一部分。通常用于某些内部方法。
@rest.cache.ttl
/** * @rest.cache.ttl 100 */
标记从该方法返回的结果可以在指定的时间内进行缓存。时间以秒为单位。请注意,此功能要求您在配置中定义Cache
服务。
@rest.header
在header
部分中有几个选项可以控制
cache.expires
:定义组件将设置在发送到浏览器的Expires
头中的ttl。如果您没有设置它,它将设置为'-1',告诉浏览器始终从服务器获取新鲜内容。status.success
:请求成功时应该返回什么响应状态码。默认情况下返回 200 - OK,对于 POST 请求,则例外,返回 201 - Created。status.errorMessage
:定义一个自定义错误消息,该消息将与响应状态码一起附加。
@rest.role
/** * @rest.role ROLE_EDITOR */
定义一个方法只能被具有特定级别或更高访问级别的用户访问。
此注解要求您在配置中定义 Security
部分。
@rest.rateControl.ignore
/** * @rest.rateControl.ignore */
此标志表示不会对该方法应用速率控制。
@rest.url
/** * @rest.url some/custom/url/{param1}/param2/{param2}/other/{param3} */
此注解通过指定一个用于 URL 匹配的自定义 URL 来提供资源命名功能,而不是使用 方法名
。 注意:当使用资源命名时,您不能在该方法上使用 @rest.default
注解,并且也不能指定可选参数。
路由和访问 API
这是一个 Router
配置示例。
Rest: ExampleApi: Router: Class: \Foo\Bar\MyServices\{foo}\{bar} Path: /services/{test}/{foo}/{mock}/{bar} Normalize: true
该配置接受以下参数
类
此参数告诉 Router
如何实现从 URL 和 Path
中匹配的参数,以获取用于调用 Rest 服务的类名。
路径
路径是一个组件尝试与当前 URL 匹配的 URL 模式。如果匹配成功,则使用匹配的参数来创建 类
名。所有模式都在花括号 {foo}
内部,并使用 ([\w-]+)
正则表达式模式进行匹配。
规范化
这是一个可选功能。它告诉组件是否应该规范化匹配的参数。在这种情况下,在 "normalize" 下,我们将参数值如 some-application
转换为 SomeApplication
。
示例
假设您已配置了上述示例。以下 URL 将产生示例类名。
URL: http://www.hats.com/services/my-app/some-longer-name/test/pac-man
类: \Foo\Bar\MyServices\SomeLongerName\PacMan
一些先决条件
您需要做的是在您的 Web 服务器上设置所有请求都路由到单个文件,例如 rest.php
。在该文件中调用静态 iniRest
方法并传入 API 名称。该方法返回一个新的 Rest 实例,您可以调用该实例的 processRequest
方法以触发服务调用。如果 URL 不匹配,则返回布尔值 false
。
try{ $rest = Rest::initRest('ExampleApi'); if($rest){ $rest->processRequest()->sendOutput(); } }catch (RestException $e){ // handle the exception }
接口
组件提供了一些接口,您可以在您的 API 类上实现这些接口以获得对组件某些方面的更多控制。
所有接口都在命名空间 Webiny\Component\Rest\Interfaces\
下。
版本和版本接口
组件为您提供了对 API 进行版本化的选项,这意味着您可以为单个 API 有多个活动版本。在部署新版本但仍然需要支持旧版本时,这非常有用。
您还有两个版本别名,这使得事情更加简单。别名实际上是一个指向实际版本的指针。两个可用的别名是 latest
和 current
。如果有人请求您的 API,并且他未定义版本,则他将被指向 current
版本,然后将其映射到实际版本。
为了实现版本化功能,您需要在您的类上实现 Webiny\Component\Rest\Interfaces\VersionInterface
。这看起来像这样
class FooService implements \Webiny\Component\Rest\Interfaces\VersionInterface { static public function getLatestVersion(){ return '2.0'; } static public function getCurrentVersion(){ return '1.0'; } static public function getAllVersions(){ return [ '1.0' => 'FooService', '2.0' => 'FooServiceNew', '2.1' => 'FooServiceBetaInTesting' ]; } }
接口将告诉您实现上述三个方法,getLatestVersion
、getCurrentVersion
和 getAllVersions
。最重要的方法是 getAllVersions
,它返回一个数组,包含支持的版本,键是版本号(格式为 X.Y),值是类名。这是用于处理请求的类。
请注意,只有具有'main'类的组件需要注册到组件 $rest = new \Webiny\Component\Rest\Rest('InternalApi', 'FooService');
。此外,主类是唯一需要实现接口的类,这使得维护变得更加容易。
如何访问特定版本
默认情况下,所有用户都将指向current
版本。要向特定版本发送请求,您需要添加请求头。请求头名称为X-Webiny-Rest-Version
,其值为版本。对于版本,您可以发送特定的版本号或别名。所有具有此头部的请求都将映射到该具体版本。
// 将请求指向版本2.1
X-Webiny-Rest-Version: 2.1
AccessInterface
如果您想实现自己的安全层,可以实现Webiny\Component\Rest\Interfaces\AccessInterface
。
class FooService implements \Webiny\Component\Rest\Interfaces\AccessInterface { public function hasAccess($role) { // do you processing here } }
该接口将要求您定义hasAccess
方法。此方法仅接受一个参数$role
。此参数包含在@rest.role
注解中定义的值。该方法应返回true
或false
,以允许或拒绝用户访问。
注意,您仍需要在您的REST配置中定义Security
部分。配置应仅包含默认所需角色。不要定义Firewall
属性。
Rest: SomeOtherApi: CompilePath: /var/www/Cache/Rest Security: Role: ROLE_ANONYMOUS
ROLE_ANONYMOUS
允许未经身份验证的用户调用服务。您可以在类和方法级别上使用@rest.role
注解覆盖所需角色。
CacheKeyInterface
Rest组件默认从以下参数创建缓存键
- URL路径
- 查询参数
- HTTP方法
- POST参数
- 有效负载参数
- API版本(我们使用实际版本号,而不是别名,如current和latest)
实现此接口以定义自己的缓存键生成方法。一些常见的用例是根据某些cookie或token生成缓存键。请注意,您仍应包括URL、查询参数和HTTP方法。始终考虑生成缓存键的实际时间不应比获取无缓存数据的时间更长。
实现看起来像这样
class FooService implements \Webiny\Component\Rest\Interfaces\CacheKeyInterface { public function getCacheKey($role) { // return your generated key } }
请注意,返回的键是“原样”使用的,不会附加任何内容,也不会进行散列,因此请确保返回的键具有合适的大小。
CrudInterface
通过实现此接口,您将获得下表中描述的基本CRUD方法和行为
RestTrait
在实践中,您通常需要使用分页、排序等类似功能,这不适合将它们作为参数放入方法中。最佳方法是使用查询参数。RestTrait
为您提供了辅助函数和建议。
在特性中,您将找到以下方法
restGetPage
:返回_page
查询参数的值restGetPerPage
:返回_perPage
查询参数的值(具有内置的1,000限制)restGetSortField
:从_sort
查询参数返回排序字段名称restGetSortFields
:返回排序字段数组,从_sort
查询参数解析而来restGetSortDirection
:从_sort
查询参数返回排序方向restGetFields
:返回_fields
查询参数的值
让我们看看如果查看此URL,将返回哪些值:http://api.example.com/my-service/get-pages/?_page=1&_perPage=10&_sort=+Title&_fields=id,title,author,slug
返回的值如下
restGetPage
: 1restGetPerPage
: 10restGetSortField
:TitlerestGetSortDirection
:1(如果字段名前有'-',则该函数将返回-1)restGetSortFields
:['Title' => 1]restGetFields
:id,title,author,slug
返回值
组件返回JSON响应,如下所示
{ "data": "this is my result" }
您的结果始终封装在 data
属性中。
如果发生错误,将省略 data
属性,您将获得包含错误信息的响应,如下所示
{ "errorReport": { "message": "This is an error.", "description": "Some custom error description." } }
您还可以添加额外的错误条目
{ "errorReport": { "message": "This is an error.", "description": "Some custom error description.", "errors": [ { "message": "This is an additional error message.", "field": "This is a custom error field." }, { "message": "Another error", "code": "23a33" } ] } }
抛出错误
当您需要抛出错误时,最佳方式是使用 RestErrorException 类。
class FooService { public function testError() { $error = new \Webiny\Component\Rest\RestErrorException("This is an error.", "Some custom error description."); $error->addError(['message'=>'This is an additional error message.', 'field'=>'This is a custom error field.']); $error->addError(['message'=>'Another error', 'code'=>'23a33']); throw $error; } }
调试
组件将在响应头和响应体的 debug
部分返回额外的调试信息。附加头信息如下
X-Webiny-Rest-Class
:所使用的 API 类的名称(根据版本了解使用了哪个类很有用)X-Webiny-Rest-ClassVersion
:实际的 API 版本X-Webiny-Rest-Method
:使用了哪种 HTTP 请求方法X-Webiny-Rest-RateControl-Limit
:速率控制的限制(在生产模式下也存在)X-Webiny-Rest-RateControl-Remaining
:达到限制前的剩余请求数量(在生产模式下也存在)X-Webiny-Rest-RateControl-Reset
:表示速率控制限制刷新日期的 Unix 时间戳X-Webiny-Rest-RequestedRole
:如果方法需要一些特定角色,则存在
以下是一个典型的输出示例
X-Webiny-Rest-Class:TestRestApiServiceNew X-Webiny-Rest-ClassVersion:2.0 X-Webiny-Rest-Method:GET X-Webiny-Rest-RateControl-Limit:10 X-Webiny-Rest-RateControl-Remaining:8 X-Webiny-Rest-RateControl-Reset:1408414512 X-Webiny-Rest-RequestedRole:SECRET_ROLE
编译器缓存
组件读取 API 类并创建一个数组,该数组包含有关该类中包含的 API 服务的不同信息。根据组件环境,该数组将被保存。如果环境是 development
,则缓存的数组将存储在静态数组中的内存中。如果环境是 production
,则数组将使用 CompilePath
写入磁盘。
如果您想创建自己的编译器缓存类,例如写入 Redis 数据库,您只需创建一个类并实现 \Webiny\Component\Rest\Compiler\CacheDrivers\CacheDriverInterface
,然后在 Rest 组件配置中定义类的路径,如下所示
Rest: ExampleApi: CompilerCacheDriver: '\Vendor\Namespace\Class'
资源
要运行单元测试,您需要使用以下命令
$ cd path/to/Webiny/Component/Rest/
$ composer.phar install
$ phpunit
请确保更新 Test/Mocks/
文件夹内的配置文件。