This site is built with Viewi itself. It is experimental and still in development. If you see any bugs please do not hesitate and open an issue or DM me on Twitter.

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, etc.. For that purpose there is an Http interceptor mechanism.

You can define the interceptor as a regular class, like this:

use Viewi\Common\HttpHandler;

class HttpLoggerInterceptor
{
    public function intercept(HttpHandler $handler)
    {
        // set request options before $handler->httpClient->setOptions(...)
        // call handle to continue with the request
        $handler->handle(function ($next) use ($handler) {
            // access or modify $handler->response after
            // call next if you are good with the response
            // otherwise it won't continue
            $next();
        });
    }
}

To use it, simply inject this service and use it like this:

$this->http
    ->with([$this->httpLogger, 'intercept'])
    ->post('/api/authorization/confirm-login', [
        'code' => $this->codeFor2FA
    ])->then // ...

Call with method with two arguments:

First one is an instance of the interceptor.

The second one is a method name.

You can have as many methods as you want inside one interceptor. So simply put, the interceptor is a class with at least one method that accepts HttpHandler as an argument.

Let’s take a look at a more complex example, the real world one that is used in a couple of projects of mine.

<?php

namespace Components\Services;

use Viewi\Common\HttpClient;
use Viewi\Common\HttpHandler;

class SessionState
{
    private HttpClient $http;
    private bool $initiated = false;
    private ?string $CSRFToken = null;
    private array $resolveQueue = [];

    public function __construct(HttpClient $httpClient)
    {
        $this->http = $httpClient;
    }

    public function csrfTokenInterceptor(HttpHandler $handler)
    {
        if ($this->CSRFToken !== null) {
            $handler->httpClient->setOptions([
                'headers' => [
                    'X-CSRF-TOKEN' => $this->CSRFToken
                ]
            ]);
            $handler->handle(function ($next) {
                $next();
            });
        } else {
            $this->putInQueue($handler);
            if (!$this->initiated) {
                $this->initiated = true;
                // get the CSRF token
                $this->http->post('/api/authorization/session')->then(function ($response) {
                    $this->CSRFToken = $response['data']['CSRFToken'];
                    $this->resolveCallbacks();
                }, function ($error) {
                    $this->resolveCallbacks($error);
                });
            } else {
                $this->resolveCallbacks();
            }
        }
    }

    public function putInQueue(HttpHandler $handler)
    {
        $this->resolveQueue[] = function (?string $csrfToken, $error) use ($handler) {
            if ($error) {
                $handler->handle(function ($next) use ($handler, $error) {
                    $handler->response->success = false;
                    $handler->response->content = $error;
                    $next();
                });
                return;
            }
            $handler->httpClient->setOptions([
                'headers' => [
                    'X-CSRF-TOKEN' => $csrfToken
                ]
            ]);
            $handler->handle(function ($next) {
                $next();
            });
        };
    }

    public function resolveCallbacks($error = null)
    {
        foreach ($this->resolveQueue as $callBack) {
            $callBack($this->CSRFToken, $error);
        }
        $this->resolveQueue = [];
    }
}

And used like this:

$this->http
    ->with([$this->session, 'csrfTokenInterceptor'])
    ->post('/api/authorization/login', [
        'username' => $this->username,
        'password' => $this->password,
        'rememberMe' => $this->rememberMe
    ])
    ->then(function ($response) {
        $this->message = $response['message'];
        if ($response['success']) {
            $this->onSuccess();
        }
        // …
    }, function () {
        $this->message = 'Something went wrong';
    });

So what exactly happens here. Once you are making a request with this interceptor, the session state interceptor does these:

  • Checks if there is CSRF token present.
  • If not - requests the token from the server and saves it.
  • Meanwhile all requests that are waiting are being kept in $resolveQueue.
  • Once the token is obtained the queue is processed by calling callbacks for each of the requests.
  • If token is present already, continue right away.