From 84b8fbdfeda76dd25da9f6f93e4e15be50a5c8d6 Mon Sep 17 00:00:00 2001 From: Matthew Robertson Date: Thu, 27 Mar 2025 01:13:35 +0000 Subject: [PATCH] feat: adds a new ignored-routes config option --- README.md | 1 + src/options.ts | 13 ++++++++++++- src/server.ts | 15 ++++++++++++--- src/testing.ts | 1 + test/integration/legacy_event.ts | 1 + test/options.ts | 8 ++++++++ 6 files changed, 35 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1039d7f8..88144492 100644 --- a/README.md +++ b/README.md @@ -182,6 +182,7 @@ ignored. | `--signature-type` | `FUNCTION_SIGNATURE_TYPE` | The signature used when writing your function. Controls unmarshalling rules and determines which arguments are used to invoke your function. Default: `http`; accepted values: `http` or `event` or `cloudevent` | | `--source` | `FUNCTION_SOURCE` | The path to the directory of your function. Default: `cwd` (the current working directory) | | `--log-execution-id`| `LOG_EXECUTION_ID` | Enables execution IDs in logs, either `true` or `false`. When not specified, default to disable. Requires Node.js 13.0.0 or later. | +| `--ignored-routes`| `IGNORED_ROUTES` | A route expression for requests that should not be routed the function. An empty 404 response will be returned. This is set to `/favicon.ico|/robots.txt` by default for `http` functions. | You can set command-line flags in your `package.json` via the `start` script. For example: diff --git a/src/options.ts b/src/options.ts index 96de71c8..a0e9c89f 100644 --- a/src/options.ts +++ b/src/options.ts @@ -56,6 +56,10 @@ export interface FrameworkOptions { * The request timeout. */ timeoutMilliseconds: number; + /** + * Routes that should return a 404 without invoking the function. + */ + ignoredRoutes: string | null; } /** @@ -86,7 +90,7 @@ class ConfigurableOption { parse(cliArgs: minimist.ParsedArgs, envVars: NodeJS.ProcessEnv): T { return this.validator( - cliArgs[this.cliOption] || envVars[this.envVar] || this.defaultValue + cliArgs[this.cliOption] ?? envVars[this.envVar] ?? this.defaultValue ); } } @@ -130,6 +134,11 @@ const TimeoutOption = new ConfigurableOption( return x * 1000; } ); +const IgnoredRoutesOption = new ConfigurableOption( + 'ignored-routes', + 'IGNORED_ROUTES', + null // null by default so we can detect if it is explicitly set to "" +); export const requiredNodeJsVersionForLogExecutionID = '13.0.0'; const ExecutionIdOption = new ConfigurableOption( @@ -181,6 +190,7 @@ export const parseOptions = ( SignatureOption.cliOption, SourceLocationOption.cliOption, TimeoutOption.cliOption, + IgnoredRoutesOption.cliOption, ], }); return { @@ -191,5 +201,6 @@ export const parseOptions = ( timeoutMilliseconds: TimeoutOption.parse(argv, envVars), printHelp: cliArgs[2] === '-h' || cliArgs[2] === '--help', enableExecutionId: ExecutionIdOption.parse(argv, envVars), + ignoredRoutes: IgnoredRoutesOption.parse(argv, envVars), }; }; diff --git a/src/server.ts b/src/server.ts index bc348141..fbf565c6 100644 --- a/src/server.ts +++ b/src/server.ts @@ -134,14 +134,23 @@ export function getServer( if (options.signatureType === 'cloudevent') { app.use(backgroundEventToCloudEventMiddleware); } - - if (options.signatureType === 'http') { + if (options.ignoredRoutes !== null) { + // Ignored routes is a configuration option that allows requests to specific paths to be prevented + // from invoking the user's function. + app.use(options.ignoredRoutes, (req, res) => { + res.status(404).send(null); + }); + } else if (options.signatureType === 'http') { + // We configure some default ignored routes, only for HTTP functions. app.use('/favicon.ico|/robots.txt', (req, res) => { // Neither crawlers nor browsers attempting to pull the icon find the body - // contents particularly useful, so we send nothing in the response body. + // contents particularly useful, so we filter these requests out from invoking + // the user's function by default. res.status(404).send(null); }); + } + if (options.signatureType === 'http') { app.use('/*', (req, res, next) => { onFinished(res, (err, res) => { res.locals.functionExecutionFinished = true; diff --git a/src/testing.ts b/src/testing.ts index 9cb62c9d..2fda93c2 100644 --- a/src/testing.ts +++ b/src/testing.ts @@ -56,5 +56,6 @@ export const getTestServer = (functionName: string): Server => { target: '', sourceLocation: '', printHelp: false, + ignoredRoutes: null, }); }; diff --git a/test/integration/legacy_event.ts b/test/integration/legacy_event.ts index a25250e6..231d0f62 100644 --- a/test/integration/legacy_event.ts +++ b/test/integration/legacy_event.ts @@ -40,6 +40,7 @@ const testOptions = { target: '', sourceLocation: '', printHelp: false, + ignoredRoutes: null, }; describe('Event Function', () => { diff --git a/test/options.ts b/test/options.ts index bf1619d0..b6279485 100644 --- a/test/options.ts +++ b/test/options.ts @@ -60,6 +60,7 @@ describe('parseOptions', () => { printHelp: false, enableExecutionId: false, timeoutMilliseconds: 0, + ignoredRoutes: null, }, }, { @@ -75,6 +76,7 @@ describe('parseOptions', () => { '--source=/source', '--timeout', '6', + '--ignored-routes=banana', ], envVars: {}, expectedOptions: { @@ -85,6 +87,7 @@ describe('parseOptions', () => { printHelp: false, enableExecutionId: false, timeoutMilliseconds: 6000, + ignoredRoutes: 'banana', }, }, { @@ -96,6 +99,7 @@ describe('parseOptions', () => { FUNCTION_SIGNATURE_TYPE: 'cloudevent', FUNCTION_SOURCE: '/source', CLOUD_RUN_TIMEOUT_SECONDS: '2', + IGNORED_ROUTES: '', }, expectedOptions: { port: '1234', @@ -105,6 +109,7 @@ describe('parseOptions', () => { printHelp: false, enableExecutionId: false, timeoutMilliseconds: 2000, + ignoredRoutes: '', }, }, { @@ -119,6 +124,7 @@ describe('parseOptions', () => { 'cloudevent', '--source=/source', '--timeout=3', + '--ignored-routes=avocado', ], envVars: { PORT: '4567', @@ -126,6 +132,7 @@ describe('parseOptions', () => { FUNCTION_SIGNATURE_TYPE: 'event', FUNCTION_SOURCE: '/somewhere/else', CLOUD_RUN_TIMEOUT_SECONDS: '5', + IGNORED_ROUTES: 'banana', }, expectedOptions: { port: '1234', @@ -135,6 +142,7 @@ describe('parseOptions', () => { printHelp: false, enableExecutionId: false, timeoutMilliseconds: 3000, + ignoredRoutes: 'avocado', }, }, ];