Skip to content

Commit 63cb628

Browse files
authoredSep 15, 2020
refactor: split invoker and router (#213)
Signed-off-by: Grant Timmerman <timmerman+devrel@google.com>
1 parent 5d61008 commit 63cb628

File tree

6 files changed

+205
-152
lines changed

6 files changed

+205
-152
lines changed
 

‎src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333

3434
import * as minimist from 'minimist';
3535
import {resolve} from 'path';
36-
3736
import {getUserFunction} from './loader';
38-
39-
import {ErrorHandler, SignatureType, getServer} from './invoker';
37+
import {ErrorHandler} from './invoker';
38+
import {getServer} from './server';
39+
import {SignatureType} from './types';
4040

4141
// Supported command-line flags
4242
const FLAG = {

‎src/invoker.ts

Lines changed: 10 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,10 @@
2020
// - ANY (all methods) '/*' for executing functions (only for servers handling
2121
// functions with HTTP trigger).
2222

23-
import * as bodyParser from 'body-parser';
2423
// eslint-disable-next-line node/no-deprecated-api
2524
import * as domain from 'domain';
2625
import * as express from 'express';
2726
import * as http from 'http';
28-
import * as onFinished from 'on-finished';
29-
3027
import {FUNCTION_STATUS_HEADER_FIELD} from './types';
3128
import {sendCrashResponse} from './logger';
3229
import {isBinaryCloudEvent, getBinaryCloudEventContext} from './cloudevents';
@@ -36,7 +33,6 @@ import {
3633
EventFunctionWithCallback,
3734
CloudEventFunction,
3835
CloudEventFunctionWithCallback,
39-
HandlerFunction,
4036
} from './functions';
4137

4238
// We optionally annotate the express Request with a rawBody field.
@@ -50,14 +46,14 @@ declare global {
5046
}
5147
}
5248

53-
export enum SignatureType {
54-
HTTP = 'http',
55-
EVENT = 'event',
56-
CLOUDEVENT = 'cloudevent',
57-
}
58-
59-
// Response object for the most recent request.
49+
/**
50+
* Response object for the most recent request.
51+
* Used for sending errors to the user.
52+
*/
6053
let latestRes: express.Response | null = null;
54+
export const setLatestRes = (res: express.Response) => {
55+
latestRes = res;
56+
};
6157

6258
/**
6359
* Sends back a response to the incoming request.
@@ -102,7 +98,7 @@ function sendResponse(result: any, err: Error | null, res: express.Response) {
10298
* @param execute Runs user's function.
10399
* @return An Express handler function.
104100
*/
105-
function makeHttpHandler(execute: HttpFunction): express.RequestHandler {
101+
export function makeHttpHandler(execute: HttpFunction): express.RequestHandler {
106102
return (req: express.Request, res: express.Response) => {
107103
const d = domain.create();
108104
// Catch unhandled errors originating from this request.
@@ -128,7 +124,7 @@ function makeHttpHandler(execute: HttpFunction): express.RequestHandler {
128124
* @param userFunction User's function.
129125
* @return HTTP function which wraps the provided event function.
130126
*/
131-
function wrapCloudEventFunction(
127+
export function wrapCloudEventFunction(
132128
userFunction: CloudEventFunction | CloudEventFunctionWithCallback
133129
): HttpFunction {
134130
return (req: express.Request, res: express.Response) => {
@@ -180,7 +176,7 @@ function wrapCloudEventFunction(
180176
* @param userFunction User's function.
181177
* @return HTTP function which wraps the provided event function.
182178
*/
183-
function wrapEventFunction(
179+
export function wrapEventFunction(
184180
userFunction: EventFunction | EventFunctionWithCallback
185181
): HttpFunction {
186182
return (req: express.Request, res: express.Response) => {
@@ -239,54 +235,6 @@ function wrapEventFunction(
239235
};
240236
}
241237

242-
/**
243-
* Registers handler functions for route paths.
244-
* @param app Express application object.
245-
* @param userFunction User's function.
246-
* @param functionSignatureType Type of user's function signature.
247-
*/
248-
function registerFunctionRoutes(
249-
app: express.Application,
250-
userFunction: HandlerFunction,
251-
functionSignatureType: SignatureType
252-
) {
253-
if (functionSignatureType === SignatureType.HTTP) {
254-
app.use('/favicon.ico|/robots.txt', (req, res) => {
255-
// Neither crawlers nor browsers attempting to pull the icon find the body
256-
// contents particularly useful, so we send nothing in the response body.
257-
res.status(404).send(null);
258-
});
259-
260-
app.use('/*', (req, res, next) => {
261-
onFinished(res, (err, res) => {
262-
res.locals.functionExecutionFinished = true;
263-
});
264-
next();
265-
});
266-
267-
app.all('/*', (req, res, next) => {
268-
const handler = makeHttpHandler(userFunction as HttpFunction);
269-
handler(req, res, next);
270-
});
271-
} else if (functionSignatureType === SignatureType.EVENT) {
272-
app.post('/*', (req, res, next) => {
273-
const wrappedUserFunction = wrapEventFunction(
274-
userFunction as EventFunction | EventFunctionWithCallback
275-
);
276-
const handler = makeHttpHandler(wrappedUserFunction);
277-
handler(req, res, next);
278-
});
279-
} else {
280-
app.post('/*', (req, res, next) => {
281-
const wrappedUserFunction = wrapCloudEventFunction(
282-
userFunction as CloudEventFunction | CloudEventFunctionWithCallback
283-
);
284-
const handler = makeHttpHandler(wrappedUserFunction);
285-
handler(req, res, next);
286-
});
287-
}
288-
}
289-
290238
// Use an exit code which is unused by Node.js:
291239
// https://nodejs.org/api/process.html#process_exit_codes
292240
const killInstance = process.exit.bind(process, 16);
@@ -333,81 +281,3 @@ export class ErrorHandler {
333281
});
334282
}
335283
}
336-
337-
/**
338-
* Creates and configures an Express application and returns an HTTP server
339-
* which will run it.
340-
* @param userFunction User's function.
341-
* @param functionSignatureType Type of user's function signature.
342-
* @return HTTP server.
343-
*/
344-
export function getServer(
345-
userFunction: HandlerFunction,
346-
functionSignatureType: SignatureType
347-
): http.Server {
348-
// App to use for function executions.
349-
const app = express();
350-
351-
// Express middleware
352-
353-
// Set request-specific values in the very first middleware.
354-
app.use('/*', (req, res, next) => {
355-
latestRes = res;
356-
res.locals.functionExecutionFinished = false;
357-
next();
358-
});
359-
360-
/**
361-
* Retains a reference to the raw body buffer to allow access to the raw body
362-
* for things like request signature validation. This is used as the "verify"
363-
* function in body-parser options.
364-
* @param req Express request object.
365-
* @param res Express response object.
366-
* @param buf Buffer to be saved.
367-
*/
368-
function rawBodySaver(
369-
req: express.Request,
370-
res: express.Response,
371-
buf: Buffer
372-
) {
373-
req.rawBody = buf;
374-
}
375-
376-
// Set limit to a value larger than 32MB, which is maximum limit of higher
377-
// level layers anyway.
378-
const requestLimit = '1024mb';
379-
const defaultBodySavingOptions = {
380-
limit: requestLimit,
381-
verify: rawBodySaver,
382-
};
383-
const cloudEventsBodySavingOptions = {
384-
type: 'application/cloudevents+json',
385-
limit: requestLimit,
386-
verify: rawBodySaver,
387-
};
388-
const rawBodySavingOptions = {
389-
limit: requestLimit,
390-
verify: rawBodySaver,
391-
type: '*/*',
392-
};
393-
394-
// Use extended query string parsing for URL-encoded bodies.
395-
const urlEncodedOptions = {
396-
limit: requestLimit,
397-
verify: rawBodySaver,
398-
extended: true,
399-
};
400-
401-
// Apply middleware
402-
app.use(bodyParser.json(cloudEventsBodySavingOptions));
403-
app.use(bodyParser.json(defaultBodySavingOptions));
404-
app.use(bodyParser.text(defaultBodySavingOptions));
405-
app.use(bodyParser.urlencoded(urlEncodedOptions));
406-
// The parser will process ALL content types so MUST come last.
407-
// Subsequent parsers will be skipped when one is matched.
408-
app.use(bodyParser.raw(rawBodySavingOptions));
409-
app.enable('trust proxy'); // To respect X-Forwarded-For header.
410-
411-
registerFunctionRoutes(app, userFunction, functionSignatureType);
412-
return http.createServer(app);
413-
}

‎src/router.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
import * as express from 'express';
15+
import * as onFinished from 'on-finished';
16+
import {HandlerFunction} from './functions';
17+
import {SignatureType} from './types';
18+
import {
19+
makeHttpHandler,
20+
wrapEventFunction,
21+
wrapCloudEventFunction,
22+
} from './invoker';
23+
import {
24+
HttpFunction,
25+
EventFunction,
26+
EventFunctionWithCallback,
27+
CloudEventFunction,
28+
CloudEventFunctionWithCallback,
29+
} from './functions';
30+
31+
/**
32+
* Registers handler functions for route paths.
33+
* @param app Express application object.
34+
* @param userFunction User's function.
35+
* @param functionSignatureType Type of user's function signature.
36+
*/
37+
export function registerFunctionRoutes(
38+
app: express.Application,
39+
userFunction: HandlerFunction,
40+
functionSignatureType: SignatureType
41+
) {
42+
if (functionSignatureType === SignatureType.HTTP) {
43+
app.use('/favicon.ico|/robots.txt', (req, res) => {
44+
// Neither crawlers nor browsers attempting to pull the icon find the body
45+
// contents particularly useful, so we send nothing in the response body.
46+
res.status(404).send(null);
47+
});
48+
49+
app.use('/*', (req, res, next) => {
50+
onFinished(res, (err, res) => {
51+
res.locals.functionExecutionFinished = true;
52+
});
53+
next();
54+
});
55+
56+
app.all('/*', (req, res, next) => {
57+
const handler = makeHttpHandler(userFunction as HttpFunction);
58+
handler(req, res, next);
59+
});
60+
} else if (functionSignatureType === SignatureType.EVENT) {
61+
app.post('/*', (req, res, next) => {
62+
const wrappedUserFunction = wrapEventFunction(
63+
userFunction as EventFunction | EventFunctionWithCallback
64+
);
65+
const handler = makeHttpHandler(wrappedUserFunction);
66+
handler(req, res, next);
67+
});
68+
} else {
69+
app.post('/*', (req, res, next) => {
70+
const wrappedUserFunction = wrapCloudEventFunction(
71+
userFunction as CloudEventFunction | CloudEventFunctionWithCallback
72+
);
73+
const handler = makeHttpHandler(wrappedUserFunction);
74+
handler(req, res, next);
75+
});
76+
}
77+
}

‎src/server.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright 2019 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import * as bodyParser from 'body-parser';
16+
import * as express from 'express';
17+
import * as http from 'http';
18+
import {HandlerFunction} from './functions';
19+
import {SignatureType} from './types';
20+
import {setLatestRes} from './invoker';
21+
import {registerFunctionRoutes} from './router';
22+
23+
/**
24+
* Creates and configures an Express application and returns an HTTP server
25+
* which will run it.
26+
* @param userFunction User's function.
27+
* @param functionSignatureType Type of user's function signature.
28+
* @return HTTP server.
29+
*/
30+
export function getServer(
31+
userFunction: HandlerFunction,
32+
functionSignatureType: SignatureType
33+
): http.Server {
34+
// App to use for function executions.
35+
const app = express();
36+
37+
// Express middleware
38+
39+
// Set request-specific values in the very first middleware.
40+
app.use('/*', (req, res, next) => {
41+
setLatestRes(res);
42+
res.locals.functionExecutionFinished = false;
43+
next();
44+
});
45+
46+
/**
47+
* Retains a reference to the raw body buffer to allow access to the raw body
48+
* for things like request signature validation. This is used as the "verify"
49+
* function in body-parser options.
50+
* @param req Express request object.
51+
* @param res Express response object.
52+
* @param buf Buffer to be saved.
53+
*/
54+
function rawBodySaver(
55+
req: express.Request,
56+
res: express.Response,
57+
buf: Buffer
58+
) {
59+
req.rawBody = buf;
60+
}
61+
62+
// Set limit to a value larger than 32MB, which is maximum limit of higher
63+
// level layers anyway.
64+
const requestLimit = '1024mb';
65+
const defaultBodySavingOptions = {
66+
limit: requestLimit,
67+
verify: rawBodySaver,
68+
};
69+
const cloudEventsBodySavingOptions = {
70+
type: 'application/cloudevents+json',
71+
limit: requestLimit,
72+
verify: rawBodySaver,
73+
};
74+
const rawBodySavingOptions = {
75+
limit: requestLimit,
76+
verify: rawBodySaver,
77+
type: '*/*',
78+
};
79+
80+
// Use extended query string parsing for URL-encoded bodies.
81+
const urlEncodedOptions = {
82+
limit: requestLimit,
83+
verify: rawBodySaver,
84+
extended: true,
85+
};
86+
87+
// Apply middleware
88+
app.use(bodyParser.json(cloudEventsBodySavingOptions));
89+
app.use(bodyParser.json(defaultBodySavingOptions));
90+
app.use(bodyParser.text(defaultBodySavingOptions));
91+
app.use(bodyParser.urlencoded(urlEncodedOptions));
92+
// The parser will process ALL content types so MUST come last.
93+
// Subsequent parsers will be skipped when one is matched.
94+
app.use(bodyParser.raw(rawBodySavingOptions));
95+
app.enable('trust proxy'); // To respect X-Forwarded-For header.
96+
97+
registerFunctionRoutes(app, userFunction, functionSignatureType);
98+
return http.createServer(app);
99+
}

0 commit comments

Comments
 (0)
Failed to load comments.