Spiral Framework provides interceptors for WebSockets services that allow you to intercept and modify requests and responses at various points in the request lifecycle.
They are typically used to add cross-cutting functionality such as logging, authentication, or monitoring to the server.
The following example shows how to create an interceptor that checks the user's authentication token.
<?php
declare(strict_types=1);
namespace App\Centrifuge\Interceptor;
use RoadRunner\Centrifugo\Request\RequestInterface;
use Spiral\Auth\TokenStorageInterface;
use Spiral\Core\CoreInterceptorInterface;
use Spiral\Core\CoreInterface;
final class AuthInterceptor implements CoreInterceptorInterface
{
public function __construct(
private readonly TokenStorageInterface $tokenStorage,
) {
}
public function process(string $controller, string $action, array $parameters, CoreInterface $core): mixed
{
\assert($parameters['request'] instanceof RequestInterface);
$userId = null;
$authToken = $parameters['request']->getData()['authToken'] ?? null;
if ($authToken && $token = $this->tokenStorage->load($authToken)) {
$userId = $token->getPayload()['userID'] ?? null
}
if(!$userId) {
$parameters['request']->disconnect('403', 'Connection is not allowed.');
return;
}
// Adds the user id to the request as an attribute.
$parameters['request'] = $parameters['request']->withAttribute(
'user_id',
$token->getPayload()['userID'] ?? null
);
return $core->callAction($controller, $action, $parameters);
}
}
And example of how to use it in a service:
/**
* @param Connect $request
*/
public function handle(RequestInterface $request): void
{
try {
$request->respond(
new ConnectResponse(
user: (string) $request->getAttribute('user_id'),
)
);
} catch (\Throwable $e) {
$request->error($e->getCode(), $e->getMessage());
}
}
The following example shows how to create an interceptor that handles errors.
<?php
declare(strict_types=1);
namespace App\GRPC\Interceptor;
use Spiral\Core\CoreInterceptorInterface;
use Spiral\Core\CoreInterface;
use Spiral\Exceptions\ExceptionReporterInterface;
use Spiral\RoadRunner\GRPC\Exception\GRPCException;
use Spiral\RoadRunner\GRPC\Exception\GRPCExceptionInterface;
final class ExceptionHandlerInterceptor implements CoreInterceptorInterface
{
public function __construct(
private readonly ExceptionReporterInterface $reporter
) {
}
public function process(string $controller, string $action, array $parameters, CoreInterface $core): mixed
{
try {
\assert($parameters['request'] instanceof RequestInterface);
return $core->callAction($controller, $action, $parameters);
} catch (\Throwable $e) {
$this->reporter->report($e);
$request->error($e->getCode(), $e->getMessage());
}
}
}
After that, you don't need to code into try/catch blocks in your services:
/**
* @param Connect $request
*/
public function handle(RequestInterface $request): void
{
$request->respond(
new ConnectResponse(
user: (string) $request->getAttribute('user_id'),
)
);
}
To use this interceptor, you will need to register them in the configuration file app/config/centrifugo.php
.
<?php
declare(strict_types=1);
use RoadRunner\Centrifugo\Request\RequestType;
use App\Centrifuge;
return [
'services' => [
//...
],
'interceptors' => [
RequestType::Connect->value => [
Centrifuge\Interceptor\AuthInterceptor::class,
],
//...
'*' => [
Centrifuge\Interceptor\ExceptionHandlerInterceptor::class,
Centrifuge\Interceptor\TelemetryInterceptor::class,
],
],
];
You can register interceptors for specific requests or for all requests using the *
wildcard.