mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[HTTP] Update static asset headers to include public
and immutable
(#180378)
## Summary
Adds the `public` and `immutable` ~header~ directives to static assets
registered in via the `core_app.ts` logic (se [mdn
docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
for more info). This will ensure that assets are stored in a share-safe
cache and that for the "fresh" duration (1 year) the browser won't reach
out to Kibana server for the asset again.
## Notes
* This does not include all static assets registered via
`registerStaticDir`
* We could consider updating [this
logic](29e3ccd626/packages/core/apps/core-apps-server-internal/src/core_app.ts (L274)
),
but those assets are not cache bustable, so we'd need to selectively add
the header to the path prefixed with static asset prefix, can do in this
PR or follow up
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
e537deb9b5
commit
f26b368180
4 changed files with 92 additions and 2 deletions
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import mockFs from 'mock-fs';
|
||||
import { kibanaResponseFactory } from '@kbn/core-http-router-server-internal';
|
||||
import { createDynamicAssetHandler } from './dynamic_asset_response';
|
||||
|
||||
function getHandler(args?: Partial<Parameters<typeof createDynamicAssetHandler>[0]>) {
|
||||
return createDynamicAssetHandler({
|
||||
bundlesPath: '/test',
|
||||
publicPath: '/public',
|
||||
fileHashCache: {
|
||||
get: jest.fn(),
|
||||
set: jest.fn(),
|
||||
del: jest.fn(),
|
||||
},
|
||||
isDist: true,
|
||||
...args,
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
mockFs.restore();
|
||||
});
|
||||
it('returns 403 if the path requested does not match bundle path', async () => {
|
||||
const handler = getHandler();
|
||||
const result = await handler(
|
||||
{} as any,
|
||||
{ params: { path: '/non-existent/abc.js' }, headers: { 'accept-encoding': '*' } } as any,
|
||||
kibanaResponseFactory
|
||||
);
|
||||
expect(result.status).toBe(403);
|
||||
});
|
||||
|
||||
it('returns 404 if the file does not exist', async () => {
|
||||
const handler = getHandler();
|
||||
mockFs({}); // no files
|
||||
const filePath = '/test/abc.js';
|
||||
const result = await handler(
|
||||
{} as any,
|
||||
{ params: { path: filePath }, headers: { 'accept-encoding': '*' } } as any,
|
||||
kibanaResponseFactory
|
||||
);
|
||||
expect(result.status).toBe(404);
|
||||
});
|
||||
|
||||
describe('headers', () => {
|
||||
it('returns the expected headers', async () => {
|
||||
const handler = getHandler();
|
||||
const filePath = '/test/abc.js';
|
||||
mockFs({
|
||||
[filePath]: Buffer.from('test'),
|
||||
});
|
||||
const result = await handler(
|
||||
{} as any,
|
||||
{ params: { path: filePath }, headers: { 'accept-encoding': 'br' } } as any,
|
||||
kibanaResponseFactory
|
||||
);
|
||||
expect(result.options.headers).toEqual({
|
||||
'cache-control': 'public, max-age=31536000, immutable',
|
||||
'content-type': 'application/javascript; charset=utf-8',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the expected headers when not in dist mode', async () => {
|
||||
const handler = getHandler({ isDist: false });
|
||||
const filePath = '/test/abc.js';
|
||||
mockFs({
|
||||
[filePath]: Buffer.from('test'),
|
||||
});
|
||||
const result = await handler(
|
||||
{} as any,
|
||||
{ params: { path: filePath }, headers: { 'accept-encoding': '*' } } as any,
|
||||
kibanaResponseFactory
|
||||
);
|
||||
expect(result.options.headers).toEqual({
|
||||
'cache-control': 'must-revalidate',
|
||||
'content-type': 'application/javascript; charset=utf-8',
|
||||
etag: expect.stringMatching(/^[a-f0-9]{40}-\/public/i),
|
||||
});
|
||||
});
|
||||
});
|
|
@ -75,7 +75,9 @@ export const createDynamicAssetHandler = ({
|
|||
|
||||
let headers: Record<string, string>;
|
||||
if (isDist) {
|
||||
headers = { 'cache-control': `max-age=${365 * DAY}` };
|
||||
headers = {
|
||||
'cache-control': `public, max-age=${365 * DAY}, immutable`,
|
||||
};
|
||||
} else {
|
||||
const stat = await fstat(fd);
|
||||
const hash = await getFileHash(fileHashCache, path, stat, fd);
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"@kbn/core-ui-settings-server",
|
||||
"@kbn/monaco",
|
||||
"@kbn/core-http-server-internal",
|
||||
"@kbn/core-http-router-server-internal",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -154,7 +154,7 @@ describe('bundle routes', () => {
|
|||
.get(`/${buildHash}/bundles/plugin/foo/gzip_chunk.js`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.get('cache-control')).toEqual('max-age=31536000');
|
||||
expect(response.get('cache-control')).toEqual('public, max-age=31536000, immutable');
|
||||
expect(response.get('etag')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue