strapi-cache 1.5.7-rc.2 → 1.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -28
- package/dist/admin/index.js +5 -1
- package/dist/admin/index.mjs +5 -1
- package/dist/admin/src/components/PurgeEntityButton/index.d.ts +1 -1
- package/dist/server/index.js +105 -8
- package/dist/server/index.mjs +105 -8
- package/dist/server/src/config/index.d.ts +2 -0
- package/dist/server/src/index.d.ts +2 -0
- package/dist/server/src/services/redis/provider.d.ts +1 -0
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ In your Strapi project, navigate to `config/plugins.js` and add the following co
|
|
|
50
50
|
size: 1024 * 1024 * 1024, // Maximum size of the cache (1 GB) (only for memory cache)
|
|
51
51
|
allowStale: false, // Allow stale cache items (only for memory cache)
|
|
52
52
|
cacheableRoutes: ['/api/products', '/api/categories'], // Caches routes which start with these paths (if empty array, all '/api' routes are cached)
|
|
53
|
+
excludeRoutes: ['/api/products/private'], // (NEW) Exclude routes which start with these paths from being cached (takes precedence over cacheableRoutes). **Note:** `excludeRoutes` takes precedence over `cacheableRoutes`.
|
|
53
54
|
provider: 'memory', // Cache provider ('memory' or 'redis')
|
|
54
55
|
redisConfig: env('REDIS_URL', 'redis://localhost:6379'), // Redis config takes either a string or an object see https://github.com/redis/ioredis for references to what object is available, the object or string is passed directly to ioredis client (if using Redis)
|
|
55
56
|
redisClusterNodes: [], // If provided any cluster node (this list is not empty), initialize ioredis redis cluster client. Each object must have keys 'host' and 'port'. See https://github.com/redis/ioredis for references
|
|
@@ -60,6 +61,7 @@ In your Strapi project, navigate to `config/plugins.js` and add the following co
|
|
|
60
61
|
cacheAuthorizedRequests: false, // Cache requests with authorization headers (set to true if you want to cache authorized requests)
|
|
61
62
|
cacheGetTimeoutInMs: 1000, // Timeout for getting cached data in milliseconds (default is 1 second)
|
|
62
63
|
autoPurgeCache: true, // Automatically purge cache on content CRUD operations
|
|
64
|
+
autoPurgeCacheOnStart: true, // Automatically purge cache on Strapi startup
|
|
63
65
|
},
|
|
64
66
|
},
|
|
65
67
|
```
|
|
@@ -95,34 +97,6 @@ All of these routes are protected by the policies `admin::isAuthenticatedAdmin`
|
|
|
95
97
|
|
|
96
98
|
If you encounter any issues, please feel free to open an issue on the [GitHub repo](https://github.com/TupiC/strapi-cache/issues/new).
|
|
97
99
|
|
|
98
|
-
## 🛠️ Troubleshooting
|
|
99
|
-
|
|
100
|
-
If you encounter an error like:
|
|
101
|
-
|
|
102
|
-
```
|
|
103
|
-
Access to fetch at 'http://your-backend.com' from origin 'http://your-origin.com' has been blocked by CORS policy:
|
|
104
|
-
Request header field cache-control is not allowed by Access-Control-Allow-Headers in preflight response.
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
You might need to adjust your CORS middleware settings in Strapi:
|
|
108
|
-
|
|
109
|
-
```javascript
|
|
110
|
-
// config/middlewares.{js,ts}
|
|
111
|
-
'strapi::cors';
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
with:
|
|
115
|
-
|
|
116
|
-
```javascript
|
|
117
|
-
// config/middlewares.{js,ts}
|
|
118
|
-
{
|
|
119
|
-
name: "strapi::cors",
|
|
120
|
-
config: {
|
|
121
|
-
headers: ["Content-Type", "Authorization", "Cache-Control"], // Add 'Cache-Control' to the allowed headers
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
```
|
|
125
|
-
|
|
126
100
|
## 🛠️ Contributing
|
|
127
101
|
|
|
128
102
|
Contributions are welcome! If you have suggestions or improvements, please open an issue or submit a pull request.
|
package/dist/admin/index.js
CHANGED
|
@@ -161,7 +161,11 @@ function PurgeCacheButton() {
|
|
|
161
161
|
function PurgeEntityButton() {
|
|
162
162
|
const { formatMessage } = reactIntl.useIntl();
|
|
163
163
|
const { id, isSingleType, contentType } = admin.unstable_useContentManagerContext();
|
|
164
|
-
const
|
|
164
|
+
const apiPath = isSingleType ? contentType?.info.singularName : id;
|
|
165
|
+
if (!apiPath) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const keyToUse = encodeURIComponent(apiPath);
|
|
165
169
|
const contentTypeName = isSingleType ? contentType?.info.singularName : contentType?.info.pluralName;
|
|
166
170
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
167
171
|
PurgeModal,
|
package/dist/admin/index.mjs
CHANGED
|
@@ -160,7 +160,11 @@ function PurgeCacheButton() {
|
|
|
160
160
|
function PurgeEntityButton() {
|
|
161
161
|
const { formatMessage } = useIntl();
|
|
162
162
|
const { id, isSingleType, contentType } = unstable_useContentManagerContext();
|
|
163
|
-
const
|
|
163
|
+
const apiPath = isSingleType ? contentType?.info.singularName : id;
|
|
164
|
+
if (!apiPath) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const keyToUse = encodeURIComponent(apiPath);
|
|
164
168
|
const contentTypeName = isSingleType ? contentType?.info.singularName : contentType?.info.pluralName;
|
|
165
169
|
return /* @__PURE__ */ jsx(
|
|
166
170
|
PurgeModal,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
declare function PurgeEntityButton(): import("react/jsx-runtime").JSX.Element;
|
|
1
|
+
declare function PurgeEntityButton(): import("react/jsx-runtime").JSX.Element | null;
|
|
2
2
|
export default PurgeEntityButton;
|
package/dist/server/index.js
CHANGED
|
@@ -73,6 +73,7 @@ const bootstrap = ({ strapi: strapi2 }) => {
|
|
|
73
73
|
try {
|
|
74
74
|
const cacheService = strapi2.plugin("strapi-cache").services.service;
|
|
75
75
|
const autoPurgeCache = strapi2.plugin("strapi-cache").config("autoPurgeCache");
|
|
76
|
+
const autoPurgeCacheOnStart = strapi2.plugin("strapi-cache").config("autoPurgeCacheOnStart");
|
|
76
77
|
const cacheStore = cacheService.getCacheInstance();
|
|
77
78
|
if (!cacheStore) {
|
|
78
79
|
loggy.error("Plugin could not be initialized");
|
|
@@ -95,6 +96,13 @@ const bootstrap = ({ strapi: strapi2 }) => {
|
|
|
95
96
|
}
|
|
96
97
|
});
|
|
97
98
|
}
|
|
99
|
+
if (autoPurgeCacheOnStart) {
|
|
100
|
+
cacheStore.reset().then(() => {
|
|
101
|
+
loggy.info("Cache purged successfully");
|
|
102
|
+
}).catch((error) => {
|
|
103
|
+
loggy.error(`Error purging cache on start: ${error.message}`);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
98
106
|
} catch (error) {
|
|
99
107
|
loggy.error("Plugin could not be initialized");
|
|
100
108
|
return;
|
|
@@ -179,6 +187,7 @@ function getCacheHeaderConfig() {
|
|
|
179
187
|
const middleware$1 = async (ctx, next) => {
|
|
180
188
|
const cacheService = strapi.plugin("strapi-cache").services.service;
|
|
181
189
|
const cacheableRoutes = strapi.plugin("strapi-cache").config("cacheableRoutes");
|
|
190
|
+
const excludeRoutes = strapi.plugin("strapi-cache").config("excludeRoutes");
|
|
182
191
|
const { cacheHeaders, cacheHeadersDenyList, cacheHeadersAllowList, cacheAuthorizedRequests } = getCacheHeaderConfig();
|
|
183
192
|
const cacheStore = cacheService.getCacheInstance();
|
|
184
193
|
const { url } = ctx.request;
|
|
@@ -186,6 +195,12 @@ const middleware$1 = async (ctx, next) => {
|
|
|
186
195
|
const cacheEntry = await cacheStore.get(key);
|
|
187
196
|
const cacheControlHeader = ctx.request.headers["cache-control"];
|
|
188
197
|
const noCache = cacheControlHeader && cacheControlHeader.includes("no-cache");
|
|
198
|
+
const routeIsExcluded = excludeRoutes.some((route) => url.startsWith(route));
|
|
199
|
+
if (routeIsExcluded) {
|
|
200
|
+
loggy.info(`Route excluded from cache: ${url}`);
|
|
201
|
+
await next();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
189
204
|
const routeIsCachable = cacheableRoutes.some((route) => url.startsWith(route)) || cacheableRoutes.length === 0 && url.startsWith("/api");
|
|
190
205
|
const authorizationHeader = ctx.request.headers["authorization"];
|
|
191
206
|
if (authorizationHeader && !cacheAuthorizedRequests) {
|
|
@@ -193,12 +208,32 @@ const middleware$1 = async (ctx, next) => {
|
|
|
193
208
|
await next();
|
|
194
209
|
return;
|
|
195
210
|
}
|
|
211
|
+
const middlewaresConfig = strapi.config.get("middlewares");
|
|
212
|
+
const corsMiddleware = middlewaresConfig.find((mw) => mw.name === "strapi::cors");
|
|
213
|
+
const corsConfig = corsMiddleware?.config;
|
|
214
|
+
const origin = ctx?.request?.headers?.origin;
|
|
215
|
+
let allowedOrigins = corsConfig?.origin ?? "*";
|
|
216
|
+
if (typeof allowedOrigins === "string") {
|
|
217
|
+
allowedOrigins = [allowedOrigins];
|
|
218
|
+
}
|
|
196
219
|
if (cacheEntry && !noCache) {
|
|
197
220
|
loggy.info(`HIT with key: ${key}`);
|
|
198
221
|
ctx.status = 200;
|
|
199
222
|
ctx.body = cacheEntry.body;
|
|
200
223
|
if (cacheHeaders) {
|
|
201
224
|
ctx.set(cacheEntry.headers);
|
|
225
|
+
}
|
|
226
|
+
if (corsMiddleware) {
|
|
227
|
+
loggy.info("CORS middleware is set, checking allowed origins");
|
|
228
|
+
if (allowedOrigins.includes(origin)) {
|
|
229
|
+
loggy.info(`Setting Access-Control-Allow-Origin to ${origin}`);
|
|
230
|
+
ctx.set("Access-Control-Allow-Origin", origin);
|
|
231
|
+
} else if (typeof origin === "undefined" || allowedOrigins.includes("*")) {
|
|
232
|
+
loggy.info("No origin header or * in allowed origins, setting to *");
|
|
233
|
+
ctx.set("Access-Control-Allow-Origin", "*");
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
loggy.info("No CORS middleware set, setting to request origin or *");
|
|
202
237
|
ctx.set("Access-Control-Allow-Origin", ctx.request.headers.origin || "*");
|
|
203
238
|
}
|
|
204
239
|
return;
|
|
@@ -212,15 +247,27 @@ const middleware$1 = async (ctx, next) => {
|
|
|
212
247
|
cacheHeadersAllowList,
|
|
213
248
|
cacheHeadersDenyList
|
|
214
249
|
);
|
|
250
|
+
let setCache = true;
|
|
251
|
+
if (corsMiddleware) {
|
|
252
|
+
if (allowedOrigins.includes(origin)) ;
|
|
253
|
+
else if (typeof origin === "undefined" || allowedOrigins.includes("*")) ;
|
|
254
|
+
else {
|
|
255
|
+
setCache = false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
215
258
|
if (ctx.body instanceof Stream__default.default) {
|
|
216
259
|
const buf = await streamToBuffer(ctx.body);
|
|
217
260
|
const contentEncoding = ctx.response.headers["content-encoding"];
|
|
218
261
|
const decompressed = await decompressBuffer(buf, contentEncoding);
|
|
219
262
|
const responseText = decodeBufferToText(decompressed);
|
|
220
|
-
|
|
263
|
+
if (setCache) {
|
|
264
|
+
await cacheStore.set(key, { body: responseText, headers: headersToStore });
|
|
265
|
+
}
|
|
221
266
|
ctx.body = buf;
|
|
222
267
|
} else {
|
|
223
|
-
|
|
268
|
+
if (setCache) {
|
|
269
|
+
await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
|
|
270
|
+
}
|
|
224
271
|
}
|
|
225
272
|
}
|
|
226
273
|
};
|
|
@@ -259,12 +306,32 @@ const middleware = async (ctx, next) => {
|
|
|
259
306
|
await next();
|
|
260
307
|
return;
|
|
261
308
|
}
|
|
309
|
+
const middlewaresConfig = strapi.config.get("middlewares");
|
|
310
|
+
const corsMiddleware = middlewaresConfig.find((mw) => mw.name === "strapi::cors");
|
|
311
|
+
const corsConfig = corsMiddleware?.config;
|
|
312
|
+
const origin = ctx?.request?.headers?.origin;
|
|
313
|
+
let allowedOrigins = corsConfig?.origin ?? "*";
|
|
314
|
+
if (typeof allowedOrigins === "string") {
|
|
315
|
+
allowedOrigins = [allowedOrigins];
|
|
316
|
+
}
|
|
262
317
|
if (cacheEntry && !noCache) {
|
|
263
318
|
loggy.info(`HIT with key: ${key}`);
|
|
264
319
|
ctx.status = 200;
|
|
265
320
|
ctx.body = cacheEntry.body;
|
|
266
321
|
if (cacheHeaders) {
|
|
267
322
|
ctx.set(cacheEntry.headers);
|
|
323
|
+
}
|
|
324
|
+
if (corsMiddleware) {
|
|
325
|
+
loggy.info("CORS middleware is set, checking allowed origins");
|
|
326
|
+
if (allowedOrigins.includes(origin)) {
|
|
327
|
+
loggy.info(`Setting Access-Control-Allow-Origin to ${origin}`);
|
|
328
|
+
ctx.set("Access-Control-Allow-Origin", origin);
|
|
329
|
+
} else if (typeof origin === "undefined" || allowedOrigins.includes("*")) {
|
|
330
|
+
loggy.info("No origin header or * in allowed origins, setting to *");
|
|
331
|
+
ctx.set("Access-Control-Allow-Origin", "*");
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
loggy.info("No CORS middleware set, setting to request origin or *");
|
|
268
335
|
ctx.set("Access-Control-Allow-Origin", ctx.request.headers.origin || "*");
|
|
269
336
|
}
|
|
270
337
|
return;
|
|
@@ -284,15 +351,27 @@ const middleware = async (ctx, next) => {
|
|
|
284
351
|
cacheHeadersAllowList,
|
|
285
352
|
cacheHeadersDenyList
|
|
286
353
|
);
|
|
354
|
+
let setCache = true;
|
|
355
|
+
if (corsMiddleware) {
|
|
356
|
+
if (allowedOrigins.includes(origin)) ;
|
|
357
|
+
else if (typeof origin === "undefined" || allowedOrigins.includes("*")) ;
|
|
358
|
+
else {
|
|
359
|
+
setCache = false;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
287
362
|
if (ctx.body instanceof Stream__default.default) {
|
|
288
363
|
const buf = await streamToBuffer(ctx.body);
|
|
289
364
|
const contentEncoding = ctx.response.headers["content-encoding"];
|
|
290
365
|
const decompressed = await decompressBuffer(buf, contentEncoding);
|
|
291
366
|
const responseText = decodeBufferToText(decompressed);
|
|
292
|
-
|
|
367
|
+
if (setCache) {
|
|
368
|
+
await cacheStore.set(key, { body: responseText, headers: headersToStore });
|
|
369
|
+
}
|
|
293
370
|
ctx.body = buf;
|
|
294
371
|
} else {
|
|
295
|
-
|
|
372
|
+
if (setCache) {
|
|
373
|
+
await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
|
|
374
|
+
}
|
|
296
375
|
}
|
|
297
376
|
}
|
|
298
377
|
};
|
|
@@ -313,6 +392,7 @@ const config = {
|
|
|
313
392
|
allowStale: false,
|
|
314
393
|
cacheableRoutes: [],
|
|
315
394
|
provider: "memory",
|
|
395
|
+
excludeRoutes: [],
|
|
316
396
|
redisConfig: env("REDIS_URL"),
|
|
317
397
|
redisClusterNodes: [],
|
|
318
398
|
redisClusterOptions: {},
|
|
@@ -321,7 +401,8 @@ const config = {
|
|
|
321
401
|
cacheHeadersAllowList: [],
|
|
322
402
|
cacheAuthorizedRequests: false,
|
|
323
403
|
cacheGetTimeoutInMs: 1e3,
|
|
324
|
-
autoPurgeCache: true
|
|
404
|
+
autoPurgeCache: true,
|
|
405
|
+
autoPurgeCacheOnStart: true
|
|
325
406
|
}),
|
|
326
407
|
validator: (config2) => {
|
|
327
408
|
if (typeof config2.debug !== "boolean") {
|
|
@@ -342,6 +423,9 @@ const config = {
|
|
|
342
423
|
if (!Array.isArray(config2.cacheableRoutes) || config2.cacheableRoutes.some((item) => typeof item !== "string")) {
|
|
343
424
|
throw new Error(`Invalid config: cacheableRoutes must be an string array`);
|
|
344
425
|
}
|
|
426
|
+
if (!Array.isArray(config2.excludeRoutes) || config2.excludeRoutes.some((item) => typeof item !== "string")) {
|
|
427
|
+
throw new Error(`Invalid config: excludeRoutes must be a string array`);
|
|
428
|
+
}
|
|
345
429
|
if (typeof config2.provider !== "string") {
|
|
346
430
|
throw new Error(`Invalid config: provider must be a string`);
|
|
347
431
|
}
|
|
@@ -382,6 +466,9 @@ const config = {
|
|
|
382
466
|
if (typeof config2.autoPurgeCache !== "boolean") {
|
|
383
467
|
throw new Error(`Invalid config: autoPurgeCache must be a boolean`);
|
|
384
468
|
}
|
|
469
|
+
if (typeof config2.autoPurgeCacheOnStart !== "boolean") {
|
|
470
|
+
throw new Error(`Invalid config: autoPurgeCacheOnStart must be a boolean`);
|
|
471
|
+
}
|
|
385
472
|
}
|
|
386
473
|
};
|
|
387
474
|
const contentTypes = {};
|
|
@@ -591,6 +678,7 @@ class RedisCacheProvider {
|
|
|
591
678
|
this.cacheGetTimeoutInMs = Number(
|
|
592
679
|
this.strapi.plugin("strapi-cache").config("cacheGetTimeoutInMs")
|
|
593
680
|
);
|
|
681
|
+
this.keyPrefix = this.strapi.plugin("strapi-cache").config("redisConfig")?.["keyPrefix"] ?? "";
|
|
594
682
|
if (redisClusterNodes.length) {
|
|
595
683
|
const redisClusterOptions = this.strapi.plugin("strapi-cache").config("redisClusterOptions");
|
|
596
684
|
if (!redisClusterOptions["redisOptions"]) {
|
|
@@ -640,8 +728,9 @@ class RedisCacheProvider {
|
|
|
640
728
|
async del(key) {
|
|
641
729
|
if (!this.ready) return null;
|
|
642
730
|
try {
|
|
643
|
-
|
|
644
|
-
|
|
731
|
+
const relativeKey = key.slice(this.keyPrefix.length);
|
|
732
|
+
loggy.info(`Redis PURGING KEY: ${relativeKey}`);
|
|
733
|
+
await this.client.del(relativeKey);
|
|
645
734
|
return true;
|
|
646
735
|
} catch (error) {
|
|
647
736
|
loggy.error(`Redis del error: ${error}`);
|
|
@@ -651,7 +740,7 @@ class RedisCacheProvider {
|
|
|
651
740
|
async keys() {
|
|
652
741
|
if (!this.ready) return null;
|
|
653
742
|
try {
|
|
654
|
-
const keys = await this.client.keys(
|
|
743
|
+
const keys = await this.client.keys(`${this.keyPrefix}*`);
|
|
655
744
|
return keys;
|
|
656
745
|
} catch (error) {
|
|
657
746
|
loggy.error(`Redis keys error: ${error}`);
|
|
@@ -661,6 +750,14 @@ class RedisCacheProvider {
|
|
|
661
750
|
async reset() {
|
|
662
751
|
if (!this.ready) return null;
|
|
663
752
|
try {
|
|
753
|
+
if (this.keyPrefix) {
|
|
754
|
+
loggy.info(`Redis FLUSHING NAMESPACE: ${this.keyPrefix}`);
|
|
755
|
+
const keys = await this.keys();
|
|
756
|
+
if (!keys) return null;
|
|
757
|
+
const toDelete = keys.filter((key) => key.startsWith(this.keyPrefix));
|
|
758
|
+
await Promise.all(toDelete.map((key) => this.del(key)));
|
|
759
|
+
return true;
|
|
760
|
+
}
|
|
664
761
|
loggy.info(`Redis FLUSHING ALL KEYS`);
|
|
665
762
|
await this.client.flushdb();
|
|
666
763
|
return true;
|
package/dist/server/index.mjs
CHANGED
|
@@ -69,6 +69,7 @@ const bootstrap = ({ strapi: strapi2 }) => {
|
|
|
69
69
|
try {
|
|
70
70
|
const cacheService = strapi2.plugin("strapi-cache").services.service;
|
|
71
71
|
const autoPurgeCache = strapi2.plugin("strapi-cache").config("autoPurgeCache");
|
|
72
|
+
const autoPurgeCacheOnStart = strapi2.plugin("strapi-cache").config("autoPurgeCacheOnStart");
|
|
72
73
|
const cacheStore = cacheService.getCacheInstance();
|
|
73
74
|
if (!cacheStore) {
|
|
74
75
|
loggy.error("Plugin could not be initialized");
|
|
@@ -91,6 +92,13 @@ const bootstrap = ({ strapi: strapi2 }) => {
|
|
|
91
92
|
}
|
|
92
93
|
});
|
|
93
94
|
}
|
|
95
|
+
if (autoPurgeCacheOnStart) {
|
|
96
|
+
cacheStore.reset().then(() => {
|
|
97
|
+
loggy.info("Cache purged successfully");
|
|
98
|
+
}).catch((error) => {
|
|
99
|
+
loggy.error(`Error purging cache on start: ${error.message}`);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
94
102
|
} catch (error) {
|
|
95
103
|
loggy.error("Plugin could not be initialized");
|
|
96
104
|
return;
|
|
@@ -175,6 +183,7 @@ function getCacheHeaderConfig() {
|
|
|
175
183
|
const middleware$1 = async (ctx, next) => {
|
|
176
184
|
const cacheService = strapi.plugin("strapi-cache").services.service;
|
|
177
185
|
const cacheableRoutes = strapi.plugin("strapi-cache").config("cacheableRoutes");
|
|
186
|
+
const excludeRoutes = strapi.plugin("strapi-cache").config("excludeRoutes");
|
|
178
187
|
const { cacheHeaders, cacheHeadersDenyList, cacheHeadersAllowList, cacheAuthorizedRequests } = getCacheHeaderConfig();
|
|
179
188
|
const cacheStore = cacheService.getCacheInstance();
|
|
180
189
|
const { url } = ctx.request;
|
|
@@ -182,6 +191,12 @@ const middleware$1 = async (ctx, next) => {
|
|
|
182
191
|
const cacheEntry = await cacheStore.get(key);
|
|
183
192
|
const cacheControlHeader = ctx.request.headers["cache-control"];
|
|
184
193
|
const noCache = cacheControlHeader && cacheControlHeader.includes("no-cache");
|
|
194
|
+
const routeIsExcluded = excludeRoutes.some((route) => url.startsWith(route));
|
|
195
|
+
if (routeIsExcluded) {
|
|
196
|
+
loggy.info(`Route excluded from cache: ${url}`);
|
|
197
|
+
await next();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
185
200
|
const routeIsCachable = cacheableRoutes.some((route) => url.startsWith(route)) || cacheableRoutes.length === 0 && url.startsWith("/api");
|
|
186
201
|
const authorizationHeader = ctx.request.headers["authorization"];
|
|
187
202
|
if (authorizationHeader && !cacheAuthorizedRequests) {
|
|
@@ -189,12 +204,32 @@ const middleware$1 = async (ctx, next) => {
|
|
|
189
204
|
await next();
|
|
190
205
|
return;
|
|
191
206
|
}
|
|
207
|
+
const middlewaresConfig = strapi.config.get("middlewares");
|
|
208
|
+
const corsMiddleware = middlewaresConfig.find((mw) => mw.name === "strapi::cors");
|
|
209
|
+
const corsConfig = corsMiddleware?.config;
|
|
210
|
+
const origin = ctx?.request?.headers?.origin;
|
|
211
|
+
let allowedOrigins = corsConfig?.origin ?? "*";
|
|
212
|
+
if (typeof allowedOrigins === "string") {
|
|
213
|
+
allowedOrigins = [allowedOrigins];
|
|
214
|
+
}
|
|
192
215
|
if (cacheEntry && !noCache) {
|
|
193
216
|
loggy.info(`HIT with key: ${key}`);
|
|
194
217
|
ctx.status = 200;
|
|
195
218
|
ctx.body = cacheEntry.body;
|
|
196
219
|
if (cacheHeaders) {
|
|
197
220
|
ctx.set(cacheEntry.headers);
|
|
221
|
+
}
|
|
222
|
+
if (corsMiddleware) {
|
|
223
|
+
loggy.info("CORS middleware is set, checking allowed origins");
|
|
224
|
+
if (allowedOrigins.includes(origin)) {
|
|
225
|
+
loggy.info(`Setting Access-Control-Allow-Origin to ${origin}`);
|
|
226
|
+
ctx.set("Access-Control-Allow-Origin", origin);
|
|
227
|
+
} else if (typeof origin === "undefined" || allowedOrigins.includes("*")) {
|
|
228
|
+
loggy.info("No origin header or * in allowed origins, setting to *");
|
|
229
|
+
ctx.set("Access-Control-Allow-Origin", "*");
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
loggy.info("No CORS middleware set, setting to request origin or *");
|
|
198
233
|
ctx.set("Access-Control-Allow-Origin", ctx.request.headers.origin || "*");
|
|
199
234
|
}
|
|
200
235
|
return;
|
|
@@ -208,15 +243,27 @@ const middleware$1 = async (ctx, next) => {
|
|
|
208
243
|
cacheHeadersAllowList,
|
|
209
244
|
cacheHeadersDenyList
|
|
210
245
|
);
|
|
246
|
+
let setCache = true;
|
|
247
|
+
if (corsMiddleware) {
|
|
248
|
+
if (allowedOrigins.includes(origin)) ;
|
|
249
|
+
else if (typeof origin === "undefined" || allowedOrigins.includes("*")) ;
|
|
250
|
+
else {
|
|
251
|
+
setCache = false;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
211
254
|
if (ctx.body instanceof Stream) {
|
|
212
255
|
const buf = await streamToBuffer(ctx.body);
|
|
213
256
|
const contentEncoding = ctx.response.headers["content-encoding"];
|
|
214
257
|
const decompressed = await decompressBuffer(buf, contentEncoding);
|
|
215
258
|
const responseText = decodeBufferToText(decompressed);
|
|
216
|
-
|
|
259
|
+
if (setCache) {
|
|
260
|
+
await cacheStore.set(key, { body: responseText, headers: headersToStore });
|
|
261
|
+
}
|
|
217
262
|
ctx.body = buf;
|
|
218
263
|
} else {
|
|
219
|
-
|
|
264
|
+
if (setCache) {
|
|
265
|
+
await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
|
|
266
|
+
}
|
|
220
267
|
}
|
|
221
268
|
}
|
|
222
269
|
};
|
|
@@ -255,12 +302,32 @@ const middleware = async (ctx, next) => {
|
|
|
255
302
|
await next();
|
|
256
303
|
return;
|
|
257
304
|
}
|
|
305
|
+
const middlewaresConfig = strapi.config.get("middlewares");
|
|
306
|
+
const corsMiddleware = middlewaresConfig.find((mw) => mw.name === "strapi::cors");
|
|
307
|
+
const corsConfig = corsMiddleware?.config;
|
|
308
|
+
const origin = ctx?.request?.headers?.origin;
|
|
309
|
+
let allowedOrigins = corsConfig?.origin ?? "*";
|
|
310
|
+
if (typeof allowedOrigins === "string") {
|
|
311
|
+
allowedOrigins = [allowedOrigins];
|
|
312
|
+
}
|
|
258
313
|
if (cacheEntry && !noCache) {
|
|
259
314
|
loggy.info(`HIT with key: ${key}`);
|
|
260
315
|
ctx.status = 200;
|
|
261
316
|
ctx.body = cacheEntry.body;
|
|
262
317
|
if (cacheHeaders) {
|
|
263
318
|
ctx.set(cacheEntry.headers);
|
|
319
|
+
}
|
|
320
|
+
if (corsMiddleware) {
|
|
321
|
+
loggy.info("CORS middleware is set, checking allowed origins");
|
|
322
|
+
if (allowedOrigins.includes(origin)) {
|
|
323
|
+
loggy.info(`Setting Access-Control-Allow-Origin to ${origin}`);
|
|
324
|
+
ctx.set("Access-Control-Allow-Origin", origin);
|
|
325
|
+
} else if (typeof origin === "undefined" || allowedOrigins.includes("*")) {
|
|
326
|
+
loggy.info("No origin header or * in allowed origins, setting to *");
|
|
327
|
+
ctx.set("Access-Control-Allow-Origin", "*");
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
330
|
+
loggy.info("No CORS middleware set, setting to request origin or *");
|
|
264
331
|
ctx.set("Access-Control-Allow-Origin", ctx.request.headers.origin || "*");
|
|
265
332
|
}
|
|
266
333
|
return;
|
|
@@ -280,15 +347,27 @@ const middleware = async (ctx, next) => {
|
|
|
280
347
|
cacheHeadersAllowList,
|
|
281
348
|
cacheHeadersDenyList
|
|
282
349
|
);
|
|
350
|
+
let setCache = true;
|
|
351
|
+
if (corsMiddleware) {
|
|
352
|
+
if (allowedOrigins.includes(origin)) ;
|
|
353
|
+
else if (typeof origin === "undefined" || allowedOrigins.includes("*")) ;
|
|
354
|
+
else {
|
|
355
|
+
setCache = false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
283
358
|
if (ctx.body instanceof Stream) {
|
|
284
359
|
const buf = await streamToBuffer(ctx.body);
|
|
285
360
|
const contentEncoding = ctx.response.headers["content-encoding"];
|
|
286
361
|
const decompressed = await decompressBuffer(buf, contentEncoding);
|
|
287
362
|
const responseText = decodeBufferToText(decompressed);
|
|
288
|
-
|
|
363
|
+
if (setCache) {
|
|
364
|
+
await cacheStore.set(key, { body: responseText, headers: headersToStore });
|
|
365
|
+
}
|
|
289
366
|
ctx.body = buf;
|
|
290
367
|
} else {
|
|
291
|
-
|
|
368
|
+
if (setCache) {
|
|
369
|
+
await cacheStore.set(key, { body: ctx.body, headers: headersToStore });
|
|
370
|
+
}
|
|
292
371
|
}
|
|
293
372
|
}
|
|
294
373
|
};
|
|
@@ -309,6 +388,7 @@ const config = {
|
|
|
309
388
|
allowStale: false,
|
|
310
389
|
cacheableRoutes: [],
|
|
311
390
|
provider: "memory",
|
|
391
|
+
excludeRoutes: [],
|
|
312
392
|
redisConfig: env("REDIS_URL"),
|
|
313
393
|
redisClusterNodes: [],
|
|
314
394
|
redisClusterOptions: {},
|
|
@@ -317,7 +397,8 @@ const config = {
|
|
|
317
397
|
cacheHeadersAllowList: [],
|
|
318
398
|
cacheAuthorizedRequests: false,
|
|
319
399
|
cacheGetTimeoutInMs: 1e3,
|
|
320
|
-
autoPurgeCache: true
|
|
400
|
+
autoPurgeCache: true,
|
|
401
|
+
autoPurgeCacheOnStart: true
|
|
321
402
|
}),
|
|
322
403
|
validator: (config2) => {
|
|
323
404
|
if (typeof config2.debug !== "boolean") {
|
|
@@ -338,6 +419,9 @@ const config = {
|
|
|
338
419
|
if (!Array.isArray(config2.cacheableRoutes) || config2.cacheableRoutes.some((item) => typeof item !== "string")) {
|
|
339
420
|
throw new Error(`Invalid config: cacheableRoutes must be an string array`);
|
|
340
421
|
}
|
|
422
|
+
if (!Array.isArray(config2.excludeRoutes) || config2.excludeRoutes.some((item) => typeof item !== "string")) {
|
|
423
|
+
throw new Error(`Invalid config: excludeRoutes must be a string array`);
|
|
424
|
+
}
|
|
341
425
|
if (typeof config2.provider !== "string") {
|
|
342
426
|
throw new Error(`Invalid config: provider must be a string`);
|
|
343
427
|
}
|
|
@@ -378,6 +462,9 @@ const config = {
|
|
|
378
462
|
if (typeof config2.autoPurgeCache !== "boolean") {
|
|
379
463
|
throw new Error(`Invalid config: autoPurgeCache must be a boolean`);
|
|
380
464
|
}
|
|
465
|
+
if (typeof config2.autoPurgeCacheOnStart !== "boolean") {
|
|
466
|
+
throw new Error(`Invalid config: autoPurgeCacheOnStart must be a boolean`);
|
|
467
|
+
}
|
|
381
468
|
}
|
|
382
469
|
};
|
|
383
470
|
const contentTypes = {};
|
|
@@ -587,6 +674,7 @@ class RedisCacheProvider {
|
|
|
587
674
|
this.cacheGetTimeoutInMs = Number(
|
|
588
675
|
this.strapi.plugin("strapi-cache").config("cacheGetTimeoutInMs")
|
|
589
676
|
);
|
|
677
|
+
this.keyPrefix = this.strapi.plugin("strapi-cache").config("redisConfig")?.["keyPrefix"] ?? "";
|
|
590
678
|
if (redisClusterNodes.length) {
|
|
591
679
|
const redisClusterOptions = this.strapi.plugin("strapi-cache").config("redisClusterOptions");
|
|
592
680
|
if (!redisClusterOptions["redisOptions"]) {
|
|
@@ -636,8 +724,9 @@ class RedisCacheProvider {
|
|
|
636
724
|
async del(key) {
|
|
637
725
|
if (!this.ready) return null;
|
|
638
726
|
try {
|
|
639
|
-
|
|
640
|
-
|
|
727
|
+
const relativeKey = key.slice(this.keyPrefix.length);
|
|
728
|
+
loggy.info(`Redis PURGING KEY: ${relativeKey}`);
|
|
729
|
+
await this.client.del(relativeKey);
|
|
641
730
|
return true;
|
|
642
731
|
} catch (error) {
|
|
643
732
|
loggy.error(`Redis del error: ${error}`);
|
|
@@ -647,7 +736,7 @@ class RedisCacheProvider {
|
|
|
647
736
|
async keys() {
|
|
648
737
|
if (!this.ready) return null;
|
|
649
738
|
try {
|
|
650
|
-
const keys = await this.client.keys(
|
|
739
|
+
const keys = await this.client.keys(`${this.keyPrefix}*`);
|
|
651
740
|
return keys;
|
|
652
741
|
} catch (error) {
|
|
653
742
|
loggy.error(`Redis keys error: ${error}`);
|
|
@@ -657,6 +746,14 @@ class RedisCacheProvider {
|
|
|
657
746
|
async reset() {
|
|
658
747
|
if (!this.ready) return null;
|
|
659
748
|
try {
|
|
749
|
+
if (this.keyPrefix) {
|
|
750
|
+
loggy.info(`Redis FLUSHING NAMESPACE: ${this.keyPrefix}`);
|
|
751
|
+
const keys = await this.keys();
|
|
752
|
+
if (!keys) return null;
|
|
753
|
+
const toDelete = keys.filter((key) => key.startsWith(this.keyPrefix));
|
|
754
|
+
await Promise.all(toDelete.map((key) => this.del(key)));
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
660
757
|
loggy.info(`Redis FLUSHING ALL KEYS`);
|
|
661
758
|
await this.client.flushdb();
|
|
662
759
|
return true;
|
|
@@ -9,6 +9,7 @@ declare const _default: {
|
|
|
9
9
|
allowStale: boolean;
|
|
10
10
|
cacheableRoutes: any[];
|
|
11
11
|
provider: string;
|
|
12
|
+
excludeRoutes: any[];
|
|
12
13
|
redisConfig: any;
|
|
13
14
|
redisClusterNodes: any[];
|
|
14
15
|
redisClusterOptions: {};
|
|
@@ -18,6 +19,7 @@ declare const _default: {
|
|
|
18
19
|
cacheAuthorizedRequests: boolean;
|
|
19
20
|
cacheGetTimeoutInMs: number;
|
|
20
21
|
autoPurgeCache: boolean;
|
|
22
|
+
autoPurgeCacheOnStart: boolean;
|
|
21
23
|
};
|
|
22
24
|
validator: (config: any) => void;
|
|
23
25
|
};
|
|
@@ -18,6 +18,7 @@ declare const _default: {
|
|
|
18
18
|
allowStale: boolean;
|
|
19
19
|
cacheableRoutes: any[];
|
|
20
20
|
provider: string;
|
|
21
|
+
excludeRoutes: any[];
|
|
21
22
|
redisConfig: any;
|
|
22
23
|
redisClusterNodes: any[];
|
|
23
24
|
redisClusterOptions: {};
|
|
@@ -27,6 +28,7 @@ declare const _default: {
|
|
|
27
28
|
cacheAuthorizedRequests: boolean;
|
|
28
29
|
cacheGetTimeoutInMs: number;
|
|
29
30
|
autoPurgeCache: boolean;
|
|
31
|
+
autoPurgeCacheOnStart: boolean;
|
|
30
32
|
};
|
|
31
33
|
validator: (config: any) => void;
|
|
32
34
|
};
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.5.
|
|
2
|
+
"version": "1.5.8",
|
|
3
3
|
"keywords": [
|
|
4
4
|
"strapi cache",
|
|
5
5
|
"strapi rest cache",
|
|
@@ -42,8 +42,7 @@
|
|
|
42
42
|
"ioredis": "^5.6.1",
|
|
43
43
|
"lru-cache": "^11.1.0",
|
|
44
44
|
"raw-body": "^3.0.0",
|
|
45
|
-
"react-intl": "^7.1.10"
|
|
46
|
-
"strapi-cache": "file:.yalc/strapi-cache"
|
|
45
|
+
"react-intl": "^7.1.10"
|
|
47
46
|
},
|
|
48
47
|
"devDependencies": {
|
|
49
48
|
"@strapi/sdk-plugin": "^5.3.2",
|