Laravel 的 JsonRpc 服务器包


README

Packagist Packagist build Psalm coverage Psalm level Coverage Status

描述

JsonRpc 服务器 - JsonRpc 2.0 规范的实现。

支持的版本

  • Laravel >= 9.0
  • PHP 8.1 | 8.2

支持

  • 通过通知方式调用远程方法,例如:名称控制器_名称方法,或者通过将逻辑分割到多个资源入口点
  • 在一个请求中调用多个远程方法
  • 按名称传递控制器方法的参数
  • 将参数映射到 DTO
  • 使用在头部传递的令牌进行身份验证
  • 按 IP 地址进行访问控制
  • 对不同服务的访问控制方法 - ACL
  • 可以配置多个入口点,每个入口点有不同的 JsonRpc 服务器配置
  • 通过 Middleware 机制扩展功能
  • 缓存所有路由以提高性能

安装

通过 composer 安装

composer require tochka-developers/jsonrpc

Laravel

对于 Laravel,可以发布所有包的配置

php artisan vendor:publish

要仅发布此包的配置,可以使用 tag 选项

php artisan vendor:publish --tag="jsonrpc-config"

配置入口点

在您的 route.php 中编写代码

\Route::post('/api/v1/public/jsonrpc', function (\Illuminate\Http\Request $request) {
    return \Tochka\JsonRpc\Facades\JsonRpcServer::handle($request->getContent());
});

如果计划在入口点地址中传递控制器名称,则路由应如下所示

Route::post('/api/v1/jsonrpc/{group}[/{action}]', function (Illuminate\Http\Request $request, $group, $action = null) {
    return \Tochka\JsonRpc\Facades\JsonRpcServer::handle($request->getContent(), 'default', $group, $action);
});

配置

return [
    // можно настроить несколько разных конфигурация для разных точек входа
    // чтобы указать в роуте, какая именно конфигурация должна быть использована - передавайте ключ конфига вторым 
    // параметром в \Tochka\JsonRpc\Facades\JsonRpcServer::handle
    'default' => [
        // корневой путь к точке входа (т.е. без указания group и action, если они используются)
        // необходим для корректного формирования и кеширования списка роутов
        'endpoint' => '/api/v1/public/jsonrpc',
        
        // Тип формирования точки входа (получать или нет из конечного URI группу методов и метод
        'dynamicEndpoint' => ServerConfig::DYNAMIC_ENDPOINT_NONE,
    
        // Краткое описание сервера
        'summary' => 'Основная точка входа',
        
        // Полное описание сервера
        'description' => 'Основная точка входа',

        // Namespace, в котором находятся контроллеры
        'namespace'   => 'App\\Http\\Controllers\\Api\\',
        
        // Suffix для контроллеров
        'controllerSuffix' => 'Controller',

        // Разделитель для имен методов
        'methodDelimiter' => '_',
        
        // список Middleware, обрабатывающих запросы
        // описание middleware ниже
        'middleware' => [ //
            Tochka\JsonRpc\Middleware\LogMiddleware::class               => [
                // Канал лога, в который будут записываться все логи
                'channel' => 'default',
            ],
             Tochka\JsonRpc\Middleware\TokenAuthMiddleware::class         => [
                 'headerName' => 'X-Tochka-Access-Key',
                 // Ключи доступа к API
                 'tokens'     => [
                     'troll' => env('JSONRPC_KEY_TROLL', ''),
                 ],
             ],
             Tochka\JsonRpc\Middleware\AccessControlListMiddleware::class => [
                'acl' => [
                    '*'                              => '*',
                    FooController::class             => ['service'],
                    FooController::class . '@method' => ['service2'],
                ],
            ],
        ],
    ],
];

动态入口点

允许从最终 URI 获取一组方法和单个方法。

通过配置中的 dynamicEndpoint 参数进行设置。

所有用于设置此参数的常量都位于类 \Tochka\JsonRpc\Support\ServerConfig 中。以下列出了此参数的可能值

ServerConfig::DYNAMIC_ENDPOINT_NONE

入口点静态,所有控制器都位于同一个命名空间中

示例

  • uri: /api/v1/public/jsonrpc
  • jsonrpc 方法: test_ping
  • controller@method: \Default\Controller\Namespace\TestController@ping

ServerConfig::DYNAMIC_ENDPOINT_CONTROLLER_NAMESPACE

URI 中与指定入口点不同的所有内容都是控制器命名空间(组)的后缀

示例

  • uri: /api/v1/public/jsonrpc/foo/bar
  • jsonrpc 方法: test_ping
  • controller@method: \Default\Controller\Namespace\Foo\Bar\TestController@ping

ServerConfig::DYNAMIC_ENDPOINT_FULL_CONTROLLER_NAME

URI 的最后一个元素是控制器(操作)名称,指定入口点之前的所有元素是控制器命名空间(组)的后缀

示例

  • uri: /api/v1/public/jsonrpc/foo/bar
  • jsonrpc 方法: test_ping
  • controller@method: \Default\Controller\Namespace\Foo\BarController@test_ping

处理器(Middleware)

处理器允许在调用指定方法之前准备请求。处理器列表由 jsonrpc.middleware 参数指定。这是一个数组,其中必须按顺序列出处理器类。默认情况下,以下处理器可用

  • Tochka\JsonRpc\Middleware\LogMiddleware - 记录传入请求
  • Tochka\JsonRpc\Middleware\TokenAuthMiddleware - 通过在头部传递令牌进行服务身份验证
  • Tochka\JsonRpc\Middleware\ServiceValidationMiddleware - 通过其 IP 地址验证服务
  • Tochka\JsonRpc\Middleware\AccessControlListMiddleware - ACL - 不同服务对不同的 Jsonrpc 服务器方法和组进行访问的规则

此外,您可以使用自己的 Middleware 来实现任何其他目的(通过 BasicAuth 进行身份验证,额外的数据过滤/验证等)

令牌身份验证(TokenAuthMiddleware)

如果需要限制对服务的访问并使用令牌在头部进行身份验证,则需要此 Middleware。处理器配置需要以下参数

[
    'headerName' => 'X-Tochka-Access-Key',
    'tokens'     => [
        'service_foo' => env('JSONRPC_KEY_SERVICE_FOO', \Str::uuid()->toString()),
        'service_bar' => env('JSONRPC_KEY_SERVICE_BAR', \Str::uuid()->toString()),
]
  • headerName - 客户端应在其中传递令牌的头部名称
  • tokens - 服务客户端列表及其令牌

如果请求未包含此标题,或者包含的令牌不在列表中,客户端将返回错误。如果身份验证成功,客户端将被识别为 service_foo (service_bar),这将允许控制对方法的访问。如果禁用身份验证,客户端将被识别为 guest

按IP验证服务(ServiceValidationMiddleware)

如果您需要限制服务客户端可以发起请求的IP地址列表,请连接此中间件。将以下参数传递给处理器的配置

[
    'servers' => [
        'service_foo' => ['192.168.0.1', '192.168.1.5'],
        'service_bar' => '*',
    ],
]

在指定示例中,只有具有IP地址 192.168.0.1192.168.1.5 的客户端才能使用服务密钥 service_foo 进行身份验证。服务 service_bar 可以从任何IP地址进行身份验证。

方法访问控制(AccessControlListMiddleware)

如果启用了 AccessControlListMiddleware 处理器,则将对方法进行访问控制。将以下参数传递给处理器的配置

[
    'acl' => [
        '*'                                           => ['service1', 'service2'],
        'App\Http\TestController'                     => ['service1'],
        'App\Http\TestController@isActivationAllowed' => ['service2'],
    ]
]

日志记录 - LogMiddleware

用于日志记录传入请求的是 LogMiddleware。将以下参数传递给处理器的配置

[
    'channel'    => 'jsonrpc',
    'hideParams' => [
        UserController::class => ['password', 'old_password', 'new_password'],
    ],
]
  • channel - 日志记录通道。配置在 logging.php 中的任何通道
  • hideParams - 隐藏日志记录中指定的参数(在涉及机密数据的情况下)。

使用关联数组描述,其中键是要隐藏参数的目标方法,值是要隐藏的参数名称。键可以是

  • * - 规则适用于整个服务器(适用于所有控制器和方法)
  • App\Http\Controllers\UserController - 规则适用于指定控制器的所有方法
  • App\Http\Controllers\UserController@auth - 规则适用于指定控制器中的指定方法

路由(路由)

为了加速查找所需方法并正确地解析参数,包会生成所有路由及其参数描述的列表。

要输出所有可用路由,您可以使用 artisan 命令 jsonrpc:route:list

> php artisan jsonrpc:route:list
+---------+--------------+-----------------------------+----------------------------------------------------------------+
| Server  | Group:Action | JsonRpc Method              | Controller@Method                                              |
+---------+--------------+-----------------------------+----------------------------------------------------------------+
| default | @:@          | check_arrearsOfTaxes        | App\Http\Controllers\Api\CheckController@arrearsOfTaxes        |
| default | @:@          | check_bankrupt              | App\Http\Controllers\Api\CheckController@bankrupt              |
| default | @:@          | check_crime                 | App\Http\Controllers\Api\CheckController@crime                 |
+---------+--------------+-----------------------------+----------------------------------------------------------------+

每次传入请求时都会重新计算路由方案。为了避免这种情况,需要使用命令 jsonrpc:route:cache 缓存所有路由。构建的方案将保存到文件中,并在后续请求中使用。

在开发时请注意 - 当存在构建的缓存的方案时 - 代码中任何路由的变化都不会反映在请求执行上。

建议在部署到工作实例后立即执行路由缓存命令(类似于Laravel的config:cache命令)。

要清除缓存 - 执行命令 jsonrpc:route:clear。清除缓存后,路由将再次在每次传入请求时构建。

您还可以动态地将新路由添加到列表中

$route = new \Tochka\JsonRpc\DTO\JsonRpcRoute('default', 'my_dynamic_method');
$route->controllerClass = MyController::class;
$route->controllerMethod = 'methodName';

\Tochka\JsonRpc\Facades\JsonRpcRouter::add($route);

将参数映射到DTO并获取完整请求

默认情况下,所有来自JsonRpc请求的参数都按其名称传递到控制器方法的参数中。在此期间,建议使用参数类型化,以便JsonRpc服务器能够在输入时验证这些参数,并在类型不匹配的情况下返回验证错误。

如果指定了参数类型为某个类 - JsonRpc服务器将尝试创建该类的实例,并将请求对象中相应字段的所有公共字段值分配给这些字段。在此期间,也会应用类型验证规则。

您还可以使用数组作为类型。如果在此期间在PhpDoc中指定了数组内元素的特定类型 - JsonRpc服务器也将尝试将请求中所有元素转换为指定的类型(包括如果类型是其他类)。

需要注意的是,JsonRpc服务器不会将请求中的类型映射到PHP中指定的类型。也就是说,如果在请求中传递了一个int类型的值,而在参数中指定了string类型,则会抛出异常,并返回包含错误信息的响应。在这里,我们所说的映射是指JsonRpc服务器尝试正确地将JsonRpc请求对象的属性“映射”到特定类的字段上。

此外,您还可以指示JsonRpc服务器将整个JsonRpc请求映射到对象,而不尝试将请求的上层参数映射到控制器的方法参数。这在请求中存在大量上层参数,或者同一组参数被多个方法使用时非常有用。使用示例

use Tochka\JsonRpc\Annotations\ApiMapRequestToObject;

class ApiController extends BaseController
{
    /**
     * @ApiMapRequestToObject(parameterName="request")
     */
     #[ApiMapRequestToObject('request')]
    public function testMethod(MyDTOForRequest $request): bool
    {
        // ...
    }
}

在这种情况下,我们通过注解/属性指定,需要将整个JsonRpc请求映射到类MyDTOForRequest,并将该值作为参数传递给方法$request

请注意,如果使用注解/属性ApiMapRequestToObject为方法指定,则不再将JsonRpc请求的参数值传递给方法的其他参数。相反,将使用Laravel的DI容器获取实例并将其作为参数传递。

忽略控制器的公共方法

默认情况下,所有在配置的命名空间中找到的控制器的公共方法都会进入路由方案。如果您需要排除部分方法,请使用注解/属性ApiIgnoreApiIgnoreMethod

use Tochka\JsonRpc\Annotations\ApiIgnore;
use Tochka\JsonRpc\Annotations\ApiIgnoreMethod;

/**
 * Использование аннотации/атрибута @ApiIgnore для класса исключает все методы класса из маршрутизации
 * @ApiIgnore()
 */
 #[ApiIgnore]
class ApiController extends BaseController
{
    // ...
}

/**
 * Использование аннотации/атрибута @ApiIgnoreMethod для класса исключает указанные методы из маршрутизации
 * @ApiIgnoreMethod(name="methodNameFoo")
 * @ApiIgnoreMethod(name="methodNameBar")
 */
 #[ApiIgnoreMethod('methodNameFoo')]
 #[ApiIgnoreMethod('methodNameBar')]
class ApiController extends BaseController
{
    /**
    * Использование аннотации/атрибута @ApiIgnore для метода исключает указанный метод из маршрутизации
    * @ApiIgnore()
    */
    #[ApiIgnore]
    public function fooMethod()
    {
        // ...
    }
    
    public function barMethod()
    {
        // ...
    }
}

它是如何工作的

客户端发送一个有效的JsonRpc2.0请求

{
    "jsonrpc": "2.0", 
    "method": "client_getInfoById",
    "params": {
        "clientCode": "100500",
        "fromAgent" : true
    },
    "id": "service-ab34f8290cfa367dacb"
 }

JsonRpc服务器尝试查找指定的方法client_getInfoById。方法名被分割成两部分:控制器名称_方法名称。控制器通过指定的命名空间(参数jsonrpc.controllerNamespace)和指定的后缀(默认为Controller)来查找。对于我们的示例,服务器将尝试连接到类'App\Http\Controller\ClientController'。如果控制器不存在,则客户端将收到错误Method not found。在找到的控制器中调用getInfoById方法。

所有传递的参数都将按名称传递给方法。也就是说,在控制器中应该有一个getInfoById($clientCode, $fromAgent)方法。所有参数都会按类型进行验证(如果指定了类型)。此外,可以通过这种方式在方法中指定可选参数 -在这种情况下,不必在请求中传递它们,未传递的参数将使用方法中的默认值。如果未传递必要的参数,则客户端将收到错误。

一个请求中的多个调用

根据JsonRpc规范,在一个请求中可以调用多个方法。为此,需要将有效的JsonRpc2.0调用作为数组传递。每个调用的方法都将从相应的控制器调用,返回的结果将按照请求的顺序返回给客户端。

如果客户端传入了id参数,则响应中始终存在参数id。该参数还允许在客户端识别自己的请求。

JsonRpc文档

JsonRpc使用OpenRpc规范(https://spec.open-rpc.org/)进行文档编制。

您可以使用与当前版本兼容的jsonrpc-server包tochka-developers/openrpc来生成OpenRpc模式。

从v3更新到v4

  1. 将您的composer.json中的版本更改为"tochka-developers/jsonrpc": "^4.0"并更新包。

  2. jsonrpc.php配置中添加以下属性

  • endpoint
  • dynamicEndpoint
  • summary
  • description
  • controllerSuffix(默认值:Controller
  • methodDelimiter(默认值:_

有关参数和可能值的描述,请参阅第3节中的配置描述。更新生产版本应用程序的构建/部署脚本,添加构建和缓存路由的命令:php artisan jsonrpc:route:cache