HTTP interceptors

Sometimes it happens that you need to perform some additional checks before sending the request, for example, make sure you protect your form with CSRF or use authorization header, etc.

For that purpose there is a way to perform some actions before and after the request.

You can define the interceptor as a class that implements Viewi\Components\Http\Interceptor\IHttpInterceptor interface:

interface IHttpInterceptor
{
    function request(Request $request, IRequestHandler $handler);
    function response(Response $response, IResponseHandler $handler);
}

Request method

request is used for intercepting request before it get send to the server. It provides a current Request instance and a handler that you can use to either continue the request or reject it:

public function request(Request $request, IRequestHandler $handler)
{
    $handler->next($request);
    // OR
    $handler->reject($request);
}

Rejecting the request will prevent it from being sent to the server.

Also, HTTP client will proceed with error handler instead of success handler.

Response method

response is used for intercepting response after receiving it from the server. It provides a current Response instance and a handler that you can use to either continue response handling or reject it:

public function response(Response $response, IResponseHandler $handler)
{
    $handler->next($response);
    // OR
    $handler->reject($response);
}

Rejecting the response will prevent it from being sent to the server.

Also, HTTP client will proceed with error handler instead of success handler.

Interceptor Example

It is recommended to make interceptors as singletons

This example shows you how to send additional header in the request.

Also we will replace original response and set it to Access denied.

<?php

namespace Components\Services\Interceptors;

use Viewi\Components\Http\Interceptor\IHttpInterceptor;
use Viewi\Components\Http\Interceptor\IRequestHandler;
use Viewi\Components\Http\Interceptor\IResponseHandler;
use Viewi\Components\Http\Message\Request;
use Viewi\Components\Http\Message\Response;
use Viewi\DI\Singleton;

#[Singleton]
class ExampleInterceptor implements IHttpInterceptor
{
    public function request(Request $request, IRequestHandler $handler)
    {
        // modify/clone request
        $newRequest = $request->withHeader('X-Test-ID', 'mytoken');
        $handler->next($newRequest);
    }

    public function response(Response $response, IResponseHandler $handler)
    {
        // modify response
        $nextResponse = $response->withBody('Access denied')->withStatus(400);
        $handler->next($nextResponse);
    }
}

Using interceptors

Once you have defined your interceptor, you can use it with your HTTP client:

$this->http
    ->withInterceptor(ExampleInterceptor::class)
    ->get("/api/post/1")
    ->then(function (?PostModel $post) {
        // success handler
        $this->post = $post;
    }, function () {
        // error handler
    });

You can use as many interceptors as you want within the same request.

Using interceptors is useful for logging or even for mocking the server.

Simulating the server, no real HTTP call will be made:

public function request(Request $request, IRequestHandler $handler)
{
    $handler->reject($request);
}

public function response(Response $response, IResponseHandler $handler)
{
    $response->status = 200; // to avoid failing
    $response->body = new PostModel();
    $response->body->id = 1;
    $response->body->name = 'My post #1';
    $handler->next($response);
}

CSRF token interceptor example

<?php

namespace Components\Services\Interceptors;

use Components\Models\PostModel;
use Viewi\Components\Http\HttpClient;
use Viewi\Components\Http\Interceptor\IHttpInterceptor;
use Viewi\Components\Http\Interceptor\IRequestHandler;
use Viewi\Components\Http\Interceptor\IResponseHandler;
use Viewi\Components\Http\Message\Request;
use Viewi\Components\Http\Message\Response;
use Viewi\DI\Singleton;

#[Singleton]
class SessionInterceptor implements IHttpInterceptor
{
    private ?string $CSRFToken = null;
    public function __construct(private HttpClient $http)
    {
    }

    public function request(Request $request, IRequestHandler $handler)
    {
        if ($this->CSRFToken === null) {
            $this->http->post("/api/session")
                ->then(function ($session) use ($request, $handler) {
                    $this->CSRFToken = $session['CSRFToken'];
                    $this->handleRequest($request, $handler);
                }, function () use ($request, $handler) {
                    $handler->reject($request);
                });
        } else {
            $this->handleRequest($request, $handler);
        }
    }

    private function handleRequest(Request $request, IRequestHandler $handler)
    {
        // modify the request
        $newRequest = $request->withHeader('X-CSRF-TOKEN', $this->CSRFToken);
        $handler->next($newRequest);
    }

    public function response(Response $response, IResponseHandler $handler)
    {
        $handler->next($response);
    }
}