diff --git a/package-lock.json b/package-lock.json index 230a7124..1ce55833 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@google-cloud/functions-framework", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/src/cloudevents.ts b/src/cloudevents.ts new file mode 100644 index 00000000..6b3b212e --- /dev/null +++ b/src/cloudevents.ts @@ -0,0 +1,34 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as express from 'express'; + +/** + * Checks whether the incoming request is a CloudEvents event in binary content + * mode. This is verified by checking the presence of required headers. + * + * @link https://github.com/cloudevents/spec/blob/master/http-transport-binding.md#31-binary-content-mode + * + * @param req Express request object. + * @return True if the request is a CloudEvents event in binary content mode, + * false otherwise. + */ +export function isBinaryCloudEvent(req: express.Request): boolean { + return !!( + req.header('ce-type') && + req.header('ce-specversion') && + req.header('ce-source') && + req.header('ce-id') + ); +} diff --git a/src/invoker.ts b/src/invoker.ts index a37c76e5..74f800b6 100644 --- a/src/invoker.ts +++ b/src/invoker.ts @@ -26,9 +26,9 @@ import * as express from 'express'; import * as http from 'http'; import * as onFinished from 'on-finished'; -// HTTP header field that is added to Worker response to signalize problems with -// executing the client function. -const FUNCTION_STATUS_HEADER_FIELD = 'X-Google-Status'; +import { FUNCTION_STATUS_HEADER_FIELD } from './types'; +import { logAndSendError } from './logger'; +import { isBinaryCloudEvent } from './cloudevents'; /** * The Cloud Functions context object for the event. @@ -267,34 +267,6 @@ function sendResponse(result: any, err: Error | null, res: express.Response) { } } -/** - * Logs an error message and sends back an error response to the incoming - * request. - * @param err Error to be logged and sent. - * @param res Express response object. - * @param callback A function to be called synchronously. - */ -function logAndSendError( - // tslint:disable-next-line:no-any - err: Error | any, - res: express.Response | null, - callback?: Function -) { - console.error(err.stack || err); - - // If user function has already sent response headers, the response with - // error message cannot be sent. This check is done inside the callback, - // right before sending the response, to make sure that no concurrent - // execution sends the response between the check and 'send' call below. - if (res && !res.headersSent) { - res.set(FUNCTION_STATUS_HEADER_FIELD, 'crash'); - res.send(err.message || err); - } - if (callback) { - callback(); - } -} - // Set limit to a value larger than 32MB, which is maximum limit of higher level // layers anyway. const requestLimit = '1024mb'; @@ -315,31 +287,6 @@ function rawBodySaver( req.rawBody = buf; } -const defaultBodySavingOptions = { - limit: requestLimit, - verify: rawBodySaver, -}; - -const cloudEventsBodySavingOptions = { - type: 'application/cloudevents+json', - limit: requestLimit, - verify: rawBodySaver, -}; - -// The parser will process ALL content types so must come last. -const rawBodySavingOptions = { - limit: requestLimit, - verify: rawBodySaver, - type: '*/*', -}; - -// Use extended query string parsing for URL-encoded bodies. -const urlEncodedOptions = { - limit: requestLimit, - verify: rawBodySaver, - extended: true, -}; - /** * Wraps the provided function into an Express handler function with additional * instrumentation logic. @@ -366,25 +313,6 @@ function makeHttpHandler(execute: HttpFunction): express.RequestHandler { }; } -/** - * Checks whether the incoming request is a CloudEvents event in binary content - * mode. This is verified by checking the presence of required headers. - * - * @link https://github.com/cloudevents/spec/blob/master/http-transport-binding.md#31-binary-content-mode - * - * @param req Express request object. - * @return True if the request is a CloudEvents event in binary content mode, - * false otherwise. - */ -function isBinaryCloudEvent(req: express.Request): boolean { - return !!( - req.header('ce-type') && - req.header('ce-specversion') && - req.header('ce-source') && - req.header('ce-id') - ); -} - /** * Returns a CloudEvents context from the given CloudEvents request. Context * attributes are retrieved from request headers. @@ -567,6 +495,30 @@ export function getServer( next(); }); + const defaultBodySavingOptions = { + limit: requestLimit, + verify: rawBodySaver, + }; + const cloudEventsBodySavingOptions = { + type: 'application/cloudevents+json', + limit: requestLimit, + verify: rawBodySaver, + }; + + // The parser will process ALL content types so must come last. + const rawBodySavingOptions = { + limit: requestLimit, + verify: rawBodySaver, + type: '*/*', + }; + + // Use extended query string parsing for URL-encoded bodies. + const urlEncodedOptions = { + limit: requestLimit, + verify: rawBodySaver, + extended: true, + }; + app.use(bodyParser.json(cloudEventsBodySavingOptions)); app.use(bodyParser.json(defaultBodySavingOptions)); app.use(bodyParser.text(defaultBodySavingOptions)); diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 00000000..d492595a --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,44 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as express from 'express'; +import { FUNCTION_STATUS_HEADER_FIELD } from './types'; + +/** + * Logs an error message and sends back an error response to the incoming + * request. + * @param err Error to be logged and sent. + * @param res Express response object. + * @param callback A function to be called synchronously. + */ +export function logAndSendError( + // tslint:disable-next-line:no-any + err: Error | any, + res: express.Response | null, + callback?: Function +) { + console.error(err.stack || err); + + // If user function has already sent response headers, the response with + // error message cannot be sent. This check is done inside the callback, + // right before sending the response, to make sure that no concurrent + // execution sends the response between the check and 'send' call below. + if (res && !res.headersSent) { + res.set(FUNCTION_STATUS_HEADER_FIELD, 'crash'); + res.send(err.message || err); + } + if (callback) { + callback(); + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 00000000..378c8982 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,17 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// HTTP header field that is added to Worker response to signalize problems with +// executing the client function. +export const FUNCTION_STATUS_HEADER_FIELD = 'X-Google-Status';