Skip to content

Instantly share code, notes, and snippets.

@Lachee
Created May 14, 2025 03:39
Show Gist options
  • Save Lachee/e3db43db2572a3f2a9eb3e25d5710024 to your computer and use it in GitHub Desktop.
Save Lachee/e3db43db2572a3f2a9eb3e25d5710024 to your computer and use it in GitHub Desktop.
Ultra-lightweight static file server to host unity builds with brotli compression.

About

Unity builds by default WebGL using brotli. This is fine, but it seems that no webserver ever knows that it needs to respond with Content-Encoding: br.

This server does that. It will detect when accessing a .br file and respond with the correct content-type and with the encoding set.

Warning

This code is not for production use. It has very little in terms of security and is only designed to verify builds are working.

DO NOT PUBLISH

Usage

This is using plain node apis, so usage is very simple:

node server.js

Your file structure should look like:

site/
  - server.js
  - index.html
  - Build/
    - site.data.br
    - site.framework.js.br
    - site.loader.js
    - site.wasm.br
  - TemplateData/
    - style.css
    - <other unity template files>

Advance Usage

You can use environment variables to configure the server:

PORT=3000 PUBLIC_DIR=public node server.js

which should look like this:

site/
  - server.js
  - public/
    - index.html
    - Build/
      - site.data.br
      - site.framework.js.br
      - site.loader.js
      - site.wasm.br
    - TemplateData/
      - style.css
      - <other unity template files>

You may want to put everything in public/ for security purposes. But for testing, having it all share server.js is fine. Just dont publish a project like this.

const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = process.env.PORT || 8080;
const PUBLIC_DIR = process.env.PUBLIC_DIR || path.join(__dirname, '');
const MIMES = {
// Page
'.html': 'text/html',
'.css': 'text/css',
// Scripts
'.js': 'application/javascript',
'.br': 'application/octet-stream',
'.wasm': 'application/wasm',
// Images
'.png': 'image/png',
'.ico': 'image/x-icon',
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
};
// Create the server
const server = http.createServer((req, res) => {
if (req.method !== 'GET') {
res.statusCode = 405;
res.setHeader('Content-Type', 'text/plain');
res.end('Method Not Allowed');
return;
}
// Ensure the validity of the path
let filePath = path.normalize(path.join(PUBLIC_DIR, req.url === '/' ? 'index.html' : req.url));
if (!filePath.startsWith(PUBLIC_DIR)) {
res.statusCode = 403;
res.setHeader('Content-Type', 'text/plain');
res.end('Forbidden');
return;
}
fs.access(filePath, fs.constants.F_OK, (err) => {
if (err) {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Not Found');
return;
}
const extname = path.extname(filePath);
let contentType = MIMES[extname] || 'application/octet-stream';
// If its brotli, we will get the original extension and update the content type.
// We will also set the header, which nobody else eems to do for some reason.
const isBrotli = extname === '.br';
if (isBrotli) {
const originalExtname = path.extname(filePath.slice(0, -3));
if (originalExtname && MIMES[originalExtname]) {
contentType = MIMES[originalExtname];
}
res.setHeader('Content-Encoding', 'br');
}
res.setHeader('Content-Type', contentType);
// Pipe the content to the http connection
const fileStream = fs.createReadStream(filePath);
fileStream.pipe(res);
fileStream.on('error', (error) => {
console.error(`Error reading file: ${error}`);
res.statusCode = 500;
res.setHeader('Content-Type', 'text/plain');
res.end('Internal Server Error');
});
});
});
// Listen
server.on('error', (err) => {
console.error(`Server error: ${err}`);
});
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
});
@Lachee
Copy link
Author

Lachee commented May 14, 2025

oh yeah, this is licensed under MIT .
Do what you want with it

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