Skip to content

Commit

Permalink
Add ability to specify capabilities in setup (#255)
Browse files Browse the repository at this point in the history
and clarify that setup options are merged instead of replaced.
  • Loading branch information
ejizba authored May 15, 2024
1 parent a2e27a2 commit 878df5b
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 18 deletions.
4 changes: 2 additions & 2 deletions src/InvocationModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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 {
Expand Down
12 changes: 7 additions & 5 deletions src/ProgrammingModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -16,14 +16,16 @@ export class ProgrammingModel implements coreTypes.ProgrammingModel {
return new InvocationModel(coreCtx);
}

async getCapabilities(capabilities: WorkerCapabilities): Promise<WorkerCapabilities> {
async getCapabilities(workerCapabilities: WorkerCapabilities): Promise<WorkerCapabilities> {
lockSetup();

if (isHttpStreamEnabled()) {
if (enableHttpStream) {
const httpUri = await setupHttpProxy();
capabilities.HttpUri = httpUri;
workerCapabilities.HttpUri = httpUri;
}

return capabilities;
Object.assign(workerCapabilities, libraryCapabilities);

return workerCapabilities;
}
}
4 changes: 2 additions & 2 deletions src/converters/toRpcHttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -19,7 +19,7 @@ export async function toRpcHttp(invocationId: string, data: unknown): Promise<Rp
}

const response = data instanceof HttpResponse ? data : new HttpResponse(data);
if (isHttpStreamEnabled()) {
if (enableHttpStream) {
// send http data over http proxy instead of rpc
await sendProxyResponse(invocationId, response);
return;
Expand Down
27 changes: 20 additions & 7 deletions src/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@

import { SetupOptions } from '../types';
import { AzFuncSystemError } from './errors';
import { isDefined } from './utils/nonNull';
import { tryGetCoreApiLazy } from './utils/tryGetCoreApiLazy';
import { workerSystemLog } from './utils/workerSystemLog';

let options: SetupOptions = {};
let setupLocked = false;

export function lockSetup(): void {
setupLocked = true;
}

export let enableHttpStream = false;
export const capabilities: Record<string, string> = {};

export function setup(opts: SetupOptions): void {
if (setupLocked) {
throw new AzFuncSystemError("Setup options can't be changed after app startup has finished.");
Expand All @@ -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.`);
}
}
46 changes: 46 additions & 0 deletions test/setup.test.ts
Original file line number Diff line number Diff line change
@@ -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 false
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' });
});
});
3 changes: 2 additions & 1 deletion types/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
8 changes: 7 additions & 1 deletion types/setup.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string | boolean>;
}

0 comments on commit 878df5b

Please sign in to comment.