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 18 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

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;
public readonly grpcClientOptions: Partial<grpc.ClientOptions>;
elena-kolevska marked this conversation as resolved.
Show resolved Hide resolved

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.grpcClientOptions.interceptors ?? []),
...this.grpcClientOptions.interceptors,
];

const clientCustom = new this.clsProxy(
`${this.grpcClient.options.daprHost}:${this.grpcClient.options.daprPort}`,
Expand Down
101 changes: 99 additions & 2 deletions src/utils/Client.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,9 +263,30 @@
defaultLoggerOptions: LoggerOptions | undefined,
): DaprClientOptions {
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.toString();

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L282-L284

Added lines #L282 - L284 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,
Expand All @@ -274,3 +295,79 @@
maxBodySizeMb: clientoptions?.maxBodySizeMb,
};
}

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

/**
* 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::999:de8:7648:6e8]:5000 -> [http, 2001:db8:1f70::999:de8:7648:6e8, 5000]
* @throws Error if the port is not a number
* @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
let scheme = "http";
let fqdn = "localhost";
let port = 80;
let addr = 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#L321-L324

Added lines #L321 - L324 were not covered by tests

const addrList = address.split("://");

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L326

Added line #L326 was not covered by tests

if (addrList.length === 2) {
// A scheme was explicitly specified
scheme = addrList[0];

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L330

Added line #L330 was not covered by tests
if (scheme === "https") {
port = 443;

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L332

Added line #L332 was not covered by tests
}
addr = addrList[1];

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L334

Added line #L334 was not covered by tests
}

const addrParts = addr.split(":");

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L337

Added line #L337 was not covered by tests
if (addrParts.length === 2) {
// A port was explicitly specified
if (addrParts[0].length > 0) {
fqdn = addrParts[0];

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L341

Added line #L341 was not covered by tests
}
// Account for Endpoints of the type http://localhost:3500/v1.0/invoke
const portParts = addrParts[1].split("/");
port = parseInt(portParts[0], 10);

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L344-L345

Added lines #L344 - L345 were not covered by tests
} else if (addrParts.length === 1) {
// No port was specified
// Account for Endpoints of the type :3500/v1.0/invoke
elena-kolevska marked this conversation as resolved.
Show resolved Hide resolved
const fqdnParts = addrParts[0].split("/");
fqdn = fqdnParts[0];

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L349-L350

Added lines #L349 - L350 were not covered by tests
} else {
// IPv6 address
const ipv6Parts = addr.split("]:");

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L353

Added line #L353 was not covered by tests
shubham1172 marked this conversation as resolved.
Show resolved Hide resolved
if (ipv6Parts.length === 2) {
// A port was explicitly specified
fqdn = ipv6Parts[0].replace("[", "");
const portParts = ipv6Parts[1].split("/");
port = parseInt(portParts[0], 10);

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L356-L358

Added lines #L356 - L358 were not covered by tests
} else if (ipv6Parts.length === 1) {
// No port was specified
const fqdnParts = ipv6Parts[0].split("/");
fqdn = fqdnParts[0].replace("[", "").replace("]", "");

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L361-L362

Added lines #L361 - L362 were not covered by tests
} else {
throw new Error(`Invalid address: ${address}`);

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L364

Added line #L364 was not covered by tests
}
}

if (isNaN(port)) {
throw new Error(`Invalid port: ${port}`);

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L369

Added line #L369 was not covered by tests
}

return [scheme, fqdn, port];

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

View check run for this annotation

Codecov / codecov/patch

src/utils/Client.util.ts#L372

Added line #L372 was not covered by tests
}
18 changes: 18 additions & 0 deletions src/utils/Settings.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
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,22 @@
return process.env.APP_PORT ?? Settings.defaultGrpcAppPort;
}

static getDefaultHttpEndpoint(): string {
if (process.env.DAPR_HTTP_ENDPOINT && process.env.DAPR_HTTP_ENDPOINT !== "") {
return process.env.DAPR_HTTP_ENDPOINT as string;

Check warning on line 92 in src/utils/Settings.util.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/Settings.util.ts#L92

Added line #L92 was not covered by tests
} else {
return Settings.defaultHttpEndpoint;
}
}

static getDefaultGrpcEndpoint(): string {
if (process.env.DAPR_GRPC_ENDPOINT && process.env.DAPR_GRPC_ENDPOINT !== "") {
elena-kolevska marked this conversation as resolved.
Show resolved Hide resolved
return process.env.DAPR_GRPC_ENDPOINT as string;

Check warning on line 100 in src/utils/Settings.util.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/Settings.util.ts#L100

Added line #L100 was not covered by tests
} else {
elena-kolevska marked this conversation as resolved.
Show resolved Hide resolved
return Settings.defaultGrpcEndpoint;
}
}

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