Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support for the DAPR_HTTP_ENDPOINT and DAPR_GRPC_ENDPOINT environment variables. Adds support for DAPR_API_TOKEN to gRPC client #519

Merged
merged 23 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
aaf074c
feat: Adds endpoint parsing
elena-kolevska Sep 6, 2023
49ab722
Adds support for the DAPR_HTTP_ENDPOINT environment variable
elena-kolevska Sep 12, 2023
0aaa913
Adds tests for endpoint environment variables (HTTP only)
elena-kolevska Sep 12, 2023
6fd90c5
test: Adds tests for endpoint environment variables (HTTP only)
elena-kolevska Sep 12, 2023
8f26003
Merge branch 'tls-support' of github.com:elena-kolevska/js-sdk into t…
elena-kolevska Sep 12, 2023
a29bc66
fix(style) Linter fixes
elena-kolevska Sep 13, 2023
6d175a2
Adds support for dapr-api-token metadata
elena-kolevska Sep 18, 2023
d009132
Adds support for the DAPR_GRPC_ENDPOINT environment variable
elena-kolevska Sep 18, 2023
e63f270
Fixes linter issues
elena-kolevska Sep 18, 2023
0e5672d
Fixes linter issues
elena-kolevska Sep 18, 2023
dc482b9
Only add api token interceptor if it’s specified
elena-kolevska Sep 18, 2023
e0fd4a3
Reorganises the code a bit
elena-kolevska Sep 18, 2023
08407db
Runs pretty-fix
elena-kolevska Sep 18, 2023
6e16458
Adds test for scheme prefix removal for grpc
elena-kolevska Sep 19, 2023
2f1a277
Apply suggestions from code review
elena-kolevska Sep 20, 2023
959ea7b
Apply suggestions from code review
elena-kolevska Sep 20, 2023
afb2eaf
Adds examples for the parseEndpoint function
elena-kolevska Sep 20, 2023
1d731d1
Adds tests for the dapr-api-token metadata in gRPC calls
elena-kolevska Sep 24, 2023
342a5a8
Updates after review
elena-kolevska Sep 25, 2023
9226112
docs: Adds info and examples about the new environment variables to t…
elena-kolevska Sep 25, 2023
605c587
Addresses review comments
elena-kolevska Sep 26, 2023
94f271e
Small formatting fix
elena-kolevska Sep 26, 2023
3b27c23
Fixes docs
elena-kolevska Sep 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
### vscode ###
.vscode/*
.vscode
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
Expand Down Expand Up @@ -144,4 +144,11 @@ temp/
build/

# version file is auto-generated
src/version.ts
src/version.ts

# OSX
/.DS_Store

# JetBrains
/.idea

16 changes: 16 additions & 0 deletions daprdocs/content/en/js-sdk-docs/js-client/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,22 @@ dapr run --app-id example-sdk --app-protocol grpc -- npm run start
npm run start:dapr-grpc
```

### Environment Variables

You can use the `DAPR_HTTP_ENDPOINT` and `DAPR_GRPC_ENDPOINT` environment variables to set the Dapr Sidecar's HTTP and gRPC endpoints respectively. When these variables are set, the `daprHost` and `daprPort` don't have to be passed to the constructor, the client will parse them automatically out of the provided endpoints.

```typescript
import { DaprClient, CommunicationProtocol } from "@dapr/dapr";

// Using HTTP, when DAPR_HTTP_ENDPOINT is set
const client = new DaprClient();

// Using gRPC, when DAPR_GRPC_ENDPOINT is set
const client = new DaprClient({ communicationProtocol: CommunicationProtocol.GRPC });
```

If the environment variables are set, but `daprHost` and `daprPort` values are passed to the constructor, the latter will take precedence over the environment variables.

## General

### Increasing Body Size
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
"test:load:http": "TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol http --app-port 50001 --dapr-http-port 50000 --components-path ./test/components -- npm run test:load 'test/load'",
"test:e2e": "jest --runInBand --detectOpenHandles",
"test:e2e:all": "npm run test:e2e:http; npm run test:e2e:grpc; npm run test:e2e:common",
"test:e2e:grpc": "npm run test:e2e:grpc:client && npm run test:e2e:grpc:server",
"test:e2e:grpc:client": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol grpc --app-port 50001 --dapr-grpc-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/grpc/(client).test.ts' ]",
"test:e2e:grpc:server": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol grpc --app-port 50001 --dapr-grpc-port 50000 --dapr-http-max-request-size 10 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/grpc/(server).test.ts' ]",
"test:e2e:grpc": "npm run test:e2e:grpc:client && npm run test:e2e:grpc:server && npm run test:e2e:grpc:clientWithApiToken",
"test:e2e:grpc:client": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol grpc --app-port 50001 --dapr-grpc-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/grpc/*client.test.ts' ]",
"test:e2e:grpc:clientWithApiToken": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 DAPR_API_TOKEN=test dapr run --app-id test-suite --app-protocol grpc --app-port 50001 --dapr-grpc-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/grpc/clientWithApiToken.test.ts' ]",
"test:e2e:grpc:server": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol grpc --app-port 50001 --dapr-grpc-port 50000 --dapr-http-max-request-size 10 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/grpc/*server.test.ts' ]",
"test:e2e:http": "npm run test:e2e:http:client && npm run test:e2e:http:server && npm run test:e2e:http:actors",
"test:e2e:http:client": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol http --app-port 50001 --dapr-http-port 50000 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/http/(client).test.ts' ]",
"test:e2e:http:server": "npm run prebuild && TEST_SECRET_1=secret_val_1 TEST_SECRET_2=secret_val_2 dapr run --app-id test-suite --app-protocol http --app-port 50001 --dapr-http-port 50000 --dapr-http-max-request-size 10 --components-path ./test/components -- jest --runInBand --detectOpenHandles --testMatch [ '**/test/e2e/http/(server).test.ts' ]",
Expand Down
65 changes: 54 additions & 11 deletions src/implementation/Client/GRPCClient/GRPCClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,18 @@
private readonly client: GrpcDaprClient;
private readonly clientCredentials: grpc.ChannelCredentials;
private readonly logger: Logger;
private readonly grpcClientOptions: Partial<grpc.ClientOptions>;

constructor(options: DaprClientOptions) {
this.options = options;
this.clientCredentials = this.generateCredentials();
this.grpcClientOptions = this.generateChannelOptions();

this.logger = new Logger("GRPCClient", "GRPCClient", options.logger);
this.isInitialized = false;

this.logger.info(`Opening connection to ${this.options.daprHost}:${this.options.daprPort}`);
this.client = this.generateClient(this.options.daprHost, this.options.daprPort, this.clientCredentials);
this.client = this.generateClient(this.options.daprHost, this.options.daprPort);
}

async getClient(requiresInitialization = true): Promise<GrpcDaprClient> {
Expand All @@ -52,8 +55,20 @@
return this.clientCredentials;
}

private generateChannelOptions(): Record<string, string | number> {
const options: Record<string, string | number> = {};
getGrpcClientOptions(): grpc.ClientOptions {
return this.grpcClientOptions;
}

private generateCredentials(): grpc.ChannelCredentials {
if (this.options.daprHost.startsWith("https")) {
return grpc.ChannelCredentials.createSsl();

Check warning on line 64 in src/implementation/Client/GRPCClient/GRPCClient.ts

View check run for this annotation

Codecov / codecov/patch

src/implementation/Client/GRPCClient/GRPCClient.ts#L64

Added line #L64 was not covered by tests
}
return grpc.ChannelCredentials.createInsecure();
}

private generateChannelOptions(): Partial<grpc.ClientOptions> {
// const options: Record<string, string | number> = {};
let options: Partial<grpc.ClientOptions> = {};

// See: GRPC_ARG_MAX_SEND_MESSAGE_LENGTH, it is in bytes
// https://grpc.github.io/grpc/core/group__grpc__arg__keys.html#ga813f94f9ac3174571dd712c96cdbbdc1
Expand All @@ -67,20 +82,48 @@
// Add user agent
options["grpc.primary_user_agent"] = "dapr-sdk-js/v" + SDK_VERSION;

// Add interceptors if we have an API token
if (this.options.daprApiToken !== "") {
options = {
interceptors: [this.generateInterceptors()],
...options,
};
}

return options;
}

private generateClient(host: string, port: string, credentials: grpc.ChannelCredentials): GrpcDaprClient {
const options = this.generateChannelOptions();
const client = new GrpcDaprClient(`${host}:${port}`, credentials, options);
private generateClient(host: string, port: string): GrpcDaprClient {
return new GrpcDaprClient(
GRPCClient.getEndpoint(host, port),
this.getClientCredentials(),
this.getGrpcClientOptions(),
);
}

return client;
// The grpc client doesn't allow http:// or https:// for grpc connections,
// so we need to remove it, if it exists
static getEndpoint(host: string, port: string): string {
let endpoint = `${host}:${port}`;
const parts = endpoint.split("://");
if (parts.length > 1 && parts[0].startsWith("http")) {
endpoint = parts[1];

Check warning on line 110 in src/implementation/Client/GRPCClient/GRPCClient.ts

View check run for this annotation

Codecov / codecov/patch

src/implementation/Client/GRPCClient/GRPCClient.ts#L110

Added line #L110 was not covered by tests
}

return endpoint;
}

// @todo: look into making secure credentials
private generateCredentials(): grpc.ChannelCredentials {
const credsChannel = grpc.ChannelCredentials.createInsecure();
return credsChannel;
private generateInterceptors(): (options: any, nextCall: any) => grpc.InterceptingCall {
return (options: any, nextCall: any) => {
return new grpc.InterceptingCall(nextCall(options), {
start: (metadata, listener, next) => {
if (metadata.get("dapr-api-token").length == 0) {
metadata.add("dapr-api-token", this.options.daprApiToken as grpc.MetadataValue);
}
next(metadata, listener);
},
});
};
}

setIsInitialized(isInitialized: boolean): void {
Expand Down
6 changes: 5 additions & 1 deletion src/implementation/Client/GRPCClient/GRPCClientProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@
this.grpcClientOptions.interceptors = [];
}

this.grpcClientOptions.interceptors = [...this.generateInterceptors(), ...this.grpcClientOptions.interceptors];
this.grpcClientOptions.interceptors = [

Check warning on line 63 in src/implementation/Client/GRPCClient/GRPCClientProxy.ts

View check run for this annotation

Codecov / codecov/patch

src/implementation/Client/GRPCClient/GRPCClientProxy.ts#L63

Added line #L63 was not covered by tests
...this.generateInterceptors(),
...(this.grpcClient.getGrpcClientOptions().interceptors ?? []),
...this.grpcClientOptions.interceptors,
];

const clientCustom = new this.clsProxy(
`${this.grpcClient.options.daprHost}:${this.grpcClient.options.daprPort}`,
Expand Down
89 changes: 78 additions & 11 deletions src/utils/Client.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
import { LoggerOptions } from "../types/logger/LoggerOptions";
import { StateConsistencyEnum } from "../enum/StateConsistency.enum";
import { StateConcurrencyEnum } from "../enum/StateConcurrency.enum";
import { URLSearchParams } from "url";
import { URL, URLSearchParams } from "url";
/**
* Adds metadata to a map.
* @param map Input map
Expand Down Expand Up @@ -253,24 +253,91 @@
/**
* Prepares DaprClientOptions for use by the DaprClient/DaprServer.
* If the user does not provide a value for a mandatory option, the default value is used.
* @param clientoptions DaprClientOptions
* @param clientOptions DaprClientOptions
* @param defaultCommunicationProtocol CommunicationProtocolEnum
* @param defaultLoggerOptions
* @returns DaprClientOptions
*/
export function getClientOptions(
clientoptions: Partial<DaprClientOptions> | undefined,
clientOptions: Partial<DaprClientOptions> | undefined,
defaultCommunicationProtocol: CommunicationProtocolEnum,
defaultLoggerOptions: LoggerOptions | undefined,
): DaprClientOptions {
const clientCommunicationProtocol = clientoptions?.communicationProtocol ?? defaultCommunicationProtocol;
const clientCommunicationProtocol = clientOptions?.communicationProtocol ?? defaultCommunicationProtocol;

// We decide the host/port/endpoint here
let daprEndpoint = "";
if (clientCommunicationProtocol == CommunicationProtocolEnum.HTTP) {
daprEndpoint = Settings.getDefaultHttpEndpoint();
} else if (clientCommunicationProtocol == CommunicationProtocolEnum.GRPC) {
daprEndpoint = Settings.getDefaultGrpcEndpoint();
}

let host = Settings.getDefaultHost();
let port = Settings.getDefaultPort(clientCommunicationProtocol);

if (clientOptions?.daprHost || clientOptions?.daprPort) {
host = clientOptions?.daprHost ?? host;
port = clientOptions?.daprPort ?? port;
} else if (daprEndpoint != "") {
const [scheme, fqdn, p] = parseEndpoint(daprEndpoint);
host = `${scheme}://${fqdn}`;
port = p;

Check warning on line 285 in src/utils/Client.util.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L283-L285

Added lines #L283 - L285 were not covered by tests
}

return {
daprHost: clientoptions?.daprHost ?? Settings.getDefaultHost(),
daprPort: clientoptions?.daprPort ?? Settings.getDefaultPort(clientCommunicationProtocol),
daprHost: host,
daprPort: port,
communicationProtocol: clientCommunicationProtocol,
isKeepAlive: clientoptions?.isKeepAlive,
logger: clientoptions?.logger ?? defaultLoggerOptions,
actor: clientoptions?.actor,
daprApiToken: clientoptions?.daprApiToken,
maxBodySizeMb: clientoptions?.maxBodySizeMb,
isKeepAlive: clientOptions?.isKeepAlive,
logger: clientOptions?.logger ?? defaultLoggerOptions,
actor: clientOptions?.actor,
daprApiToken: clientOptions?.daprApiToken,
maxBodySizeMb: clientOptions?.maxBodySizeMb,
};
}

/**
* Scheme, fqdn and port
*/
type EndpointTuple = [string, string, string];

/**
* Parses an endpoint to scheme, fqdn and port
elena-kolevska marked this conversation as resolved.
Show resolved Hide resolved
* Examples:
* - http://localhost:3500 -> [http, localhost, 3500]
* - localhost:3500 -> [http, localhost, 3500]
* - :3500 -> [http, localhost, 3500]
* - localhost -> [http, localhost, 80]
* - https://localhost:3500 -> [https, localhost, 3500]
* - [::1]:3500 -> [http, ::1, 3500]
* - [::1] -> [http, ::1, 80]
* - http://[2001:db8:1f70:0:999:de8:7648:6e8]:5000 -> [http, 2001:db8:1f70:0:999:de8:7648:6e8, 5000]
* @throws Error if the address is invalid
* @param address Endpoint address
* @returns EndpointTuple (scheme, fqdn, port)
*/
export function parseEndpoint(address: string): EndpointTuple {
elena-kolevska marked this conversation as resolved.
Show resolved Hide resolved
// Prefix with a scheme and host when they're not present,
// because the URL library won't parse it otherwise
if (address.startsWith(":")) {
address = "http://localhost" + address;

Check warning on line 324 in src/utils/Client.util.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L324

Added line #L324 was not covered by tests
}
if (!address.includes("://")) {
address = "http://" + address;

Check warning on line 327 in src/utils/Client.util.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L327

Added line #L327 was not covered by tests
}

let scheme, fqdn, port: string;

try {
const myURL = new URL(address);
scheme = myURL.protocol.replace(":", "");
fqdn = myURL.hostname.replace("[", "");
fqdn = fqdn.replace("]", "");

Check warning on line 336 in src/utils/Client.util.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L332-L336

Added lines #L332 - L336 were not covered by tests
port = myURL.port || (myURL.protocol == "https:" ? "443" : "80");
} catch (error) {
throw new Error(`Invalid address: ${address}`);

Check warning on line 339 in src/utils/Client.util.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L339

Added line #L339 was not covered by tests
}

return [scheme, fqdn, port];

Check warning on line 342 in src/utils/Client.util.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L342

Added line #L342 was not covered by tests
}
10 changes: 10 additions & 0 deletions src/utils/Settings.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export class Settings {
private static readonly defaultHttpPort: string = "3500";
private static readonly defaultGrpcAppPort: string = "50000";
private static readonly defaultGrpcPort: string = "50001";
private static readonly defaultHttpEndpoint: string = "";
private static readonly defaultGrpcEndpoint: string = "";
private static readonly defaultCommunicationProtocol: CommunicationProtocolEnum = CommunicationProtocolEnum.HTTP;
private static readonly defaultKeepAlive: boolean = true;
private static readonly defaultStateGetBulkParallelism: number = 10;
Expand Down Expand Up @@ -85,6 +87,14 @@ export class Settings {
return process.env.APP_PORT ?? Settings.defaultGrpcAppPort;
}

static getDefaultHttpEndpoint(): string {
return process.env.DAPR_HTTP_ENDPOINT || Settings.defaultHttpEndpoint;
}

static getDefaultGrpcEndpoint(): string {
return process.env.DAPR_GRPC_ENDPOINT || Settings.defaultGrpcEndpoint;
}

/**
* Gets the default port that the application is listening on.
* @param communicationProtocolEnum communication protocol
Expand Down
Loading
Loading