Кэширование ответов в Laravel

Кэширование ответов в Laravel

Когда в ваше приложение приходит запрос — оно возвращает ответ. Чтобы создать этот ответ, ваше приложение должно произвести некоторую работу. Скорее всего, будут сделаны запросы к базе данных. Все это занимает время. Было бы неплохо, если бы на одинаковые запросы, мы могли возвращать ответы, который уже генерировали раньше.

Именно это делает наш пакет laravel-responsecache. Он может ускорить ваше приложение, кэшируя ответ. Недавно мы выпустили новую мажорную версию пакета с новыми функциями. Теперь он может кэшировать страницы, которые содержат небольшие динамические фрагменты, такие как токен csrf.

Основное использование

После того, как вы установили пакет (что можно сделать с помощью простого composer require), все GET запросы к вашему приложению будут кэшироваться по умолчанию на неделю. Разумеется, вы можете настроить этот период.

Фактически, вы можете настроить весь режим кэширования, реализовав свой собственный профиль. Этот профиль является классом, который реализует интерфейс CacheProfile. Он отвечает за принятие решения о необходимости кэширования запроса/ответа. Вот как выглядит этот интерфейс:

interface CacheProfile
{
    /*
     * Determine if the response cache middleware should be enabled.
     */
    public function enabled(Request $request): bool;

    /*
     * Determine if the given request should be cached.
     */
    public function shouldCacheRequest(Request $request): bool;

    /*
     * Determine if the given response should be cached.
     */
    public function shouldCacheResponse(Response $response): bool;

    /*
     * Return the time when the cache must be invalidated.
     */
    public function cacheRequestUntil(Request $request): DateTime;

    /**
     * Return a string to differentiate this request from others.
     *
     * For example: if you want a different cache per user you could return the id of
     * the logged in user.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return mixed
     */
    public function cacheNameSuffix(Request $request);
}

По умолчанию пакет использует реализацию CacheAllSuccessfulGetRequests — кэшировать все успешные запросы GET в течение недели. Он также позаботится о том, чтобы кэшировались только текстовые ответы, такие как HTML и JSON. Вот так он выглядит:

namespace Spatie\ResponseCache\CacheProfiles;

use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class CacheAllSuccessfulGetRequests extends BaseCacheProfile
{
    public function shouldCacheRequest(Request $request): bool
    {
        if ($request->ajax()) {
            return false;
        }

        if ($this->isRunningInConsole()) {
            return false;
        }

        return $request->isMethod('get');
    }

    public function shouldCacheResponse(Response $response): bool
    {
        if (! $this->hasCacheableResponseCode($response)) {
            return false;
        }

        if (! $this->hasCacheableContentType($response)) {
            return false;
        }

        return true;
    }

    public function hasCacheableResponseCode(Response $response): bool
    {
        if ($response->isSuccessful()) {
            return true;
        }

        if ($response->isRedirection()) {
            return true;
        }

        return false;
    }

    public function hasCacheableContentType(Response $response)
    {
        $contentType = $response->headers->get('Content-Type', '');

        return Str::startsWith($contentType, 'text');
    }
}

 

Использование заменителей

Кэширование всего ответа может вызывать проблемы для некоторых страниц. Представьте, что ваша страница содержит форму. Чтобы иметь возможность отправить её безопасно — нужен свежий токен csrf. Если бы мы кэшировали всю страницу, то в HTML-файле содержался бы старый токен csrf, который вызвал бы ошибку при отправке формы.

Для решения этой проблемы в свежевыпущенной шестой версии пакеты мы ввели поддержку заменителей (replacers). Заменитель — это класс, который может заменить крошечный фрагмент ответа до его кэширования. Например, он может заменить текущий токен csrf на заполнитель (placeholder). Заменитель также может, когда запрос поступает во второй раз, изменить кэшированный ответ перед его отправкой в браузеры. Таким образом, он может заменить заполнитель новым токеном csrf.

По умолчанию наш пакет поставляется с заменителем токена csrf. Вот как это выглядит.

namespace Spatie\ResponseCache\Replacers;

use Symfony\Component\HttpFoundation\Response;

class CsrfTokenReplacer implements Replacer
{
    protected $replacementString = '<csrf-token-here>';

    public function prepareResponseToCache(Response $response): void
    {
        if (! $response->getContent()) {
            return;
        }

        $response->setContent(str_replace(
            csrf_token(),
            $this->replacementString,
            $response->getContent()
        ));
    }

    public function replaceInCachedResponse(Response $response): void
    {
        if (! $response->getContent()) {
            return;
        }

        $response->setContent(str_replace(
            $this->replacementString,
            csrf_token(),
            $response->getContent()
        ));
    }
}

Теперь вы можете кэшировать страницы, даже содержащие формы, и при этом не пострадает безопасность их обработки.

Альтернативы

Есть несколько отличных альтернатив для кэширования ответов.

Джозеф Силбер создал Laravel Page Cache, который может записывать свой кэш на диск и позволять Nginx его забирать. Поскольку, в таком случае, PHP больше не нужен для обработки ответа, то производительность возрастает весьма значительно. Все это требует сложной процедуры установки (вам придется повозиться с настройками nginx) и у вас не будет возможности использовать динамический контент на страницах.

Другая альтернатива, на которую стоит обратить внимание — это laravel-httpcache, созданный Barry Vd. Heuvel. Он позволит вашему приложению использовать HttpCache.

Varnish — это обратный прокси-сервер, который можно использовать для кэширования контента. Требуется большая настройка, но как вы только с ней справитесь, то получите весьма эффективное решение. Я сам использовал его и даже написал об этом статью. Спойлер: мне удалось сделать простой сервер, обрабатывающий 6 000 запросов в секунду.

В заключение

Наш пакет не может скрыть проблемы с производительностью. Все приложения должны быть оптимизированы таким образом, чтобы они отвечали в приемлемые сроки без использования кэширования ответов. Имейте в виду, что существует множество других аспектов, которые необходимо учитывать при попытке обеспечить быстрый опыт.

Хотя существует много хороших (и более быстрых) доступных альтернатив, я считаю, что laravel-responsecache — это самый простой пакет для начала работы с кэшированием ответов.

 

Автор: Freek Van der Herten
Перевод: Алексей Широков

Наш Телеграм-канал — следите за новостями о Laravel.