mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 00:40:14 +01:00
💽 fix: Exclude index page / from static cache settings (#7382)
* Disable default static caching for app's index page * Update index.html related environment variables in `.env.example` * Fix linting * Update index.spec.js --------- Co-authored-by: Danny Avila <danacordially@gmail.com>
This commit is contained in:
parent
a92ac23c44
commit
353adceb0c
7 changed files with 96 additions and 6 deletions
|
|
@ -563,9 +563,9 @@ HELP_AND_FAQ_URL=https://librechat.ai
|
||||||
# users always get the latest version. Customize #
|
# users always get the latest version. Customize #
|
||||||
# only if you understand caching implications. #
|
# only if you understand caching implications. #
|
||||||
|
|
||||||
# INDEX_HTML_CACHE_CONTROL=no-cache, no-store, must-revalidate
|
# INDEX_CACHE_CONTROL=no-cache, no-store, must-revalidate
|
||||||
# INDEX_HTML_PRAGMA=no-cache
|
# INDEX_PRAGMA=no-cache
|
||||||
# INDEX_HTML_EXPIRES=0
|
# INDEX_EXPIRES=0
|
||||||
|
|
||||||
# no-cache: Forces validation with server before using cached version
|
# no-cache: Forces validation with server before using cached version
|
||||||
# no-store: Prevents storing the response entirely
|
# no-store: Prevents storing the response entirely
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,13 @@ const routes = require('./routes');
|
||||||
|
|
||||||
const { PORT, HOST, ALLOW_SOCIAL_LOGIN, DISABLE_COMPRESSION, TRUST_PROXY } = process.env ?? {};
|
const { PORT, HOST, ALLOW_SOCIAL_LOGIN, DISABLE_COMPRESSION, TRUST_PROXY } = process.env ?? {};
|
||||||
|
|
||||||
const port = Number(PORT) || 3080;
|
// Allow PORT=0 to be used for automatic free port assignment
|
||||||
|
const port = isNaN(Number(PORT)) ? 3080 : Number(PORT);
|
||||||
const host = HOST || 'localhost';
|
const host = HOST || 'localhost';
|
||||||
const trusted_proxy = Number(TRUST_PROXY) || 1; /* trust first proxy by default */
|
const trusted_proxy = Number(TRUST_PROXY) || 1; /* trust first proxy by default */
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
const startServer = async () => {
|
const startServer = async () => {
|
||||||
if (typeof Bun !== 'undefined') {
|
if (typeof Bun !== 'undefined') {
|
||||||
axios.defaults.headers.common['Accept-Encoding'] = 'gzip';
|
axios.defaults.headers.common['Accept-Encoding'] = 'gzip';
|
||||||
|
|
@ -36,7 +39,6 @@ const startServer = async () => {
|
||||||
logger.info('Connected to MongoDB');
|
logger.info('Connected to MongoDB');
|
||||||
await indexSync();
|
await indexSync();
|
||||||
|
|
||||||
const app = express();
|
|
||||||
app.disable('x-powered-by');
|
app.disable('x-powered-by');
|
||||||
app.set('trust proxy', trusted_proxy);
|
app.set('trust proxy', trusted_proxy);
|
||||||
|
|
||||||
|
|
@ -179,3 +181,6 @@ process.on('uncaughtException', (err) => {
|
||||||
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// export app for easier testing purposes
|
||||||
|
module.exports = app;
|
||||||
|
|
|
||||||
78
api/server/index.spec.js
Normal file
78
api/server/index.spec.js
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const request = require('supertest');
|
||||||
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
describe('Server Configuration', () => {
|
||||||
|
// Increase the default timeout to allow for Mongo cleanup
|
||||||
|
jest.setTimeout(30_000);
|
||||||
|
|
||||||
|
let mongoServer;
|
||||||
|
let app;
|
||||||
|
|
||||||
|
/** Mocked fs.readFileSync for index.html */
|
||||||
|
const originalReadFileSync = fs.readFileSync;
|
||||||
|
beforeAll(() => {
|
||||||
|
fs.readFileSync = function (filepath, options) {
|
||||||
|
if (filepath.includes('index.html')) {
|
||||||
|
return '<!DOCTYPE html><html><head><title>LibreChat</title></head><body><div id="root"></div></body></html>';
|
||||||
|
}
|
||||||
|
return originalReadFileSync(filepath, options);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
// Restore original fs.readFileSync
|
||||||
|
fs.readFileSync = originalReadFileSync;
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
mongoServer = await MongoMemoryServer.create();
|
||||||
|
process.env.MONGO_URI = mongoServer.getUri();
|
||||||
|
process.env.PORT = '0'; // Use a random available port
|
||||||
|
app = require('~/server');
|
||||||
|
|
||||||
|
// Wait for the app to be healthy
|
||||||
|
await healthCheckPoll(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
await mongoServer.stop();
|
||||||
|
await mongoose.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return OK for /health', async () => {
|
||||||
|
const response = await request(app).get('/health');
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.text).toBe('OK');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not cache index page', async () => {
|
||||||
|
const response = await request(app).get('/');
|
||||||
|
expect(response.status).toBe(200);
|
||||||
|
expect(response.headers['cache-control']).toBe('no-cache, no-store, must-revalidate');
|
||||||
|
expect(response.headers['pragma']).toBe('no-cache');
|
||||||
|
expect(response.headers['expires']).toBe('0');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Polls the /health endpoint every 30ms for up to 10 seconds to wait for the server to start completely
|
||||||
|
async function healthCheckPoll(app, retries = 0) {
|
||||||
|
const maxRetries = Math.floor(10000 / 30); // 10 seconds / 30ms
|
||||||
|
try {
|
||||||
|
const response = await request(app).get('/health');
|
||||||
|
if (response.status === 200) {
|
||||||
|
return; // App is healthy
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore connection errors during polling
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retries < maxRetries) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 30));
|
||||||
|
await healthCheckPoll(app, retries + 1);
|
||||||
|
} else {
|
||||||
|
throw new Error('App did not become healthy within 10 seconds.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ const staticCache = (staticPath) =>
|
||||||
res.setHeader('Cache-Control', `public, max-age=${maxAge}, s-maxage=${sMaxAge}`);
|
res.setHeader('Cache-Control', `public, max-age=${maxAge}, s-maxage=${sMaxAge}`);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
index: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = staticCache;
|
module.exports = staticCache;
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ const {
|
||||||
|
|
||||||
// Check required environment variables
|
// Check required environment variables
|
||||||
if (!LDAP_URL || !LDAP_USER_SEARCH_BASE) {
|
if (!LDAP_URL || !LDAP_USER_SEARCH_BASE) {
|
||||||
return null;
|
module.exports = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchAttributes = [
|
const searchAttributes = [
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ jest.mock('winston', () => {
|
||||||
mockFormatFunction.printf = jest.fn();
|
mockFormatFunction.printf = jest.fn();
|
||||||
mockFormatFunction.errors = jest.fn();
|
mockFormatFunction.errors = jest.fn();
|
||||||
mockFormatFunction.splat = jest.fn();
|
mockFormatFunction.splat = jest.fn();
|
||||||
|
mockFormatFunction.json = jest.fn();
|
||||||
return {
|
return {
|
||||||
format: mockFormatFunction,
|
format: mockFormatFunction,
|
||||||
createLogger: jest.fn().mockReturnValue({
|
createLogger: jest.fn().mockReturnValue({
|
||||||
|
|
@ -19,6 +20,7 @@ jest.mock('winston', () => {
|
||||||
transports: {
|
transports: {
|
||||||
Console: jest.fn(),
|
Console: jest.fn(),
|
||||||
DailyRotateFile: jest.fn(),
|
DailyRotateFile: jest.fn(),
|
||||||
|
File: jest.fn(),
|
||||||
},
|
},
|
||||||
addColors: jest.fn(),
|
addColors: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,7 @@ process.env.BAN_VIOLATIONS = 'true';
|
||||||
process.env.BAN_DURATION = '7200000';
|
process.env.BAN_DURATION = '7200000';
|
||||||
process.env.BAN_INTERVAL = '20';
|
process.env.BAN_INTERVAL = '20';
|
||||||
process.env.CI = 'true';
|
process.env.CI = 'true';
|
||||||
|
process.env.JWT_SECRET = 'test';
|
||||||
|
process.env.JWT_REFRESH_SECRET = 'test';
|
||||||
|
process.env.CREDS_KEY = 'test';
|
||||||
|
process.env.CREDS_IV = 'test';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue