Aramazenamento em cache no lado do cliente

Este tutorial é compatível com hapi v16

O protocolo HTTP fornece várias cebeçalhos diferentes para controlar os recursos de cache do navegador. Para decidir quais cabeçalhos são validos para o seu caso de uso, varifique no Google developers guide ou neste resources. Este tutorial oferece uma visão geral de como usar estas técnicas com hapi.

Cache-Control

O cabeçalho Cache-Control diz ao browser e a qualquer cache intermediário se um recurso é cacheavel e por quanto tempo. Por exemplo, Cache-Control:max-age=30, must-revalidate, private significa que o browser pode cachear os recursos associados por trinta segundos e private significa que não deve ser cacheado por caches intermediarios, somente pelo browser. must-revalidate significa que uma vez que expire tem de solicitar o recurso novamente para o servidor.

Vamos ver como nos podemos colocar estes cabeçalhos no hapi:

server.route({
    path: '/hapi/{ttl?}',
    method: 'GET',
    handler: function (request, reply) {

        const response = reply({ be: 'hapi' });
        if (request.params.ttl) {
            response.ttl(request.params.ttl);
        }
    },
    options: {
        cache: {
            expiresIn: 30 * 1000,
            privacy: 'private'
        }
    }
});

Listagem 1 Setar Cache-Control no cabeçalho

Na Listagem 1 ilustra que o valor expiresIn pode ser sobrescrito com o método ttl(msec) pelo objeto de resposta.

Veja route-options por mais informações sobre as opções de configuração comuns do cache.

Last-Modified

Em alguns casos o servidor pode fornecer informações de quando os recursos foram modificados pela última vez. Quando usar o plugin inert pase servir conteúdo estáticos, um cabeçalho Last-Modified é adicionado em toda carga de resposta.

Quando o cebeçalho Last-Modified é setado em um resposta, o hapi compare ele com o cabeçalho If-Modified-Since vindo pelo cliente para decidir se o código do status da resposta deve ser 304.

Assumindo que lastModified é um objeto do tipo Date, é possivel definir no cabeçalho via interface objeto de resposta como pode ser visto abaixo, na Listagem 2.

reply(result).header('Last-Modified', lastModified.toUTCString());

Listagem 2 Configurar Last-Modified no cabeçalho

Este é mais um exemplo usando Last-Modified na última seção deste tutorial.

ETag

O cabeçalho ETag é uma alternativa para o Last-Modified qunado o servidor fornece um token (geralmente o checksum deste recurso) ao invés da últma timestamp de qunado foi modificação. O browser irá usar este token para setar o cabeçalho If-None-Match na próxima requisição. O servidor compara os valores do cebeçalhos com a nova ETag checksum e responde com 304 se eles são iguais.

Você só precisa definir ETag no seu manipulador com a função etag(tag, options):

reply(result).etag('xxxxxxxxx');

Listagem 3 Configurar ETag no cabeçalho

Verificar o documento da etag no objeto de resposta para mais detalhes sobre os parâmetros e as opções desponíveis.

Cacheando no lado do servidor

hapi fornece um sistema de cache poderoso do lado do servidor via catbox e torna o uso do cache bem mais confiavel. Este seção do tutorial vai ajudar você a entender como o catbox é utilizado dentro do hapi e como você pode influênciar nele.

Catbox tem duas interfaces; client e policy.

Cliente

Client é um interface de baixo nível que permite você setar/pegar pares de chave-valor. Este é inicializado com um dos adaptadores disponíveis: (Memory, Redis, mongoDB, Memcached, ou Riak).

hapi sempre incializa com um client padrão com o adaptador memory. Vamos ver como nos podemos definir mais clients.

'use strict';

const Hapi = require('hapi');

const server = new Hapi.Server({
    cache: [
        {
            name: 'mongoCache',
            engine: require('catbox-mongodb'),
            host: '127.0.0.1',
            partition: 'cache'
        },
        {
            name: 'redisCache',
            engine: require('catbox-redis'),
            host: '127.0.0.1',
            partition: 'cache'
        }
    ]
});

server.connection({
    port: 8000
});

Listagem 4 Definindo catbox clients

Na Listagem 4, nós definimos 2 catbox clients; mongoCache e redisCache. Incluindo o memory cache padrão criada pelo hapi, existem três cliente de cache disponivel. Você pode replicar o cliente padrão por omitindo a propriedade name quando estiver registrando o novo cliente de cache. partition diz ao adaptador como o cache deve ser nomeado ('catbox' por padrão). Em caso de mongoDB é o nome do banco de dados, em caso de redis é usado o prefixo da chave.

Policy

Policy é um inteface de mais baixo nível do que o Cliente. Vamos fingir que nós estemos lidando com algo mais complicado do que somar dois números e queremos cachear os resultados. server.cache cria uma nova policy, que irá ser usada na manipulação de rota.

const add = function (a, b, next) {

    return next(null, Number(a) + Number(b));
};

const sumCache = server.cache({
    cache: 'mongoCache',
    expiresIn: 20 * 1000,
    segment: 'customSegment',
    generateFunc: function (id, next) {

        add(id.a, id.b, next);
    },
    generateTimeout: 100
});

server.route({
    path: '/add/{a}/{b}',
    method: 'GET',
    handler: function (request, reply) {

        const id = request.params.a + ':' + request.params.b;
        sumCache.get({ id: id, a: request.params.a, b: request.params.b }, (err, result) => {

            if (err) {
                return reply(err);
            }
            reply(result);
        });
    }
});

Listagem 5 Usando server.cache

cache diz ao hapi como user o client

O primeiro parâmeto da função sumCache.get é um objeto com uma chave obrigatória id, que é uma string única de identificação do item.

Além de partições, tem segmentos que permite você isolar os caches sem um client. Se você deseja armazernar o resultado em cache por dois firentes métodos, você geralmente não vai querer misturar os resultados juntos. No adaptador mongoDB, segment representa uma colcação e no redis isto é um prefixo adiciona, juntamento com a opção partition.

O valor padrão para segment quando server.cache é chamado dentro de um plugin será '!pluginName'. Ao criar server methods, o valor do segment será '#methodName'. Se você tiver usando em um caso várias policies que compartilham um segment exite uma opção shared disponível também.

Métodos de servidor

Mas ele pode ficar melhor que isto! Em 95% dos casos você pode user server methods para fins de armazenamento em cache, porque isso reduz a padronização para o minimo. Vamos reescrever a Listagem 5 usando os métodos do servidor:

const add = function (a, b, next) {

    return next(null, Number(a) + Number(b));
};

server.method('sum', add, {
    cache: {
        cache: 'mongoCache',
        expiresIn: 30 * 1000,
        generateTimeout: 100
    }
});

server.route({
    path: '/add/{a}/{b}',
    method: 'GET',
    handler: function (request, reply) {

        server.methods.sum(request.params.a, request.params.b, (err, result) => {

            if (err) {
                return reply(err);
            }
            reply(result);
        });
    }
});

Listagem 6 Usando cache via métodos do servidor

server.method() cria uma nova policy com segment: '#sum' automaticamente para nós. Além disso, o único item id (chave do cache) esta gerda automaticamente a partir dos parâmetros. Por padrão, ele lida com parâmetros do tipo string, number e boolean. Para parâmetros mais complexos, você tem que fornecer sua próprio função generateKey para criar os ids único baseado nos parêmetros - confira os métodos do servidor tutorial para mais informações.

Qual a próxima?

  • De uma olhada melhor em catbox policy options e preste bastante atenção em staleIn, staleTimeout, generateTimeout, que aproveita todo o potencial do armazenamento em cache do catbox.
  • Varifique os métodos do servidor tutorial para mais exemplos.

Caheando no lado do cliente e servidor.

Catbox Policy fornece mais dois parâmetros cached e report que fornece alguns detalhes quando um item é armazenado

Neste caso nós usamos cached.stored timestamp para adicionar no header last-modified.

server.route({
    path: '/add/{a}/{b}',
    method: 'GET',
    handler: function (request, reply) {

        server.methods.sum(request.params.a, request.params.b, (err, result, cached, report) => {

            if (err) {
                return reply(err);
            }
            const lastModified = cached ? new Date(cached.stored) : new Date();
            return reply(result).header('last-modified', lastModified.toUTCString());
        });
    }
});

Listagem 7 Cacheando no lado do servidor e cliente juntos