Spiral provides interceptors for WebSockets services that allow you to intercept and modify requests and responses at various points in the request lifecycle.
See more
Read more about interceptors in the Framework — Interceptors section.
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 and provides the
user's identity to the service. Where authToken
is the name of the field in the request data that contains the
authentication token.
namespace App\Entrypoint\Centrifugo\Interceptor;
use Psr\EventDispatcher\EventDispatcherInterface;
use RoadRunner\Centrifugo\Request\RequestInterface;
use Spiral\Auth\ActorProviderInterface;
use Spiral\Auth\AuthContext;
use Spiral\Auth\AuthContextInterface;
use Spiral\Core\CoreInterceptorInterface;
use Spiral\Core\CoreInterface;
use Spiral\Core\ScopeInterface;
use Spiral\Prototype\Traits\PrototypeTrait;
final class AuthenticatorInterceptor implements CoreInterceptorInterface
{
use PrototypeTrait;
public function __construct(
private readonly ScopeInterface $scope,
private readonly ActorProviderInterface $actorProvider,
private readonly ?EventDispatcherInterface $eventDispatcher = null,
) {
}
public function process(string $controller, string $action, array $parameters, CoreInterface $core): mixed
{
$request = $parameters['request'];
\assert($request instanceof RequestInterface);
$authToken = $request->getData()['authToken'] ?? null;
if (!$authToken || !$token = $this->authTokens->load($authToken)) {
$request->error(403, 'Unauthorized');
return null;
}
$auth = new AuthContext($this->actorProvider, $this->eventDispatcher);
$auth->start($token);
return $this->scope->runScope([
AuthContextInterface::class => $auth,
], fn () => $core->callAction($controller, $action, $parameters));
}
}
And example of how to use it in a service:
namespace App\Endpoint\Centrifugo;
use App\Database\User;
use RoadRunner\Centrifugo\Payload\ConnectResponse;
use RoadRunner\Centrifugo\Request\Connect;
use RoadRunner\Centrifugo\Request\RequestInterface;
use Spiral\Prototype\Traits\PrototypeTrait;
use Spiral\RoadRunnerBridge\Centrifugo\ServiceInterface;
final class ConnectService implements ServiceInterface
{
use PrototypeTrait;
/** @param Connect $request */
public function handle(RequestInterface $request): void
{
try {
$user = $this->auth->getActor();
$request->respond(
new ConnectResponse(
user: (string)$user->getId(),
data: ['user' => $user->jsonSerialize()],
channels: ['chat'],
),
);
} catch (\Throwable $e) {
$request->error($e->getCode(), $e->getMessage());
}
}
}
The following example shows how to create an interceptor that handles errors.
namespace App\Entrypoint\Centrifugo\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:
namespace App\Endpoint\Centrifugo;
/**
* @param Connect $request
*/
public function handle(RequestInterface $request): void
{
if (!$this->auth->isAuthenticated()) {
thorw new \Exception('Unauthorized', 403);
}
$request->respond(
...
);
}
To use this interceptor, you will need to register them in the configuration file app/config/centrifugo.php
.
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.