From d9f35076af846c088279a9c7ad74f6569a1ae084 Mon Sep 17 00:00:00 2001 From: Eric Jizba Date: Wed, 8 May 2024 12:09:46 -0700 Subject: [PATCH 1/3] Add ability to specify capabilities in setup And clarify that setup options are merged instead of replaced. Otherwise there would be no way for multiple places (i.e. the user's app and an otel package) to adjust setup config. --- src/InvocationModel.ts | 4 ++-- src/ProgrammingModel.ts | 6 ++++-- src/converters/toRpcHttp.ts | 4 ++-- src/setup.ts | 27 ++++++++++++++++++++------- types/app.d.ts | 3 ++- types/setup.d.ts | 8 +++++++- 6 files changed, 37 insertions(+), 15 deletions(-) diff --git a/src/InvocationModel.ts b/src/InvocationModel.ts index bfb9227..d1958ae 100644 --- a/src/InvocationModel.ts +++ b/src/InvocationModel.ts @@ -24,7 +24,7 @@ import { AzFuncSystemError } from './errors'; import { waitForProxyRequest } from './http/httpProxy'; import { createStreamRequest } from './http/HttpRequest'; import { InvocationContext } from './InvocationContext'; -import { isHttpStreamEnabled } from './setup'; +import { enableHttpStream } from './setup'; import { isHttpTrigger, isTimerTrigger, isTrigger } from './utils/isTrigger'; import { isDefined, nonNullProp, nonNullValue } from './utils/nonNull'; @@ -76,7 +76,7 @@ export class InvocationModel implements coreTypes.InvocationModel { const bindingType = rpcBinding.type; let input: unknown; - if (isHttpTrigger(bindingType) && isHttpStreamEnabled()) { + if (isHttpTrigger(bindingType) && enableHttpStream) { const proxyRequest = await waitForProxyRequest(this.#coreCtx.invocationId); input = createStreamRequest(proxyRequest, nonNullProp(req, 'triggerMetadata')); } else { diff --git a/src/ProgrammingModel.ts b/src/ProgrammingModel.ts index bf245b1..8a73cd2 100644 --- a/src/ProgrammingModel.ts +++ b/src/ProgrammingModel.ts @@ -6,7 +6,7 @@ import { CoreInvocationContext, WorkerCapabilities } from '@azure/functions-core import { version } from './constants'; import { setupHttpProxy } from './http/httpProxy'; import { InvocationModel } from './InvocationModel'; -import { isHttpStreamEnabled, lockSetup } from './setup'; +import { capabilities as libraryCapabilities, enableHttpStream, lockSetup } from './setup'; export class ProgrammingModel implements coreTypes.ProgrammingModel { name = '@azure/functions'; @@ -19,11 +19,13 @@ export class ProgrammingModel implements coreTypes.ProgrammingModel { async getCapabilities(capabilities: WorkerCapabilities): Promise { lockSetup(); - if (isHttpStreamEnabled()) { + if (enableHttpStream) { const httpUri = await setupHttpProxy(); capabilities.HttpUri = httpUri; } + Object.assign(capabilities, libraryCapabilities); + return capabilities; } } diff --git a/src/converters/toRpcHttp.ts b/src/converters/toRpcHttp.ts index 9767237..c314f17 100644 --- a/src/converters/toRpcHttp.ts +++ b/src/converters/toRpcHttp.ts @@ -5,7 +5,7 @@ import { RpcHttpData, RpcTypedData } from '@azure/functions-core'; import { AzFuncSystemError } from '../errors'; import { sendProxyResponse } from '../http/httpProxy'; import { HttpResponse } from '../http/HttpResponse'; -import { isHttpStreamEnabled } from '../setup'; +import { enableHttpStream } from '../setup'; import { toRpcHttpCookie } from './toRpcHttpCookie'; import { toRpcTypedData } from './toRpcTypedData'; @@ -19,7 +19,7 @@ export async function toRpcHttp(invocationId: string, data: unknown): Promise = {}; + export function setup(opts: SetupOptions): void { if (setupLocked) { throw new AzFuncSystemError("Setup options can't be changed after app startup has finished."); @@ -27,10 +29,21 @@ export function setup(opts: SetupOptions): void { } } - options = opts; - workerSystemLog('information', `Setup options: ${JSON.stringify(options)}`); -} + if (isDefined(opts.enableHttpStream)) { + enableHttpStream = opts.enableHttpStream; + } -export function isHttpStreamEnabled(): boolean { - return !!options.enableHttpStream; + if (opts.capabilities) { + for (let [key, val] of Object.entries(opts.capabilities)) { + if (isDefined(val)) { + val = String(val); + workerSystemLog('debug', `Capability ${key} set to ${val}.`); + capabilities[key] = val; + } + } + } + + if (enableHttpStream) { + workerSystemLog('debug', `HTTP streaming enabled.`); + } } diff --git a/types/app.d.ts b/types/app.d.ts index 43e4dea..c2f7608 100644 --- a/types/app.d.ts +++ b/types/app.d.ts @@ -15,7 +15,8 @@ import { WarmupFunctionOptions } from './warmup'; /** * Optional method to configure the behavior of your app. - * This can only be done during app startup, before invocations occur + * This can only be done during app startup, before invocations occur. + * If called multiple times, options will be merged with the previous options specified. */ export declare function setup(options: SetupOptions): void; diff --git a/types/setup.d.ts b/types/setup.d.ts index c5237fb..92af251 100644 --- a/types/setup.d.ts +++ b/types/setup.d.ts @@ -3,8 +3,14 @@ export interface SetupOptions { /** - * PREVIEW: Stream http requests and responses instead of loading entire body in memory. + * Stream http requests and responses instead of loading entire body in memory. * [Learn more here](https://aka.ms/AzFuncNodeHttpStreams) */ enableHttpStream?: boolean; + + /** + * Dictionary of Node.js worker capabilities. + * This will be merged with existing capabilities specified by the Node.js worker and library. + */ + capabilities?: Record; } From a83eb37f2be90e35003a9e6385507397a85783da Mon Sep 17 00:00:00 2001 From: Eric Jizba Date: Thu, 9 May 2024 15:48:20 -0500 Subject: [PATCH 2/3] add a few unit tests --- test/setup.test.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 test/setup.test.ts diff --git a/test/setup.test.ts b/test/setup.test.ts new file mode 100644 index 0000000..9485322 --- /dev/null +++ b/test/setup.test.ts @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. + +import 'mocha'; +import { expect } from 'chai'; +import { capabilities, enableHttpStream, setup } from '../src/setup'; + +describe('setup', () => { + it('enableHttpStream', () => { + // default + expect(enableHttpStream).to.equal(false); + + // set to true + setup({ enableHttpStream: true }); + expect(enableHttpStream).to.equal(true); + + // don't change if not explicitly set + setup({}); + expect(enableHttpStream).to.equal(true); + setup({ capabilities: {} }); + expect(enableHttpStream).to.equal(true); + + // set to true + setup({ enableHttpStream: false }); + expect(enableHttpStream).to.equal(false); + }); + + it('capabilities', () => { + // default + expect(capabilities).to.deep.equal({}); + + // various setting & merging without replacing + setup({ capabilities: { a: '1' } }); + expect(capabilities).to.deep.equal({ a: '1' }); + setup({}); + expect(capabilities).to.deep.equal({ a: '1' }); + setup({ capabilities: { b: '2' } }); + expect(capabilities).to.deep.equal({ a: '1', b: '2' }); + setup({ capabilities: { a: '3' } }); + expect(capabilities).to.deep.equal({ a: '3', b: '2' }); + + // boolean converted to string + setup({ capabilities: { c: true } }); + expect(capabilities).to.deep.equal({ a: '3', b: '2', c: 'true' }); + }); +}); From b785d60856861f618b0448e0fa5e665a310dbb11 Mon Sep 17 00:00:00 2001 From: Eric Jizba Date: Wed, 15 May 2024 14:21:57 -0700 Subject: [PATCH 3/3] feedback --- src/ProgrammingModel.ts | 8 ++++---- test/setup.test.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ProgrammingModel.ts b/src/ProgrammingModel.ts index 8a73cd2..009ed92 100644 --- a/src/ProgrammingModel.ts +++ b/src/ProgrammingModel.ts @@ -16,16 +16,16 @@ export class ProgrammingModel implements coreTypes.ProgrammingModel { return new InvocationModel(coreCtx); } - async getCapabilities(capabilities: WorkerCapabilities): Promise { + async getCapabilities(workerCapabilities: WorkerCapabilities): Promise { lockSetup(); if (enableHttpStream) { const httpUri = await setupHttpProxy(); - capabilities.HttpUri = httpUri; + workerCapabilities.HttpUri = httpUri; } - Object.assign(capabilities, libraryCapabilities); + Object.assign(workerCapabilities, libraryCapabilities); - return capabilities; + return workerCapabilities; } } diff --git a/test/setup.test.ts b/test/setup.test.ts index 9485322..a650341 100644 --- a/test/setup.test.ts +++ b/test/setup.test.ts @@ -20,7 +20,7 @@ describe('setup', () => { setup({ capabilities: {} }); expect(enableHttpStream).to.equal(true); - // set to true + // set to false setup({ enableHttpStream: false }); expect(enableHttpStream).to.equal(false); });