1 | const hash = require('object-hash');
|
2 |
|
3 | const typemap = {
|
4 | REQUEST: 'request_condition',
|
5 | RESPONSE: 'response_condition',
|
6 | CACHE: 'cache_condition',
|
7 | };
|
8 |
|
9 | /** Helper class with high-level operations for condition-management. */
|
10 | class Headers {
|
11 | constructor(fastly) {
|
12 | this._fastly = fastly;
|
13 | }
|
14 |
|
15 | /**
|
16 | * Creates functions for multi-step creation of missing and deletion of
|
17 | * superflous conditional headers.
|
18 | *
|
19 | * @param {number} version - Service config version.
|
20 | * @param {string} type - Condition type, can be `REQUEST`, `RESPONSE`, or `CACHE`.
|
21 | * @param {string} commentprefix - The prefix to be used for comments.
|
22 | * @param {string} nameprefix - - The prefix to be used for names.
|
23 | * @param {string} action - What do do with the header, can be `set`, `append`, `delete`.
|
24 | * @param {string} header - The name of the header to set.
|
25 | * @param {string} sub - Name of the subroutine where the header
|
26 | * should be applied, can be `request`, `fetch`, `cache`, or `response`.
|
27 | * @returns {Function[]} A pair of a create and cleanup function.
|
28 | */
|
29 | update(version, type, commentprefix, nameprefix, action, header, sub) {
|
30 | const makeheaders = (headers, namedcondition) => headers.map(({ condition, expression }) => {
|
31 | const hashable = {
|
32 | type: sub,
|
33 | action,
|
34 | dst: header,
|
35 | src: expression,
|
36 | };
|
37 |
|
38 | // set `request_condition` etc. to the *name* that corresponds to the condition statement
|
39 | hashable[typemap[type]] = namedcondition[condition].name;
|
40 |
|
41 | const name = `${nameprefix}-${hash(hashable)}`;
|
42 |
|
43 | return {
|
44 | name,
|
45 | priority: '10',
|
46 | ...hashable,
|
47 | };
|
48 | });
|
49 |
|
50 | const [
|
51 | createconditions,
|
52 | cleanupconditions] = this._fastly.conditions.multistepupdate(
|
53 | version,
|
54 | type,
|
55 | commentprefix,
|
56 | nameprefix,
|
57 | );
|
58 |
|
59 | // each header is a tuple like this:
|
60 | // `{ condition: 'req.url ~ "foo/(.*)/bar"', expression: '"bar/" + re.group.1 + "/foo"'}`
|
61 | // this function extracts the condition bit.
|
62 | const conditions = (headers) => headers.map(({ condition }) => condition);
|
63 |
|
64 | // this function takes care of the creation of new headers by looking
|
65 | // at existing headers and finding ones that are missing from the service
|
66 | const createheaders = async (...headers) => {
|
67 | const jobs = [];
|
68 | // get the map of condition statements to condition names
|
69 | const namedconditions = await createconditions(...conditions(headers));
|
70 |
|
71 | const existing = (await this._fastly.readHeaders(version)).data;
|
72 | // keep a list of known names in the remote service
|
73 | const existingnames = new Set(existing.map(({ name }) => name));
|
74 |
|
75 | const headernames = makeheaders(headers, namedconditions);
|
76 | const headernameset = new Set(headernames.map(({ name }) => name));
|
77 |
|
78 | const headerstobecreated = headernames
|
79 | // only consider conditions that don't exist in the remote service
|
80 | .filter(({ name }) => !existingnames.has(name))
|
81 | // schedule each condition that does not yet exist on Fastly
|
82 | // but was passed as an argument to be created
|
83 | .map((h) => this._fastly.createHeader(version, h));
|
84 |
|
85 | // all headers need to be created
|
86 | jobs.push(headerstobecreated);
|
87 |
|
88 | const todelete = existing
|
89 | // only look at headers that start with 'our' prefix
|
90 | .filter(({ name }) => name.startsWith(`${nameprefix}-`))
|
91 | // only keep those that are *not* in the list of generated names
|
92 | .filter(({ name }) => !headernameset.has(name))
|
93 | // only consider those for deletetion where type, action, and dst do match
|
94 | .filter((h) => h.type === sub)
|
95 | .filter((h) => h.action === action)
|
96 | .filter((h) => h.dst === header);
|
97 |
|
98 | // schedule each remaining header that exists on Fastly, but wasn't passed as
|
99 | // an argument for deletion
|
100 | jobs.push(todelete.map(({ name }) => this._fastly.deleteHeader(version, name)));
|
101 |
|
102 | jobs.push(todelete);
|
103 | // finally, clean up the conditions we no longer need
|
104 | jobs.push(cleanupconditions(...conditions(headers)));
|
105 |
|
106 | // return a promise that waits for the creation of all headers
|
107 | return Promise.all(jobs);
|
108 | };
|
109 |
|
110 | return createheaders;
|
111 | }
|
112 | }
|
113 |
|
114 | module.exports = Headers;
|