Skip to content

Instantly share code, notes, and snippets.

@davidgilbertson
Last active October 9, 2023 06:09
Show Gist options
  • Save davidgilbertson/e5690c04e06c4882cf5761f8acff36ec to your computer and use it in GitHub Desktop.
Save davidgilbertson/e5690c04e06c4882cf5761f8acff36ec 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}`);
@mike-boddin
Copy link

I just wanted to star this gist on my mobile but I didn't find the button to do so.
So I guess I will find it again later through my comment...

Great article!
“The 100% correct way to split your chunks with Webpack” by David Gilbertson https://link.medium.com/VHcUdzDCSS

@soundyogi
Copy link

Great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment