webiny/rest

Webiny REST 组件

v1.6.1 2017-09-29 08:12 UTC

README

一个简单但强大的 REST 库,不会干扰。

安装组件

安装组件的最佳方式是使用 Composer。

composer require webiny/rest

要获取该软件包的额外版本,请访问 Packagist 页面

用法

一些内置功能

  • 支持 GETPOSTPUTPATCHDELETE 请求
  • 资源命名(通过 @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。支持请求类型有GETPOSTPUTPATCHDELETE。它们不区分大小写。

@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 有多个活动版本。在部署新版本但仍然需要支持旧版本时,这非常有用。

您还有两个版本别名,这使得事情更加简单。别名实际上是一个指向实际版本的指针。两个可用的别名是 latestcurrent。如果有人请求您的 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'
        ];
    }
}

接口将告诉您实现上述三个方法,getLatestVersiongetCurrentVersiongetAllVersions。最重要的方法是 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注解中定义的值。该方法应返回truefalse,以允许或拒绝用户访问。

注意,您仍需要在您的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: 1
  • restGetPerPage: 10
  • restGetSortField:Title
  • restGetSortDirection: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/ 文件夹内的配置文件。