Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
@@ -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/`;
38 changes: 34 additions & 4 deletions src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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);
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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'});
Expand Down
13 changes: 13 additions & 0 deletions src/www-immutable/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello HTML</title>
<!-- paths under immutable will be cached by nginx server by convention
Top-level HTML will always hit the server, so the cache applies to related files-->
<link rel="stylesheet" href="./styles.css">
</head>
<body>
Hello World with immutable style sheet serving
</body>
</html>
3 changes: 3 additions & 0 deletions src/www-immutable/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
body {
background-color: green;
}
5 changes: 5 additions & 0 deletions test/unit/utils/configs-test.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/*global describe, it*/
import * as chai from 'chai';
import {clearAppConfig, getConfigs} from "../../../src/utils/configs.js";
import * as constants from "../../../src/constants.js";
import {GIT_COMMIT_HASH_SHORT} from "../../../src/constants.js";

let expect = chai.expect;

Expand Down Expand Up @@ -48,4 +50,7 @@ function _verifyConfigs(configs) {
expect(configs.mysql.host.length).to.gt(0);
expect(configs.mysql.database.length).to.gt(0);

expect(constants.STATIC_WWW_IMMUTABLE_PREFIX).to.contain("/www-immutable/");
expect(constants.STATIC_WWW_IMMUTABLE_PREFIX).to.contain(constants.GIT_COMMIT_HASH_SHORT);
expect(constants.STATIC_WWW_PREFIX).to.contain("/www/");
}
Loading