Skip to content

Commit

Permalink
Implement explicit cache control configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
Siegrift committed Nov 25, 2023
1 parent bf90897 commit 50082b3
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 32 deletions.
5 changes: 1 addition & 4 deletions packages/api/config/signed-api.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,5 @@
}
],
"allowedAirnodes": ["0x8A791620dd6260079BF849Dc5567aDC3F2FdC318"],
"maxBatchSize": 10,
"cache": {
"maxAgeSeconds": 300
}
"maxBatchSize": 10
}
10 changes: 0 additions & 10 deletions packages/api/src/constants.ts

This file was deleted.

6 changes: 0 additions & 6 deletions packages/api/src/handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,6 @@ describe(getData.name, () => {
headers: {
'access-control-allow-methods': '*',
'access-control-allow-origin': '*',
'cache-control': 'no-store',
'cdn-cache-control': 'max-age=10',
'content-type': 'application/json',
},
statusCode: 200,
Expand Down Expand Up @@ -203,8 +201,6 @@ describe(getData.name, () => {
headers: {
'access-control-allow-methods': '*',
'access-control-allow-origin': '*',
'cache-control': 'no-store',
'cdn-cache-control': 'max-age=10',
'content-type': 'application/json',
},
statusCode: 200,
Expand All @@ -228,8 +224,6 @@ describe(listAirnodeAddresses.name, () => {
headers: {
'access-control-allow-methods': '*',
'access-control-allow-origin': '*',
'cache-control': 'no-store',
'cdn-cache-control': 'max-age=300',
'content-type': 'application/json',
},
statusCode: 200,
Expand Down
8 changes: 4 additions & 4 deletions packages/api/src/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { go, goSync } from '@api3/promise-utils';
import { isEmpty, isNil, omit, size } from 'lodash';

import { getConfig } from './config';
import { CACHE_HEADERS, COMMON_HEADERS } from './constants';
import { deriveBeaconId, recoverSignerAddress } from './evm';
import { createResponseHeaders } from './headers';
import { get, getAll, getAllAirnodeAddresses, prune, putAll } from './in-memory-cache';
import { logger } from './logger';
import { type SignedData, batchSignedDataSchema, evmAddressSchema } from './schema';
Expand Down Expand Up @@ -112,7 +112,7 @@ export const batchInsertData = async (requestBody: unknown): Promise<ApiResponse

return {
statusCode: 201,
headers: COMMON_HEADERS,
headers: createResponseHeaders(getConfig().cache),
body: JSON.stringify({
count: newSignedData.length,
skipped: batchSignedData.length - newSignedData.length,
Expand Down Expand Up @@ -148,7 +148,7 @@ export const getData = async (airnodeAddress: string, delaySeconds: number): Pro

return {
statusCode: 200,
headers: { ...COMMON_HEADERS, ...CACHE_HEADERS },
headers: createResponseHeaders(getConfig().cache),
body: JSON.stringify({ count: goReadDb.data.length, data }),
};
};
Expand All @@ -165,7 +165,7 @@ export const listAirnodeAddresses = async (): Promise<ApiResponse> => {

return {
statusCode: 200,
headers: { ...COMMON_HEADERS, ...CACHE_HEADERS, 'cdn-cache-control': `max-age=${getConfig().cache.maxAgeSeconds}` },
headers: createResponseHeaders(getConfig().cache),
body: JSON.stringify({ count: airnodeAddresses.length, 'available-airnodes': airnodeAddresses }),
};
};
43 changes: 43 additions & 0 deletions packages/api/src/headers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { createResponseHeaders } from './headers';

describe(createResponseHeaders.name, () => {
it('returns common headers when cache is not set', () => {
const expectedHeaders = {
'content-type': 'application/json',
'access-control-allow-origin': '*',
'access-control-allow-methods': '*',
};
expect(createResponseHeaders()).toStrictEqual(expectedHeaders);
});

it('returns browser cache headers when cache type is browser', () => {
const expectedHeaders = {
'content-type': 'application/json',
'access-control-allow-origin': '*',
'access-control-allow-methods': '*',
'cache-control': 'max-age=3600',
};
expect(
createResponseHeaders({
type: 'browser',
maxAgeSeconds: 3600,
})
).toStrictEqual(expectedHeaders);
});

it('returns CDN cache headers when cache type is cdn', () => {
const expectedHeaders = {
'content-type': 'application/json',
'access-control-allow-origin': '*',
'access-control-allow-methods': '*',
'cache-control': 'no-store',
'cdn-cache-control': 'max-age=3600',
};
expect(
createResponseHeaders({
type: 'cdn',
maxAgeSeconds: 3600,
})
).toStrictEqual(expectedHeaders);
});
});
28 changes: 28 additions & 0 deletions packages/api/src/headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Cache } from './schema';

const COMMON_HEADERS = {
'content-type': 'application/json',
'access-control-allow-origin': '*',
'access-control-allow-methods': '*',
};

export const createResponseHeaders = (cache?: Cache | undefined) => {
if (!cache) return COMMON_HEADERS;

const { type, maxAgeSeconds } = cache;
switch (type) {
case 'browser': {
return {
...COMMON_HEADERS,
'cache-control': `max-age=${maxAgeSeconds}`,
};
}
case 'cdn': {
return {
...COMMON_HEADERS,
'cache-control': 'no-store', // Disable browser-caching
'cdn-cache-control': `max-age=${maxAgeSeconds}`,
};
}
}
};
11 changes: 8 additions & 3 deletions packages/api/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,17 @@ export const endpointsSchema = z

export const allowedAirnodesSchema = z.union([z.literal('*'), z.array(evmAddressSchema).nonempty()]);

export const cacheSchema = z.strictObject({
type: z.union([z.literal('browser'), z.literal('cdn')]),
maxAgeSeconds: z.number().nonnegative().int(),
});

export type Cache = z.infer<typeof cacheSchema>;

export const configSchema = z.strictObject({
endpoints: endpointsSchema,
maxBatchSize: z.number().nonnegative().int(),
cache: z.strictObject({
maxAgeSeconds: z.number().nonnegative().int(),
}),
cache: cacheSchema.optional(),
allowedAirnodes: allowedAirnodesSchema,
});

Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { COMMON_HEADERS } from './constants';
import { createResponseHeaders } from './headers';
import type { BatchSignedData, SignedData } from './schema';
import type { ApiResponse } from './types';

Expand All @@ -18,5 +18,5 @@ export const generateErrorResponse = (
detail?: string,
extra?: unknown
): ApiResponse => {
return { statusCode, headers: COMMON_HEADERS, body: JSON.stringify({ message, detail, extra }) };
return { statusCode, headers: createResponseHeaders(), body: JSON.stringify({ message, detail, extra }) };
};
3 changes: 0 additions & 3 deletions packages/e2e/src/signed-api/signed-api.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,5 @@
}
],
"maxBatchSize": 10,
"cache": {
"maxAgeSeconds": 0
},
"allowedAirnodes": ["0xbF3137b0a7574563a23a8fC8badC6537F98197CC"]
}

0 comments on commit 50082b3

Please sign in to comment.