From 9ccf41c4c5c18562a0c630594ae9382398e77811 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Mon, 23 Oct 2023 19:33:23 +0100 Subject: [PATCH 01/22] Adds new parsing logic Signed-off-by: Elena Kolevska --- src/utils/Client.util.ts | 216 +++++++++ test/unit/utils/Client.util.test.ts | 663 +++++++++++++--------------- 2 files changed, 532 insertions(+), 347 deletions(-) diff --git a/src/utils/Client.util.ts b/src/utils/Client.util.ts index 5766c482..0b7c2f15 100644 --- a/src/utils/Client.util.ts +++ b/src/utils/Client.util.ts @@ -31,6 +31,7 @@ import { LoggerOptions } from "../types/logger/LoggerOptions"; import { StateConsistencyEnum } from "../enum/StateConsistency.enum"; import { StateConcurrencyEnum } from "../enum/StateConcurrency.enum"; import { URL, URLSearchParams } from "url"; + /** * Adds metadata to a map. * @param map Input map @@ -341,3 +342,218 @@ export function parseEndpoint(address: string): EndpointTuple { return [scheme, fqdn, port]; } + + +/** + * 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] + */ + + +class URIParseConfig { + static readonly DEFAULT_SCHEME = "dns"; + static readonly DEFAULT_HOSTNAME = "localhost"; + static readonly DEFAULT_PORT = 443; + static readonly DEFAULT_AUTHORITY = ""; + static readonly ACCEPTED_SCHEMES = ["dns", "unix", "unix-abstract", "vsock", "http", "https", "grpc", "grpcs"]; +} + +export class GrpcEndpoint { + private _scheme: string = ""; + private _hostname: string = ""; + private _port: number = 0; + private _tls: boolean = false; + private _authority: string; + private _url: string; + private _parsedUrl: URL; + private _endpoint: string = ""; + + constructor(url: string) { + this._authority = URIParseConfig.DEFAULT_AUTHORITY; + this._url = url; + + this._parsedUrl = new URL(this._preprocessUri(url)); + this.validatePathAndQuery(); + + this.setTls(); + this.setHostname(); + this.setScheme(); + this.setPort(); + this.setEndpoint(); + } + + private _preprocessUri(url: string): string { + let urlList = url.split(":"); + + if (urlList.length === 3 && !url.includes("://")) { + // A URI like dns:mydomain:5000 or vsock:mycid:5000 was used + url = url.replace(":", "://"); + } else if (urlList.length >= 2 && !url.includes("://") && URIParseConfig.ACCEPTED_SCHEMES.includes(urlList[0])) { + // A URI like dns:mydomain was used + url = url.replace(":", "://"); + } else { + urlList = url.split("://"); + if (urlList.length === 1) { + // If a scheme was not explicitly specified in the URL + // we need to add a default scheme, + // because of how URL works in JavaScript + + // We also need to check if the provided uri is not of the form :5000 + // if it is, we need to add a default hostname, because the URL class can't parse it + if (url[0] === ':') { + url = `${URIParseConfig.DEFAULT_SCHEME}://${URIParseConfig.DEFAULT_HOSTNAME}${url}`; + } else { + url = `${URIParseConfig.DEFAULT_SCHEME}://${url}`; + } + + } else { + // If a scheme was explicitly specified in the URL + // we need to make sure it is a valid scheme + const scheme = urlList[0]; + if (!URIParseConfig.ACCEPTED_SCHEMES.includes(scheme)) { + throw new Error(`Invalid scheme '${scheme}' in URL '${url}'`); + } + + // We should do a special check if the scheme is dns, and it uses + // an authority in the format of dns:[//authority/]host[:port] + if (scheme.toLowerCase() === "dns") { + // A URI like dns://authority/mydomain was used + urlList = url.split("/"); + if (urlList.length < 4) { + throw new Error(`Invalid dns authority '${urlList[2]}' in URL '${url}'`); + } + this._authority = urlList[2]; + url = `dns://${urlList[3]}`; + } + } + } + return url; + } + + private validatePathAndQuery(): void { + if (this._parsedUrl.pathname && this._parsedUrl.pathname !== '/') { + throw new Error(`Paths are not supported for gRPC endpoints: '${this._parsedUrl.pathname}'`); + } + + const params = new URLSearchParams(this._parsedUrl.search); + if (params.has('tls') && (this._parsedUrl.protocol === 'http:' || this._parsedUrl.protocol === 'https:')) { + throw new Error(`The tls query parameter is not supported for http(s) endpoints: '${this._parsedUrl.search}'`); + } + + params.delete('tls'); + if (Array.from(params.keys()).length > 0) { + throw new Error(`Query parameters are not supported for gRPC endpoints: '${this._parsedUrl.search}'`); + } + } + + private setTls(): void { + const params = new URLSearchParams(this._parsedUrl.search); + const tlsStr = params.get('tls') || ""; + this._tls = tlsStr.toLowerCase() === 'true'; + + if (this._parsedUrl.protocol == "https:"){ + this._tls = true; + } + } + + get tls(): boolean { + return this._tls; + } + + private setHostname(): void { + if (!this._parsedUrl.hostname) { + this._hostname = URIParseConfig.DEFAULT_HOSTNAME; + return; + } + + + this._hostname = this._parsedUrl.hostname; + } + + get hostname(): string { + return this._hostname; + } + + private setScheme(): void { + if (!this._parsedUrl.protocol) { + this._scheme = URIParseConfig.DEFAULT_SCHEME; + return; + } + + const scheme = this._parsedUrl.protocol.slice(0, -1); // Remove trailing ':' + if (scheme === 'http' || scheme === 'https') { + this._scheme = URIParseConfig.DEFAULT_SCHEME; + console.warn("http and https schemes are deprecated, use grpc or grpcs instead"); + return; + } + + if (!URIParseConfig.ACCEPTED_SCHEMES.includes(scheme)) { + throw new Error(`Invalid scheme '${scheme}' in URL '${this._url}'`); + } + + this._scheme = scheme; + } + + get scheme(): string { + return this._scheme; + } + + private setPort(): void { + if (this._scheme === 'unix' || this._scheme === 'unix-abstract') { + this._port = 0; + return; + } + + this._port = this._parsedUrl.port ? parseInt(this._parsedUrl.port) : URIParseConfig.DEFAULT_PORT; + } + + get port(): string { + return this._port === 0 ? '' : this._port.toString(); + } + + get portAsInt(): number { + return this._port; + } + + private setEndpoint(): void { + let port = this._port ? `:${this.port}` : ""; + + if (this._scheme === "unix") { + const separator = this._url.startsWith("unix://") ? "://" : ":"; + this._endpoint = `${this._scheme}${separator}${this._hostname}`; + return; + } + + if (this._scheme === "vsock") { + this._endpoint = `${this._scheme}:${this._hostname}:${this.port}`; + return; + } + + if (this._scheme === "unix-abstract") { + this._endpoint = `${this._scheme}:${this._hostname}${port}`; + return; + } + + if (this._scheme === "dns") { + const authority = this._authority ? `//${this._authority}/` : ""; + this._endpoint = `${this._scheme}:${authority}${this._hostname}${port}`; + return; + } + + this._endpoint = `${this._scheme}:${this._hostname}${port}`; + } + + + get endpoint(): string { + return this._endpoint; + } + + +} \ No newline at end of file diff --git a/test/unit/utils/Client.util.test.ts b/test/unit/utils/Client.util.test.ts index 2ec1bfdb..f15b26e9 100644 --- a/test/unit/utils/Client.util.test.ts +++ b/test/unit/utils/Client.util.test.ts @@ -19,8 +19,7 @@ import { getBulkPublishEntries, getBulkPublishResponse, getClientOptions, - createHTTPQueryParam, - parseEndpoint, + createHTTPQueryParam, GrpcEndpoint, } from "../../../src/utils/Client.util"; import { Map } from "google-protobuf"; import { PubSubBulkPublishEntry } from "../../../src/types/pubsub/PubSubBulkPublishEntry.type"; @@ -409,383 +408,353 @@ describe("Client.util", () => { }); }); - describe("parseEndpoint", () => { + describe("parseGRPCEndpoint", () => { const testCases = [ - { endpoint: ":5000", scheme: "http", host: "localhost", port: "5000" }, + // Port only { - endpoint: ":5000/v1/dapr", - scheme: "http", + url: ":5000", + error: false, + secure: false, + scheme: "", host: "localhost", - port: "5000", + port: 5000, + endpoint: "dns:localhost:5000" }, - - { endpoint: "localhost", scheme: "http", host: "localhost", port: "80" }, - { - endpoint: "localhost/v1/dapr", - scheme: "http", - host: "localhost", - port: "80", - }, - { - endpoint: "localhost:5000", - scheme: "http", - host: "localhost", - port: "5000", - }, - { - endpoint: "localhost:5000/v1/dapr", - scheme: "http", - host: "localhost", - port: "5000", - }, - - { - endpoint: "http://localhost", - scheme: "http", - host: "localhost", - port: "80", - }, - { - endpoint: "http://localhost/v1/dapr", - scheme: "http", - host: "localhost", - port: "80", - }, - { - endpoint: "http://localhost:5000", - scheme: "http", - host: "localhost", - port: "5000", - }, - { - endpoint: "http://localhost:5000/v1/dapr", - scheme: "http", - host: "localhost", - port: "5000", - }, - { - endpoint: "https://localhost", - scheme: "https", + url: ":5000?tls=false", + error: false, + secure: false, + scheme: "", host: "localhost", - port: "443", + port: 5000, + endpoint: "dns:localhost:5000" }, { - endpoint: "https://localhost/v1/dapr", - scheme: "https", + url: ":5000?tls=true", + error: false, + secure: true, + scheme: "", host: "localhost", + port: 5000, + endpoint: "dns:localhost:5000" + }, + // Host only + { + url: "myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + { + url: "myhost?tls=false", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + { + url: "myhost?tls=true", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + // Host and port + { + url: "myhost:443", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + { + url: "myhost:443?tls=false", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + { + url: "myhost:443?tls=true", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + // Scheme, host and port + { + url: "http://myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + {url: "http://myhost?tls=false", error: true}, + {url: "http://myhost?tls=true", error: true}, + { + url: "http://myhost:443", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + {url: "http://myhost:443?tls=false", error: true}, + {url: "http://myhost:443?tls=true", error: true}, + { + url: "http://myhost:5000", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 5000, + endpoint: "dns:myhost:5000" + }, + {url: "http://myhost:5000?tls=false", error: true}, + {url: "http://myhost:5000?tls=true", error: true}, + { + url: "https://myhost:443", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + {url: "https://myhost:443?tls=false", error: true}, + {url: "https://myhost:443?tls=true", error: true}, + // Scheme = dns + { + url: "dns:myhost", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + { + url: "dns:myhost?tls=false", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + { + url: "dns:myhost?tls=true", + error: false, + secure: true, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443" + }, + // Scheme = dns with authority + { + url: "dns://myauthority:53/myhost", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns://myauthority:53/myhost:443" + }, + { + url: "dns://myauthority:53/myhost?tls=false", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns://myauthority:53/myhost:443" + }, + { + url: "dns://myauthority:53/myhost?tls=true", + error: false, + secure: true, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns://myauthority:53/myhost:443" + }, + {url: "dns://myhost", error: true}, + // Unix sockets + { + url: "unix:my.sock", + error: false, + secure: false, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix:my.sock" + }, + { + url: "unix:my.sock?tls=true", + error: false, + secure: true, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix:my.sock" + }, + // Unix sockets with absolute path + { + url: "unix://my.sock", + error: false, + secure: false, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix://my.sock" + }, + { + url: "unix://my.sock?tls=true", + error: false, + secure: true, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix://my.sock" + }, + // Unix abstract sockets + { + url: "unix-abstract:my.sock", + error: false, + secure: false, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix-abstract:my.sock" + }, + { + url: "unix-abstract:my.sock?tls=true", + error: false, + secure: true, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix-abstract:my.sock" + }, + // Vsock +// Vsock + { + url: "vsock:mycid", + error: false, + secure: false, + scheme: "vsock", + host: "mycid", port: "443", + endpoint: "vsock:mycid:443" }, { - endpoint: "https://localhost:5000", - scheme: "https", - host: "localhost", - port: "5000", + url: "vsock:mycid:5000", + error: false, + secure: false, + scheme: "vsock", + host: "mycid", + port: 5000, + endpoint: "vsock:mycid:5000" }, { - endpoint: "https://localhost:5000/v1/dapr", - scheme: "https", - host: "localhost", - port: "5000", + url: "vsock:mycid:5000?tls=true", + error: false, + secure: true, + scheme: "vsock", + host: "mycid", + port: 5000, + endpoint: "vsock:mycid:5000" }, - { endpoint: "127.0.0.1", scheme: "http", host: "127.0.0.1", port: "80" }, - { - endpoint: "127.0.0.1/v1/dapr", - scheme: "http", - host: "127.0.0.1", - port: "80", - }, + // IPv6 addresses { - endpoint: "127.0.0.1:5000", - scheme: "http", - host: "127.0.0.1", - port: "5000", + url: "[2001:db8:1f70:0:999:de8:7648:6e8]", + error: false, + secure: false, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 443, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443" }, { - endpoint: "127.0.0.1:5000/v1/dapr", - scheme: "http", - host: "127.0.0.1", - port: "5000", + url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]", + error: false, + secure: false, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 443, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443" }, - { - endpoint: "http://127.0.0.1", - scheme: "http", - host: "127.0.0.1", - port: "80", + url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + error: false, + secure: false, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 5000, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000" }, { - endpoint: "http://127.0.0.1/v1/dapr", - scheme: "http", - host: "127.0.0.1", - port: "80", + url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000?abc=[]", + error: true }, { - endpoint: "http://127.0.0.1:5000", - scheme: "http", - host: "127.0.0.1", - port: "5000", + url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]", + error: false, + secure: true, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 443, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443" }, { - endpoint: "http://127.0.0.1:5000/v1/dapr", - scheme: "http", - host: "127.0.0.1", - port: "5000", + url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + error: false, + secure: true, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 5000, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000" }, - { - endpoint: "https://127.0.0.1", - scheme: "https", - host: "127.0.0.1", - port: "443", - }, - { - endpoint: "https://127.0.0.1/v1/dapr", - scheme: "https", - host: "127.0.0.1", - port: "443", - }, - { - endpoint: "https://127.0.0.1:5000", - scheme: "https", - host: "127.0.0.1", - port: "5000", - }, - { - endpoint: "https://127.0.0.1:5000/v1/dapr", - scheme: "https", - host: "127.0.0.1", - port: "5000", - }, + // Invalid addresses (with path and queries) + {url: "host:5000/v1/dapr", error: true}, + {url: "host:5000/?a=1", error: true}, - { - endpoint: "[2001:db8:1f70:0:999:de8:7648:6e8]", - scheme: "http", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "80", - }, - { - endpoint: "[2001:db8:1f70:0:999:de8:7648:6e8]/v1/dapr", - scheme: "http", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "80", - }, - { - endpoint: "[2001:db8:1f70:0:999:de8:7648:6e8]:5000", - scheme: "http", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "5000", - }, - { - endpoint: "[2001:db8:1f70:0:999:de8:7648:6e8]:5000/v1/dapr", - scheme: "http", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "5000", - }, - - { - endpoint: "http://[2001:db8:1f70:0:999:de8:7648:6e8]", - scheme: "http", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "80", - }, - { - endpoint: "http://[2001:db8:1f70:0:999:de8:7648:6e8]/v1/dapr", - scheme: "http", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "80", - }, - { - endpoint: "http://[2001:db8:1f70:0:999:de8:7648:6e8]:5000", - scheme: "http", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "5000", - }, - { - endpoint: "http://[2001:db8:1f70:0:999:de8:7648:6e8]:5000/v1/dapr", - scheme: "http", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "5000", - }, - - { - endpoint: "https://[2001:db8:1f70:0:999:de8:7648:6e8]", - scheme: "https", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "443", - }, - { - endpoint: "https://[2001:db8:1f70:0:999:de8:7648:6e8]/v1/dapr", - scheme: "https", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "443", - }, - { - endpoint: "https://[2001:db8:1f70:0:999:de8:7648:6e8]:5000", - scheme: "https", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "5000", - }, - { - endpoint: "https://[2001:db8:1f70:0:999:de8:7648:6e8]:5000/v1/dapr", - scheme: "https", - host: "2001:db8:1f70:0:999:de8:7648:6e8", - port: "5000", - }, - - { endpoint: "domain.com", scheme: "http", host: "domain.com", port: "80" }, - { - endpoint: "domain.com/v1/grpc", - scheme: "http", - host: "domain.com", - port: "80", - }, - { - endpoint: "domain.com:5000", - scheme: "http", - host: "domain.com", - port: "5000", - }, - { - endpoint: "domain.com:5000/v1/dapr", - scheme: "http", - host: "domain.com", - port: "5000", - }, - - { - endpoint: "http://domain.com", - scheme: "http", - host: "domain.com", - port: "80", - }, - { - endpoint: "http://domain.com/v1/dapr", - scheme: "http", - host: "domain.com", - port: "80", - }, - { - endpoint: "http://domain.com:5000", - scheme: "http", - host: "domain.com", - port: "5000", - }, - { - endpoint: "http://domain.com:5000/v1/dapr", - scheme: "http", - host: "domain.com", - port: "5000", - }, - - { - endpoint: "https://domain.com", - scheme: "https", - host: "domain.com", - port: "443", - }, - { - endpoint: "https://domain.com/v1/dapr", - scheme: "https", - host: "domain.com", - port: "443", - }, - { - endpoint: "https://domain.com:5000", - scheme: "https", - host: "domain.com", - port: "5000", - }, - { - endpoint: "https://domain.com:5000/v1/dapr", - scheme: "https", - host: "domain.com", - port: "5000", - }, - - { - endpoint: "abc.domain.com", - scheme: "http", - host: "abc.domain.com", - port: "80", - }, - { - endpoint: "abc.domain.com/v1/grpc", - scheme: "http", - host: "abc.domain.com", - port: "80", - }, - { - endpoint: "abc.domain.com:5000", - scheme: "http", - host: "abc.domain.com", - port: "5000", - }, - { - endpoint: "abc.domain.com:5000/v1/dapr", - scheme: "http", - host: "abc.domain.com", - port: "5000", - }, - - { - endpoint: "http://abc.domain.com/v1/dapr", - scheme: "http", - host: "abc.domain.com", - port: "80", - }, - { - endpoint: "http://abc.domain.com/v1/dapr", - scheme: "http", - host: "abc.domain.com", - port: "80", - }, - { - endpoint: "http://abc.domain.com:5000/v1/dapr", - scheme: "http", - host: "abc.domain.com", - port: "5000", - }, - { - endpoint: "http://abc.domain.com:5000/v1/dapr/v1/dapr", - scheme: "http", - host: "abc.domain.com", - port: "5000", - }, - - { - endpoint: "https://abc.domain.com/v1/dapr", - scheme: "https", - host: "abc.domain.com", - port: "443", - }, - { - endpoint: "https://abc.domain.com/v1/dapr", - scheme: "https", - host: "abc.domain.com", - port: "443", - }, - { - endpoint: "https://abc.domain.com:5000/v1/dapr", - scheme: "https", - host: "abc.domain.com", - port: "5000", - }, - { - endpoint: "https://abc.domain.com:5000/v1/dapr/v1/dapr", - scheme: "https", - host: "abc.domain.com", - port: "5000", - }, + // Invalid scheme + {url: "inv-scheme://myhost", error: true}, + {url: "inv-scheme:myhost:5000", error: true} ]; - testCases.forEach(({ endpoint, scheme, host, port }) => { - it(`should correctly parse ${endpoint}`, () => { - const result = parseEndpoint(endpoint); - expect(result[0]).toEqual(scheme); - expect(result[1]).toEqual(host); - expect(result[2]).toEqual(port); + + testCases.forEach((testCase) => { + test(`Testing URL: ${testCase.url}`, () => { + if (testCase.error) { + expect(() => new GrpcEndpoint(testCase.url)).toThrow(Error); + } else { + const url = new GrpcEndpoint(testCase.url); + expect(url.endpoint).toBe(testCase.endpoint); + expect(url.tls).toBe(testCase.secure); + expect(url.hostname).toBe(testCase.host); + expect(url.port).toBe(String(testCase.port)); + } }); }); }); From aeae624556307be70534787e3d521573ebbf2826 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 24 Oct 2023 11:41:46 +0100 Subject: [PATCH 02/22] wip Signed-off-by: Elena Kolevska --- .../Client/GRPCClient/GRPCClient.ts | 26 +- src/types/DaprClientOptions.ts | 10 +- src/utils/Client.util.ts | 239 +++++++++--------- 3 files changed, 126 insertions(+), 149 deletions(-) diff --git a/src/implementation/Client/GRPCClient/GRPCClient.ts b/src/implementation/Client/GRPCClient/GRPCClient.ts index 397f8f14..49699386 100644 --- a/src/implementation/Client/GRPCClient/GRPCClient.ts +++ b/src/implementation/Client/GRPCClient/GRPCClient.ts @@ -39,7 +39,10 @@ export default class GRPCClient implements IClient { 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.client = new GrpcDaprClient(this.options.daprEndpoint.endpoint, + this.getClientCredentials(), + this.getGrpcClientOptions(), + ) } async getClient(requiresInitialization = true): Promise { @@ -60,7 +63,7 @@ export default class GRPCClient implements IClient { } private generateCredentials(): grpc.ChannelCredentials { - if (this.options.daprHost.startsWith("https")) { + if (this.options.daprEndpoint.tls) { return grpc.ChannelCredentials.createSsl(); } return grpc.ChannelCredentials.createInsecure(); @@ -93,25 +96,6 @@ export default class GRPCClient implements IClient { return options; } - private generateClient(host: string, port: string): GrpcDaprClient { - return new GrpcDaprClient( - GRPCClient.getEndpoint(host, port), - this.getClientCredentials(), - this.getGrpcClientOptions(), - ); - } - - // 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]; - } - - return endpoint; - } private generateInterceptors(): (options: any, nextCall: any) => grpc.InterceptingCall { return (options: any, nextCall: any) => { diff --git a/src/types/DaprClientOptions.ts b/src/types/DaprClientOptions.ts index 53c5506d..75534e12 100644 --- a/src/types/DaprClientOptions.ts +++ b/src/types/DaprClientOptions.ts @@ -14,19 +14,25 @@ limitations under the License. import CommunicationProtocolEnum from "../enum/CommunicationProtocol.enum"; import { ActorRuntimeOptions } from "./actors/ActorRuntimeOptions"; import { LoggerOptions } from "./logger/LoggerOptions"; +import {Endpoint} from "../utils/Client.util"; export type DaprClientOptions = { /** * Host location of the Dapr sidecar. * Default is 127.0.0.1. */ - daprHost: string; + daprHost?: string; /** * Port of the Dapr sidecar. * Default is 3500. */ - daprPort: string; + daprPort?: string; + + /** + * gRPC endpoint of the Dapr sidecar. + */ + daprEndpoint: Endpoint; /** * Protocol to use to communicate with the Dapr sidecar. diff --git a/src/utils/Client.util.ts b/src/utils/Client.util.ts index 0b7c2f15..53b6e47a 100644 --- a/src/utils/Client.util.ts +++ b/src/utils/Client.util.ts @@ -260,89 +260,91 @@ function getType(o: any) { * @returns DaprClientOptions */ export function getClientOptions( - clientOptions: Partial | undefined, - defaultCommunicationProtocol: CommunicationProtocolEnum, - defaultLoggerOptions: LoggerOptions | undefined, + clientOptions: Partial | undefined, + defaultCommunicationProtocol: CommunicationProtocolEnum, + 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(); - } + const clientCommunicationProtocol = clientOptions?.communicationProtocol ?? defaultCommunicationProtocol; + + // We decide the host/port/endpoint here + let host = Settings.getDefaultHost(); + let port = Settings.getDefaultPort(clientCommunicationProtocol); + let uri = `${host}:${port}`; + + let endpoint: Endpoint; + + if (clientOptions?.daprHost || clientOptions?.daprPort) { + host = clientOptions?.daprHost ?? host; + port = clientOptions?.daprPort ?? port; + uri = `${host}:${port}`; + } else if (clientCommunicationProtocol == CommunicationProtocolEnum.HTTP && Settings.getDefaultHttpEndpoint() != "") { + uri = Settings.getDefaultHttpEndpoint(); + } else if (clientCommunicationProtocol == CommunicationProtocolEnum.GRPC && Settings.getDefaultGrpcEndpoint() != "") { + uri = 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; - } + if (clientCommunicationProtocol == CommunicationProtocolEnum.HTTP) { + endpoint = new HttpEndpoint(uri); + } else { + endpoint = new GrpcEndpoint(uri); + } - return { - daprHost: host, - daprPort: port, - communicationProtocol: clientCommunicationProtocol, - isKeepAlive: clientOptions?.isKeepAlive, - logger: clientOptions?.logger ?? defaultLoggerOptions, - actor: clientOptions?.actor, - daprApiToken: clientOptions?.daprApiToken ?? Settings.getDefaultApiToken(), - maxBodySizeMb: clientOptions?.maxBodySizeMb, - }; + return { + daprEndpoint: endpoint, + communicationProtocol: clientCommunicationProtocol, + isKeepAlive: clientOptions?.isKeepAlive, + logger: clientOptions?.logger ?? defaultLoggerOptions, + actor: clientOptions?.actor, + daprApiToken: clientOptions?.daprApiToken ?? Settings.getDefaultApiToken(), + maxBodySizeMb: clientOptions?.maxBodySizeMb, + }; } -/** - * Scheme, fqdn and port - */ -type EndpointTuple = [string, string, string]; +class URIParseConfig { + static readonly DEFAULT_SCHEME_GRPC = "dns"; + static readonly DEFAULT_SCHEME_HTTP = "http"; + static readonly DEFAULT_HOSTNAME = "localhost"; + static readonly DEFAULT_PORT = 443; + static readonly DEFAULT_AUTHORITY = ""; + static readonly ACCEPTED_SCHEMES_GRPC = ["dns", "unix", "unix-abstract", "vsock", "http", "https", "grpc", "grpcs"]; + static readonly ACCEPTED_SCHEMES_HTTPS = ["http", "https"]; +} -/** - * Parses an endpoint to scheme, fqdn and port - * 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 { - // 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; - } - if (!address.includes("://")) { - address = "http://" + address; - } +export abstract class Endpoint { + protected _scheme = ""; + protected _hostname = ""; + protected _port = 0; + protected _tls = false; + protected _authority = ""; + protected _url: string; + protected _endpoint = ""; + protected _parsedUrl!: URL; + + protected constructor(url: string) { + this._url = url; + } - let scheme, fqdn, port: string; + get tls(): boolean { + return this._tls; + } - try { - const myURL = new URL(address); - scheme = myURL.protocol.replace(":", ""); - fqdn = myURL.hostname.replace("[", ""); - fqdn = fqdn.replace("]", ""); - port = myURL.port || (myURL.protocol == "https:" ? "443" : "80"); - } catch (error) { - throw new Error(`Invalid address: ${address}`); - } + get hostname(): string { + return this._hostname; + } - return [scheme, fqdn, port]; -} + get scheme(): string { + return this._scheme; + } + get port(): string { + return this._port === 0 ? '' : this._port.toString(); + } + + get endpoint(): string { + return this._endpoint; + } +} /** * Examples: @@ -355,31 +357,43 @@ export function parseEndpoint(address: string): EndpointTuple { * - [::1] -> [http, ::1, 80] * - http://[2001:db8:1f70:0:999:de8:7648:6e8]:5000 -> [http, 2001:db8:1f70:0:999:de8:7648:6e8, 5000] */ +export class HttpEndpoint extends Endpoint{ + constructor(url: string) { + super(url); + this._parsedUrl = new URL(this.preprocessUri(url)); + + try { + this._scheme = this._parsedUrl.protocol.replace(":", ""); + this._hostname = this._parsedUrl.hostname.replace("[", ""); + this._hostname = this._hostname.replace("]", ""); + this._port = parseInt(this._parsedUrl.port) || (this._scheme == "https:" ? 443 : 80); + this._tls = this._scheme == "https:"; + this._endpoint = this._scheme + "://" + this._hostname + ":" + this._port.toString(); + } catch (error) { + throw new Error(`Invalid address: ${url}`); + } + } - -class URIParseConfig { - static readonly DEFAULT_SCHEME = "dns"; - static readonly DEFAULT_HOSTNAME = "localhost"; - static readonly DEFAULT_PORT = 443; - static readonly DEFAULT_AUTHORITY = ""; - static readonly ACCEPTED_SCHEMES = ["dns", "unix", "unix-abstract", "vsock", "http", "https", "grpc", "grpcs"]; + // We need to add a default scheme and hostname to the url + // if they are not specified so that the URL class can parse it + private preprocessUri(url: string) { + if (url.startsWith(":")) { + url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; + } + if (!url.includes("://")) { + url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + url; + } + return url; + } } -export class GrpcEndpoint { - private _scheme: string = ""; - private _hostname: string = ""; - private _port: number = 0; - private _tls: boolean = false; - private _authority: string; - private _url: string; - private _parsedUrl: URL; - private _endpoint: string = ""; +export class GrpcEndpoint extends Endpoint { constructor(url: string) { + super(url); this._authority = URIParseConfig.DEFAULT_AUTHORITY; - this._url = url; - this._parsedUrl = new URL(this._preprocessUri(url)); + this._parsedUrl = new URL(this.preprocessUri(url)); this.validatePathAndQuery(); this.setTls(); @@ -389,13 +403,13 @@ export class GrpcEndpoint { this.setEndpoint(); } - private _preprocessUri(url: string): string { + private preprocessUri(url: string): string { let urlList = url.split(":"); if (urlList.length === 3 && !url.includes("://")) { // A URI like dns:mydomain:5000 or vsock:mycid:5000 was used url = url.replace(":", "://"); - } else if (urlList.length >= 2 && !url.includes("://") && URIParseConfig.ACCEPTED_SCHEMES.includes(urlList[0])) { + } else if (urlList.length >= 2 && !url.includes("://") && URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(urlList[0])) { // A URI like dns:mydomain was used url = url.replace(":", "://"); } else { @@ -408,16 +422,16 @@ export class GrpcEndpoint { // We also need to check if the provided uri is not of the form :5000 // if it is, we need to add a default hostname, because the URL class can't parse it if (url[0] === ':') { - url = `${URIParseConfig.DEFAULT_SCHEME}://${URIParseConfig.DEFAULT_HOSTNAME}${url}`; + url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${URIParseConfig.DEFAULT_HOSTNAME}${url}`; } else { - url = `${URIParseConfig.DEFAULT_SCHEME}://${url}`; + url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${url}`; } } else { // If a scheme was explicitly specified in the URL // we need to make sure it is a valid scheme const scheme = urlList[0]; - if (!URIParseConfig.ACCEPTED_SCHEMES.includes(scheme)) { + if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { throw new Error(`Invalid scheme '${scheme}' in URL '${url}'`); } @@ -463,10 +477,6 @@ export class GrpcEndpoint { } } - get tls(): boolean { - return this._tls; - } - private setHostname(): void { if (!this._parsedUrl.hostname) { this._hostname = URIParseConfig.DEFAULT_HOSTNAME; @@ -477,34 +487,26 @@ export class GrpcEndpoint { this._hostname = this._parsedUrl.hostname; } - get hostname(): string { - return this._hostname; - } - private setScheme(): void { if (!this._parsedUrl.protocol) { - this._scheme = URIParseConfig.DEFAULT_SCHEME; + this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; return; } const scheme = this._parsedUrl.protocol.slice(0, -1); // Remove trailing ':' if (scheme === 'http' || scheme === 'https') { - this._scheme = URIParseConfig.DEFAULT_SCHEME; + this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; console.warn("http and https schemes are deprecated, use grpc or grpcs instead"); return; } - if (!URIParseConfig.ACCEPTED_SCHEMES.includes(scheme)) { + if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { throw new Error(`Invalid scheme '${scheme}' in URL '${this._url}'`); } this._scheme = scheme; } - get scheme(): string { - return this._scheme; - } - private setPort(): void { if (this._scheme === 'unix' || this._scheme === 'unix-abstract') { this._port = 0; @@ -514,16 +516,8 @@ export class GrpcEndpoint { this._port = this._parsedUrl.port ? parseInt(this._parsedUrl.port) : URIParseConfig.DEFAULT_PORT; } - get port(): string { - return this._port === 0 ? '' : this._port.toString(); - } - - get portAsInt(): number { - return this._port; - } - private setEndpoint(): void { - let port = this._port ? `:${this.port}` : ""; + const port = this._port ? `:${this.port}` : ""; if (this._scheme === "unix") { const separator = this._url.startsWith("unix://") ? "://" : ":"; @@ -549,11 +543,4 @@ export class GrpcEndpoint { this._endpoint = `${this._scheme}:${this._hostname}${port}`; } - - - get endpoint(): string { - return this._endpoint; - } - - } \ No newline at end of file From 333edeb23505023263a47d2e99990713406ade45 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 24 Oct 2023 16:01:08 +0100 Subject: [PATCH 03/22] Cleanup Signed-off-by: Elena Kolevska --- src/types/DaprClientOptions.ts | 4 ++-- src/utils/Client.util.ts | 18 ++++-------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/types/DaprClientOptions.ts b/src/types/DaprClientOptions.ts index 75534e12..db234d47 100644 --- a/src/types/DaprClientOptions.ts +++ b/src/types/DaprClientOptions.ts @@ -21,13 +21,13 @@ export type DaprClientOptions = { * Host location of the Dapr sidecar. * Default is 127.0.0.1. */ - daprHost?: string; + daprHost: string; /** * Port of the Dapr sidecar. * Default is 3500. */ - daprPort?: string; + daprPort: string; /** * gRPC endpoint of the Dapr sidecar. diff --git a/src/utils/Client.util.ts b/src/utils/Client.util.ts index 53b6e47a..ac9156ac 100644 --- a/src/utils/Client.util.ts +++ b/src/utils/Client.util.ts @@ -291,6 +291,8 @@ export function getClientOptions( } return { + daprHost: endpoint.hostname, + daprPort: endpoint.port, daprEndpoint: endpoint, communicationProtocol: clientCommunicationProtocol, isKeepAlive: clientOptions?.isKeepAlive, @@ -308,7 +310,6 @@ class URIParseConfig { static readonly DEFAULT_PORT = 443; static readonly DEFAULT_AUTHORITY = ""; static readonly ACCEPTED_SCHEMES_GRPC = ["dns", "unix", "unix-abstract", "vsock", "http", "https", "grpc", "grpcs"]; - static readonly ACCEPTED_SCHEMES_HTTPS = ["http", "https"]; } export abstract class Endpoint { @@ -346,17 +347,6 @@ export abstract class Endpoint { } } -/** - * 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] - */ export class HttpEndpoint extends Endpoint{ constructor(url: string) { super(url); @@ -366,8 +356,8 @@ export class HttpEndpoint extends Endpoint{ this._scheme = this._parsedUrl.protocol.replace(":", ""); this._hostname = this._parsedUrl.hostname.replace("[", ""); this._hostname = this._hostname.replace("]", ""); - this._port = parseInt(this._parsedUrl.port) || (this._scheme == "https:" ? 443 : 80); - this._tls = this._scheme == "https:"; + this._port = parseInt(this._parsedUrl.port) || (this._scheme == "https" ? 443 : 80); + this._tls = this._scheme == "https"; this._endpoint = this._scheme + "://" + this._hostname + ":" + this._port.toString(); } catch (error) { throw new Error(`Invalid address: ${url}`); From 5ae0855249ffc0be997f0e7846b00b7866279175 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 24 Oct 2023 17:00:11 +0100 Subject: [PATCH 04/22] http endpoint fix Signed-off-by: Elena Kolevska --- src/implementation/Client/HTTPClient/HTTPClient.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/implementation/Client/HTTPClient/HTTPClient.ts b/src/implementation/Client/HTTPClient/HTTPClient.ts index b2cceb3d..bca07aaf 100644 --- a/src/implementation/Client/HTTPClient/HTTPClient.ts +++ b/src/implementation/Client/HTTPClient/HTTPClient.ts @@ -39,11 +39,7 @@ export default class HTTPClient implements IClient { this.options = options; this.logger = new Logger("HTTPClient", "HTTPClient", this.options.logger); this.isInitialized = false; - - this.clientUrl = `${this.options.daprHost}:${this.options.daprPort}/v1.0`; - if (!this.clientUrl.startsWith("http://") && !this.clientUrl.startsWith("https://")) { - this.clientUrl = `http://${this.clientUrl}`; - } + this.clientUrl = `${this.options.daprEndpoint.endpoint}/v1.0`; if (!HTTPClient.client) { HTTPClient.client = fetch; From ad68707be1642365f63909aafc4fe0012aa90fea Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 24 Oct 2023 19:27:45 +0100 Subject: [PATCH 05/22] Fixes linter errors Signed-off-by: Elena Kolevska --- .../Client/GRPCClient/GRPCClient.ts | 6 +- src/types/DaprClientOptions.ts | 2 +- src/utils/Client.util.ts | 446 +++++++++--------- test/unit/utils/Client.util.test.ts | 100 ++-- 4 files changed, 277 insertions(+), 277 deletions(-) diff --git a/src/implementation/Client/GRPCClient/GRPCClient.ts b/src/implementation/Client/GRPCClient/GRPCClient.ts index 49699386..9454fa81 100644 --- a/src/implementation/Client/GRPCClient/GRPCClient.ts +++ b/src/implementation/Client/GRPCClient/GRPCClient.ts @@ -39,10 +39,11 @@ export default class GRPCClient implements IClient { this.isInitialized = false; this.logger.info(`Opening connection to ${this.options.daprHost}:${this.options.daprPort}`); - this.client = new GrpcDaprClient(this.options.daprEndpoint.endpoint, + this.client = new GrpcDaprClient( + this.options.daprEndpoint.endpoint, this.getClientCredentials(), this.getGrpcClientOptions(), - ) + ); } async getClient(requiresInitialization = true): Promise { @@ -96,7 +97,6 @@ export default class GRPCClient implements IClient { return options; } - private generateInterceptors(): (options: any, nextCall: any) => grpc.InterceptingCall { return (options: any, nextCall: any) => { return new grpc.InterceptingCall(nextCall(options), { diff --git a/src/types/DaprClientOptions.ts b/src/types/DaprClientOptions.ts index db234d47..4890c68e 100644 --- a/src/types/DaprClientOptions.ts +++ b/src/types/DaprClientOptions.ts @@ -14,7 +14,7 @@ limitations under the License. import CommunicationProtocolEnum from "../enum/CommunicationProtocol.enum"; import { ActorRuntimeOptions } from "./actors/ActorRuntimeOptions"; import { LoggerOptions } from "./logger/LoggerOptions"; -import {Endpoint} from "../utils/Client.util"; +import { Endpoint } from "../utils/Client.util"; export type DaprClientOptions = { /** diff --git a/src/utils/Client.util.ts b/src/utils/Client.util.ts index ac9156ac..a1d1d3e3 100644 --- a/src/utils/Client.util.ts +++ b/src/utils/Client.util.ts @@ -260,277 +260,277 @@ function getType(o: any) { * @returns DaprClientOptions */ export function getClientOptions( - clientOptions: Partial | undefined, - defaultCommunicationProtocol: CommunicationProtocolEnum, - defaultLoggerOptions: LoggerOptions | undefined, + clientOptions: Partial | undefined, + defaultCommunicationProtocol: CommunicationProtocolEnum, + defaultLoggerOptions: LoggerOptions | undefined, ): DaprClientOptions { - const clientCommunicationProtocol = clientOptions?.communicationProtocol ?? defaultCommunicationProtocol; - - // We decide the host/port/endpoint here - let host = Settings.getDefaultHost(); - let port = Settings.getDefaultPort(clientCommunicationProtocol); - let uri = `${host}:${port}`; - - let endpoint: Endpoint; - - if (clientOptions?.daprHost || clientOptions?.daprPort) { - host = clientOptions?.daprHost ?? host; - port = clientOptions?.daprPort ?? port; - uri = `${host}:${port}`; - } else if (clientCommunicationProtocol == CommunicationProtocolEnum.HTTP && Settings.getDefaultHttpEndpoint() != "") { - uri = Settings.getDefaultHttpEndpoint(); - } else if (clientCommunicationProtocol == CommunicationProtocolEnum.GRPC && Settings.getDefaultGrpcEndpoint() != "") { - uri = Settings.getDefaultGrpcEndpoint(); - } - + const clientCommunicationProtocol = clientOptions?.communicationProtocol ?? defaultCommunicationProtocol; + + // We decide the host/port/endpoint here + let host = Settings.getDefaultHost(); + let port = Settings.getDefaultPort(clientCommunicationProtocol); + let uri = `${host}:${port}`; + + let endpoint: Endpoint; + + if (clientOptions?.daprHost || clientOptions?.daprPort) { + host = clientOptions?.daprHost ?? host; + port = clientOptions?.daprPort ?? port; + uri = `${host}:${port}`; + } else if (clientCommunicationProtocol == CommunicationProtocolEnum.HTTP && Settings.getDefaultHttpEndpoint() != "") { + uri = Settings.getDefaultHttpEndpoint(); + } else if (clientCommunicationProtocol == CommunicationProtocolEnum.GRPC && Settings.getDefaultGrpcEndpoint() != "") { + uri = Settings.getDefaultGrpcEndpoint(); + } - if (clientCommunicationProtocol == CommunicationProtocolEnum.HTTP) { - endpoint = new HttpEndpoint(uri); - } else { - endpoint = new GrpcEndpoint(uri); - } + if (clientCommunicationProtocol == CommunicationProtocolEnum.HTTP) { + endpoint = new HttpEndpoint(uri); + } else { + endpoint = new GrpcEndpoint(uri); + } - return { - daprHost: endpoint.hostname, - daprPort: endpoint.port, - daprEndpoint: endpoint, - communicationProtocol: clientCommunicationProtocol, - isKeepAlive: clientOptions?.isKeepAlive, - logger: clientOptions?.logger ?? defaultLoggerOptions, - actor: clientOptions?.actor, - daprApiToken: clientOptions?.daprApiToken ?? Settings.getDefaultApiToken(), - maxBodySizeMb: clientOptions?.maxBodySizeMb, - }; + return { + daprHost: endpoint.hostname, + daprPort: endpoint.port, + daprEndpoint: endpoint, + communicationProtocol: clientCommunicationProtocol, + isKeepAlive: clientOptions?.isKeepAlive, + logger: clientOptions?.logger ?? defaultLoggerOptions, + actor: clientOptions?.actor, + daprApiToken: clientOptions?.daprApiToken, + maxBodySizeMb: clientOptions?.maxBodySizeMb, + }; } class URIParseConfig { - static readonly DEFAULT_SCHEME_GRPC = "dns"; - static readonly DEFAULT_SCHEME_HTTP = "http"; - static readonly DEFAULT_HOSTNAME = "localhost"; - static readonly DEFAULT_PORT = 443; - static readonly DEFAULT_AUTHORITY = ""; - static readonly ACCEPTED_SCHEMES_GRPC = ["dns", "unix", "unix-abstract", "vsock", "http", "https", "grpc", "grpcs"]; + static readonly DEFAULT_SCHEME_GRPC = "dns"; + static readonly DEFAULT_SCHEME_HTTP = "http"; + static readonly DEFAULT_HOSTNAME = "localhost"; + static readonly DEFAULT_PORT = 443; + static readonly DEFAULT_AUTHORITY = ""; + static readonly ACCEPTED_SCHEMES_GRPC = ["dns", "unix", "unix-abstract", "vsock", "http", "https", "grpc", "grpcs"]; } export abstract class Endpoint { - protected _scheme = ""; - protected _hostname = ""; - protected _port = 0; - protected _tls = false; - protected _authority = ""; - protected _url: string; - protected _endpoint = ""; - protected _parsedUrl!: URL; - - protected constructor(url: string) { - this._url = url; - } + protected _scheme = ""; + protected _hostname = ""; + protected _port = 0; + protected _tls = false; + protected _authority = ""; + protected _url: string; + protected _endpoint = ""; + protected _parsedUrl!: URL; + + protected constructor(url: string) { + this._url = url; + } - get tls(): boolean { - return this._tls; - } + get tls(): boolean { + return this._tls; + } - get hostname(): string { - return this._hostname; - } + get hostname(): string { + return this._hostname; + } - get scheme(): string { - return this._scheme; - } + get scheme(): string { + return this._scheme; + } - get port(): string { - return this._port === 0 ? '' : this._port.toString(); - } + get port(): string { + return this._port === 0 ? "" : this._port.toString(); + } - get endpoint(): string { - return this._endpoint; - } + get endpoint(): string { + return this._endpoint; + } } -export class HttpEndpoint extends Endpoint{ - constructor(url: string) { - super(url); - this._parsedUrl = new URL(this.preprocessUri(url)); - - try { - this._scheme = this._parsedUrl.protocol.replace(":", ""); - this._hostname = this._parsedUrl.hostname.replace("[", ""); - this._hostname = this._hostname.replace("]", ""); - this._port = parseInt(this._parsedUrl.port) || (this._scheme == "https" ? 443 : 80); - this._tls = this._scheme == "https"; - this._endpoint = this._scheme + "://" + this._hostname + ":" + this._port.toString(); - } catch (error) { - throw new Error(`Invalid address: ${url}`); - } +export class HttpEndpoint extends Endpoint { + constructor(url: string) { + super(url); + this._parsedUrl = new URL(this.preprocessUri(url)); + + try { + this._scheme = this._parsedUrl.protocol.replace(":", ""); + this._hostname = this._parsedUrl.hostname.replace("[", ""); + this._hostname = this._hostname.replace("]", ""); + this._port = parseInt(this._parsedUrl.port) || (this._scheme == "https" ? 443 : 80); + this._tls = this._scheme == "https"; + this._endpoint = this._scheme + "://" + this._hostname + ":" + this._port.toString(); + } catch (error) { + throw new Error(`Invalid address: ${url}`); } + } - // We need to add a default scheme and hostname to the url - // if they are not specified so that the URL class can parse it - private preprocessUri(url: string) { - if (url.startsWith(":")) { - url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; - } - if (!url.includes("://")) { - url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + url; - } - return url; + // We need to add a default scheme and hostname to the url + // if they are not specified so that the URL class can parse it + private preprocessUri(url: string) { + if (url.startsWith(":")) { + url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; + } + if (!url.includes("://")) { + url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + url; } + return url; + } } export class GrpcEndpoint extends Endpoint { + constructor(url: string) { + super(url); + this._authority = URIParseConfig.DEFAULT_AUTHORITY; + + this._parsedUrl = new URL(this.preprocessUri(url)); + this.validatePathAndQuery(); + + this.setTls(); + this.setHostname(); + this.setScheme(); + this.setPort(); + this.setEndpoint(); + } - constructor(url: string) { - super(url); - this._authority = URIParseConfig.DEFAULT_AUTHORITY; - - this._parsedUrl = new URL(this.preprocessUri(url)); - this.validatePathAndQuery(); - - this.setTls(); - this.setHostname(); - this.setScheme(); - this.setPort(); - this.setEndpoint(); - } - - private preprocessUri(url: string): string { - let urlList = url.split(":"); - - if (urlList.length === 3 && !url.includes("://")) { - // A URI like dns:mydomain:5000 or vsock:mycid:5000 was used - url = url.replace(":", "://"); - } else if (urlList.length >= 2 && !url.includes("://") && URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(urlList[0])) { - // A URI like dns:mydomain was used - url = url.replace(":", "://"); + private preprocessUri(url: string): string { + let urlList = url.split(":"); + + if (urlList.length === 3 && !url.includes("://")) { + // A URI like dns:mydomain:5000 or vsock:mycid:5000 was used + url = url.replace(":", "://"); + } else if ( + urlList.length >= 2 && + !url.includes("://") && + URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(urlList[0]) + ) { + // A URI like dns:mydomain was used + url = url.replace(":", "://"); + } else { + urlList = url.split("://"); + if (urlList.length === 1) { + // If a scheme was not explicitly specified in the URL + // we need to add a default scheme, + // because of how URL works in JavaScript + + // We also need to check if the provided uri is not of the form :5000 + // if it is, we need to add a default hostname, because the URL class can't parse it + if (url[0] === ":") { + url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${URIParseConfig.DEFAULT_HOSTNAME}${url}`; } else { - urlList = url.split("://"); - if (urlList.length === 1) { - // If a scheme was not explicitly specified in the URL - // we need to add a default scheme, - // because of how URL works in JavaScript - - // We also need to check if the provided uri is not of the form :5000 - // if it is, we need to add a default hostname, because the URL class can't parse it - if (url[0] === ':') { - url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${URIParseConfig.DEFAULT_HOSTNAME}${url}`; - } else { - url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${url}`; - } - - } else { - // If a scheme was explicitly specified in the URL - // we need to make sure it is a valid scheme - const scheme = urlList[0]; - if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { - throw new Error(`Invalid scheme '${scheme}' in URL '${url}'`); - } - - // We should do a special check if the scheme is dns, and it uses - // an authority in the format of dns:[//authority/]host[:port] - if (scheme.toLowerCase() === "dns") { - // A URI like dns://authority/mydomain was used - urlList = url.split("/"); - if (urlList.length < 4) { - throw new Error(`Invalid dns authority '${urlList[2]}' in URL '${url}'`); - } - this._authority = urlList[2]; - url = `dns://${urlList[3]}`; - } - } + url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${url}`; } - return url; - } - - private validatePathAndQuery(): void { - if (this._parsedUrl.pathname && this._parsedUrl.pathname !== '/') { - throw new Error(`Paths are not supported for gRPC endpoints: '${this._parsedUrl.pathname}'`); - } - - const params = new URLSearchParams(this._parsedUrl.search); - if (params.has('tls') && (this._parsedUrl.protocol === 'http:' || this._parsedUrl.protocol === 'https:')) { - throw new Error(`The tls query parameter is not supported for http(s) endpoints: '${this._parsedUrl.search}'`); + } else { + // If a scheme was explicitly specified in the URL + // we need to make sure it is a valid scheme + const scheme = urlList[0]; + if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { + throw new Error(`Invalid scheme '${scheme}' in URL '${url}'`); } - params.delete('tls'); - if (Array.from(params.keys()).length > 0) { - throw new Error(`Query parameters are not supported for gRPC endpoints: '${this._parsedUrl.search}'`); + // We should do a special check if the scheme is dns, and it uses + // an authority in the format of dns:[//authority/]host[:port] + if (scheme.toLowerCase() === "dns") { + // A URI like dns://authority/mydomain was used + urlList = url.split("/"); + if (urlList.length < 4) { + throw new Error(`Invalid dns authority '${urlList[2]}' in URL '${url}'`); + } + this._authority = urlList[2]; + url = `dns://${urlList[3]}`; } } + } + return url; + } - private setTls(): void { - const params = new URLSearchParams(this._parsedUrl.search); - const tlsStr = params.get('tls') || ""; - this._tls = tlsStr.toLowerCase() === 'true'; + private validatePathAndQuery(): void { + if (this._parsedUrl.pathname && this._parsedUrl.pathname !== "/") { + throw new Error(`Paths are not supported for gRPC endpoints: '${this._parsedUrl.pathname}'`); + } - if (this._parsedUrl.protocol == "https:"){ - this._tls = true; - } + const params = new URLSearchParams(this._parsedUrl.search); + if (params.has("tls") && (this._parsedUrl.protocol === "http:" || this._parsedUrl.protocol === "https:")) { + throw new Error(`The tls query parameter is not supported for http(s) endpoints: '${this._parsedUrl.search}'`); } - private setHostname(): void { - if (!this._parsedUrl.hostname) { - this._hostname = URIParseConfig.DEFAULT_HOSTNAME; - return; - } + params.delete("tls"); + if (Array.from(params.keys()).length > 0) { + throw new Error(`Query parameters are not supported for gRPC endpoints: '${this._parsedUrl.search}'`); + } + } + private setTls(): void { + const params = new URLSearchParams(this._parsedUrl.search); + const tlsStr = params.get("tls") || ""; + this._tls = tlsStr.toLowerCase() === "true"; - this._hostname = this._parsedUrl.hostname; + if (this._parsedUrl.protocol == "https:") { + this._tls = true; } + } - private setScheme(): void { - if (!this._parsedUrl.protocol) { - this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; - return; - } + private setHostname(): void { + if (!this._parsedUrl.hostname) { + this._hostname = URIParseConfig.DEFAULT_HOSTNAME; + return; + } - const scheme = this._parsedUrl.protocol.slice(0, -1); // Remove trailing ':' - if (scheme === 'http' || scheme === 'https') { - this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; - console.warn("http and https schemes are deprecated, use grpc or grpcs instead"); - return; - } + this._hostname = this._parsedUrl.hostname; + } - if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { - throw new Error(`Invalid scheme '${scheme}' in URL '${this._url}'`); - } + private setScheme(): void { + if (!this._parsedUrl.protocol) { + this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; + return; + } - this._scheme = scheme; + const scheme = this._parsedUrl.protocol.slice(0, -1); // Remove trailing ':' + if (scheme === "http" || scheme === "https") { + this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; + console.warn("http and https schemes are deprecated, use grpc or grpcs instead"); + return; } - private setPort(): void { - if (this._scheme === 'unix' || this._scheme === 'unix-abstract') { - this._port = 0; - return; - } + if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { + throw new Error(`Invalid scheme '${scheme}' in URL '${this._url}'`); + } - this._port = this._parsedUrl.port ? parseInt(this._parsedUrl.port) : URIParseConfig.DEFAULT_PORT; + this._scheme = scheme; + } + + private setPort(): void { + if (this._scheme === "unix" || this._scheme === "unix-abstract") { + this._port = 0; + return; } - private setEndpoint(): void { - const port = this._port ? `:${this.port}` : ""; + this._port = this._parsedUrl.port ? parseInt(this._parsedUrl.port) : URIParseConfig.DEFAULT_PORT; + } - if (this._scheme === "unix") { - const separator = this._url.startsWith("unix://") ? "://" : ":"; - this._endpoint = `${this._scheme}${separator}${this._hostname}`; - return; - } + private setEndpoint(): void { + const port = this._port ? `:${this.port}` : ""; - if (this._scheme === "vsock") { - this._endpoint = `${this._scheme}:${this._hostname}:${this.port}`; - return; - } + if (this._scheme === "unix") { + const separator = this._url.startsWith("unix://") ? "://" : ":"; + this._endpoint = `${this._scheme}${separator}${this._hostname}`; + return; + } - if (this._scheme === "unix-abstract") { - this._endpoint = `${this._scheme}:${this._hostname}${port}`; - return; - } + if (this._scheme === "vsock") { + this._endpoint = `${this._scheme}:${this._hostname}:${this.port}`; + return; + } - if (this._scheme === "dns") { - const authority = this._authority ? `//${this._authority}/` : ""; - this._endpoint = `${this._scheme}:${authority}${this._hostname}${port}`; - return; - } + if (this._scheme === "unix-abstract") { + this._endpoint = `${this._scheme}:${this._hostname}${port}`; + return; + } - this._endpoint = `${this._scheme}:${this._hostname}${port}`; + if (this._scheme === "dns") { + const authority = this._authority ? `//${this._authority}/` : ""; + this._endpoint = `${this._scheme}:${authority}${this._hostname}${port}`; + return; } -} \ No newline at end of file + + this._endpoint = `${this._scheme}:${this._hostname}${port}`; + } +} diff --git a/test/unit/utils/Client.util.test.ts b/test/unit/utils/Client.util.test.ts index f15b26e9..3aa27d84 100644 --- a/test/unit/utils/Client.util.test.ts +++ b/test/unit/utils/Client.util.test.ts @@ -19,7 +19,8 @@ import { getBulkPublishEntries, getBulkPublishResponse, getClientOptions, - createHTTPQueryParam, GrpcEndpoint, + createHTTPQueryParam, + GrpcEndpoint, } from "../../../src/utils/Client.util"; import { Map } from "google-protobuf"; import { PubSubBulkPublishEntry } from "../../../src/types/pubsub/PubSubBulkPublishEntry.type"; @@ -418,7 +419,7 @@ describe("Client.util", () => { scheme: "", host: "localhost", port: 5000, - endpoint: "dns:localhost:5000" + endpoint: "dns:localhost:5000", }, { url: ":5000?tls=false", @@ -427,7 +428,7 @@ describe("Client.util", () => { scheme: "", host: "localhost", port: 5000, - endpoint: "dns:localhost:5000" + endpoint: "dns:localhost:5000", }, { url: ":5000?tls=true", @@ -436,7 +437,7 @@ describe("Client.util", () => { scheme: "", host: "localhost", port: 5000, - endpoint: "dns:localhost:5000" + endpoint: "dns:localhost:5000", }, // Host only { @@ -446,7 +447,7 @@ describe("Client.util", () => { scheme: "", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, { url: "myhost?tls=false", @@ -455,7 +456,7 @@ describe("Client.util", () => { scheme: "", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, { url: "myhost?tls=true", @@ -464,7 +465,7 @@ describe("Client.util", () => { scheme: "", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, // Host and port { @@ -474,7 +475,7 @@ describe("Client.util", () => { scheme: "", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, { url: "myhost:443?tls=false", @@ -483,7 +484,7 @@ describe("Client.util", () => { scheme: "", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, { url: "myhost:443?tls=true", @@ -492,7 +493,7 @@ describe("Client.util", () => { scheme: "", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, // Scheme, host and port { @@ -502,10 +503,10 @@ describe("Client.util", () => { scheme: "", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, - {url: "http://myhost?tls=false", error: true}, - {url: "http://myhost?tls=true", error: true}, + { url: "http://myhost?tls=false", error: true }, + { url: "http://myhost?tls=true", error: true }, { url: "http://myhost:443", error: false, @@ -513,10 +514,10 @@ describe("Client.util", () => { scheme: "", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, - {url: "http://myhost:443?tls=false", error: true}, - {url: "http://myhost:443?tls=true", error: true}, + { url: "http://myhost:443?tls=false", error: true }, + { url: "http://myhost:443?tls=true", error: true }, { url: "http://myhost:5000", error: false, @@ -524,10 +525,10 @@ describe("Client.util", () => { scheme: "", host: "myhost", port: 5000, - endpoint: "dns:myhost:5000" + endpoint: "dns:myhost:5000", }, - {url: "http://myhost:5000?tls=false", error: true}, - {url: "http://myhost:5000?tls=true", error: true}, + { url: "http://myhost:5000?tls=false", error: true }, + { url: "http://myhost:5000?tls=true", error: true }, { url: "https://myhost:443", error: false, @@ -535,10 +536,10 @@ describe("Client.util", () => { scheme: "", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, - {url: "https://myhost:443?tls=false", error: true}, - {url: "https://myhost:443?tls=true", error: true}, + { url: "https://myhost:443?tls=false", error: true }, + { url: "https://myhost:443?tls=true", error: true }, // Scheme = dns { url: "dns:myhost", @@ -547,7 +548,7 @@ describe("Client.util", () => { scheme: "dns", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, { url: "dns:myhost?tls=false", @@ -556,7 +557,7 @@ describe("Client.util", () => { scheme: "dns", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, { url: "dns:myhost?tls=true", @@ -565,7 +566,7 @@ describe("Client.util", () => { scheme: "dns", host: "myhost", port: 443, - endpoint: "dns:myhost:443" + endpoint: "dns:myhost:443", }, // Scheme = dns with authority { @@ -575,7 +576,7 @@ describe("Client.util", () => { scheme: "dns", host: "myhost", port: 443, - endpoint: "dns://myauthority:53/myhost:443" + endpoint: "dns://myauthority:53/myhost:443", }, { url: "dns://myauthority:53/myhost?tls=false", @@ -584,7 +585,7 @@ describe("Client.util", () => { scheme: "dns", host: "myhost", port: 443, - endpoint: "dns://myauthority:53/myhost:443" + endpoint: "dns://myauthority:53/myhost:443", }, { url: "dns://myauthority:53/myhost?tls=true", @@ -593,9 +594,9 @@ describe("Client.util", () => { scheme: "dns", host: "myhost", port: 443, - endpoint: "dns://myauthority:53/myhost:443" + endpoint: "dns://myauthority:53/myhost:443", }, - {url: "dns://myhost", error: true}, + { url: "dns://myhost", error: true }, // Unix sockets { url: "unix:my.sock", @@ -604,7 +605,7 @@ describe("Client.util", () => { scheme: "unix", host: "my.sock", port: "", - endpoint: "unix:my.sock" + endpoint: "unix:my.sock", }, { url: "unix:my.sock?tls=true", @@ -613,7 +614,7 @@ describe("Client.util", () => { scheme: "unix", host: "my.sock", port: "", - endpoint: "unix:my.sock" + endpoint: "unix:my.sock", }, // Unix sockets with absolute path { @@ -623,7 +624,7 @@ describe("Client.util", () => { scheme: "unix", host: "my.sock", port: "", - endpoint: "unix://my.sock" + endpoint: "unix://my.sock", }, { url: "unix://my.sock?tls=true", @@ -632,7 +633,7 @@ describe("Client.util", () => { scheme: "unix", host: "my.sock", port: "", - endpoint: "unix://my.sock" + endpoint: "unix://my.sock", }, // Unix abstract sockets { @@ -642,7 +643,7 @@ describe("Client.util", () => { scheme: "unix", host: "my.sock", port: "", - endpoint: "unix-abstract:my.sock" + endpoint: "unix-abstract:my.sock", }, { url: "unix-abstract:my.sock?tls=true", @@ -651,10 +652,10 @@ describe("Client.util", () => { scheme: "unix", host: "my.sock", port: "", - endpoint: "unix-abstract:my.sock" + endpoint: "unix-abstract:my.sock", }, // Vsock -// Vsock + // Vsock { url: "vsock:mycid", error: false, @@ -662,7 +663,7 @@ describe("Client.util", () => { scheme: "vsock", host: "mycid", port: "443", - endpoint: "vsock:mycid:443" + endpoint: "vsock:mycid:443", }, { url: "vsock:mycid:5000", @@ -671,7 +672,7 @@ describe("Client.util", () => { scheme: "vsock", host: "mycid", port: 5000, - endpoint: "vsock:mycid:5000" + endpoint: "vsock:mycid:5000", }, { url: "vsock:mycid:5000?tls=true", @@ -680,7 +681,7 @@ describe("Client.util", () => { scheme: "vsock", host: "mycid", port: 5000, - endpoint: "vsock:mycid:5000" + endpoint: "vsock:mycid:5000", }, // IPv6 addresses @@ -691,7 +692,7 @@ describe("Client.util", () => { scheme: "", host: "[2001:db8:1f70:0:999:de8:7648:6e8]", port: 443, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443" + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", }, { url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]", @@ -700,7 +701,7 @@ describe("Client.util", () => { scheme: "", host: "[2001:db8:1f70:0:999:de8:7648:6e8]", port: 443, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443" + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", }, { url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", @@ -709,11 +710,11 @@ describe("Client.util", () => { scheme: "", host: "[2001:db8:1f70:0:999:de8:7648:6e8]", port: 5000, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000" + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", }, { url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000?abc=[]", - error: true + error: true, }, { url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]", @@ -722,7 +723,7 @@ describe("Client.util", () => { scheme: "", host: "[2001:db8:1f70:0:999:de8:7648:6e8]", port: 443, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443" + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", }, { url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]:5000", @@ -731,19 +732,18 @@ describe("Client.util", () => { scheme: "", host: "[2001:db8:1f70:0:999:de8:7648:6e8]", port: 5000, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000" + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", }, // Invalid addresses (with path and queries) - {url: "host:5000/v1/dapr", error: true}, - {url: "host:5000/?a=1", error: true}, + { url: "host:5000/v1/dapr", error: true }, + { url: "host:5000/?a=1", error: true }, // Invalid scheme - {url: "inv-scheme://myhost", error: true}, - {url: "inv-scheme:myhost:5000", error: true} + { url: "inv-scheme://myhost", error: true }, + { url: "inv-scheme:myhost:5000", error: true }, ]; - testCases.forEach((testCase) => { test(`Testing URL: ${testCase.url}`, () => { if (testCase.error) { From eb02a8c60b172d11ba9410b3cb264d4dba7413d7 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Wed, 25 Oct 2023 13:30:27 +0100 Subject: [PATCH 06/22] Fixes Signed-off-by: Elena Kolevska --- src/implementation/Client/DaprClient.ts | 5 ----- src/implementation/Client/GRPCClient/GRPCClient.ts | 11 ++++++++++- src/implementation/Client/HTTPClient/HTTPClient.ts | 11 +++++++++++ src/types/DaprClientOptions.ts | 2 +- src/utils/Client.util.ts | 10 +++++++++- 5 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/implementation/Client/DaprClient.ts b/src/implementation/Client/DaprClient.ts index 8954ff36..7d5aea0c 100644 --- a/src/implementation/Client/DaprClient.ts +++ b/src/implementation/Client/DaprClient.ts @@ -90,11 +90,6 @@ export default class DaprClient { this.options = getClientOptions(options, Settings.getDefaultCommunicationProtocol(), undefined); this.logger = new Logger("DaprClient", "DaprClient", this.options.logger); - // Validation on port - if (this.options.daprPort && !/^[0-9]+$/.test(this.options.daprPort)) { - throw new Error("DAPR_INCORRECT_SIDECAR_PORT"); - } - // Builder switch (options.communicationProtocol) { case CommunicationProtocolEnum.GRPC: { diff --git a/src/implementation/Client/GRPCClient/GRPCClient.ts b/src/implementation/Client/GRPCClient/GRPCClient.ts index 9454fa81..4fa0f1fd 100644 --- a/src/implementation/Client/GRPCClient/GRPCClient.ts +++ b/src/implementation/Client/GRPCClient/GRPCClient.ts @@ -20,6 +20,8 @@ import { Logger } from "../../../logger/Logger"; import GRPCClientSidecar from "./sidecar"; import DaprClient from "../DaprClient"; import { SDK_VERSION } from "../../../version"; +import CommunicationProtocolEnum from "../../../enum/CommunicationProtocol.enum"; +import {GrpcEndpoint, HttpEndpoint} from "../../../utils/Client.util"; export default class GRPCClient implements IClient { readonly options: DaprClientOptions; @@ -32,6 +34,13 @@ export default class GRPCClient implements IClient { constructor(options: DaprClientOptions) { this.options = options; + + // If the instantiation was done directly, through GRPCClient(), and not through DaprClient() + // we need to set the endpoint object + if (this.options.daprEndpoint === undefined) { + this.options.daprEndpoint = new GrpcEndpoint(`${this.options.daprHost}:${this.options.daprPort}`); + } + this.clientCredentials = this.generateCredentials(); this.grpcClientOptions = this.generateChannelOptions(); @@ -64,7 +73,7 @@ export default class GRPCClient implements IClient { } private generateCredentials(): grpc.ChannelCredentials { - if (this.options.daprEndpoint.tls) { + if (this.options.daprEndpoint?.tls) { return grpc.ChannelCredentials.createSsl(); } return grpc.ChannelCredentials.createInsecure(); diff --git a/src/implementation/Client/HTTPClient/HTTPClient.ts b/src/implementation/Client/HTTPClient/HTTPClient.ts index bca07aaf..db7be483 100644 --- a/src/implementation/Client/HTTPClient/HTTPClient.ts +++ b/src/implementation/Client/HTTPClient/HTTPClient.ts @@ -23,6 +23,8 @@ import { Logger } from "../../../logger/Logger"; import HTTPClientSidecar from "./sidecar"; import { SDK_VERSION } from "../../../version"; import * as SerializerUtil from "../../../utils/Serializer.util"; +import CommunicationProtocolEnum from "../../../enum/CommunicationProtocol.enum"; +import {GrpcEndpoint, HttpEndpoint} from "../../../utils/Client.util"; export default class HTTPClient implements IClient { readonly options: DaprClientOptions; @@ -37,6 +39,15 @@ export default class HTTPClient implements IClient { constructor(options: DaprClientOptions) { this.options = options; + + + + // If the instantiation was done directly, through HTTPClient(), and not through DaprClient() + // we need to set the endpoint object + if (this.options.daprEndpoint === undefined) { + this.options.daprEndpoint = new HttpEndpoint(`${this.options.daprHost}:${this.options.daprPort}`); + } + this.logger = new Logger("HTTPClient", "HTTPClient", this.options.logger); this.isInitialized = false; this.clientUrl = `${this.options.daprEndpoint.endpoint}/v1.0`; diff --git a/src/types/DaprClientOptions.ts b/src/types/DaprClientOptions.ts index 4890c68e..bbd10c82 100644 --- a/src/types/DaprClientOptions.ts +++ b/src/types/DaprClientOptions.ts @@ -32,7 +32,7 @@ export type DaprClientOptions = { /** * gRPC endpoint of the Dapr sidecar. */ - daprEndpoint: Endpoint; + daprEndpoint?: Endpoint; /** * Protocol to use to communicate with the Dapr sidecar. diff --git a/src/utils/Client.util.ts b/src/utils/Client.util.ts index a1d1d3e3..6c7bbf2e 100644 --- a/src/utils/Client.util.ts +++ b/src/utils/Client.util.ts @@ -277,6 +277,14 @@ export function getClientOptions( host = clientOptions?.daprHost ?? host; port = clientOptions?.daprPort ?? port; uri = `${host}:${port}`; + + // Legacy validation on port + // URI validation is done later, when we instantiate the HttpEndpoint or GrpcEndpoint object + // but we need to keep this additional check for backward compatibility + // TODO: Remove this validation in the next major version + if (port && !/^[0-9]+$/.test(port)) { + throw new Error("DAPR_INCORRECT_SIDECAR_PORT"); + } } else if (clientCommunicationProtocol == CommunicationProtocolEnum.HTTP && Settings.getDefaultHttpEndpoint() != "") { uri = Settings.getDefaultHttpEndpoint(); } else if (clientCommunicationProtocol == CommunicationProtocolEnum.GRPC && Settings.getDefaultGrpcEndpoint() != "") { @@ -349,9 +357,9 @@ export abstract class Endpoint { export class HttpEndpoint extends Endpoint { constructor(url: string) { super(url); - this._parsedUrl = new URL(this.preprocessUri(url)); try { + this._parsedUrl = new URL(this.preprocessUri(url)); this._scheme = this._parsedUrl.protocol.replace(":", ""); this._hostname = this._parsedUrl.hostname.replace("[", ""); this._hostname = this._hostname.replace("]", ""); From cfd75ae37a4bcda0c2187e4a3349615c3a22a870 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Wed, 25 Oct 2023 23:39:12 +0100 Subject: [PATCH 07/22] Reorganises code Signed-off-by: Elena Kolevska --- src/actors/runtime/AbstractActor.ts | 8 ++-- src/implementation/Client/DaprClient.ts | 11 ++++- .../Client/GRPCClient/GRPCClient.ts | 48 ++++++++++++++----- .../Client/HTTPClient/HTTPClient.ts | 48 +++++++++++++------ src/implementation/Server/DaprServer.ts | 2 +- src/types/DaprClientOptions.ts | 6 --- src/utils/Client.util.ts | 40 ++-------------- 7 files changed, 87 insertions(+), 76 deletions(-) diff --git a/src/actors/runtime/AbstractActor.ts b/src/actors/runtime/AbstractActor.ts index f42fbb98..a1ffcd39 100644 --- a/src/actors/runtime/AbstractActor.ts +++ b/src/actors/runtime/AbstractActor.ts @@ -54,10 +54,10 @@ export default abstract class AbstractActor { constructor(daprClient: DaprClient, id: ActorId) { this.daprClient = daprClient; this.actorClient = new ActorClient( - daprClient.options.daprHost, - daprClient.options.daprPort, - daprClient.options.communicationProtocol, - daprClient.options, + daprClient.daprClient.options.daprHost, + daprClient.daprClient.options.daprPort, + daprClient.daprClient.options.communicationProtocol, + daprClient.daprClient.options, ); this.logger = new Logger("Actors", "AbstractActor", daprClient.options.logger); this.id = id; diff --git a/src/implementation/Client/DaprClient.ts b/src/implementation/Client/DaprClient.ts index 7d5aea0c..68cc4cb8 100644 --- a/src/implementation/Client/DaprClient.ts +++ b/src/implementation/Client/DaprClient.ts @@ -67,7 +67,7 @@ import * as NodeJSUtils from "../../utils/NodeJS.util"; import { getClientOptions } from "../../utils/Client.util"; export default class DaprClient { - readonly options: DaprClientOptions; + readonly options: Partial; readonly daprClient: IClient; readonly actor: IClientActorBuilder; readonly binding: IClientBinding; @@ -90,6 +90,15 @@ export default class DaprClient { this.options = getClientOptions(options, Settings.getDefaultCommunicationProtocol(), undefined); this.logger = new Logger("DaprClient", "DaprClient", this.options.logger); + // Legacy validation on port + // URI validation is done later, when we instantiate the HttpEndpoint or GrpcEndpoint + // object in the HttpClient or GrpcClient constructor, but we need to + // keep this additional check for backward compatibility + // TODO: Remove this validation in the next major version + if (this.options?.daprPort && !/^[0-9]+$/.test(this.options?.daprPort)) { + throw new Error("DAPR_INCORRECT_SIDECAR_PORT"); + } + // Builder switch (options.communicationProtocol) { case CommunicationProtocolEnum.GRPC: { diff --git a/src/implementation/Client/GRPCClient/GRPCClient.ts b/src/implementation/Client/GRPCClient/GRPCClient.ts index 4fa0f1fd..98aaea4a 100644 --- a/src/implementation/Client/GRPCClient/GRPCClient.ts +++ b/src/implementation/Client/GRPCClient/GRPCClient.ts @@ -20,8 +20,8 @@ import { Logger } from "../../../logger/Logger"; import GRPCClientSidecar from "./sidecar"; import DaprClient from "../DaprClient"; import { SDK_VERSION } from "../../../version"; -import CommunicationProtocolEnum from "../../../enum/CommunicationProtocol.enum"; -import {GrpcEndpoint, HttpEndpoint} from "../../../utils/Client.util"; +import { GrpcEndpoint } from "../../../utils/Client.util"; +import communicationProtocolEnum from "../../../enum/CommunicationProtocol.enum"; export default class GRPCClient implements IClient { readonly options: DaprClientOptions; @@ -31,15 +31,21 @@ export default class GRPCClient implements IClient { private readonly clientCredentials: grpc.ChannelCredentials; private readonly logger: Logger; private readonly grpcClientOptions: Partial; - - constructor(options: DaprClientOptions) { - this.options = options; - - // If the instantiation was done directly, through GRPCClient(), and not through DaprClient() - // we need to set the endpoint object - if (this.options.daprEndpoint === undefined) { - this.options.daprEndpoint = new GrpcEndpoint(`${this.options.daprHost}:${this.options.daprPort}`); - } + private daprEndpoint: GrpcEndpoint; + + constructor(options: Partial) { + this.daprEndpoint = this.generateEndpoint(options); + + this.options = { + daprHost: options?.daprHost || this.daprEndpoint.hostname, + daprPort: options?.daprPort || this.daprEndpoint.port, + communicationProtocol: communicationProtocolEnum.GRPC, + isKeepAlive: options?.isKeepAlive, + logger: options?.logger, + actor: options?.actor, + daprApiToken: options?.daprApiToken, + maxBodySizeMb: options?.maxBodySizeMb, + }; this.clientCredentials = this.generateCredentials(); this.grpcClientOptions = this.generateChannelOptions(); @@ -49,7 +55,7 @@ export default class GRPCClient implements IClient { this.logger.info(`Opening connection to ${this.options.daprHost}:${this.options.daprPort}`); this.client = new GrpcDaprClient( - this.options.daprEndpoint.endpoint, + this.daprEndpoint.endpoint, this.getClientCredentials(), this.getGrpcClientOptions(), ); @@ -72,8 +78,24 @@ export default class GRPCClient implements IClient { return this.grpcClientOptions; } + private generateEndpoint(options: Partial): GrpcEndpoint { + let host = Settings.getDefaultHost(); + let port = Settings.getDefaultPort(communicationProtocolEnum.GRPC); + let uri = `${host}:${port}`; + + if (options?.daprHost || options?.daprPort) { + host = options?.daprHost ?? Settings.getDefaultHost(); + port = options?.daprPort ?? Settings.getDefaultPort(communicationProtocolEnum.GRPC); + uri = `${host}:${port}`; + } else if (Settings.getDefaultGrpcEndpoint() != "") { + uri = Settings.getDefaultGrpcEndpoint(); + } + + return new GrpcEndpoint(uri); + } + private generateCredentials(): grpc.ChannelCredentials { - if (this.options.daprEndpoint?.tls) { + if (this.daprEndpoint?.tls) { return grpc.ChannelCredentials.createSsl(); } return grpc.ChannelCredentials.createInsecure(); diff --git a/src/implementation/Client/HTTPClient/HTTPClient.ts b/src/implementation/Client/HTTPClient/HTTPClient.ts index db7be483..29157fdd 100644 --- a/src/implementation/Client/HTTPClient/HTTPClient.ts +++ b/src/implementation/Client/HTTPClient/HTTPClient.ts @@ -23,8 +23,8 @@ import { Logger } from "../../../logger/Logger"; import HTTPClientSidecar from "./sidecar"; import { SDK_VERSION } from "../../../version"; import * as SerializerUtil from "../../../utils/Serializer.util"; -import CommunicationProtocolEnum from "../../../enum/CommunicationProtocol.enum"; -import {GrpcEndpoint, HttpEndpoint} from "../../../utils/Client.util"; +import { HttpEndpoint } from "../../../utils/Client.util"; +import communicationProtocolEnum from "../../../enum/CommunicationProtocol.enum"; export default class HTTPClient implements IClient { readonly options: DaprClientOptions; @@ -36,21 +36,25 @@ export default class HTTPClient implements IClient { private static httpAgent: http.Agent; private static httpsAgent: https.Agent; - - constructor(options: DaprClientOptions) { - this.options = options; - - - - // If the instantiation was done directly, through HTTPClient(), and not through DaprClient() - // we need to set the endpoint object - if (this.options.daprEndpoint === undefined) { - this.options.daprEndpoint = new HttpEndpoint(`${this.options.daprHost}:${this.options.daprPort}`); - } + private daprEndpoint: HttpEndpoint; + + constructor(options: Partial) { + this.daprEndpoint = this.generateEndpoint(options); + + this.options = { + daprHost: options?.daprHost || this.daprEndpoint.hostname, + daprPort: options?.daprPort || this.daprEndpoint.port, + communicationProtocol: communicationProtocolEnum.HTTP, + isKeepAlive: options?.isKeepAlive, + logger: options?.logger, + actor: options?.actor, + daprApiToken: options?.daprApiToken, + maxBodySizeMb: options?.maxBodySizeMb, + }; this.logger = new Logger("HTTPClient", "HTTPClient", this.options.logger); this.isInitialized = false; - this.clientUrl = `${this.options.daprEndpoint.endpoint}/v1.0`; + this.clientUrl = `${this.daprEndpoint.endpoint}/v1.0`; if (!HTTPClient.client) { HTTPClient.client = fetch; @@ -70,6 +74,22 @@ export default class HTTPClient implements IClient { } } + private generateEndpoint(options: Partial): HttpEndpoint { + let host = Settings.getDefaultHost(); + let port = Settings.getDefaultPort(communicationProtocolEnum.HTTP); + let uri = `${host}:${port}`; + + if (options?.daprHost || options?.daprPort) { + host = options?.daprHost ?? Settings.getDefaultHost(); + port = options?.daprPort ?? Settings.getDefaultPort(communicationProtocolEnum.HTTP); + uri = `${host}:${port}`; + } else if (Settings.getDefaultGrpcEndpoint() != "") { + uri = Settings.getDefaultGrpcEndpoint(); + } + + return new HttpEndpoint(uri); + } + async getClient(requiresInitialization = true): Promise { // Ensure the sidecar has been started if (requiresInitialization && !this.isInitialized) { diff --git a/src/implementation/Server/DaprServer.ts b/src/implementation/Server/DaprServer.ts index 8a110cda..041dffba 100644 --- a/src/implementation/Server/DaprServer.ts +++ b/src/implementation/Server/DaprServer.ts @@ -69,7 +69,7 @@ export default class DaprServer { throw new Error("DAPR_INCORRECT_SERVER_PORT"); } - if (!/^[0-9]+$/.test(clientOptions.daprPort)) { + if (clientOptions?.daprPort && !/^[0-9]+$/.test(clientOptions?.daprPort)) { throw new Error("DAPR_INCORRECT_SIDECAR_PORT"); } diff --git a/src/types/DaprClientOptions.ts b/src/types/DaprClientOptions.ts index bbd10c82..53c5506d 100644 --- a/src/types/DaprClientOptions.ts +++ b/src/types/DaprClientOptions.ts @@ -14,7 +14,6 @@ limitations under the License. import CommunicationProtocolEnum from "../enum/CommunicationProtocol.enum"; import { ActorRuntimeOptions } from "./actors/ActorRuntimeOptions"; import { LoggerOptions } from "./logger/LoggerOptions"; -import { Endpoint } from "../utils/Client.util"; export type DaprClientOptions = { /** @@ -29,11 +28,6 @@ export type DaprClientOptions = { */ daprPort: string; - /** - * gRPC endpoint of the Dapr sidecar. - */ - daprEndpoint?: Endpoint; - /** * Protocol to use to communicate with the Dapr sidecar. * Default is HTTP. diff --git a/src/utils/Client.util.ts b/src/utils/Client.util.ts index 6c7bbf2e..4c43218d 100644 --- a/src/utils/Client.util.ts +++ b/src/utils/Client.util.ts @@ -26,7 +26,6 @@ import { PubSubBulkPublishMessage } from "../types/pubsub/PubSubBulkPublishMessa import { PubSubBulkPublishApiResponse } from "../types/pubsub/PubSubBulkPublishApiResponse.type"; import { DaprClientOptions } from "../types/DaprClientOptions"; import CommunicationProtocolEnum from "../enum/CommunicationProtocol.enum"; -import { Settings } from "./Settings.util"; import { LoggerOptions } from "../types/logger/LoggerOptions"; import { StateConsistencyEnum } from "../enum/StateConsistency.enum"; import { StateConcurrencyEnum } from "../enum/StateConcurrency.enum"; @@ -263,44 +262,11 @@ export function getClientOptions( clientOptions: Partial | undefined, defaultCommunicationProtocol: CommunicationProtocolEnum, defaultLoggerOptions: LoggerOptions | undefined, -): DaprClientOptions { +): Partial { const clientCommunicationProtocol = clientOptions?.communicationProtocol ?? defaultCommunicationProtocol; - - // We decide the host/port/endpoint here - let host = Settings.getDefaultHost(); - let port = Settings.getDefaultPort(clientCommunicationProtocol); - let uri = `${host}:${port}`; - - let endpoint: Endpoint; - - if (clientOptions?.daprHost || clientOptions?.daprPort) { - host = clientOptions?.daprHost ?? host; - port = clientOptions?.daprPort ?? port; - uri = `${host}:${port}`; - - // Legacy validation on port - // URI validation is done later, when we instantiate the HttpEndpoint or GrpcEndpoint object - // but we need to keep this additional check for backward compatibility - // TODO: Remove this validation in the next major version - if (port && !/^[0-9]+$/.test(port)) { - throw new Error("DAPR_INCORRECT_SIDECAR_PORT"); - } - } else if (clientCommunicationProtocol == CommunicationProtocolEnum.HTTP && Settings.getDefaultHttpEndpoint() != "") { - uri = Settings.getDefaultHttpEndpoint(); - } else if (clientCommunicationProtocol == CommunicationProtocolEnum.GRPC && Settings.getDefaultGrpcEndpoint() != "") { - uri = Settings.getDefaultGrpcEndpoint(); - } - - if (clientCommunicationProtocol == CommunicationProtocolEnum.HTTP) { - endpoint = new HttpEndpoint(uri); - } else { - endpoint = new GrpcEndpoint(uri); - } - return { - daprHost: endpoint.hostname, - daprPort: endpoint.port, - daprEndpoint: endpoint, + daprHost: clientOptions?.daprHost, + daprPort: clientOptions?.daprPort, communicationProtocol: clientCommunicationProtocol, isKeepAlive: clientOptions?.isKeepAlive, logger: clientOptions?.logger ?? defaultLoggerOptions, From a773c909d39ffd82ca84c45d28acd9da8ffc9271 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Thu, 26 Oct 2023 13:29:55 +0100 Subject: [PATCH 08/22] Fixes tests Signed-off-by: Elena Kolevska --- src/implementation/Client/DaprClient.ts | 4 + .../Client/HTTPClient/HTTPClient.ts | 4 +- src/implementation/Server/DaprServer.ts | 21 +- test/e2e/common/client.test.ts | 126 ---------- test/unit/utils/Client.util.test.ts | 237 +++++++++++++++++- 5 files changed, 245 insertions(+), 147 deletions(-) diff --git a/src/implementation/Client/DaprClient.ts b/src/implementation/Client/DaprClient.ts index 68cc4cb8..f2950f8f 100644 --- a/src/implementation/Client/DaprClient.ts +++ b/src/implementation/Client/DaprClient.ts @@ -103,6 +103,8 @@ export default class DaprClient { switch (options.communicationProtocol) { case CommunicationProtocolEnum.GRPC: { const client = new GRPCClient(this.options); + this.options.daprHost = client.options.daprHost; + this.options.daprPort = client.options.daprPort; this.daprClient = client; this.state = new GRPCClientState(client); @@ -124,6 +126,8 @@ export default class DaprClient { case CommunicationProtocolEnum.HTTP: default: { const client = new HTTPClient(this.options); + this.options.daprHost = client.options.daprHost; + this.options.daprPort = client.options.daprPort; this.daprClient = client; this.actor = new HTTPClientActor(client); // we use an abstractor here since we interface through a builder with the Actor Runtime diff --git a/src/implementation/Client/HTTPClient/HTTPClient.ts b/src/implementation/Client/HTTPClient/HTTPClient.ts index 29157fdd..f5cf415c 100644 --- a/src/implementation/Client/HTTPClient/HTTPClient.ts +++ b/src/implementation/Client/HTTPClient/HTTPClient.ts @@ -83,8 +83,8 @@ export default class HTTPClient implements IClient { host = options?.daprHost ?? Settings.getDefaultHost(); port = options?.daprPort ?? Settings.getDefaultPort(communicationProtocolEnum.HTTP); uri = `${host}:${port}`; - } else if (Settings.getDefaultGrpcEndpoint() != "") { - uri = Settings.getDefaultGrpcEndpoint(); + } else if (Settings.getDefaultHttpEndpoint() != "") { + uri = Settings.getDefaultHttpEndpoint(); } return new HttpEndpoint(uri); diff --git a/src/implementation/Server/DaprServer.ts b/src/implementation/Server/DaprServer.ts index 041dffba..14326a6f 100644 --- a/src/implementation/Server/DaprServer.ts +++ b/src/implementation/Server/DaprServer.ts @@ -48,31 +48,38 @@ export default class DaprServer { constructor(serverOptions: Partial = {}) { const communicationProtocol = serverOptions.communicationProtocol ?? Settings.getDefaultCommunicationProtocol(); const clientOptions = getClientOptions(serverOptions.clientOptions, communicationProtocol, serverOptions?.logger); + + // Legacy validation on port + // URI validation is done later, when we instantiate the HttpEndpoint or GrpcEndpoint + // object in the HttpClient or GrpcClient constructor, but we need to + // keep this additional check for backward compatibility + // TODO: Remove this validation in the next major version + if (clientOptions?.daprPort && !/^[0-9]+$/.test(clientOptions?.daprPort)) { + throw new Error("DAPR_INCORRECT_SIDECAR_PORT"); + } + + this.client = new DaprClient(clientOptions); + this.serverOptions = { serverHost: serverOptions.serverHost ?? Settings.getDefaultHost(), serverPort: serverOptions.serverPort ?? Settings.getDefaultAppPort(communicationProtocol), communicationProtocol: communicationProtocol, maxBodySizeMb: serverOptions.maxBodySizeMb, serverHttp: serverOptions.serverHttp, - clientOptions: clientOptions, + clientOptions: this.client.options, logger: serverOptions.logger, }; // Create a client to interface with the sidecar from the server side - this.client = new DaprClient(clientOptions); // If DAPR_SERVER_PORT was not set, we set it process.env.DAPR_SERVER_PORT = this.serverOptions.serverPort; - process.env.DAPR_CLIENT_PORT = clientOptions.daprPort; + process.env.DAPR_CLIENT_PORT = this.client.options.daprPort; // Validation on port if (!/^[0-9]+$/.test(this.serverOptions.serverPort)) { throw new Error("DAPR_INCORRECT_SERVER_PORT"); } - if (clientOptions?.daprPort && !/^[0-9]+$/.test(clientOptions?.daprPort)) { - throw new Error("DAPR_INCORRECT_SIDECAR_PORT"); - } - // Builder switch (serverOptions.communicationProtocol) { case CommunicationProtocolEnum.GRPC: { diff --git a/test/e2e/common/client.test.ts b/test/e2e/common/client.test.ts index e8b48d01..5f1bdb40 100644 --- a/test/e2e/common/client.test.ts +++ b/test/e2e/common/client.test.ts @@ -21,7 +21,6 @@ import { } from "../../../src"; import { sleep } from "../../../src/utils/NodeJS.util"; import { LockStatus } from "../../../src/types/lock/UnlockResponse"; -import { Settings } from "../../../src/utils/Settings.util"; const daprHost = "127.0.0.1"; const daprGrpcPort = "50000"; @@ -558,128 +557,3 @@ describe("common/client", () => { }); }); }); - -describe("http/client with environment variables", () => { - let client: DaprClient; - - // We need to start listening on some endpoints already - // this because Dapr is not dynamic and registers endpoints on boot - // we put a timeout of 10s since it takes around 4s for Dapr to boot up - - afterAll(async () => { - await client.stop(); - }); - - it("should give preference to host and port in constructor arguments over endpoint environment variables ", async () => { - process.env.DAPR_HTTP_ENDPOINT = "https://httpdomain.com"; - process.env.DAPR_GRPC_ENDPOINT = "https://grpcdomain.com"; - - client = new DaprClient({ - daprHost, - daprPort: daprHttpPort, - communicationProtocol: CommunicationProtocolEnum.HTTP, - isKeepAlive: false, - }); - - expect(client.options.daprHost).toEqual(daprHost); - expect(client.options.daprPort).toEqual(daprHttpPort); - - client = new DaprClient({ - daprHost, - daprPort: daprGrpcPort, - communicationProtocol: CommunicationProtocolEnum.GRPC, - isKeepAlive: false, - }); - - expect(client.options.daprHost).toEqual(daprHost); - expect(client.options.daprPort).toEqual(daprGrpcPort); - }); - - it("should give preference to port with no host in constructor arguments over environment variables ", async () => { - process.env.DAPR_HTTP_ENDPOINT = "https://httpdomain.com"; - process.env.DAPR_GRPC_ENDPOINT = "https://grpcdomain.com"; - - client = new DaprClient({ - daprPort: daprHttpPort, - communicationProtocol: CommunicationProtocolEnum.HTTP, - isKeepAlive: false, - }); - - expect(client.options.daprHost).toEqual(Settings.getDefaultHost()); - expect(client.options.daprPort).toEqual(daprHttpPort); - - client = new DaprClient({ - daprPort: daprGrpcPort, - communicationProtocol: CommunicationProtocolEnum.GRPC, - isKeepAlive: false, - }); - - expect(client.options.daprHost).toEqual(Settings.getDefaultHost()); - expect(client.options.daprPort).toEqual(daprGrpcPort); - }); - - it("should give preference to host with no port in constructor arguments over environment variables ", async () => { - process.env.DAPR_HTTP_ENDPOINT = "https://httpdomain.com"; - process.env.DAPR_GRPC_ENDPOINT = "https://grpcdomain.com"; - - client = new DaprClient({ - daprHost: daprHost, - communicationProtocol: CommunicationProtocolEnum.HTTP, - isKeepAlive: false, - }); - - expect(client.options.daprHost).toEqual(daprHost); - expect(client.options.daprPort).toEqual(Settings.getDefaultPort(CommunicationProtocolEnum.HTTP)); - - client = new DaprClient({ - daprHost: daprHost, - communicationProtocol: CommunicationProtocolEnum.GRPC, - isKeepAlive: false, - }); - - expect(client.options.daprHost).toEqual(daprHost); - expect(client.options.daprPort).toEqual(Settings.getDefaultPort(CommunicationProtocolEnum.GRPC)); - }); - - it("should use environment variable endpoint for HTTP", async () => { - process.env.DAPR_HTTP_ENDPOINT = "https://httpdomain.com"; - client = new DaprClient({ - communicationProtocol: CommunicationProtocolEnum.HTTP, - isKeepAlive: false, - }); - - expect(client.options.daprHost).toEqual("https://httpdomain.com"); - expect(client.options.daprPort).toEqual("443"); - }); - - it("should use environment variable endpoint for GRPC", async () => { - process.env.DAPR_GRPC_ENDPOINT = "https://grpcdomain.com"; - client = new DaprClient({ - communicationProtocol: CommunicationProtocolEnum.GRPC, - isKeepAlive: false, - }); - - expect(client.options.daprHost).toEqual("https://grpcdomain.com"); - expect(client.options.daprPort).toEqual("443"); - }); - - it("should use default host and port when no other parameters provided", async () => { - process.env.DAPR_HTTP_ENDPOINT = ""; - process.env.DAPR_GRPC_ENDPOINT = ""; - client = new DaprClient({ - communicationProtocol: CommunicationProtocolEnum.HTTP, - isKeepAlive: false, - }); - - expect(client.options.daprHost).toEqual(Settings.getDefaultHost()); - expect(client.options.daprPort).toEqual(Settings.getDefaultPort(CommunicationProtocolEnum.HTTP)); - - client = new DaprClient({ - communicationProtocol: CommunicationProtocolEnum.GRPC, - isKeepAlive: false, - }); - - expect(client.options.daprHost).toEqual(Settings.getDefaultHost()); - expect(client.options.daprPort).toEqual(Settings.getDefaultPort(CommunicationProtocolEnum.GRPC)); - }); -}); diff --git a/test/unit/utils/Client.util.test.ts b/test/unit/utils/Client.util.test.ts index 3aa27d84..7a0a3b57 100644 --- a/test/unit/utils/Client.util.test.ts +++ b/test/unit/utils/Client.util.test.ts @@ -26,6 +26,9 @@ import { Map } from "google-protobuf"; import { PubSubBulkPublishEntry } from "../../../src/types/pubsub/PubSubBulkPublishEntry.type"; import { PubSubBulkPublishApiResponse } from "../../../src/types/pubsub/PubSubBulkPublishApiResponse.type"; import { CommunicationProtocolEnum, DaprClientOptions, LogLevel } from "../../../src"; +import { HttpEndpoint } from "../../../build/utils/Client.util"; +import { DaprClient } from "../../../src"; +import { Settings } from "../../../src/utils/Settings.util"; describe("Client.util", () => { describe("addMetadataToMap", () => { @@ -341,10 +344,13 @@ describe("Client.util", () => { daprPort: "50001", communicationProtocol: CommunicationProtocolEnum.GRPC, }; + const options = getClientOptions(inOptions, CommunicationProtocolEnum.HTTP, { level: LogLevel.Error }); - const expectedOptions: Partial = inOptions; - expectedOptions.daprHost = "127.0.0.1"; - expectedOptions.logger = { level: LogLevel.Error }; + const expectedOptions: Partial = { + daprPort: inOptions.daprPort, + communicationProtocol: inOptions.communicationProtocol, + logger: { level: LogLevel.Error }, + }; expect(options).toEqual(expectedOptions); }); @@ -353,9 +359,9 @@ describe("Client.util", () => { communicationProtocol: CommunicationProtocolEnum.GRPC, }; const options = getClientOptions(inOptions, CommunicationProtocolEnum.HTTP, undefined); - const expectedOptions: Partial = inOptions; - expectedOptions.daprHost = "127.0.0.1"; - expectedOptions.daprPort = "50001"; + const expectedOptions: Partial = { + communicationProtocol: CommunicationProtocolEnum.GRPC, + }; expect(options).toEqual(expectedOptions); }); @@ -391,8 +397,6 @@ describe("Client.util", () => { it("returns correct Dapr Client Options when undefined options provided", () => { const options = getClientOptions(undefined, CommunicationProtocolEnum.GRPC, undefined); const expectedOptions: Partial = { - daprHost: "127.0.0.1", - daprPort: "50001", communicationProtocol: CommunicationProtocolEnum.GRPC, }; expect(options).toEqual(expectedOptions); @@ -401,15 +405,13 @@ describe("Client.util", () => { it("returns correct Dapr Client Options when undefined options provided and default HTTP communication", () => { const options = getClientOptions(undefined, CommunicationProtocolEnum.HTTP, undefined); const expectedOptions: Partial = { - daprHost: "127.0.0.1", - daprPort: "3500", communicationProtocol: CommunicationProtocolEnum.HTTP, }; expect(options).toEqual(expectedOptions); }); }); - describe("parseGRPCEndpoint", () => { + describe("parse GRPC Endpoint", () => { const testCases = [ // Port only { @@ -655,7 +657,6 @@ describe("Client.util", () => { endpoint: "unix-abstract:my.sock", }, // Vsock - // Vsock { url: "vsock:mycid", error: false, @@ -758,4 +759,216 @@ describe("Client.util", () => { }); }); }); + + describe("parse HTTP Endpoint", () => { + const testCases = [ + { + url: "myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 80, + endpoint: "http://myhost:80", + }, + { + url: "http://myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 80, + endpoint: "http://myhost:80", + }, + { + url: "http://myhost:443", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "http://myhost:443", + }, + { + url: "http://myhost:5000", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 5000, + endpoint: "http://myhost:5000", + }, + { + url: "https://myhost:443", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "https://myhost:443", + }, + { + url: "https://myhost:5000", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 5000, + endpoint: "https://myhost:5000", + }, + ]; + + testCases.forEach((testCase) => { + test(`Testing URL: ${testCase.url}`, () => { + if (testCase.error) { + expect(() => new HttpEndpoint(testCase.url)).toThrow(Error); + } else { + const url = new HttpEndpoint(testCase.url); + expect(url.endpoint).toBe(testCase.endpoint); + expect(url.tls).toBe(testCase.secure); + expect(url.hostname).toBe(testCase.host); + expect(url.port).toBe(String(testCase.port)); + } + }); + }); + }); + + describe("test correct client instantiation", () => { + let client: DaprClient; + const daprHost = "127.0.0.1"; + const daprGrpcPort = "50000"; + const daprHttpPort = "3500"; + + // We need to start listening on some endpoints already + // this because Dapr is not dynamic and registers endpoints on boot + // we put a timeout of 10s since it takes around 4s for Dapr to boot up + + afterAll(async () => { + await client.stop(); + }); + + it("should give preference to host and port in constructor arguments over endpoint environment variables ", async () => { + process.env.DAPR_HTTP_ENDPOINT = "https://httpdomain.com"; + process.env.DAPR_GRPC_ENDPOINT = "https://grpcdomain.com"; + + // HTTP + client = new DaprClient({ + daprHost, + daprPort: daprHttpPort, + communicationProtocol: CommunicationProtocolEnum.HTTP, + isKeepAlive: false, + }); + + expect(client.options.daprHost).toEqual(daprHost); + expect(client.options.daprPort).toEqual(daprHttpPort); + + // GRPC + client = new DaprClient({ + daprHost, + daprPort: daprGrpcPort, + communicationProtocol: CommunicationProtocolEnum.GRPC, + isKeepAlive: false, + }); + + expect(client.options.daprHost).toEqual(daprHost); + expect(client.options.daprPort).toEqual(daprGrpcPort); + }); + + it("should give preference to port with no host in constructor arguments over environment variables ", async () => { + process.env.DAPR_HTTP_ENDPOINT = "https://httpdomain.com"; + process.env.DAPR_GRPC_ENDPOINT = "https://grpcdomain.com"; + + // HTTP + client = new DaprClient({ + daprPort: daprHttpPort, + communicationProtocol: CommunicationProtocolEnum.HTTP, + isKeepAlive: false, + }); + + expect(client.options.daprHost).toEqual(Settings.getDefaultHost()); + expect(client.options.daprPort).toEqual(daprHttpPort); + + // GRPC + client = new DaprClient({ + daprPort: daprGrpcPort, + communicationProtocol: CommunicationProtocolEnum.GRPC, + isKeepAlive: false, + }); + + expect(client.options.daprHost).toEqual(Settings.getDefaultHost()); + expect(client.options.daprPort).toEqual(daprGrpcPort); + }); + + it("should give preference to host with no port in constructor arguments over environment variables ", async () => { + process.env.DAPR_HTTP_ENDPOINT = "https://httpdomain.com"; + process.env.DAPR_GRPC_ENDPOINT = "https://grpcdomain.com"; + + // HTTP + client = new DaprClient({ + daprHost: daprHost, + communicationProtocol: CommunicationProtocolEnum.HTTP, + isKeepAlive: false, + }); + + expect(client.options.daprHost).toEqual(daprHost); + expect(client.options.daprPort).toEqual(Settings.getDefaultPort(CommunicationProtocolEnum.HTTP)); + + // GRPC + client = new DaprClient({ + daprHost: daprHost, + communicationProtocol: CommunicationProtocolEnum.GRPC, + isKeepAlive: false, + }); + + expect(client.options.daprHost).toEqual(daprHost); + expect(client.options.daprPort).toEqual(Settings.getDefaultPort(CommunicationProtocolEnum.GRPC)); + }); + + it("should use environment variable endpoint for HTTP", async () => { + process.env.DAPR_HTTP_ENDPOINT = "https://httpdomain.com"; + process.env.DAPR_GRPC_ENDPOINT = "https://grpcdomain.com"; + client = new DaprClient({ + communicationProtocol: CommunicationProtocolEnum.HTTP, + isKeepAlive: false, + }); + + expect(client.options.daprHost).toEqual("httpdomain.com"); + expect(client.options.daprPort).toEqual("443"); + }); + + it("should use environment variable endpoint for GRPC", async () => { + process.env.DAPR_HTTP_ENDPOINT = "https://httpdomain.com"; + process.env.DAPR_GRPC_ENDPOINT = "https://grpcdomain.com"; + client = new DaprClient({ + communicationProtocol: CommunicationProtocolEnum.GRPC, + isKeepAlive: false, + }); + + expect(client.options.daprHost).toEqual("grpcdomain.com"); + expect(client.options.daprPort).toEqual("443"); + }); + + it("should use default host and port when no other parameters provided", async () => { + process.env.DAPR_HTTP_ENDPOINT = ""; + process.env.DAPR_GRPC_ENDPOINT = ""; + + // HTTP + client = new DaprClient({ + communicationProtocol: CommunicationProtocolEnum.HTTP, + isKeepAlive: false, + }); + + expect(client.options.daprHost).toEqual(Settings.getDefaultHost()); + expect(client.options.daprPort).toEqual(Settings.getDefaultPort(CommunicationProtocolEnum.HTTP)); + + // GRPC + client = new DaprClient({ + communicationProtocol: CommunicationProtocolEnum.GRPC, + isKeepAlive: false, + }); + + expect(client.options.daprHost).toEqual(Settings.getDefaultHost()); + expect(client.options.daprPort).toEqual(Settings.getDefaultPort(CommunicationProtocolEnum.GRPC)); + }); + }); }); From 6d121b25e2e43de69d369b256a4991f436914cdb Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Fri, 27 Oct 2023 17:58:41 +0100 Subject: [PATCH 09/22] Removes unneeded test (after rebase) Signed-off-by: Elena Kolevska --- test/unit/protocols/grpc/GRPCClient.test.ts | 28 --------------------- 1 file changed, 28 deletions(-) delete mode 100644 test/unit/protocols/grpc/GRPCClient.test.ts diff --git a/test/unit/protocols/grpc/GRPCClient.test.ts b/test/unit/protocols/grpc/GRPCClient.test.ts deleted file mode 100644 index 529934f6..00000000 --- a/test/unit/protocols/grpc/GRPCClient.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* -Copyright 2023 The Dapr Authors -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -import { GRPCClient } from "../../../../src"; - -describe("grpc", () => { - it("getEndpoint should remove http and https from endpoint", () => { - const testCases = [ - { host: "http://localhost", port: "5000", expected: "localhost:5000" }, - { host: "https://localhost", port: "5000", expected: "localhost:5000" }, - { host: "localhost", port: "5000", expected: "localhost:5000" }, - ]; - - testCases.forEach((testCase) => { - expect(GRPCClient.getEndpoint(testCase.host, testCase.port)).toBe(testCase.expected); - }); - }); -}); From b4c8f35998faf5dec01afc67a50c1852e782c0b1 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Sun, 3 Dec 2023 02:24:16 +0000 Subject: [PATCH 10/22] Fixes rebase manual merge Signed-off-by: Elena Kolevska --- src/utils/Client.util.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/Client.util.ts b/src/utils/Client.util.ts index 4c43218d..b5895098 100644 --- a/src/utils/Client.util.ts +++ b/src/utils/Client.util.ts @@ -30,6 +30,7 @@ import { LoggerOptions } from "../types/logger/LoggerOptions"; import { StateConsistencyEnum } from "../enum/StateConsistency.enum"; import { StateConcurrencyEnum } from "../enum/StateConcurrency.enum"; import { URL, URLSearchParams } from "url"; +import {Settings} from "./Settings.util"; /** * Adds metadata to a map. @@ -271,7 +272,7 @@ export function getClientOptions( isKeepAlive: clientOptions?.isKeepAlive, logger: clientOptions?.logger ?? defaultLoggerOptions, actor: clientOptions?.actor, - daprApiToken: clientOptions?.daprApiToken, + daprApiToken: clientOptions?.daprApiToken ?? Settings.getDefaultApiToken(), maxBodySizeMb: clientOptions?.maxBodySizeMb, }; } From 7f3c167f3b7ab1afb3cc0cd07818a13f4cec68cc Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Sun, 3 Dec 2023 02:40:54 +0000 Subject: [PATCH 11/22] Make dapr DaprClient.options not partial again Signed-off-by: Elena Kolevska --- src/actors/runtime/AbstractActor.ts | 8 +++--- src/implementation/Client/DaprClient.ts | 35 ++++++++++++++++++------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/actors/runtime/AbstractActor.ts b/src/actors/runtime/AbstractActor.ts index a1ffcd39..f42fbb98 100644 --- a/src/actors/runtime/AbstractActor.ts +++ b/src/actors/runtime/AbstractActor.ts @@ -54,10 +54,10 @@ export default abstract class AbstractActor { constructor(daprClient: DaprClient, id: ActorId) { this.daprClient = daprClient; this.actorClient = new ActorClient( - daprClient.daprClient.options.daprHost, - daprClient.daprClient.options.daprPort, - daprClient.daprClient.options.communicationProtocol, - daprClient.daprClient.options, + daprClient.options.daprHost, + daprClient.options.daprPort, + daprClient.options.communicationProtocol, + daprClient.options, ); this.logger = new Logger("Actors", "AbstractActor", daprClient.options.logger); this.id = id; diff --git a/src/implementation/Client/DaprClient.ts b/src/implementation/Client/DaprClient.ts index f2950f8f..1eb320b6 100644 --- a/src/implementation/Client/DaprClient.ts +++ b/src/implementation/Client/DaprClient.ts @@ -67,7 +67,7 @@ import * as NodeJSUtils from "../../utils/NodeJS.util"; import { getClientOptions } from "../../utils/Client.util"; export default class DaprClient { - readonly options: Partial; + readonly options: DaprClientOptions; readonly daprClient: IClient; readonly actor: IClientActorBuilder; readonly binding: IClientBinding; @@ -87,24 +87,26 @@ export default class DaprClient { private readonly logger: Logger; constructor(options: Partial = {}) { - this.options = getClientOptions(options, Settings.getDefaultCommunicationProtocol(), undefined); - this.logger = new Logger("DaprClient", "DaprClient", this.options.logger); + options = getClientOptions(options, Settings.getDefaultCommunicationProtocol(), undefined); + // this.options = getClientOptions(options, Settings.getDefaultCommunicationProtocol(), undefined); + this.logger = new Logger("DaprClient", "DaprClient", options.logger); // Legacy validation on port // URI validation is done later, when we instantiate the HttpEndpoint or GrpcEndpoint // object in the HttpClient or GrpcClient constructor, but we need to // keep this additional check for backward compatibility // TODO: Remove this validation in the next major version - if (this.options?.daprPort && !/^[0-9]+$/.test(this.options?.daprPort)) { + if (options?.daprPort && !/^[0-9]+$/.test(options?.daprPort)) { throw new Error("DAPR_INCORRECT_SIDECAR_PORT"); } // Builder switch (options.communicationProtocol) { case CommunicationProtocolEnum.GRPC: { - const client = new GRPCClient(this.options); - this.options.daprHost = client.options.daprHost; - this.options.daprPort = client.options.daprPort; + const client = new GRPCClient(options); + options.daprHost = client.options.daprHost; + options.daprPort = client.options.daprPort; + // this.options = options this.daprClient = client; this.state = new GRPCClientState(client); @@ -125,9 +127,10 @@ export default class DaprClient { } case CommunicationProtocolEnum.HTTP: default: { - const client = new HTTPClient(this.options); - this.options.daprHost = client.options.daprHost; - this.options.daprPort = client.options.daprPort; + const client = new HTTPClient(options); + options.daprHost = client.options.daprHost; + options.daprPort = client.options.daprPort; + // this.options = options this.daprClient = client; this.actor = new HTTPClientActor(client); // we use an abstractor here since we interface through a builder with the Actor Runtime @@ -146,7 +149,19 @@ export default class DaprClient { this.workflow = new HTTPClientWorkflow(client); break; } + } + + this.options = { + daprHost: options.daprHost, + daprPort: options.daprPort, + communicationProtocol: options.communicationProtocol ?? Settings.getDefaultCommunicationProtocol(), + isKeepAlive: options.isKeepAlive, + logger: options.logger, + actor: options.actor, + daprApiToken: options.daprApiToken, + maxBodySizeMb: options.maxBodySizeMb, + }; } static create(client: IClient): DaprClient { From aece61947297c1f135f1e33412d42742d839c255 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Sun, 3 Dec 2023 02:56:23 +0000 Subject: [PATCH 12/22] Reorganises the network classes Signed-off-by: Elena Kolevska --- src/implementation/Client/DaprClient.ts | 3 - .../Client/GRPCClient/GRPCClient.ts | 2 +- .../Client/HTTPClient/HTTPClient.ts | 2 +- src/network/AbstractEndpoint.ts | 36 +++ src/network/GrpcEndpoint.ts | 162 ++++++++++++ src/network/HttpEndpoint.ts | 33 +++ src/network/Network.util.ts | 8 + src/utils/Client.util.ts | 233 ------------------ test/unit/utils/Client.util.test.ts | 3 +- 9 files changed, 243 insertions(+), 239 deletions(-) create mode 100644 src/network/AbstractEndpoint.ts create mode 100644 src/network/GrpcEndpoint.ts create mode 100644 src/network/HttpEndpoint.ts create mode 100644 src/network/Network.util.ts diff --git a/src/implementation/Client/DaprClient.ts b/src/implementation/Client/DaprClient.ts index 1eb320b6..68a0b1a9 100644 --- a/src/implementation/Client/DaprClient.ts +++ b/src/implementation/Client/DaprClient.ts @@ -88,7 +88,6 @@ export default class DaprClient { constructor(options: Partial = {}) { options = getClientOptions(options, Settings.getDefaultCommunicationProtocol(), undefined); - // this.options = getClientOptions(options, Settings.getDefaultCommunicationProtocol(), undefined); this.logger = new Logger("DaprClient", "DaprClient", options.logger); // Legacy validation on port @@ -106,7 +105,6 @@ export default class DaprClient { const client = new GRPCClient(options); options.daprHost = client.options.daprHost; options.daprPort = client.options.daprPort; - // this.options = options this.daprClient = client; this.state = new GRPCClientState(client); @@ -130,7 +128,6 @@ export default class DaprClient { const client = new HTTPClient(options); options.daprHost = client.options.daprHost; options.daprPort = client.options.daprPort; - // this.options = options this.daprClient = client; this.actor = new HTTPClientActor(client); // we use an abstractor here since we interface through a builder with the Actor Runtime diff --git a/src/implementation/Client/GRPCClient/GRPCClient.ts b/src/implementation/Client/GRPCClient/GRPCClient.ts index 98aaea4a..217c8087 100644 --- a/src/implementation/Client/GRPCClient/GRPCClient.ts +++ b/src/implementation/Client/GRPCClient/GRPCClient.ts @@ -20,8 +20,8 @@ import { Logger } from "../../../logger/Logger"; import GRPCClientSidecar from "./sidecar"; import DaprClient from "../DaprClient"; import { SDK_VERSION } from "../../../version"; -import { GrpcEndpoint } from "../../../utils/Client.util"; import communicationProtocolEnum from "../../../enum/CommunicationProtocol.enum"; +import {GrpcEndpoint} from "../../../network/GrpcEndpoint"; export default class GRPCClient implements IClient { readonly options: DaprClientOptions; diff --git a/src/implementation/Client/HTTPClient/HTTPClient.ts b/src/implementation/Client/HTTPClient/HTTPClient.ts index f5cf415c..954275b3 100644 --- a/src/implementation/Client/HTTPClient/HTTPClient.ts +++ b/src/implementation/Client/HTTPClient/HTTPClient.ts @@ -23,8 +23,8 @@ import { Logger } from "../../../logger/Logger"; import HTTPClientSidecar from "./sidecar"; import { SDK_VERSION } from "../../../version"; import * as SerializerUtil from "../../../utils/Serializer.util"; -import { HttpEndpoint } from "../../../utils/Client.util"; import communicationProtocolEnum from "../../../enum/CommunicationProtocol.enum"; +import {HttpEndpoint} from "../../../network/HttpEndpoint"; export default class HTTPClient implements IClient { readonly options: DaprClientOptions; diff --git a/src/network/AbstractEndpoint.ts b/src/network/AbstractEndpoint.ts new file mode 100644 index 00000000..f42ffed2 --- /dev/null +++ b/src/network/AbstractEndpoint.ts @@ -0,0 +1,36 @@ +import {URL} from "url"; + +export abstract class Endpoint { + protected _scheme = ""; + protected _hostname = ""; + protected _port = 0; + protected _tls = false; + protected _authority = ""; + protected _url: string; + protected _endpoint = ""; + protected _parsedUrl!: URL; + + protected constructor(url: string) { + this._url = url; + } + + get tls(): boolean { + return this._tls; + } + + get hostname(): string { + return this._hostname; + } + + get scheme(): string { + return this._scheme; + } + + get port(): string { + return this._port === 0 ? "" : this._port.toString(); + } + + get endpoint(): string { + return this._endpoint; + } +} \ No newline at end of file diff --git a/src/network/GrpcEndpoint.ts b/src/network/GrpcEndpoint.ts new file mode 100644 index 00000000..3d9d14fa --- /dev/null +++ b/src/network/GrpcEndpoint.ts @@ -0,0 +1,162 @@ +import {Endpoint} from "./AbstractEndpoint"; +import {URIParseConfig} from "./Network.util"; +import {URL, URLSearchParams} from "url"; + +export class GrpcEndpoint extends Endpoint { + constructor(url: string) { + super(url); + this._authority = URIParseConfig.DEFAULT_AUTHORITY; + + this._parsedUrl = new URL(this.preprocessUri(url)); + this.validatePathAndQuery(); + + this.setTls(); + this.setHostname(); + this.setScheme(); + this.setPort(); + this.setEndpoint(); + } + + private preprocessUri(url: string): string { + let urlList = url.split(":"); + + if (urlList.length === 3 && !url.includes("://")) { + // A URI like dns:mydomain:5000 or vsock:mycid:5000 was used + url = url.replace(":", "://"); + } else if ( + urlList.length >= 2 && + !url.includes("://") && + URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(urlList[0]) + ) { + // A URI like dns:mydomain was used + url = url.replace(":", "://"); + } else { + urlList = url.split("://"); + if (urlList.length === 1) { + // If a scheme was not explicitly specified in the URL + // we need to add a default scheme, + // because of how URL works in JavaScript + + // We also need to check if the provided uri is not of the form :5000 + // if it is, we need to add a default hostname, because the URL class can't parse it + if (url[0] === ":") { + url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${URIParseConfig.DEFAULT_HOSTNAME}${url}`; + } else { + url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${url}`; + } + } else { + // If a scheme was explicitly specified in the URL + // we need to make sure it is a valid scheme + const scheme = urlList[0]; + if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { + throw new Error(`Invalid scheme '${scheme}' in URL '${url}'`); + } + + // We should do a special check if the scheme is dns, and it uses + // an authority in the format of dns:[//authority/]host[:port] + if (scheme.toLowerCase() === "dns") { + // A URI like dns://authority/mydomain was used + urlList = url.split("/"); + if (urlList.length < 4) { + throw new Error(`Invalid dns authority '${urlList[2]}' in URL '${url}'`); + } + this._authority = urlList[2]; + url = `dns://${urlList[3]}`; + } + } + } + return url; + } + + private validatePathAndQuery(): void { + if (this._parsedUrl.pathname && this._parsedUrl.pathname !== "/") { + throw new Error(`Paths are not supported for gRPC endpoints: '${this._parsedUrl.pathname}'`); + } + + const params = new URLSearchParams(this._parsedUrl.search); + if (params.has("tls") && (this._parsedUrl.protocol === "http:" || this._parsedUrl.protocol === "https:")) { + throw new Error(`The tls query parameter is not supported for http(s) endpoints: '${this._parsedUrl.search}'`); + } + + params.delete("tls"); + if (Array.from(params.keys()).length > 0) { + throw new Error(`Query parameters are not supported for gRPC endpoints: '${this._parsedUrl.search}'`); + } + } + + private setTls(): void { + const params = new URLSearchParams(this._parsedUrl.search); + const tlsStr = params.get("tls") || ""; + this._tls = tlsStr.toLowerCase() === "true"; + + if (this._parsedUrl.protocol == "https:") { + this._tls = true; + } + } + + private setHostname(): void { + if (!this._parsedUrl.hostname) { + this._hostname = URIParseConfig.DEFAULT_HOSTNAME; + return; + } + + this._hostname = this._parsedUrl.hostname; + } + + private setScheme(): void { + if (!this._parsedUrl.protocol) { + this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; + return; + } + + const scheme = this._parsedUrl.protocol.slice(0, -1); // Remove trailing ':' + if (scheme === "http" || scheme === "https") { + this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; + console.warn("http and https schemes are deprecated, use grpc or grpcs instead"); + return; + } + + if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { + throw new Error(`Invalid scheme '${scheme}' in URL '${this._url}'`); + } + + this._scheme = scheme; + } + + private setPort(): void { + if (this._scheme === "unix" || this._scheme === "unix-abstract") { + this._port = 0; + return; + } + + this._port = this._parsedUrl.port ? parseInt(this._parsedUrl.port) : URIParseConfig.DEFAULT_PORT; + } + + private setEndpoint(): void { + const port = this._port ? `:${this.port}` : ""; + + if (this._scheme === "unix") { + const separator = this._url.startsWith("unix://") ? "://" : ":"; + this._endpoint = `${this._scheme}${separator}${this._hostname}`; + return; + } + + if (this._scheme === "vsock") { + this._endpoint = `${this._scheme}:${this._hostname}:${this.port}`; + return; + } + + if (this._scheme === "unix-abstract") { + this._endpoint = `${this._scheme}:${this._hostname}${port}`; + return; + } + + if (this._scheme === "dns") { + const authority = this._authority ? `//${this._authority}/` : ""; + this._endpoint = `${this._scheme}:${authority}${this._hostname}${port}`; + return; + } + + this._endpoint = `${this._scheme}:${this._hostname}${port}`; + } +} \ No newline at end of file diff --git a/src/network/HttpEndpoint.ts b/src/network/HttpEndpoint.ts new file mode 100644 index 00000000..13adea0a --- /dev/null +++ b/src/network/HttpEndpoint.ts @@ -0,0 +1,33 @@ +import {Endpoint} from "./AbstractEndpoint"; +import {URL} from "url"; +import {URIParseConfig} from "./Network.util"; + +export class HttpEndpoint extends Endpoint { + constructor(url: string) { + super(url); + + try { + this._parsedUrl = new URL(this.preprocessUri(url)); + this._scheme = this._parsedUrl.protocol.replace(":", ""); + this._hostname = this._parsedUrl.hostname.replace("[", ""); + this._hostname = this._hostname.replace("]", ""); + this._port = parseInt(this._parsedUrl.port) || (this._scheme == "https" ? 443 : 80); + this._tls = this._scheme == "https"; + this._endpoint = this._scheme + "://" + this._hostname + ":" + this._port.toString(); + } catch (error) { + throw new Error(`Invalid address: ${url}`); + } + } + + // We need to add a default scheme and hostname to the url + // if they are not specified so that the URL class can parse it + private preprocessUri(url: string) { + if (url.startsWith(":")) { + url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; + } + if (!url.includes("://")) { + url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + url; + } + return url; + } +} \ No newline at end of file diff --git a/src/network/Network.util.ts b/src/network/Network.util.ts new file mode 100644 index 00000000..cbfff136 --- /dev/null +++ b/src/network/Network.util.ts @@ -0,0 +1,8 @@ +export class URIParseConfig { + static readonly DEFAULT_SCHEME_GRPC = "dns"; + static readonly DEFAULT_SCHEME_HTTP = "http"; + static readonly DEFAULT_HOSTNAME = "localhost"; + static readonly DEFAULT_PORT = 443; + static readonly DEFAULT_AUTHORITY = ""; + static readonly ACCEPTED_SCHEMES_GRPC = ["dns", "unix", "unix-abstract", "vsock", "http", "https", "grpc", "grpcs"]; +} \ No newline at end of file diff --git a/src/utils/Client.util.ts b/src/utils/Client.util.ts index b5895098..e118549d 100644 --- a/src/utils/Client.util.ts +++ b/src/utils/Client.util.ts @@ -276,236 +276,3 @@ export function getClientOptions( maxBodySizeMb: clientOptions?.maxBodySizeMb, }; } - -class URIParseConfig { - static readonly DEFAULT_SCHEME_GRPC = "dns"; - static readonly DEFAULT_SCHEME_HTTP = "http"; - static readonly DEFAULT_HOSTNAME = "localhost"; - static readonly DEFAULT_PORT = 443; - static readonly DEFAULT_AUTHORITY = ""; - static readonly ACCEPTED_SCHEMES_GRPC = ["dns", "unix", "unix-abstract", "vsock", "http", "https", "grpc", "grpcs"]; -} - -export abstract class Endpoint { - protected _scheme = ""; - protected _hostname = ""; - protected _port = 0; - protected _tls = false; - protected _authority = ""; - protected _url: string; - protected _endpoint = ""; - protected _parsedUrl!: URL; - - protected constructor(url: string) { - this._url = url; - } - - get tls(): boolean { - return this._tls; - } - - get hostname(): string { - return this._hostname; - } - - get scheme(): string { - return this._scheme; - } - - get port(): string { - return this._port === 0 ? "" : this._port.toString(); - } - - get endpoint(): string { - return this._endpoint; - } -} - -export class HttpEndpoint extends Endpoint { - constructor(url: string) { - super(url); - - try { - this._parsedUrl = new URL(this.preprocessUri(url)); - this._scheme = this._parsedUrl.protocol.replace(":", ""); - this._hostname = this._parsedUrl.hostname.replace("[", ""); - this._hostname = this._hostname.replace("]", ""); - this._port = parseInt(this._parsedUrl.port) || (this._scheme == "https" ? 443 : 80); - this._tls = this._scheme == "https"; - this._endpoint = this._scheme + "://" + this._hostname + ":" + this._port.toString(); - } catch (error) { - throw new Error(`Invalid address: ${url}`); - } - } - - // We need to add a default scheme and hostname to the url - // if they are not specified so that the URL class can parse it - private preprocessUri(url: string) { - if (url.startsWith(":")) { - url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; - } - if (!url.includes("://")) { - url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + url; - } - return url; - } -} - -export class GrpcEndpoint extends Endpoint { - constructor(url: string) { - super(url); - this._authority = URIParseConfig.DEFAULT_AUTHORITY; - - this._parsedUrl = new URL(this.preprocessUri(url)); - this.validatePathAndQuery(); - - this.setTls(); - this.setHostname(); - this.setScheme(); - this.setPort(); - this.setEndpoint(); - } - - private preprocessUri(url: string): string { - let urlList = url.split(":"); - - if (urlList.length === 3 && !url.includes("://")) { - // A URI like dns:mydomain:5000 or vsock:mycid:5000 was used - url = url.replace(":", "://"); - } else if ( - urlList.length >= 2 && - !url.includes("://") && - URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(urlList[0]) - ) { - // A URI like dns:mydomain was used - url = url.replace(":", "://"); - } else { - urlList = url.split("://"); - if (urlList.length === 1) { - // If a scheme was not explicitly specified in the URL - // we need to add a default scheme, - // because of how URL works in JavaScript - - // We also need to check if the provided uri is not of the form :5000 - // if it is, we need to add a default hostname, because the URL class can't parse it - if (url[0] === ":") { - url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${URIParseConfig.DEFAULT_HOSTNAME}${url}`; - } else { - url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${url}`; - } - } else { - // If a scheme was explicitly specified in the URL - // we need to make sure it is a valid scheme - const scheme = urlList[0]; - if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { - throw new Error(`Invalid scheme '${scheme}' in URL '${url}'`); - } - - // We should do a special check if the scheme is dns, and it uses - // an authority in the format of dns:[//authority/]host[:port] - if (scheme.toLowerCase() === "dns") { - // A URI like dns://authority/mydomain was used - urlList = url.split("/"); - if (urlList.length < 4) { - throw new Error(`Invalid dns authority '${urlList[2]}' in URL '${url}'`); - } - this._authority = urlList[2]; - url = `dns://${urlList[3]}`; - } - } - } - return url; - } - - private validatePathAndQuery(): void { - if (this._parsedUrl.pathname && this._parsedUrl.pathname !== "/") { - throw new Error(`Paths are not supported for gRPC endpoints: '${this._parsedUrl.pathname}'`); - } - - const params = new URLSearchParams(this._parsedUrl.search); - if (params.has("tls") && (this._parsedUrl.protocol === "http:" || this._parsedUrl.protocol === "https:")) { - throw new Error(`The tls query parameter is not supported for http(s) endpoints: '${this._parsedUrl.search}'`); - } - - params.delete("tls"); - if (Array.from(params.keys()).length > 0) { - throw new Error(`Query parameters are not supported for gRPC endpoints: '${this._parsedUrl.search}'`); - } - } - - private setTls(): void { - const params = new URLSearchParams(this._parsedUrl.search); - const tlsStr = params.get("tls") || ""; - this._tls = tlsStr.toLowerCase() === "true"; - - if (this._parsedUrl.protocol == "https:") { - this._tls = true; - } - } - - private setHostname(): void { - if (!this._parsedUrl.hostname) { - this._hostname = URIParseConfig.DEFAULT_HOSTNAME; - return; - } - - this._hostname = this._parsedUrl.hostname; - } - - private setScheme(): void { - if (!this._parsedUrl.protocol) { - this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; - return; - } - - const scheme = this._parsedUrl.protocol.slice(0, -1); // Remove trailing ':' - if (scheme === "http" || scheme === "https") { - this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; - console.warn("http and https schemes are deprecated, use grpc or grpcs instead"); - return; - } - - if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { - throw new Error(`Invalid scheme '${scheme}' in URL '${this._url}'`); - } - - this._scheme = scheme; - } - - private setPort(): void { - if (this._scheme === "unix" || this._scheme === "unix-abstract") { - this._port = 0; - return; - } - - this._port = this._parsedUrl.port ? parseInt(this._parsedUrl.port) : URIParseConfig.DEFAULT_PORT; - } - - private setEndpoint(): void { - const port = this._port ? `:${this.port}` : ""; - - if (this._scheme === "unix") { - const separator = this._url.startsWith("unix://") ? "://" : ":"; - this._endpoint = `${this._scheme}${separator}${this._hostname}`; - return; - } - - if (this._scheme === "vsock") { - this._endpoint = `${this._scheme}:${this._hostname}:${this.port}`; - return; - } - - if (this._scheme === "unix-abstract") { - this._endpoint = `${this._scheme}:${this._hostname}${port}`; - return; - } - - if (this._scheme === "dns") { - const authority = this._authority ? `//${this._authority}/` : ""; - this._endpoint = `${this._scheme}:${authority}${this._hostname}${port}`; - return; - } - - this._endpoint = `${this._scheme}:${this._hostname}${port}`; - } -} diff --git a/test/unit/utils/Client.util.test.ts b/test/unit/utils/Client.util.test.ts index 7a0a3b57..953c9703 100644 --- a/test/unit/utils/Client.util.test.ts +++ b/test/unit/utils/Client.util.test.ts @@ -20,7 +20,7 @@ import { getBulkPublishResponse, getClientOptions, createHTTPQueryParam, - GrpcEndpoint, + } from "../../../src/utils/Client.util"; import { Map } from "google-protobuf"; import { PubSubBulkPublishEntry } from "../../../src/types/pubsub/PubSubBulkPublishEntry.type"; @@ -29,6 +29,7 @@ import { CommunicationProtocolEnum, DaprClientOptions, LogLevel } from "../../.. import { HttpEndpoint } from "../../../build/utils/Client.util"; import { DaprClient } from "../../../src"; import { Settings } from "../../../src/utils/Settings.util"; +import {GrpcEndpoint} from "../../../src/network/GrpcEndpoint"; describe("Client.util", () => { describe("addMetadataToMap", () => { From 2a7fa82be4d475392052ad8f059edd452dd1644a Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Sun, 3 Dec 2023 09:24:36 +0000 Subject: [PATCH 13/22] Fixes linter errors Signed-off-by: Elena Kolevska --- src/implementation/Client/DaprClient.ts | 21 +- .../Client/GRPCClient/GRPCClient.ts | 2 +- .../Client/HTTPClient/HTTPClient.ts | 2 +- src/network/AbstractEndpoint.ts | 81 ++--- src/network/GrpcEndpoint.ts | 277 +++++++++--------- src/network/HttpEndpoint.ts | 67 +++-- src/network/Network.util.ts | 27 +- src/utils/Client.util.ts | 3 +- test/unit/utils/Client.util.test.ts | 3 +- 9 files changed, 266 insertions(+), 217 deletions(-) diff --git a/src/implementation/Client/DaprClient.ts b/src/implementation/Client/DaprClient.ts index 68a0b1a9..b66f562c 100644 --- a/src/implementation/Client/DaprClient.ts +++ b/src/implementation/Client/DaprClient.ts @@ -146,19 +146,18 @@ export default class DaprClient { this.workflow = new HTTPClientWorkflow(client); break; } - } - this.options = { - daprHost: options.daprHost, - daprPort: options.daprPort, - communicationProtocol: options.communicationProtocol ?? Settings.getDefaultCommunicationProtocol(), - isKeepAlive: options.isKeepAlive, - logger: options.logger, - actor: options.actor, - daprApiToken: options.daprApiToken, - maxBodySizeMb: options.maxBodySizeMb, - }; + this.options = { + daprHost: options.daprHost, + daprPort: options.daprPort, + communicationProtocol: options.communicationProtocol ?? Settings.getDefaultCommunicationProtocol(), + isKeepAlive: options.isKeepAlive, + logger: options.logger, + actor: options.actor, + daprApiToken: options.daprApiToken, + maxBodySizeMb: options.maxBodySizeMb, + }; } static create(client: IClient): DaprClient { diff --git a/src/implementation/Client/GRPCClient/GRPCClient.ts b/src/implementation/Client/GRPCClient/GRPCClient.ts index 217c8087..82ddcb6c 100644 --- a/src/implementation/Client/GRPCClient/GRPCClient.ts +++ b/src/implementation/Client/GRPCClient/GRPCClient.ts @@ -21,7 +21,7 @@ import GRPCClientSidecar from "./sidecar"; import DaprClient from "../DaprClient"; import { SDK_VERSION } from "../../../version"; import communicationProtocolEnum from "../../../enum/CommunicationProtocol.enum"; -import {GrpcEndpoint} from "../../../network/GrpcEndpoint"; +import { GrpcEndpoint } from "../../../network/GrpcEndpoint"; export default class GRPCClient implements IClient { readonly options: DaprClientOptions; diff --git a/src/implementation/Client/HTTPClient/HTTPClient.ts b/src/implementation/Client/HTTPClient/HTTPClient.ts index 954275b3..cd797b6f 100644 --- a/src/implementation/Client/HTTPClient/HTTPClient.ts +++ b/src/implementation/Client/HTTPClient/HTTPClient.ts @@ -24,7 +24,7 @@ import HTTPClientSidecar from "./sidecar"; import { SDK_VERSION } from "../../../version"; import * as SerializerUtil from "../../../utils/Serializer.util"; import communicationProtocolEnum from "../../../enum/CommunicationProtocol.enum"; -import {HttpEndpoint} from "../../../network/HttpEndpoint"; +import { HttpEndpoint } from "../../../network/HttpEndpoint"; export default class HTTPClient implements IClient { readonly options: DaprClientOptions; diff --git a/src/network/AbstractEndpoint.ts b/src/network/AbstractEndpoint.ts index f42ffed2..2e4f5660 100644 --- a/src/network/AbstractEndpoint.ts +++ b/src/network/AbstractEndpoint.ts @@ -1,36 +1,49 @@ -import {URL} from "url"; +/* +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { URL } from "url"; export abstract class Endpoint { - protected _scheme = ""; - protected _hostname = ""; - protected _port = 0; - protected _tls = false; - protected _authority = ""; - protected _url: string; - protected _endpoint = ""; - protected _parsedUrl!: URL; - - protected constructor(url: string) { - this._url = url; - } - - get tls(): boolean { - return this._tls; - } - - get hostname(): string { - return this._hostname; - } - - get scheme(): string { - return this._scheme; - } - - get port(): string { - return this._port === 0 ? "" : this._port.toString(); - } - - get endpoint(): string { - return this._endpoint; - } -} \ No newline at end of file + protected _scheme = ""; + protected _hostname = ""; + protected _port = 0; + protected _tls = false; + protected _authority = ""; + protected _url: string; + protected _endpoint = ""; + protected _parsedUrl!: URL; + + protected constructor(url: string) { + this._url = url; + } + + get tls(): boolean { + return this._tls; + } + + get hostname(): string { + return this._hostname; + } + + get scheme(): string { + return this._scheme; + } + + get port(): string { + return this._port === 0 ? "" : this._port.toString(); + } + + get endpoint(): string { + return this._endpoint; + } +} diff --git a/src/network/GrpcEndpoint.ts b/src/network/GrpcEndpoint.ts index 3d9d14fa..d666dd9e 100644 --- a/src/network/GrpcEndpoint.ts +++ b/src/network/GrpcEndpoint.ts @@ -1,162 +1,175 @@ -import {Endpoint} from "./AbstractEndpoint"; -import {URIParseConfig} from "./Network.util"; -import {URL, URLSearchParams} from "url"; +/* +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Endpoint } from "./AbstractEndpoint"; +import { URIParseConfig } from "./Network.util"; +import { URL, URLSearchParams } from "url"; export class GrpcEndpoint extends Endpoint { - constructor(url: string) { - super(url); - this._authority = URIParseConfig.DEFAULT_AUTHORITY; - - this._parsedUrl = new URL(this.preprocessUri(url)); - this.validatePathAndQuery(); - - this.setTls(); - this.setHostname(); - this.setScheme(); - this.setPort(); - this.setEndpoint(); - } - - private preprocessUri(url: string): string { - let urlList = url.split(":"); - - if (urlList.length === 3 && !url.includes("://")) { - // A URI like dns:mydomain:5000 or vsock:mycid:5000 was used - url = url.replace(":", "://"); - } else if ( - urlList.length >= 2 && - !url.includes("://") && - URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(urlList[0]) - ) { - // A URI like dns:mydomain was used - url = url.replace(":", "://"); + constructor(url: string) { + super(url); + this._authority = URIParseConfig.DEFAULT_AUTHORITY; + + this._parsedUrl = new URL(this.preprocessUri(url)); + this.validatePathAndQuery(); + + this.setTls(); + this.setHostname(); + this.setScheme(); + this.setPort(); + this.setEndpoint(); + } + + private preprocessUri(url: string): string { + let urlList = url.split(":"); + + if (urlList.length === 3 && !url.includes("://")) { + // A URI like dns:mydomain:5000 or vsock:mycid:5000 was used + url = url.replace(":", "://"); + } else if ( + urlList.length >= 2 && + !url.includes("://") && + URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(urlList[0]) + ) { + // A URI like dns:mydomain was used + url = url.replace(":", "://"); + } else { + urlList = url.split("://"); + if (urlList.length === 1) { + // If a scheme was not explicitly specified in the URL + // we need to add a default scheme, + // because of how URL works in JavaScript + + // We also need to check if the provided uri is not of the form :5000 + // if it is, we need to add a default hostname, because the URL class can't parse it + if (url[0] === ":") { + url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${URIParseConfig.DEFAULT_HOSTNAME}${url}`; } else { - urlList = url.split("://"); - if (urlList.length === 1) { - // If a scheme was not explicitly specified in the URL - // we need to add a default scheme, - // because of how URL works in JavaScript - - // We also need to check if the provided uri is not of the form :5000 - // if it is, we need to add a default hostname, because the URL class can't parse it - if (url[0] === ":") { - url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${URIParseConfig.DEFAULT_HOSTNAME}${url}`; - } else { - url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${url}`; - } - } else { - // If a scheme was explicitly specified in the URL - // we need to make sure it is a valid scheme - const scheme = urlList[0]; - if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { - throw new Error(`Invalid scheme '${scheme}' in URL '${url}'`); - } - - // We should do a special check if the scheme is dns, and it uses - // an authority in the format of dns:[//authority/]host[:port] - if (scheme.toLowerCase() === "dns") { - // A URI like dns://authority/mydomain was used - urlList = url.split("/"); - if (urlList.length < 4) { - throw new Error(`Invalid dns authority '${urlList[2]}' in URL '${url}'`); - } - this._authority = urlList[2]; - url = `dns://${urlList[3]}`; - } - } + url = `${URIParseConfig.DEFAULT_SCHEME_GRPC}://${url}`; } - return url; - } - - private validatePathAndQuery(): void { - if (this._parsedUrl.pathname && this._parsedUrl.pathname !== "/") { - throw new Error(`Paths are not supported for gRPC endpoints: '${this._parsedUrl.pathname}'`); + } else { + // If a scheme was explicitly specified in the URL + // we need to make sure it is a valid scheme + const scheme = urlList[0]; + if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { + throw new Error(`Invalid scheme '${scheme}' in URL '${url}'`); } - const params = new URLSearchParams(this._parsedUrl.search); - if (params.has("tls") && (this._parsedUrl.protocol === "http:" || this._parsedUrl.protocol === "https:")) { - throw new Error(`The tls query parameter is not supported for http(s) endpoints: '${this._parsedUrl.search}'`); + // We should do a special check if the scheme is dns, and it uses + // an authority in the format of dns:[//authority/]host[:port] + if (scheme.toLowerCase() === "dns") { + // A URI like dns://authority/mydomain was used + urlList = url.split("/"); + if (urlList.length < 4) { + throw new Error(`Invalid dns authority '${urlList[2]}' in URL '${url}'`); + } + this._authority = urlList[2]; + url = `dns://${urlList[3]}`; } + } + } + return url; + } - params.delete("tls"); - if (Array.from(params.keys()).length > 0) { - throw new Error(`Query parameters are not supported for gRPC endpoints: '${this._parsedUrl.search}'`); - } + private validatePathAndQuery(): void { + if (this._parsedUrl.pathname && this._parsedUrl.pathname !== "/") { + throw new Error(`Paths are not supported for gRPC endpoints: '${this._parsedUrl.pathname}'`); } - private setTls(): void { - const params = new URLSearchParams(this._parsedUrl.search); - const tlsStr = params.get("tls") || ""; - this._tls = tlsStr.toLowerCase() === "true"; + const params = new URLSearchParams(this._parsedUrl.search); + if (params.has("tls") && (this._parsedUrl.protocol === "http:" || this._parsedUrl.protocol === "https:")) { + throw new Error(`The tls query parameter is not supported for http(s) endpoints: '${this._parsedUrl.search}'`); + } - if (this._parsedUrl.protocol == "https:") { - this._tls = true; - } + params.delete("tls"); + if (Array.from(params.keys()).length > 0) { + throw new Error(`Query parameters are not supported for gRPC endpoints: '${this._parsedUrl.search}'`); } + } - private setHostname(): void { - if (!this._parsedUrl.hostname) { - this._hostname = URIParseConfig.DEFAULT_HOSTNAME; - return; - } + private setTls(): void { + const params = new URLSearchParams(this._parsedUrl.search); + const tlsStr = params.get("tls") || ""; + this._tls = tlsStr.toLowerCase() === "true"; - this._hostname = this._parsedUrl.hostname; + if (this._parsedUrl.protocol == "https:") { + this._tls = true; } + } - private setScheme(): void { - if (!this._parsedUrl.protocol) { - this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; - return; - } + private setHostname(): void { + if (!this._parsedUrl.hostname) { + this._hostname = URIParseConfig.DEFAULT_HOSTNAME; + return; + } - const scheme = this._parsedUrl.protocol.slice(0, -1); // Remove trailing ':' - if (scheme === "http" || scheme === "https") { - this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; - console.warn("http and https schemes are deprecated, use grpc or grpcs instead"); - return; - } + this._hostname = this._parsedUrl.hostname; + } - if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { - throw new Error(`Invalid scheme '${scheme}' in URL '${this._url}'`); - } + private setScheme(): void { + if (!this._parsedUrl.protocol) { + this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; + return; + } - this._scheme = scheme; + const scheme = this._parsedUrl.protocol.slice(0, -1); // Remove trailing ':' + if (scheme === "http" || scheme === "https") { + this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; + console.warn("http and https schemes are deprecated, use grpc or grpcs instead"); + return; } - private setPort(): void { - if (this._scheme === "unix" || this._scheme === "unix-abstract") { - this._port = 0; - return; - } + if (!URIParseConfig.ACCEPTED_SCHEMES_GRPC.includes(scheme)) { + throw new Error(`Invalid scheme '${scheme}' in URL '${this._url}'`); + } - this._port = this._parsedUrl.port ? parseInt(this._parsedUrl.port) : URIParseConfig.DEFAULT_PORT; + this._scheme = scheme; + } + + private setPort(): void { + if (this._scheme === "unix" || this._scheme === "unix-abstract") { + this._port = 0; + return; } - private setEndpoint(): void { - const port = this._port ? `:${this.port}` : ""; + this._port = this._parsedUrl.port ? parseInt(this._parsedUrl.port) : URIParseConfig.DEFAULT_PORT; + } - if (this._scheme === "unix") { - const separator = this._url.startsWith("unix://") ? "://" : ":"; - this._endpoint = `${this._scheme}${separator}${this._hostname}`; - return; - } + private setEndpoint(): void { + const port = this._port ? `:${this.port}` : ""; - if (this._scheme === "vsock") { - this._endpoint = `${this._scheme}:${this._hostname}:${this.port}`; - return; - } + if (this._scheme === "unix") { + const separator = this._url.startsWith("unix://") ? "://" : ":"; + this._endpoint = `${this._scheme}${separator}${this._hostname}`; + return; + } - if (this._scheme === "unix-abstract") { - this._endpoint = `${this._scheme}:${this._hostname}${port}`; - return; - } + if (this._scheme === "vsock") { + this._endpoint = `${this._scheme}:${this._hostname}:${this.port}`; + return; + } - if (this._scheme === "dns") { - const authority = this._authority ? `//${this._authority}/` : ""; - this._endpoint = `${this._scheme}:${authority}${this._hostname}${port}`; - return; - } + if (this._scheme === "unix-abstract") { + this._endpoint = `${this._scheme}:${this._hostname}${port}`; + return; + } - this._endpoint = `${this._scheme}:${this._hostname}${port}`; + if (this._scheme === "dns") { + const authority = this._authority ? `//${this._authority}/` : ""; + this._endpoint = `${this._scheme}:${authority}${this._hostname}${port}`; + return; } -} \ No newline at end of file + + this._endpoint = `${this._scheme}:${this._hostname}${port}`; + } +} diff --git a/src/network/HttpEndpoint.ts b/src/network/HttpEndpoint.ts index 13adea0a..40db69e7 100644 --- a/src/network/HttpEndpoint.ts +++ b/src/network/HttpEndpoint.ts @@ -1,33 +1,46 @@ -import {Endpoint} from "./AbstractEndpoint"; -import {URL} from "url"; -import {URIParseConfig} from "./Network.util"; +/* +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import { Endpoint } from "./AbstractEndpoint"; +import { URL } from "url"; +import { URIParseConfig } from "./Network.util"; export class HttpEndpoint extends Endpoint { - constructor(url: string) { - super(url); + constructor(url: string) { + super(url); - try { - this._parsedUrl = new URL(this.preprocessUri(url)); - this._scheme = this._parsedUrl.protocol.replace(":", ""); - this._hostname = this._parsedUrl.hostname.replace("[", ""); - this._hostname = this._hostname.replace("]", ""); - this._port = parseInt(this._parsedUrl.port) || (this._scheme == "https" ? 443 : 80); - this._tls = this._scheme == "https"; - this._endpoint = this._scheme + "://" + this._hostname + ":" + this._port.toString(); - } catch (error) { - throw new Error(`Invalid address: ${url}`); - } + try { + this._parsedUrl = new URL(this.preprocessUri(url)); + this._scheme = this._parsedUrl.protocol.replace(":", ""); + this._hostname = this._parsedUrl.hostname.replace("[", ""); + this._hostname = this._hostname.replace("]", ""); + this._port = parseInt(this._parsedUrl.port) || (this._scheme == "https" ? 443 : 80); + this._tls = this._scheme == "https"; + this._endpoint = this._scheme + "://" + this._hostname + ":" + this._port.toString(); + } catch (error) { + throw new Error(`Invalid address: ${url}`); } + } - // We need to add a default scheme and hostname to the url - // if they are not specified so that the URL class can parse it - private preprocessUri(url: string) { - if (url.startsWith(":")) { - url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; - } - if (!url.includes("://")) { - url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + url; - } - return url; + // We need to add a default scheme and hostname to the url + // if they are not specified so that the URL class can parse it + private preprocessUri(url: string) { + if (url.startsWith(":")) { + url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; + } + if (!url.includes("://")) { + url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + url; } -} \ No newline at end of file + return url; + } +} diff --git a/src/network/Network.util.ts b/src/network/Network.util.ts index cbfff136..9114107b 100644 --- a/src/network/Network.util.ts +++ b/src/network/Network.util.ts @@ -1,8 +1,21 @@ +/* +Copyright 2023 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + export class URIParseConfig { - static readonly DEFAULT_SCHEME_GRPC = "dns"; - static readonly DEFAULT_SCHEME_HTTP = "http"; - static readonly DEFAULT_HOSTNAME = "localhost"; - static readonly DEFAULT_PORT = 443; - static readonly DEFAULT_AUTHORITY = ""; - static readonly ACCEPTED_SCHEMES_GRPC = ["dns", "unix", "unix-abstract", "vsock", "http", "https", "grpc", "grpcs"]; -} \ No newline at end of file + static readonly DEFAULT_SCHEME_GRPC = "dns"; + static readonly DEFAULT_SCHEME_HTTP = "http"; + static readonly DEFAULT_HOSTNAME = "localhost"; + static readonly DEFAULT_PORT = 443; + static readonly DEFAULT_AUTHORITY = ""; + static readonly ACCEPTED_SCHEMES_GRPC = ["dns", "unix", "unix-abstract", "vsock", "http", "https", "grpc", "grpcs"]; +} diff --git a/src/utils/Client.util.ts b/src/utils/Client.util.ts index e118549d..fd4fb73f 100644 --- a/src/utils/Client.util.ts +++ b/src/utils/Client.util.ts @@ -29,8 +29,7 @@ import CommunicationProtocolEnum from "../enum/CommunicationProtocol.enum"; import { LoggerOptions } from "../types/logger/LoggerOptions"; import { StateConsistencyEnum } from "../enum/StateConsistency.enum"; import { StateConcurrencyEnum } from "../enum/StateConcurrency.enum"; -import { URL, URLSearchParams } from "url"; -import {Settings} from "./Settings.util"; +import { Settings } from "./Settings.util"; /** * Adds metadata to a map. diff --git a/test/unit/utils/Client.util.test.ts b/test/unit/utils/Client.util.test.ts index 953c9703..b94ca036 100644 --- a/test/unit/utils/Client.util.test.ts +++ b/test/unit/utils/Client.util.test.ts @@ -20,7 +20,6 @@ import { getBulkPublishResponse, getClientOptions, createHTTPQueryParam, - } from "../../../src/utils/Client.util"; import { Map } from "google-protobuf"; import { PubSubBulkPublishEntry } from "../../../src/types/pubsub/PubSubBulkPublishEntry.type"; @@ -29,7 +28,7 @@ import { CommunicationProtocolEnum, DaprClientOptions, LogLevel } from "../../.. import { HttpEndpoint } from "../../../build/utils/Client.util"; import { DaprClient } from "../../../src"; import { Settings } from "../../../src/utils/Settings.util"; -import {GrpcEndpoint} from "../../../src/network/GrpcEndpoint"; +import { GrpcEndpoint } from "../../../src/network/GrpcEndpoint"; describe("Client.util", () => { describe("addMetadataToMap", () => { From 16ccf46a4d1b01a787143475b8592f3edd97b6e9 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 5 Dec 2023 10:50:55 +0000 Subject: [PATCH 14/22] Apply suggestions from code review Co-authored-by: Shubham Sharma Signed-off-by: Elena Kolevska --- src/implementation/Client/DaprClient.ts | 2 +- .../Client/GRPCClient/GRPCClient.ts | 16 ++++++++-------- src/network/HttpEndpoint.ts | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/implementation/Client/DaprClient.ts b/src/implementation/Client/DaprClient.ts index b66f562c..72e0ff09 100644 --- a/src/implementation/Client/DaprClient.ts +++ b/src/implementation/Client/DaprClient.ts @@ -151,7 +151,7 @@ export default class DaprClient { this.options = { daprHost: options.daprHost, daprPort: options.daprPort, - communicationProtocol: options.communicationProtocol ?? Settings.getDefaultCommunicationProtocol(), + communicationProtocol: this.daprClient.options.communicationProtocol, isKeepAlive: options.isKeepAlive, logger: options.logger, actor: options.actor, diff --git a/src/implementation/Client/GRPCClient/GRPCClient.ts b/src/implementation/Client/GRPCClient/GRPCClient.ts index 82ddcb6c..9ea4447f 100644 --- a/src/implementation/Client/GRPCClient/GRPCClient.ts +++ b/src/implementation/Client/GRPCClient/GRPCClient.ts @@ -79,16 +79,16 @@ export default class GRPCClient implements IClient { } private generateEndpoint(options: Partial): GrpcEndpoint { - let host = Settings.getDefaultHost(); - let port = Settings.getDefaultPort(communicationProtocolEnum.GRPC); + let host = options?.daprHost ?? Settings.getDefaultHost(); + let port = options?.daprPort ?? Settings.getDefaultGrpcPort(); let uri = `${host}:${port}`; - if (options?.daprHost || options?.daprPort) { - host = options?.daprHost ?? Settings.getDefaultHost(); - port = options?.daprPort ?? Settings.getDefaultPort(communicationProtocolEnum.GRPC); - uri = `${host}:${port}`; - } else if (Settings.getDefaultGrpcEndpoint() != "") { - uri = Settings.getDefaultGrpcEndpoint(); + if (!(options?.daprHost || options?.daprPort)) { + // If neither host nor port are specified, check the endpoint environment variable. + let endpoint = Settings.getDefaultGrpcEndpoint(); + if (endpoint != "") { + uri = endpoint; + } } return new GrpcEndpoint(uri); diff --git a/src/network/HttpEndpoint.ts b/src/network/HttpEndpoint.ts index 40db69e7..e6509c23 100644 --- a/src/network/HttpEndpoint.ts +++ b/src/network/HttpEndpoint.ts @@ -36,10 +36,10 @@ export class HttpEndpoint extends Endpoint { // if they are not specified so that the URL class can parse it private preprocessUri(url: string) { if (url.startsWith(":")) { - url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; +return URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; } if (!url.includes("://")) { - url = URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + url; +return URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + url; } return url; } From f596f5c7220af4fc66d753603d0e4fba81fe4f13 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 5 Dec 2023 11:10:03 +0000 Subject: [PATCH 15/22] Adds changes from review Signed-off-by: Elena Kolevska --- src/implementation/Client/DaprClient.ts | 8 +- .../Client/GRPCClient/GRPCClient.ts | 10 +- .../Client/HTTPClient/HTTPClient.ts | 20 +- src/network/GrpcEndpoint.ts | 2 +- src/network/HttpEndpoint.ts | 2 +- .../{Network.util.ts => Network.consts.ts} | 0 test/unit/utils/Client.util.test.ts | 424 ----------------- test/unit/utils/Network.test.ts | 439 ++++++++++++++++++ 8 files changed, 458 insertions(+), 447 deletions(-) rename src/network/{Network.util.ts => Network.consts.ts} (100%) create mode 100644 test/unit/utils/Network.test.ts diff --git a/src/implementation/Client/DaprClient.ts b/src/implementation/Client/DaprClient.ts index 72e0ff09..3a3b1ad6 100644 --- a/src/implementation/Client/DaprClient.ts +++ b/src/implementation/Client/DaprClient.ts @@ -103,8 +103,6 @@ export default class DaprClient { switch (options.communicationProtocol) { case CommunicationProtocolEnum.GRPC: { const client = new GRPCClient(options); - options.daprHost = client.options.daprHost; - options.daprPort = client.options.daprPort; this.daprClient = client; this.state = new GRPCClientState(client); @@ -126,8 +124,6 @@ export default class DaprClient { case CommunicationProtocolEnum.HTTP: default: { const client = new HTTPClient(options); - options.daprHost = client.options.daprHost; - options.daprPort = client.options.daprPort; this.daprClient = client; this.actor = new HTTPClientActor(client); // we use an abstractor here since we interface through a builder with the Actor Runtime @@ -149,8 +145,8 @@ export default class DaprClient { } this.options = { - daprHost: options.daprHost, - daprPort: options.daprPort, + daprHost: this.daprClient.options.daprHost, + daprPort: this.daprClient.options.daprPort, communicationProtocol: this.daprClient.options.communicationProtocol, isKeepAlive: options.isKeepAlive, logger: options.logger, diff --git a/src/implementation/Client/GRPCClient/GRPCClient.ts b/src/implementation/Client/GRPCClient/GRPCClient.ts index 9ea4447f..a73b51f2 100644 --- a/src/implementation/Client/GRPCClient/GRPCClient.ts +++ b/src/implementation/Client/GRPCClient/GRPCClient.ts @@ -37,8 +37,8 @@ export default class GRPCClient implements IClient { this.daprEndpoint = this.generateEndpoint(options); this.options = { - daprHost: options?.daprHost || this.daprEndpoint.hostname, - daprPort: options?.daprPort || this.daprEndpoint.port, + daprHost: this.daprEndpoint.hostname, + daprPort: this.daprEndpoint.port, communicationProtocol: communicationProtocolEnum.GRPC, isKeepAlive: options?.isKeepAlive, logger: options?.logger, @@ -79,13 +79,13 @@ export default class GRPCClient implements IClient { } private generateEndpoint(options: Partial): GrpcEndpoint { - let host = options?.daprHost ?? Settings.getDefaultHost(); - let port = options?.daprPort ?? Settings.getDefaultGrpcPort(); + const host = options?.daprHost ?? Settings.getDefaultHost(); + const port = options?.daprPort ?? Settings.getDefaultGrpcPort(); let uri = `${host}:${port}`; if (!(options?.daprHost || options?.daprPort)) { // If neither host nor port are specified, check the endpoint environment variable. - let endpoint = Settings.getDefaultGrpcEndpoint(); + const endpoint = Settings.getDefaultGrpcEndpoint(); if (endpoint != "") { uri = endpoint; } diff --git a/src/implementation/Client/HTTPClient/HTTPClient.ts b/src/implementation/Client/HTTPClient/HTTPClient.ts index cd797b6f..eb08d83f 100644 --- a/src/implementation/Client/HTTPClient/HTTPClient.ts +++ b/src/implementation/Client/HTTPClient/HTTPClient.ts @@ -42,8 +42,8 @@ export default class HTTPClient implements IClient { this.daprEndpoint = this.generateEndpoint(options); this.options = { - daprHost: options?.daprHost || this.daprEndpoint.hostname, - daprPort: options?.daprPort || this.daprEndpoint.port, + daprHost: this.daprEndpoint.hostname, + daprPort: this.daprEndpoint.port, communicationProtocol: communicationProtocolEnum.HTTP, isKeepAlive: options?.isKeepAlive, logger: options?.logger, @@ -75,16 +75,16 @@ export default class HTTPClient implements IClient { } private generateEndpoint(options: Partial): HttpEndpoint { - let host = Settings.getDefaultHost(); - let port = Settings.getDefaultPort(communicationProtocolEnum.HTTP); + const host = options?.daprHost ?? Settings.getDefaultHost(); + const port = options?.daprPort ?? Settings.getDefaultHttpPort(); let uri = `${host}:${port}`; - if (options?.daprHost || options?.daprPort) { - host = options?.daprHost ?? Settings.getDefaultHost(); - port = options?.daprPort ?? Settings.getDefaultPort(communicationProtocolEnum.HTTP); - uri = `${host}:${port}`; - } else if (Settings.getDefaultHttpEndpoint() != "") { - uri = Settings.getDefaultHttpEndpoint(); + if (!(options?.daprHost || options?.daprPort)) { + // If neither host nor port are specified, check the endpoint environment variable. + const endpoint = Settings.getDefaultHttpEndpoint(); + if (endpoint != "") { + uri = endpoint; + } } return new HttpEndpoint(uri); diff --git a/src/network/GrpcEndpoint.ts b/src/network/GrpcEndpoint.ts index d666dd9e..a7c84893 100644 --- a/src/network/GrpcEndpoint.ts +++ b/src/network/GrpcEndpoint.ts @@ -12,7 +12,7 @@ limitations under the License. */ import { Endpoint } from "./AbstractEndpoint"; -import { URIParseConfig } from "./Network.util"; +import { URIParseConfig } from "./Network.consts"; import { URL, URLSearchParams } from "url"; export class GrpcEndpoint extends Endpoint { diff --git a/src/network/HttpEndpoint.ts b/src/network/HttpEndpoint.ts index e6509c23..5a52bb03 100644 --- a/src/network/HttpEndpoint.ts +++ b/src/network/HttpEndpoint.ts @@ -13,7 +13,7 @@ limitations under the License. import { Endpoint } from "./AbstractEndpoint"; import { URL } from "url"; -import { URIParseConfig } from "./Network.util"; +import { URIParseConfig } from "./Network.consts"; export class HttpEndpoint extends Endpoint { constructor(url: string) { diff --git a/src/network/Network.util.ts b/src/network/Network.consts.ts similarity index 100% rename from src/network/Network.util.ts rename to src/network/Network.consts.ts diff --git a/test/unit/utils/Client.util.test.ts b/test/unit/utils/Client.util.test.ts index b94ca036..94992cca 100644 --- a/test/unit/utils/Client.util.test.ts +++ b/test/unit/utils/Client.util.test.ts @@ -25,10 +25,8 @@ import { Map } from "google-protobuf"; import { PubSubBulkPublishEntry } from "../../../src/types/pubsub/PubSubBulkPublishEntry.type"; import { PubSubBulkPublishApiResponse } from "../../../src/types/pubsub/PubSubBulkPublishApiResponse.type"; import { CommunicationProtocolEnum, DaprClientOptions, LogLevel } from "../../../src"; -import { HttpEndpoint } from "../../../build/utils/Client.util"; import { DaprClient } from "../../../src"; import { Settings } from "../../../src/utils/Settings.util"; -import { GrpcEndpoint } from "../../../src/network/GrpcEndpoint"; describe("Client.util", () => { describe("addMetadataToMap", () => { @@ -411,428 +409,6 @@ describe("Client.util", () => { }); }); - describe("parse GRPC Endpoint", () => { - const testCases = [ - // Port only - { - url: ":5000", - error: false, - secure: false, - scheme: "", - host: "localhost", - port: 5000, - endpoint: "dns:localhost:5000", - }, - { - url: ":5000?tls=false", - error: false, - secure: false, - scheme: "", - host: "localhost", - port: 5000, - endpoint: "dns:localhost:5000", - }, - { - url: ":5000?tls=true", - error: false, - secure: true, - scheme: "", - host: "localhost", - port: 5000, - endpoint: "dns:localhost:5000", - }, - // Host only - { - url: "myhost", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "myhost?tls=false", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "myhost?tls=true", - error: false, - secure: true, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - // Host and port - { - url: "myhost:443", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "myhost:443?tls=false", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "myhost:443?tls=true", - error: false, - secure: true, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - // Scheme, host and port - { - url: "http://myhost", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { url: "http://myhost?tls=false", error: true }, - { url: "http://myhost?tls=true", error: true }, - { - url: "http://myhost:443", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { url: "http://myhost:443?tls=false", error: true }, - { url: "http://myhost:443?tls=true", error: true }, - { - url: "http://myhost:5000", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 5000, - endpoint: "dns:myhost:5000", - }, - { url: "http://myhost:5000?tls=false", error: true }, - { url: "http://myhost:5000?tls=true", error: true }, - { - url: "https://myhost:443", - error: false, - secure: true, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { url: "https://myhost:443?tls=false", error: true }, - { url: "https://myhost:443?tls=true", error: true }, - // Scheme = dns - { - url: "dns:myhost", - error: false, - secure: false, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "dns:myhost?tls=false", - error: false, - secure: false, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "dns:myhost?tls=true", - error: false, - secure: true, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - // Scheme = dns with authority - { - url: "dns://myauthority:53/myhost", - error: false, - secure: false, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns://myauthority:53/myhost:443", - }, - { - url: "dns://myauthority:53/myhost?tls=false", - error: false, - secure: false, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns://myauthority:53/myhost:443", - }, - { - url: "dns://myauthority:53/myhost?tls=true", - error: false, - secure: true, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns://myauthority:53/myhost:443", - }, - { url: "dns://myhost", error: true }, - // Unix sockets - { - url: "unix:my.sock", - error: false, - secure: false, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix:my.sock", - }, - { - url: "unix:my.sock?tls=true", - error: false, - secure: true, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix:my.sock", - }, - // Unix sockets with absolute path - { - url: "unix://my.sock", - error: false, - secure: false, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix://my.sock", - }, - { - url: "unix://my.sock?tls=true", - error: false, - secure: true, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix://my.sock", - }, - // Unix abstract sockets - { - url: "unix-abstract:my.sock", - error: false, - secure: false, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix-abstract:my.sock", - }, - { - url: "unix-abstract:my.sock?tls=true", - error: false, - secure: true, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix-abstract:my.sock", - }, - // Vsock - { - url: "vsock:mycid", - error: false, - secure: false, - scheme: "vsock", - host: "mycid", - port: "443", - endpoint: "vsock:mycid:443", - }, - { - url: "vsock:mycid:5000", - error: false, - secure: false, - scheme: "vsock", - host: "mycid", - port: 5000, - endpoint: "vsock:mycid:5000", - }, - { - url: "vsock:mycid:5000?tls=true", - error: false, - secure: true, - scheme: "vsock", - host: "mycid", - port: 5000, - endpoint: "vsock:mycid:5000", - }, - - // IPv6 addresses - { - url: "[2001:db8:1f70:0:999:de8:7648:6e8]", - error: false, - secure: false, - scheme: "", - host: "[2001:db8:1f70:0:999:de8:7648:6e8]", - port: 443, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", - }, - { - url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]", - error: false, - secure: false, - scheme: "", - host: "[2001:db8:1f70:0:999:de8:7648:6e8]", - port: 443, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", - }, - { - url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", - error: false, - secure: false, - scheme: "", - host: "[2001:db8:1f70:0:999:de8:7648:6e8]", - port: 5000, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", - }, - { - url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000?abc=[]", - error: true, - }, - { - url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]", - error: false, - secure: true, - scheme: "", - host: "[2001:db8:1f70:0:999:de8:7648:6e8]", - port: 443, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", - }, - { - url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]:5000", - error: false, - secure: true, - scheme: "", - host: "[2001:db8:1f70:0:999:de8:7648:6e8]", - port: 5000, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", - }, - - // Invalid addresses (with path and queries) - { url: "host:5000/v1/dapr", error: true }, - { url: "host:5000/?a=1", error: true }, - - // Invalid scheme - { url: "inv-scheme://myhost", error: true }, - { url: "inv-scheme:myhost:5000", error: true }, - ]; - - testCases.forEach((testCase) => { - test(`Testing URL: ${testCase.url}`, () => { - if (testCase.error) { - expect(() => new GrpcEndpoint(testCase.url)).toThrow(Error); - } else { - const url = new GrpcEndpoint(testCase.url); - expect(url.endpoint).toBe(testCase.endpoint); - expect(url.tls).toBe(testCase.secure); - expect(url.hostname).toBe(testCase.host); - expect(url.port).toBe(String(testCase.port)); - } - }); - }); - }); - - describe("parse HTTP Endpoint", () => { - const testCases = [ - { - url: "myhost", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 80, - endpoint: "http://myhost:80", - }, - { - url: "http://myhost", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 80, - endpoint: "http://myhost:80", - }, - { - url: "http://myhost:443", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "http://myhost:443", - }, - { - url: "http://myhost:5000", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 5000, - endpoint: "http://myhost:5000", - }, - { - url: "https://myhost:443", - error: false, - secure: true, - scheme: "", - host: "myhost", - port: 443, - endpoint: "https://myhost:443", - }, - { - url: "https://myhost:5000", - error: false, - secure: true, - scheme: "", - host: "myhost", - port: 5000, - endpoint: "https://myhost:5000", - }, - ]; - - testCases.forEach((testCase) => { - test(`Testing URL: ${testCase.url}`, () => { - if (testCase.error) { - expect(() => new HttpEndpoint(testCase.url)).toThrow(Error); - } else { - const url = new HttpEndpoint(testCase.url); - expect(url.endpoint).toBe(testCase.endpoint); - expect(url.tls).toBe(testCase.secure); - expect(url.hostname).toBe(testCase.host); - expect(url.port).toBe(String(testCase.port)); - } - }); - }); - }); - describe("test correct client instantiation", () => { let client: DaprClient; const daprHost = "127.0.0.1"; diff --git a/test/unit/utils/Network.test.ts b/test/unit/utils/Network.test.ts new file mode 100644 index 00000000..48c5499c --- /dev/null +++ b/test/unit/utils/Network.test.ts @@ -0,0 +1,439 @@ +/* +Copyright 2022 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import {GrpcEndpoint} from "../../../src/network/GrpcEndpoint"; +import {HttpEndpoint} from "../../../build/utils/Client.util"; + +describe("Client.util", () => { + describe("parse GRPC Endpoint", () => { + const testCases = [ + // Port only + { + url: ":5000", + error: false, + secure: false, + scheme: "", + host: "localhost", + port: 5000, + endpoint: "dns:localhost:5000", + }, + { + url: ":5000?tls=false", + error: false, + secure: false, + scheme: "", + host: "localhost", + port: 5000, + endpoint: "dns:localhost:5000", + }, + { + url: ":5000?tls=true", + error: false, + secure: true, + scheme: "", + host: "localhost", + port: 5000, + endpoint: "dns:localhost:5000", + }, + // Host only + { + url: "myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "myhost?tls=false", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "myhost?tls=true", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + // Host and port + { + url: "myhost:443", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "myhost:443?tls=false", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "myhost:443?tls=true", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + // Scheme, host and port + { + url: "http://myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + {url: "http://myhost?tls=false", error: true}, + {url: "http://myhost?tls=true", error: true}, + { + url: "http://myhost:443", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + {url: "http://myhost:443?tls=false", error: true}, + {url: "http://myhost:443?tls=true", error: true}, + { + url: "http://myhost:5000", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 5000, + endpoint: "dns:myhost:5000", + }, + {url: "http://myhost:5000?tls=false", error: true}, + {url: "http://myhost:5000?tls=true", error: true}, + { + url: "https://myhost:443", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + {url: "https://myhost:443?tls=false", error: true}, + {url: "https://myhost:443?tls=true", error: true}, + // Scheme = dns + { + url: "dns:myhost", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "dns:myhost?tls=false", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "dns:myhost?tls=true", + error: false, + secure: true, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + // Scheme = dns with authority + { + url: "dns://myauthority:53/myhost", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns://myauthority:53/myhost:443", + }, + { + url: "dns://myauthority:53/myhost?tls=false", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns://myauthority:53/myhost:443", + }, + { + url: "dns://myauthority:53/myhost?tls=true", + error: false, + secure: true, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns://myauthority:53/myhost:443", + }, + {url: "dns://myhost", error: true}, + // Unix sockets + { + url: "unix:my.sock", + error: false, + secure: false, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix:my.sock", + }, + { + url: "unix:my.sock?tls=true", + error: false, + secure: true, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix:my.sock", + }, + // Unix sockets with absolute path + { + url: "unix://my.sock", + error: false, + secure: false, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix://my.sock", + }, + { + url: "unix://my.sock?tls=true", + error: false, + secure: true, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix://my.sock", + }, + // Unix abstract sockets + { + url: "unix-abstract:my.sock", + error: false, + secure: false, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix-abstract:my.sock", + }, + { + url: "unix-abstract:my.sock?tls=true", + error: false, + secure: true, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix-abstract:my.sock", + }, + // Vsock + { + url: "vsock:mycid", + error: false, + secure: false, + scheme: "vsock", + host: "mycid", + port: "443", + endpoint: "vsock:mycid:443", + }, + { + url: "vsock:mycid:5000", + error: false, + secure: false, + scheme: "vsock", + host: "mycid", + port: 5000, + endpoint: "vsock:mycid:5000", + }, + { + url: "vsock:mycid:5000?tls=true", + error: false, + secure: true, + scheme: "vsock", + host: "mycid", + port: 5000, + endpoint: "vsock:mycid:5000", + }, + + // IPv6 addresses + { + url: "[2001:db8:1f70:0:999:de8:7648:6e8]", + error: false, + secure: false, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 443, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", + }, + { + url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]", + error: false, + secure: false, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 443, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", + }, + { + url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + error: false, + secure: false, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 5000, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + }, + { + url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000?abc=[]", + error: true, + }, + { + url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]", + error: false, + secure: true, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 443, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", + }, + { + url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + error: false, + secure: true, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 5000, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + }, + + // Invalid addresses (with path and queries) + {url: "host:5000/v1/dapr", error: true}, + {url: "host:5000/?a=1", error: true}, + + // Invalid scheme + {url: "inv-scheme://myhost", error: true}, + {url: "inv-scheme:myhost:5000", error: true}, + ]; + + testCases.forEach((testCase) => { + test(`Testing URL: ${testCase.url}`, () => { + if (testCase.error) { + expect(() => new GrpcEndpoint(testCase.url)).toThrow(Error); + } else { + const url = new GrpcEndpoint(testCase.url); + expect(url.endpoint).toBe(testCase.endpoint); + expect(url.tls).toBe(testCase.secure); + expect(url.hostname).toBe(testCase.host); + expect(url.port).toBe(String(testCase.port)); + } + }); + }); + }); + + describe("parse HTTP Endpoint", () => { + const testCases = [ + { + url: "myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 80, + endpoint: "http://myhost:80", + }, + { + url: "http://myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 80, + endpoint: "http://myhost:80", + }, + { + url: "http://myhost:443", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "http://myhost:443", + }, + { + url: "http://myhost:5000", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 5000, + endpoint: "http://myhost:5000", + }, + { + url: "https://myhost:443", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "https://myhost:443", + }, + { + url: "https://myhost:5000", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 5000, + endpoint: "https://myhost:5000", + }, + ]; + + testCases.forEach((testCase) => { + test(`Testing URL: ${testCase.url}`, () => { + if (testCase.error) { + expect(() => new HttpEndpoint(testCase.url)).toThrow(Error); + } else { + const url = new HttpEndpoint(testCase.url); + expect(url.endpoint).toBe(testCase.endpoint); + expect(url.tls).toBe(testCase.secure); + expect(url.hostname).toBe(testCase.host); + expect(url.port).toBe(String(testCase.port)); + } + }); + }); + }) +}); \ No newline at end of file From b2aa80970e0ba4134d40f894573ae517545674da Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 5 Dec 2023 11:14:44 +0000 Subject: [PATCH 16/22] Apply suggestions from code review Co-authored-by: Shubham Sharma Signed-off-by: Elena Kolevska --- src/network/AbstractEndpoint.ts | 3 --- src/network/HttpEndpoint.ts | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/network/AbstractEndpoint.ts b/src/network/AbstractEndpoint.ts index 2e4f5660..3d76da7f 100644 --- a/src/network/AbstractEndpoint.ts +++ b/src/network/AbstractEndpoint.ts @@ -11,17 +11,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { URL } from "url"; export abstract class Endpoint { protected _scheme = ""; protected _hostname = ""; protected _port = 0; protected _tls = false; - protected _authority = ""; protected _url: string; protected _endpoint = ""; - protected _parsedUrl!: URL; protected constructor(url: string) { this._url = url; diff --git a/src/network/HttpEndpoint.ts b/src/network/HttpEndpoint.ts index 5a52bb03..c079db35 100644 --- a/src/network/HttpEndpoint.ts +++ b/src/network/HttpEndpoint.ts @@ -20,7 +20,7 @@ export class HttpEndpoint extends Endpoint { super(url); try { - this._parsedUrl = new URL(this.preprocessUri(url)); + const parsedUri = new URL(HttpEndpoint.preprocessUri(url)); this._scheme = this._parsedUrl.protocol.replace(":", ""); this._hostname = this._parsedUrl.hostname.replace("[", ""); this._hostname = this._hostname.replace("]", ""); @@ -34,7 +34,7 @@ export class HttpEndpoint extends Endpoint { // We need to add a default scheme and hostname to the url // if they are not specified so that the URL class can parse it - private preprocessUri(url: string) { + private static preprocessUri(url: string) { if (url.startsWith(":")) { return URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; } From 3bbb028ca297dd07cc2a8ed875412f77bb8f171d Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 5 Dec 2023 12:11:52 +0000 Subject: [PATCH 17/22] WIP adding comments from review Signed-off-by: Elena Kolevska --- src/network/GrpcEndpoint.ts | 50 +++++++++++++++++++------------------ src/network/HttpEndpoint.ts | 14 ++++++----- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/src/network/GrpcEndpoint.ts b/src/network/GrpcEndpoint.ts index a7c84893..f153f7bf 100644 --- a/src/network/GrpcEndpoint.ts +++ b/src/network/GrpcEndpoint.ts @@ -16,17 +16,19 @@ import { URIParseConfig } from "./Network.consts"; import { URL, URLSearchParams } from "url"; export class GrpcEndpoint extends Endpoint { + private _authority = ""; + constructor(url: string) { super(url); this._authority = URIParseConfig.DEFAULT_AUTHORITY; - this._parsedUrl = new URL(this.preprocessUri(url)); - this.validatePathAndQuery(); + const parsedUrl = new URL(this.preprocessUri(url)); + this.validatePathAndQuery(parsedUrl); - this.setTls(); - this.setHostname(); - this.setScheme(); - this.setPort(); + this.setTls(parsedUrl); + this.setHostname(parsedUrl); + this.setScheme(parsedUrl); + this.setPort(parsedUrl); this.setEndpoint(); } @@ -81,48 +83,48 @@ export class GrpcEndpoint extends Endpoint { return url; } - private validatePathAndQuery(): void { - if (this._parsedUrl.pathname && this._parsedUrl.pathname !== "/") { - throw new Error(`Paths are not supported for gRPC endpoints: '${this._parsedUrl.pathname}'`); + private validatePathAndQuery(parsedUrl: URL): void { + if (parsedUrl.pathname && parsedUrl.pathname !== "/") { + throw new Error(`Paths are not supported for gRPC endpoints: '${parsedUrl.pathname}'`); } - const params = new URLSearchParams(this._parsedUrl.search); - if (params.has("tls") && (this._parsedUrl.protocol === "http:" || this._parsedUrl.protocol === "https:")) { - throw new Error(`The tls query parameter is not supported for http(s) endpoints: '${this._parsedUrl.search}'`); + const params = new URLSearchParams(parsedUrl.search); + if (params.has("tls") && (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:")) { + throw new Error(`The tls query parameter is not supported for http(s) endpoints: '${parsedUrl.search}'`); } params.delete("tls"); if (Array.from(params.keys()).length > 0) { - throw new Error(`Query parameters are not supported for gRPC endpoints: '${this._parsedUrl.search}'`); + throw new Error(`Query parameters are not supported for gRPC endpoints: '${parsedUrl.search}'`); } } - private setTls(): void { - const params = new URLSearchParams(this._parsedUrl.search); + private setTls(parsedUrl: URL): void { + const params = new URLSearchParams(parsedUrl.search); const tlsStr = params.get("tls") || ""; this._tls = tlsStr.toLowerCase() === "true"; - if (this._parsedUrl.protocol == "https:") { + if (parsedUrl.protocol == "https:") { this._tls = true; } } - private setHostname(): void { - if (!this._parsedUrl.hostname) { + private setHostname(parsedUrl: URL): void { + if (!parsedUrl.hostname) { this._hostname = URIParseConfig.DEFAULT_HOSTNAME; return; } - this._hostname = this._parsedUrl.hostname; + this._hostname = parsedUrl.hostname; } - private setScheme(): void { - if (!this._parsedUrl.protocol) { + private setScheme(parsedUrl: URL): void { + if (!parsedUrl.protocol) { this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; return; } - const scheme = this._parsedUrl.protocol.slice(0, -1); // Remove trailing ':' + const scheme = parsedUrl.protocol.slice(0, -1); // Remove trailing ':' if (scheme === "http" || scheme === "https") { this._scheme = URIParseConfig.DEFAULT_SCHEME_GRPC; console.warn("http and https schemes are deprecated, use grpc or grpcs instead"); @@ -136,13 +138,13 @@ export class GrpcEndpoint extends Endpoint { this._scheme = scheme; } - private setPort(): void { + private setPort(parsedUrl: URL): void { if (this._scheme === "unix" || this._scheme === "unix-abstract") { this._port = 0; return; } - this._port = this._parsedUrl.port ? parseInt(this._parsedUrl.port) : URIParseConfig.DEFAULT_PORT; + this._port = parsedUrl.port ? parseInt(parsedUrl.port) : URIParseConfig.DEFAULT_PORT; } private setEndpoint(): void { diff --git a/src/network/HttpEndpoint.ts b/src/network/HttpEndpoint.ts index c079db35..c61e7652 100644 --- a/src/network/HttpEndpoint.ts +++ b/src/network/HttpEndpoint.ts @@ -20,11 +20,11 @@ export class HttpEndpoint extends Endpoint { super(url); try { - const parsedUri = new URL(HttpEndpoint.preprocessUri(url)); - this._scheme = this._parsedUrl.protocol.replace(":", ""); - this._hostname = this._parsedUrl.hostname.replace("[", ""); + const parsedUrl = new URL(HttpEndpoint.preprocessUri(url)); + this._scheme = parsedUrl.protocol.replace(":", ""); + this._hostname = parsedUrl.hostname.replace("[", ""); this._hostname = this._hostname.replace("]", ""); - this._port = parseInt(this._parsedUrl.port) || (this._scheme == "https" ? 443 : 80); + this._port = parseInt(parsedUrl.port) || (this._scheme == "https" ? 443 : 80); this._tls = this._scheme == "https"; this._endpoint = this._scheme + "://" + this._hostname + ":" + this._port.toString(); } catch (error) { @@ -34,12 +34,14 @@ export class HttpEndpoint extends Endpoint { // We need to add a default scheme and hostname to the url // if they are not specified so that the URL class can parse it + // Ex: 127.0.0.1 -> http://127.0.0.1 + // Ex: :5000 -> http://127.0.0.1:5000 private static preprocessUri(url: string) { if (url.startsWith(":")) { -return URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; + return URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + URIParseConfig.DEFAULT_HOSTNAME + url; } if (!url.includes("://")) { -return URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + url; + return URIParseConfig.DEFAULT_SCHEME_HTTP + "://" + url; } return url; } From 98a0492b7af9f2092e5e1e830ff1694c7a2a9485 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 5 Dec 2023 12:45:25 +0000 Subject: [PATCH 18/22] Fixes linter Signed-off-by: Elena Kolevska --- src/network/AbstractEndpoint.ts | 1 - test/unit/utils/Network.test.ts | 834 ++++++++++++++++---------------- 2 files changed, 417 insertions(+), 418 deletions(-) diff --git a/src/network/AbstractEndpoint.ts b/src/network/AbstractEndpoint.ts index 3d76da7f..47f905ba 100644 --- a/src/network/AbstractEndpoint.ts +++ b/src/network/AbstractEndpoint.ts @@ -11,7 +11,6 @@ See the License for the specific language governing permissions and limitations under the License. */ - export abstract class Endpoint { protected _scheme = ""; protected _hostname = ""; diff --git a/test/unit/utils/Network.test.ts b/test/unit/utils/Network.test.ts index 48c5499c..76f13514 100644 --- a/test/unit/utils/Network.test.ts +++ b/test/unit/utils/Network.test.ts @@ -11,429 +11,429 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {GrpcEndpoint} from "../../../src/network/GrpcEndpoint"; -import {HttpEndpoint} from "../../../build/utils/Client.util"; +import { GrpcEndpoint } from "../../../src/network/GrpcEndpoint"; +import { HttpEndpoint } from "../../../build/utils/Client.util"; describe("Client.util", () => { - describe("parse GRPC Endpoint", () => { - const testCases = [ - // Port only - { - url: ":5000", - error: false, - secure: false, - scheme: "", - host: "localhost", - port: 5000, - endpoint: "dns:localhost:5000", - }, - { - url: ":5000?tls=false", - error: false, - secure: false, - scheme: "", - host: "localhost", - port: 5000, - endpoint: "dns:localhost:5000", - }, - { - url: ":5000?tls=true", - error: false, - secure: true, - scheme: "", - host: "localhost", - port: 5000, - endpoint: "dns:localhost:5000", - }, - // Host only - { - url: "myhost", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "myhost?tls=false", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "myhost?tls=true", - error: false, - secure: true, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - // Host and port - { - url: "myhost:443", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "myhost:443?tls=false", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "myhost:443?tls=true", - error: false, - secure: true, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - // Scheme, host and port - { - url: "http://myhost", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - {url: "http://myhost?tls=false", error: true}, - {url: "http://myhost?tls=true", error: true}, - { - url: "http://myhost:443", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - {url: "http://myhost:443?tls=false", error: true}, - {url: "http://myhost:443?tls=true", error: true}, - { - url: "http://myhost:5000", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 5000, - endpoint: "dns:myhost:5000", - }, - {url: "http://myhost:5000?tls=false", error: true}, - {url: "http://myhost:5000?tls=true", error: true}, - { - url: "https://myhost:443", - error: false, - secure: true, - scheme: "", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - {url: "https://myhost:443?tls=false", error: true}, - {url: "https://myhost:443?tls=true", error: true}, - // Scheme = dns - { - url: "dns:myhost", - error: false, - secure: false, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "dns:myhost?tls=false", - error: false, - secure: false, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - { - url: "dns:myhost?tls=true", - error: false, - secure: true, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns:myhost:443", - }, - // Scheme = dns with authority - { - url: "dns://myauthority:53/myhost", - error: false, - secure: false, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns://myauthority:53/myhost:443", - }, - { - url: "dns://myauthority:53/myhost?tls=false", - error: false, - secure: false, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns://myauthority:53/myhost:443", - }, - { - url: "dns://myauthority:53/myhost?tls=true", - error: false, - secure: true, - scheme: "dns", - host: "myhost", - port: 443, - endpoint: "dns://myauthority:53/myhost:443", - }, - {url: "dns://myhost", error: true}, - // Unix sockets - { - url: "unix:my.sock", - error: false, - secure: false, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix:my.sock", - }, - { - url: "unix:my.sock?tls=true", - error: false, - secure: true, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix:my.sock", - }, - // Unix sockets with absolute path - { - url: "unix://my.sock", - error: false, - secure: false, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix://my.sock", - }, - { - url: "unix://my.sock?tls=true", - error: false, - secure: true, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix://my.sock", - }, - // Unix abstract sockets - { - url: "unix-abstract:my.sock", - error: false, - secure: false, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix-abstract:my.sock", - }, - { - url: "unix-abstract:my.sock?tls=true", - error: false, - secure: true, - scheme: "unix", - host: "my.sock", - port: "", - endpoint: "unix-abstract:my.sock", - }, - // Vsock - { - url: "vsock:mycid", - error: false, - secure: false, - scheme: "vsock", - host: "mycid", - port: "443", - endpoint: "vsock:mycid:443", - }, - { - url: "vsock:mycid:5000", - error: false, - secure: false, - scheme: "vsock", - host: "mycid", - port: 5000, - endpoint: "vsock:mycid:5000", - }, - { - url: "vsock:mycid:5000?tls=true", - error: false, - secure: true, - scheme: "vsock", - host: "mycid", - port: 5000, - endpoint: "vsock:mycid:5000", - }, + describe("parse GRPC Endpoint", () => { + const testCases = [ + // Port only + { + url: ":5000", + error: false, + secure: false, + scheme: "", + host: "localhost", + port: 5000, + endpoint: "dns:localhost:5000", + }, + { + url: ":5000?tls=false", + error: false, + secure: false, + scheme: "", + host: "localhost", + port: 5000, + endpoint: "dns:localhost:5000", + }, + { + url: ":5000?tls=true", + error: false, + secure: true, + scheme: "", + host: "localhost", + port: 5000, + endpoint: "dns:localhost:5000", + }, + // Host only + { + url: "myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "myhost?tls=false", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "myhost?tls=true", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + // Host and port + { + url: "myhost:443", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "myhost:443?tls=false", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "myhost:443?tls=true", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + // Scheme, host and port + { + url: "http://myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { url: "http://myhost?tls=false", error: true }, + { url: "http://myhost?tls=true", error: true }, + { + url: "http://myhost:443", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { url: "http://myhost:443?tls=false", error: true }, + { url: "http://myhost:443?tls=true", error: true }, + { + url: "http://myhost:5000", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 5000, + endpoint: "dns:myhost:5000", + }, + { url: "http://myhost:5000?tls=false", error: true }, + { url: "http://myhost:5000?tls=true", error: true }, + { + url: "https://myhost:443", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { url: "https://myhost:443?tls=false", error: true }, + { url: "https://myhost:443?tls=true", error: true }, + // Scheme = dns + { + url: "dns:myhost", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "dns:myhost?tls=false", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + { + url: "dns:myhost?tls=true", + error: false, + secure: true, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns:myhost:443", + }, + // Scheme = dns with authority + { + url: "dns://myauthority:53/myhost", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns://myauthority:53/myhost:443", + }, + { + url: "dns://myauthority:53/myhost?tls=false", + error: false, + secure: false, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns://myauthority:53/myhost:443", + }, + { + url: "dns://myauthority:53/myhost?tls=true", + error: false, + secure: true, + scheme: "dns", + host: "myhost", + port: 443, + endpoint: "dns://myauthority:53/myhost:443", + }, + { url: "dns://myhost", error: true }, + // Unix sockets + { + url: "unix:my.sock", + error: false, + secure: false, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix:my.sock", + }, + { + url: "unix:my.sock?tls=true", + error: false, + secure: true, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix:my.sock", + }, + // Unix sockets with absolute path + { + url: "unix://my.sock", + error: false, + secure: false, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix://my.sock", + }, + { + url: "unix://my.sock?tls=true", + error: false, + secure: true, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix://my.sock", + }, + // Unix abstract sockets + { + url: "unix-abstract:my.sock", + error: false, + secure: false, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix-abstract:my.sock", + }, + { + url: "unix-abstract:my.sock?tls=true", + error: false, + secure: true, + scheme: "unix", + host: "my.sock", + port: "", + endpoint: "unix-abstract:my.sock", + }, + // Vsock + { + url: "vsock:mycid", + error: false, + secure: false, + scheme: "vsock", + host: "mycid", + port: "443", + endpoint: "vsock:mycid:443", + }, + { + url: "vsock:mycid:5000", + error: false, + secure: false, + scheme: "vsock", + host: "mycid", + port: 5000, + endpoint: "vsock:mycid:5000", + }, + { + url: "vsock:mycid:5000?tls=true", + error: false, + secure: true, + scheme: "vsock", + host: "mycid", + port: 5000, + endpoint: "vsock:mycid:5000", + }, - // IPv6 addresses - { - url: "[2001:db8:1f70:0:999:de8:7648:6e8]", - error: false, - secure: false, - scheme: "", - host: "[2001:db8:1f70:0:999:de8:7648:6e8]", - port: 443, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", - }, - { - url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]", - error: false, - secure: false, - scheme: "", - host: "[2001:db8:1f70:0:999:de8:7648:6e8]", - port: 443, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", - }, - { - url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", - error: false, - secure: false, - scheme: "", - host: "[2001:db8:1f70:0:999:de8:7648:6e8]", - port: 5000, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", - }, - { - url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000?abc=[]", - error: true, - }, - { - url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]", - error: false, - secure: true, - scheme: "", - host: "[2001:db8:1f70:0:999:de8:7648:6e8]", - port: 443, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", - }, - { - url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]:5000", - error: false, - secure: true, - scheme: "", - host: "[2001:db8:1f70:0:999:de8:7648:6e8]", - port: 5000, - endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", - }, + // IPv6 addresses + { + url: "[2001:db8:1f70:0:999:de8:7648:6e8]", + error: false, + secure: false, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 443, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", + }, + { + url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]", + error: false, + secure: false, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 443, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", + }, + { + url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + error: false, + secure: false, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 5000, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + }, + { + url: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000?abc=[]", + error: true, + }, + { + url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]", + error: false, + secure: true, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 443, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:443", + }, + { + url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + error: false, + secure: true, + scheme: "", + host: "[2001:db8:1f70:0:999:de8:7648:6e8]", + port: 5000, + endpoint: "dns:[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + }, - // Invalid addresses (with path and queries) - {url: "host:5000/v1/dapr", error: true}, - {url: "host:5000/?a=1", error: true}, + // Invalid addresses (with path and queries) + { url: "host:5000/v1/dapr", error: true }, + { url: "host:5000/?a=1", error: true }, - // Invalid scheme - {url: "inv-scheme://myhost", error: true}, - {url: "inv-scheme:myhost:5000", error: true}, - ]; + // Invalid scheme + { url: "inv-scheme://myhost", error: true }, + { url: "inv-scheme:myhost:5000", error: true }, + ]; - testCases.forEach((testCase) => { - test(`Testing URL: ${testCase.url}`, () => { - if (testCase.error) { - expect(() => new GrpcEndpoint(testCase.url)).toThrow(Error); - } else { - const url = new GrpcEndpoint(testCase.url); - expect(url.endpoint).toBe(testCase.endpoint); - expect(url.tls).toBe(testCase.secure); - expect(url.hostname).toBe(testCase.host); - expect(url.port).toBe(String(testCase.port)); - } - }); - }); + testCases.forEach((testCase) => { + test(`Testing URL: ${testCase.url}`, () => { + if (testCase.error) { + expect(() => new GrpcEndpoint(testCase.url)).toThrow(Error); + } else { + const url = new GrpcEndpoint(testCase.url); + expect(url.endpoint).toBe(testCase.endpoint); + expect(url.tls).toBe(testCase.secure); + expect(url.hostname).toBe(testCase.host); + expect(url.port).toBe(String(testCase.port)); + } + }); }); + }); - describe("parse HTTP Endpoint", () => { - const testCases = [ - { - url: "myhost", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 80, - endpoint: "http://myhost:80", - }, - { - url: "http://myhost", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 80, - endpoint: "http://myhost:80", - }, - { - url: "http://myhost:443", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 443, - endpoint: "http://myhost:443", - }, - { - url: "http://myhost:5000", - error: false, - secure: false, - scheme: "", - host: "myhost", - port: 5000, - endpoint: "http://myhost:5000", - }, - { - url: "https://myhost:443", - error: false, - secure: true, - scheme: "", - host: "myhost", - port: 443, - endpoint: "https://myhost:443", - }, - { - url: "https://myhost:5000", - error: false, - secure: true, - scheme: "", - host: "myhost", - port: 5000, - endpoint: "https://myhost:5000", - }, - ]; + describe("parse HTTP Endpoint", () => { + const testCases = [ + { + url: "myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 80, + endpoint: "http://myhost:80", + }, + { + url: "http://myhost", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 80, + endpoint: "http://myhost:80", + }, + { + url: "http://myhost:443", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 443, + endpoint: "http://myhost:443", + }, + { + url: "http://myhost:5000", + error: false, + secure: false, + scheme: "", + host: "myhost", + port: 5000, + endpoint: "http://myhost:5000", + }, + { + url: "https://myhost:443", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 443, + endpoint: "https://myhost:443", + }, + { + url: "https://myhost:5000", + error: false, + secure: true, + scheme: "", + host: "myhost", + port: 5000, + endpoint: "https://myhost:5000", + }, + ]; - testCases.forEach((testCase) => { - test(`Testing URL: ${testCase.url}`, () => { - if (testCase.error) { - expect(() => new HttpEndpoint(testCase.url)).toThrow(Error); - } else { - const url = new HttpEndpoint(testCase.url); - expect(url.endpoint).toBe(testCase.endpoint); - expect(url.tls).toBe(testCase.secure); - expect(url.hostname).toBe(testCase.host); - expect(url.port).toBe(String(testCase.port)); - } - }); - }); - }) -}); \ No newline at end of file + testCases.forEach((testCase) => { + test(`Testing URL: ${testCase.url}`, () => { + if (testCase.error) { + expect(() => new HttpEndpoint(testCase.url)).toThrow(Error); + } else { + const url = new HttpEndpoint(testCase.url); + expect(url.endpoint).toBe(testCase.endpoint); + expect(url.tls).toBe(testCase.secure); + expect(url.hostname).toBe(testCase.host); + expect(url.port).toBe(String(testCase.port)); + } + }); + }); + }); +}); From daaf83ae6e43bafba8fa6df629f220acc5fb047a Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 5 Dec 2023 13:05:32 +0000 Subject: [PATCH 19/22] Fixes import Signed-off-by: Elena Kolevska --- test/unit/utils/Network.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/utils/Network.test.ts b/test/unit/utils/Network.test.ts index 76f13514..6f7d8cf9 100644 --- a/test/unit/utils/Network.test.ts +++ b/test/unit/utils/Network.test.ts @@ -12,7 +12,7 @@ limitations under the License. */ import { GrpcEndpoint } from "../../../src/network/GrpcEndpoint"; -import { HttpEndpoint } from "../../../build/utils/Client.util"; +import { HttpEndpoint } from "../../../src/network/HttpEndpoint"; describe("Client.util", () => { describe("parse GRPC Endpoint", () => { From bfdc37a5165dd22faa37d991abd718a1ad124535 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 5 Dec 2023 13:35:29 +0000 Subject: [PATCH 20/22] Accounts for ipv6 addresses for http endpoints Signed-off-by: Elena Kolevska --- src/network/HttpEndpoint.ts | 8 +++++--- test/unit/utils/Network.test.ts | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/network/HttpEndpoint.ts b/src/network/HttpEndpoint.ts index c61e7652..51218263 100644 --- a/src/network/HttpEndpoint.ts +++ b/src/network/HttpEndpoint.ts @@ -14,6 +14,7 @@ limitations under the License. import { Endpoint } from "./AbstractEndpoint"; import { URL } from "url"; import { URIParseConfig } from "./Network.consts"; +import {log} from "util"; export class HttpEndpoint extends Endpoint { constructor(url: string) { @@ -22,11 +23,12 @@ export class HttpEndpoint extends Endpoint { try { const parsedUrl = new URL(HttpEndpoint.preprocessUri(url)); this._scheme = parsedUrl.protocol.replace(":", ""); - this._hostname = parsedUrl.hostname.replace("[", ""); - this._hostname = this._hostname.replace("]", ""); + this._hostname = parsedUrl.hostname.replace(/[\]\[]/gi, ""); this._port = parseInt(parsedUrl.port) || (this._scheme == "https" ? 443 : 80); this._tls = this._scheme == "https"; - this._endpoint = this._scheme + "://" + this._hostname + ":" + this._port.toString(); + // Remove brackets if it's a IPv6 addresses + const hostPart = parsedUrl.hostname.includes("[") ? `[${this._hostname}]` : this._hostname; + this._endpoint = `${this._scheme}://${hostPart}:${this._port}`; } catch (error) { throw new Error(`Invalid address: ${url}`); } diff --git a/test/unit/utils/Network.test.ts b/test/unit/utils/Network.test.ts index 6f7d8cf9..0ec0c3bd 100644 --- a/test/unit/utils/Network.test.ts +++ b/test/unit/utils/Network.test.ts @@ -420,6 +420,15 @@ describe("Client.util", () => { port: 5000, endpoint: "https://myhost:5000", }, + { + url: "https://[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + error: false, + secure: true, + scheme: "", + host: "2001:db8:1f70:0:999:de8:7648:6e8", + port: 5000, + endpoint: "https://[2001:db8:1f70:0:999:de8:7648:6e8]:5000", + }, ]; testCases.forEach((testCase) => { From 6790d025e56835a41c64fb36cf0758999d83ca28 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 5 Dec 2023 13:42:53 +0000 Subject: [PATCH 21/22] Fix Signed-off-by: Elena Kolevska --- src/network/HttpEndpoint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/network/HttpEndpoint.ts b/src/network/HttpEndpoint.ts index 51218263..0e0fad76 100644 --- a/src/network/HttpEndpoint.ts +++ b/src/network/HttpEndpoint.ts @@ -14,7 +14,6 @@ limitations under the License. import { Endpoint } from "./AbstractEndpoint"; import { URL } from "url"; import { URIParseConfig } from "./Network.consts"; -import {log} from "util"; export class HttpEndpoint extends Endpoint { constructor(url: string) { @@ -23,7 +22,8 @@ export class HttpEndpoint extends Endpoint { try { const parsedUrl = new URL(HttpEndpoint.preprocessUri(url)); this._scheme = parsedUrl.protocol.replace(":", ""); - this._hostname = parsedUrl.hostname.replace(/[\]\[]/gi, ""); + var re = /[\]\[]/gi; + this._hostname = parsedUrl.hostname.replace(re, ""); this._port = parseInt(parsedUrl.port) || (this._scheme == "https" ? 443 : 80); this._tls = this._scheme == "https"; // Remove brackets if it's a IPv6 addresses From 1befe91c42462413eee57670113eaa4296584ae0 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Tue, 5 Dec 2023 13:50:27 +0000 Subject: [PATCH 22/22] Fixes linter Signed-off-by: Elena Kolevska --- src/network/HttpEndpoint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/network/HttpEndpoint.ts b/src/network/HttpEndpoint.ts index 0e0fad76..f9f32f0a 100644 --- a/src/network/HttpEndpoint.ts +++ b/src/network/HttpEndpoint.ts @@ -22,7 +22,7 @@ export class HttpEndpoint extends Endpoint { try { const parsedUrl = new URL(HttpEndpoint.preprocessUri(url)); this._scheme = parsedUrl.protocol.replace(":", ""); - var re = /[\]\[]/gi; + const re = /[\][]/gi; this._hostname = parsedUrl.hostname.replace(re, ""); this._port = parseInt(parsedUrl.port) || (this._scheme == "https" ? 443 : 80); this._tls = this._scheme == "https";