Habilitando HTTP/2

e Conversando com o browser

Trilha - Node.js

Matheus Donizete

Web Developer

@MathDonizete

  • Corinthiano
  • JavaScript developer
  • Vim do Front
  • Curitiba
  • Node-RED   
  • Aplicações em Tempo Real
  • IoT

#AlwaysBetOnJavaScript

matheusdonizete.github.io

Objetivo: Performance

HTTP/2 Server Push + WebCache API

Como era: HTTP/1.1

Funcionamento

  • Request / Response
  • Serial
  • Plain Text

Funcionamento

  • Request / Response
  • Serial
  • Plain Text

Gargalos

  • Requisições Síncronas
  • Redundância de Cabeçalhos
  • Latência
  • Web Mobile

Funcionamento

  • Request / Response
  • Serial
  • Plain Text

Gargalos

  • Requisições Síncronas
  • Redundância de Cabeçalhos
  • Latência
  • Web Mobile

Otimizações

  • gzip
  • Keep-Alive
  • Minificação
  • CDN's
  • Recursos Inline

O Precursor:

SPDY

SPDY

  • Criado em 2009
  • 55% mais rápido
  • 23% em redes móveis
  • SPDY/4 (última versão)
  • Descontinuado em maio de 2016

Vamos ao que interessa

HTTP/2

O que é HTTP/2?

Recursos

  • Multiplexing 
  • Paralelização
  • Priorização de Requests
  • Server Push
  • HPACK (Compressor de Cabeçalhos)
  • Separação em Frames

Multiplexação

Cabeçalhos

  • Método de compressão: HPACK
  • Listas
  • Eliminação de Redundância
  • Segurança

Streams

Canais entre o cliente e o servidor

Breve Comparação

HTTP/2 e Node.js

Implementações Existentes:

  • node-http2
  • spdy
  • nodejs/http2

express + Spdy

const spdy = require('spdy');
const fs = require('fs');
const app = require('express')();
const options = {
    key: fs.readFileSync(`${__dirname}/server.key`),
    cert: fs.readFileSync(`${__dirname}/server.crt`)
};
const server = spdy.createServer(options, app);

app.get('/', (req, res) => {
    const pageHTML = fs.readFileSync(`${__dirname}/index.html`);
    res.set('Content-Type', 'text/html');
    res.send(new Buffer(pageHTML));
});

server.listen(process.env.PORT || 443, () => {
    const host = server.address().address;
    const port = server.address().port;
    console.log(`Listening at: https://${host}:${port}`);
});

Começando com o Básico

Server Push

<link src="path/to/img" as="image" rel="preload"/>
const server = spdy.createServer(options, app);
app.get('/', (req, res) => {
    const pageHTML = fs.readFileSync(`${__dirname}/index.html`);

    const jsOptions = {
        request: {
            accept: '*/*'
        },
        response: {
            'content-type': 'application/javascript'
        }
    };

    let stream = res.push(`/app.js`, jsOptions);
    
    stream.on('error', () => {});
    stream.write(`alert('HTTP/2 Server Push Habilitado :)');`); 
    stream.end(fs.readFileSync(`${__dirname}/app.js`));

    res.set('Content-Type', 'text/html');
    res.send(new Buffer(pageHTML));
});

Agora com Server Push

WebCache API

O Que é?

  • Service Worker Interface
  • Baseado em Promises
  • Manipulação do Cache
  • Escopo do Browser
  • Base das PWA's

O Problema:

Problemas de Conexão

A Solução:

Service Workers

Ciclo de Vida

Instaling

Installed

Activating

Activated

Redundant

Eventos

Como funciona

Registrando Service Worker

// sw.js

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('./sw-cache.js', { scope: './' })
    .then((reg) => {
        if (reg.installing) {
            console.log('Service worker installing');
        } else if (reg.waiting) {
            console.log('Service worker installed');
        } else if (reg.active) {
            console.log('Service worker active');
        }
    }).catch(function(error) {
        console.warn('Registration failed with ' + error);
    });
}

Escutando Eventos Pt.1

// sw-cache.js
'use strict';
const cacheVersion = 'v1';
const cacheUrls = [
    '/index.html',
    '/assets/css/style.css',
    'https://fonts.googleapis.com/css?family=Lato|Open+Sans|Roboto'
];

self.addEventListener('install', function(event) {
    event.waitUntil(
        caches.open(cacheVersion})
        .then(function(cache) {
            return cache.addAll(cacheUrls);
        })
    );
});

Escutando Eventos Pt.2

// sw-cache.js
self.addEventListener('activate', function(event) {
const expectedCacheNames = Object.keys(currentCaches)
    .map(key => currentCaches[key]);

event.waitUntil(
    caches.keys().then(cacheNames => {

        //Remove Cache Desatualizado
        const deletedCache = cacheNames
            .filter(cacheName => expectedCacheNames.indexOf(cacheName) == -1)
            .map(cacheName => caches.delete(cacheName));

        return Promise.all(deletedCache);
    })
);
});

Escutando Eventos Pt.3

// sw-cache.js
// Exemplo de Cache Seletivo
self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches.open(currentCaches.font).then(cache => {
            return cache.match(event.request).then(function(response) {
                if (response) {
                    return response; //Arquivo encontrado no Cache
                }

                return fetch(event.request.clone()).then(function(response) {
                    if (response.status < 400 && response.headers.has('content-type') 
                        && response.headers.get('content-type').match('font')) {
                        //Adicionando/Atualizando Cache
                        cache.put(event.request, response.clone()); 
                    }
                    return response;
                });
            }).catch(function(error) {
                throw error;
            });
        })
    );
});

Quando não encontra :(

Depois do Cache!!!

Requisição para SW: 304.25ms

Tempo de Download: 2.38s

Requisição para SW: 17.12ms

Tempo de Download: 167.34s

 

TO THE RESCUE

Conclusão:

HTTP/2 SIM!

O Futuro: QUIC + TLS + HTTP / 2

Obrigado pela Atenção!

math.d@live.com

@MathDonizete

Exemplos: http://bit.ly/tdc-http2

Slides: http://bit.ly/slides-http2Â