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");