From e4ac42f0346b42e79a18cadee54aa60f23afeb62 Mon Sep 17 00:00:00 2001 From: matt burnett Date: Sun, 4 Aug 2024 21:17:59 -0400 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20feat:=20Static=20File=20Caching=20(?= =?UTF-8?q?#3455)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add static file cache * disable compression env variable --- .env.example | 12 +++++++ api/package.json | 1 + api/server/index.js | 14 ++++++--- api/server/routes/static.js | 3 +- api/server/utils/staticCache.js | 19 ++++++++++++ package-lock.json | 55 +++++++++++++++++++++++++++++++++ 6 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 api/server/utils/staticCache.js diff --git a/.env.example b/.env.example index cefc3e6b6..02a571b83 100644 --- a/.env.example +++ b/.env.example @@ -425,6 +425,18 @@ FIREBASE_APP_ID= ALLOW_SHARED_LINKS=true ALLOW_SHARED_LINKS_PUBLIC=true +#==============================# +# Static File Cache Control # +#==============================# + +# Leave commented out to use default of 1 month for max-age and 1 week for s-maxage +# NODE_ENV must be set to production for these to take effect +# STATIC_CACHE_MAX_AGE=604800 +# STATIC_CACHE_S_MAX_AGE=259200 + +# If you have another service in front of your LibreChat doing compression, disable express based compression here +# DISABLE_COMPRESSION=true + #===================================================# # UI # #===================================================# diff --git a/api/package.json b/api/package.json index 52413f7ac..ddff1e638 100644 --- a/api/package.json +++ b/api/package.json @@ -45,6 +45,7 @@ "bcryptjs": "^2.4.3", "cheerio": "^1.0.0-rc.12", "cohere-ai": "^7.9.1", + "compression": "^1.7.4", "connect-redis": "^7.1.0", "cookie": "^0.5.0", "cors": "^2.8.5", diff --git a/api/server/index.js b/api/server/index.js index 6fcee201d..39529f73f 100644 --- a/api/server/index.js +++ b/api/server/index.js @@ -4,6 +4,7 @@ require('module-alias')({ base: path.resolve(__dirname, '..') }); const cors = require('cors'); const axios = require('axios'); const express = require('express'); +const compression = require('compression'); const passport = require('passport'); const mongoSanitize = require('express-mongo-sanitize'); const { jwtLogin, passportLogin } = require('~/strategies'); @@ -17,8 +18,9 @@ const configureSocialLogins = require('./socialLogins'); const AppService = require('./services/AppService'); const noIndex = require('./middleware/noIndex'); const routes = require('./routes'); +const staticCache = require('./utils/staticCache'); -const { PORT, HOST, ALLOW_SOCIAL_LOGIN } = process.env ?? {}; +const { PORT, HOST, ALLOW_SOCIAL_LOGIN, DISABLE_COMPRESSION } = process.env ?? {}; const port = Number(PORT) || 3080; const host = HOST || 'localhost'; @@ -43,12 +45,16 @@ const startServer = async () => { app.use(express.json({ limit: '3mb' })); app.use(mongoSanitize()); app.use(express.urlencoded({ extended: true, limit: '3mb' })); - app.use(express.static(app.locals.paths.dist)); - app.use(express.static(app.locals.paths.fonts)); - app.use(express.static(app.locals.paths.assets)); + app.use(staticCache(app.locals.paths.dist)); + app.use(staticCache(app.locals.paths.fonts)); + app.use(staticCache(app.locals.paths.assets)); app.set('trust proxy', 1); // trust first proxy app.use(cors()); + if (DISABLE_COMPRESSION !== 'true') { + app.use(compression()); + } + if (!ALLOW_SOCIAL_LOGIN) { console.warn( 'Social logins are disabled. Set Environment Variable "ALLOW_SOCIAL_LOGIN" to true to enable them.', diff --git a/api/server/routes/static.js b/api/server/routes/static.js index 116f7c8dd..2db55ebeb 100644 --- a/api/server/routes/static.js +++ b/api/server/routes/static.js @@ -1,7 +1,8 @@ const express = require('express'); +const staticCache = require('../utils/staticCache'); const paths = require('~/config/paths'); const router = express.Router(); -router.use(express.static(paths.imageOutput)); +router.use(staticCache(paths.imageOutput)); module.exports = router; diff --git a/api/server/utils/staticCache.js b/api/server/utils/staticCache.js new file mode 100644 index 000000000..79b98dd17 --- /dev/null +++ b/api/server/utils/staticCache.js @@ -0,0 +1,19 @@ +const express = require('express'); + +const oneWeekInSeconds = 24 * 60 * 60 * 7; + +const sMaxAge = process.env.STATIC_CACHE_S_MAX_AGE || oneWeekInSeconds; +const maxAge = process.env.STATIC_CACHE_MAX_AGE || oneWeekInSeconds * 4; + +const staticCache = (staticPath) => + express.static(staticPath, { + setHeaders: (res) => { + if (process.env.NODE_ENV.toLowerCase() !== 'production') { + return; + } + + res.setHeader('Cache-Control', `public, max-age=${maxAge}, s-maxage=${sMaxAge}`); + }, + }); + +module.exports = staticCache; diff --git a/package-lock.json b/package-lock.json index f70c035d5..7772d493f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,6 +55,7 @@ "bcryptjs": "^2.4.3", "cheerio": "^1.0.0-rc.12", "cohere-ai": "^7.9.1", + "compression": "^1.7.4", "connect-redis": "^7.1.0", "cookie": "^0.5.0", "cors": "^2.8.5", @@ -12729,6 +12730,60 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",