From 92dcc1a3853b31990ad9f55c678aa94466d1f893 Mon Sep 17 00:00:00 2001 From: Vladislav Polyakov <39828645+polRk@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:19:48 +0300 Subject: [PATCH] Allow user-provided User-Agent request header (#1272) --- packages/connect-web-bench/README.md | 8 ++--- packages/connect-web-bench/chart.svg | 10 +++--- .../protocol-connect/request-header.spec.ts | 17 +++++++++ .../src/protocol-connect/request-header.ts | 7 +++- .../protocol-grpc-web/request-header.spec.ts | 36 +++++++++++++++++++ .../src/protocol-grpc-web/request-header.ts | 11 ++++-- .../src/protocol-grpc/request-header.spec.ts | 11 ++++++ .../src/protocol-grpc/request-header.ts | 13 ++++--- 8 files changed, 97 insertions(+), 16 deletions(-) diff --git a/packages/connect-web-bench/README.md b/packages/connect-web-bench/README.md index c25bf6564..068f509f4 100644 --- a/packages/connect-web-bench/README.md +++ b/packages/connect-web-bench/README.md @@ -15,10 +15,10 @@ usually do. We repeat this for an increasing number of RPCs. | code generator | RPCs | bundle size | minified | compressed | | -------------- | ---: | ----------: | --------: | ---------: | -| Connect-ES | 1 | 276,211 b | 176,242 b | 35,720 b | -| Connect-ES | 4 | 280,463 b | 179,344 b | 36,504 b | -| Connect-ES | 8 | 285,326 b | 183,775 b | 37,426 b | -| Connect-ES | 16 | 294,454 b | 191,399 b | 38,961 b | +| Connect-ES | 1 | 276,243 b | 176,254 b | 35,712 b | +| Connect-ES | 4 | 280,495 b | 179,356 b | 36,466 b | +| Connect-ES | 8 | 285,358 b | 183,787 b | 37,481 b | +| Connect-ES | 16 | 294,486 b | 191,411 b | 38,982 b | | gRPC-Web | 1 | 876,563 b | 548,495 b | 52,300 b | | gRPC-Web | 4 | 928,964 b | 580,477 b | 54,673 b | | gRPC-Web | 8 | 1,004,833 b | 628,223 b | 57,118 b | diff --git a/packages/connect-web-bench/chart.svg b/packages/connect-web-bench/chart.svg index 2dd999b2c..35b3ffbf6 100644 --- a/packages/connect-web-bench/chart.svg +++ b/packages/connect-web-bench/chart.svg @@ -42,13 +42,13 @@ 0 KiB - + Connect-ES -Connect-ES 34.88 KiB for 1 RPCs -Connect-ES 35.65 KiB for 4 RPCs -Connect-ES 36.55 KiB for 8 RPCs -Connect-ES 38.05 KiB for 16 RPCs +Connect-ES 34.88 KiB for 1 RPCs +Connect-ES 35.61 KiB for 4 RPCs +Connect-ES 36.6 KiB for 8 RPCs +Connect-ES 38.07 KiB for 16 RPCs diff --git a/packages/connect/src/protocol-connect/request-header.spec.ts b/packages/connect/src/protocol-connect/request-header.spec.ts index e39cd6581..2ab3e7243 100644 --- a/packages/connect/src/protocol-connect/request-header.spec.ts +++ b/packages/connect/src/protocol-connect/request-header.spec.ts @@ -54,6 +54,23 @@ describe("requestHeader", () => { expect(headers.get("Connect-Timeout-Ms")).toBe("10"); }); + it("should create request headers with user provided user-agent", () => { + const headers = requestHeader( + "unary", + true, + 10, + { "User-Agent": "grpc-es/0.0.0" }, + true, + ); + expect(listHeaderKeys(headers)).toEqual([ + "connect-protocol-version", + "connect-timeout-ms", + "content-type", + "user-agent", + ]); + expect(headers.get("User-Agent")).toBe("grpc-es/0.0.0"); + }); + it("should exclude user-agent", () => { const headers = requestHeader("unary", true, undefined, undefined, false); expect(listHeaderKeys(headers)).toEqual([ diff --git a/packages/connect/src/protocol-connect/request-header.ts b/packages/connect/src/protocol-connect/request-header.ts index 37398607c..90e1d0e79 100644 --- a/packages/connect/src/protocol-connect/request-header.ts +++ b/packages/connect/src/protocol-connect/request-header.ts @@ -59,9 +59,14 @@ export function requestHeader( : contentTypeStreamJson, ); result.set(headerProtocolVersion, protocolVersion); - if (setUserAgent) { + + if (!result.has(headerUserAgent) && setUserAgent) { + // Note that we do not strictly comply with gRPC user agents. + // We use "connect-es/1.2.3" where gRPC would use "grpc-es/1.2.3". + // See https://github.com/grpc/grpc/blob/c462bb8d485fc1434ecfae438823ca8d14cf3154/doc/PROTOCOL-HTTP2.md#user-agents result.set(headerUserAgent, "CONNECT_ES_USER_AGENT"); } + return result; } diff --git a/packages/connect/src/protocol-grpc-web/request-header.spec.ts b/packages/connect/src/protocol-grpc-web/request-header.spec.ts index f666513fc..25b51b48a 100644 --- a/packages/connect/src/protocol-grpc-web/request-header.spec.ts +++ b/packages/connect/src/protocol-grpc-web/request-header.spec.ts @@ -66,6 +66,42 @@ describe("requestHeader", () => { expect(headers.get("Grpc-Timeout")).toBe("10m"); }); + it("should set user provided user-agent header", () => { + const headers = requestHeader( + true, + 10, + { "User-Agent": "grpc-es/0.0.0" }, + true, + ); + expect(listHeaderKeys(headers)).toEqual([ + "content-type", + "grpc-timeout", + "user-agent", + "x-grpc-web", + "x-user-agent", + ]); + expect(headers.get("User-Agent")).toBe("grpc-es/0.0.0"); + expect(headers.get("X-User-Agent")).toBe("grpc-es/0.0.0"); + }); + + it("should set user provided x-user-agent header", () => { + const headers = requestHeader( + true, + 10, + { "X-User-Agent": "grpc-es/0.0.0" }, + true, + ); + expect(listHeaderKeys(headers)).toEqual([ + "content-type", + "grpc-timeout", + "user-agent", + "x-grpc-web", + "x-user-agent", + ]); + expect(headers.get("User-Agent")).toBe("grpc-es/0.0.0"); + expect(headers.get("X-User-Agent")).toBe("grpc-es/0.0.0"); + }); + it("should create request headers with compression", () => { const compressionMock: Compression = { name: "gzip", diff --git a/packages/connect/src/protocol-grpc-web/request-header.ts b/packages/connect/src/protocol-grpc-web/request-header.ts index 77994f5de..d10458f8b 100644 --- a/packages/connect/src/protocol-grpc-web/request-header.ts +++ b/packages/connect/src/protocol-grpc-web/request-header.ts @@ -46,9 +46,16 @@ export function requestHeader( // Note that we do not strictly comply with gRPC user agents. // We use "connect-es/1.2.3" where gRPC would use "grpc-es/1.2.3". // See https://github.com/grpc/grpc/blob/c462bb8d485fc1434ecfae438823ca8d14cf3154/doc/PROTOCOL-HTTP2.md#user-agents - result.set(headerXUserAgent, "CONNECT_ES_USER_AGENT"); + let userAgent = "CONNECT_ES_USER_AGENT"; + userAgent = result.has(headerUserAgent) + ? result.get(headerUserAgent)! + : result.has(headerXUserAgent) + ? result.get(headerXUserAgent)! + : userAgent; + + result.set(headerXUserAgent, userAgent); if (setUserAgent) { - result.set(headerUserAgent, "CONNECT_ES_USER_AGENT"); + result.set(headerUserAgent, userAgent); } if (timeoutMs !== undefined) { result.set(headerTimeout, `${timeoutMs}m`); diff --git a/packages/connect/src/protocol-grpc/request-header.spec.ts b/packages/connect/src/protocol-grpc/request-header.spec.ts index 2cbfac3fe..f80c86efd 100644 --- a/packages/connect/src/protocol-grpc/request-header.spec.ts +++ b/packages/connect/src/protocol-grpc/request-header.spec.ts @@ -48,6 +48,17 @@ describe("requestHeader", () => { expect(headers.get("Grpc-Timeout")).toBe("10m"); }); + it("should create request headers with user provided user-agent", () => { + const headers = requestHeader(true, 10, { "User-Agent": "grpc-es/0.0.0" }); + expect(listHeaderKeys(headers)).toEqual([ + "content-type", + "grpc-timeout", + "te", + "user-agent", + ]); + expect(headers.get("User-Agent")).toBe("grpc-es/0.0.0"); + }); + it("should create request headers with compression", () => { const compressionMock: Compression = { name: "gzip", diff --git a/packages/connect/src/protocol-grpc/request-header.ts b/packages/connect/src/protocol-grpc/request-header.ts index 14c1c1115..946ee3d7a 100644 --- a/packages/connect/src/protocol-grpc/request-header.ts +++ b/packages/connect/src/protocol-grpc/request-header.ts @@ -37,13 +37,18 @@ export function requestHeader( headerContentType, useBinaryFormat ? contentTypeProto : contentTypeJson, ); - // Note that we do not strictly comply with gRPC user agents. - // We use "connect-es/1.2.3" where gRPC would use "grpc-es/1.2.3". - // See https://github.com/grpc/grpc/blob/c462bb8d485fc1434ecfae438823ca8d14cf3154/doc/PROTOCOL-HTTP2.md#user-agents - result.set(headerUserAgent, "CONNECT_ES_USER_AGENT"); + + if (!result.has(headerUserAgent)) { + // Note that we do not strictly comply with gRPC user agents. + // We use "connect-es/1.2.3" where gRPC would use "grpc-es/1.2.3". + // See https://github.com/grpc/grpc/blob/c462bb8d485fc1434ecfae438823ca8d14cf3154/doc/PROTOCOL-HTTP2.md#user-agents + result.set(headerUserAgent, "CONNECT_ES_USER_AGENT"); + } + if (timeoutMs !== undefined) { result.set(headerTimeout, `${timeoutMs}m`); } + // The gRPC-HTTP2 specification requires this - it flushes out proxies that // don't support HTTP trailers. result.set("Te", "trailers");