diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..5c7ff21 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,24 @@ +import {execSync} from "child_process"; + +// Get git commit hash at startup - will crash if not a git repository +let GIT_COMMIT_HASH_SHORT_VAL; +try { + GIT_COMMIT_HASH_SHORT_VAL = execSync('git rev-parse --short HEAD', {encoding: 'utf8'}).trim(); +} catch (error) { + console.error('FATAL ERROR: This application must be run from a git repository for static file cache consistency across load-balanced backends.'); + console.error('Git command failed:', error.message); + throw new Error('Application must be in a git repository for static file cache consistency'); +} + +export const GIT_COMMIT_HASH_SHORT = GIT_COMMIT_HASH_SHORT_VAL; + +// Immutable Static file serving paths. These path will be cached permanently by our nginx reverse proxy by convention. +// The static files are served from the cache at nginx itself leading to less traffic at this api server. +// GIT_COMMIT_HASH ensures strong consistency across load-balanced backends for the file content. +export const STATIC_WWW_IMMUTABLE_PATH_ON_DISC = 'www-immutable'; +export const STATIC_WWW_IMMUTABLE_PREFIX = `/www-immutable/${GIT_COMMIT_HASH_SHORT}/`; + +// the below paths are just for static file serving and not cached at nginx. Using the cached path is +// strongly recommended for scalability and performance. +export const STATIC_WWW_PATH_ON_DISC = 'www'; +export const STATIC_WWW_PREFIX = `/www/`; diff --git a/src/server.js b/src/server.js index 5d96273..ab4a6a8 100644 --- a/src/server.js +++ b/src/server.js @@ -4,6 +4,7 @@ */ import fastify from "fastify"; +import * as crypto from "crypto"; import {createFastifyLogger} from "./utils/logger.js"; import {init, isAuthenticated, addUnAuthenticatedAPI} from "./auth/auth.js"; import {HTTP_STATUS_CODES} from "@aicore/libcommonutils"; @@ -14,6 +15,12 @@ import compression from '@fastify/compress'; import path from 'path'; import {fileURLToPath} from 'url'; +import { + STATIC_WWW_IMMUTABLE_PATH_ON_DISC, + STATIC_WWW_IMMUTABLE_PREFIX, + STATIC_WWW_PATH_ON_DISC, + STATIC_WWW_PREFIX +} from "./constants.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -25,7 +32,9 @@ const server = fastify({ disableRequestLogging: true, trustProxy: true, connectionTimeout: 30000, - keepAliveTimeout: 30000 + keepAliveTimeout: 30000, + // Generate longer, unique request IDs using UUIDs + genReqId: () => crypto.randomUUID() }); // Add Request ID Propagation server.decorateRequest('setCorrelationId', function (correlationId) { @@ -113,7 +122,7 @@ server.addHook('onRequest', (request, reply, done) => { const urlWithoutQuery = request.url.split('?')[0]; const sanitizedUrl = urlWithoutQuery.replace(/[<>]/g, ''); // Skip authentication for static files - if (request.url.startsWith('/www/')) { + if (request.url.startsWith(STATIC_WWW_PREFIX) || request.url.startsWith(STATIC_WWW_IMMUTABLE_PREFIX)) { done(); return; } @@ -176,13 +185,31 @@ server.addHook('onResponse', (request, reply, done) => { // Register static file serving server.register(fastifyStatic, { - root: path.join(__dirname, 'www'), - prefix: '/www/', + root: path.join(__dirname, STATIC_WWW_PATH_ON_DISC), + prefix: STATIC_WWW_PREFIX, decorateReply: false, serve: true, index: ['index.html', 'index.htm'] }); +server.log.info("Serving static files at " + STATIC_WWW_PREFIX); +// Register static file serving +server.register(fastifyStatic, { + root: path.join(__dirname, STATIC_WWW_IMMUTABLE_PATH_ON_DISC), + prefix: STATIC_WWW_IMMUTABLE_PREFIX, + cacheControl: false, // Disable default cache control, we set it ourselves + etag: false, // Not needed for immutable content + lastModified: false, // Not needed for immutable content + serve: true, + setHeaders: (res) => { + // Cache assets for 1 year with immutable directive + // immutable tells browsers the content will never change at this URL + // This eliminates revalidation requests even on hard refresh (in supporting browsers) + res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); + }, + index: ['index.html', 'index.htm'] +}); +server.log.info("Serving NGINX cached static files at " + STATIC_WWW_IMMUTABLE_PREFIX); // Enhanced Health Check endpoint server.get('/health', async (request, reply) => { @@ -227,6 +254,9 @@ server.get('/health', async (request, reply) => { } }); +addUnAuthenticatedAPI(`${STATIC_WWW_IMMUTABLE_PREFIX}*`); +addUnAuthenticatedAPI(`${STATIC_WWW_PREFIX}*`); + addUnAuthenticatedAPI('/ping'); server.get('/ping', async (request, reply) => { reply.send({status: 'OK'}); diff --git a/src/www-immutable/index.html b/src/www-immutable/index.html new file mode 100644 index 0000000..fd91039 --- /dev/null +++ b/src/www-immutable/index.html @@ -0,0 +1,13 @@ + + +
+ +