Skip to content

Instantly share code, notes, and snippets.

@radum
Forked from davidgilbertson/http2.js
Created September 22, 2018 14:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save radum/fc4bde8cf78b7c6612c14d51d034ca7f to your computer and use it in GitHub Desktop.
Save radum/fc4bde8cf78b7c6612c14d51d034ca7f to your computer and use it in GitHub Desktop.
HTTP2 server with compression and caching
const http2 = require('http2');
const fs = require('fs');
const path = require('path');
const zlib = require('zlib');
const brotli = require('brotli'); // npm package
const PORT = 3032;
const BROTLI_QUALITY = 11; // slow, but we're caching so who cares
const STATIC_DIRECTORY = path.resolve(__dirname, '../dist/');
const cache = {};
/*
-- To generate keys --
openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \
-keyout localhost-privkey.pem -out localhost-cert.pem
*/
const server = http2.createSecureServer({
key: fs.readFileSync(path.resolve(__dirname, './localhost-privkey.pem')),
cert: fs.readFileSync(path.resolve(__dirname, './localhost-cert.pem')),
});
const getStaticFile = (fileName, useBrotli) => {
const cacheKey = `${fileName}-${useBrotli}`; // we must cache gzipped and brotli responses separately
if (cacheKey in cache) return cache[cacheKey];
const fileBuffer = fs.readFileSync(path.resolve(STATIC_DIRECTORY, fileName.replace(/^\//, '')));
const compressedFile = useBrotli
? brotli.compress(fileBuffer, { quality: BROTLI_QUALITY })
: zlib.gzipSync(fileBuffer.toString());
cache[cacheKey] = compressedFile;
return compressedFile;
};
const getItemAtPath = ({ path, useBrotli }) => {
const notFoundResponse = {
body: `<h1>No ${path} for you!</h1>`,
responseHeader: {
':status': 404,
},
};
try {
const commonHeader = {
'Content-Encoding': useBrotli ? 'br' : 'gzip',
':status': 200,
};
if (path === '/') {
return {
body: getStaticFile('index.html', useBrotli),
responseHeader: {
...commonHeader,
'content-type': 'text/html',
},
};
}
if (path.endsWith('.js')) {
return {
body: getStaticFile(path, useBrotli),
responseHeader: {
...commonHeader,
'Cache-Control': 'max-age=31536000',
'content-type': 'application/javascript',
},
};
}
if (path.endsWith('.css')) {
return {
body: getStaticFile(path, useBrotli),
responseHeader: {
...commonHeader,
'Cache-Control': 'max-age=31536000',
'content-type': 'text/css',
},
};
}
return notFoundResponse;
} catch (err) {
return notFoundResponse;
}
}
server.on('stream', (stream, headers) => {
const { body, responseHeader } = getItemAtPath({
path: headers[':path'],
useBrotli: headers['accept-encoding'].includes('br'),
})
stream.respond(responseHeader);
stream.end(body);
});
server.listen(PORT);
console.info(`Server is probably ready on https://localhost:${PORT}`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment