api,netty: Add custom header support for HTTP CONNECT proxy · grpc/grpc-java@bbc0aa3 (original) (raw)
``
1
`+
/*
`
``
2
`+
- Copyright 2025 The gRPC Authors
`
``
3
`+
`
``
4
`+
- Licensed under the Apache License, Version 2.0 (the "License");
`
``
5
`+
- you may not use this file except in compliance with the License.
`
``
6
`+
- You may obtain a copy of the License at
`
``
7
`+
`
``
8
`+
`
``
9
`+
`
``
10
`+
- Unless required by applicable law or agreed to in writing, software
`
``
11
`+
- distributed under the License is distributed on an "AS IS" BASIS,
`
``
12
`+
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
`
``
13
`+
- See the License for the specific language governing permissions and
`
``
14
`+
- limitations under the License.
`
``
15
`+
*/
`
``
16
+
``
17
`+
package io.grpc;
`
``
18
+
``
19
`+
import static com.google.common.truth.Truth.assertThat;
`
``
20
`+
import static org.junit.Assert.assertNotEquals;
`
``
21
`+
import static org.junit.Assert.assertThrows;
`
``
22
+
``
23
`+
import com.google.common.testing.EqualsTester;
`
``
24
`+
import java.net.InetAddress;
`
``
25
`+
import java.net.InetSocketAddress;
`
``
26
`+
import java.util.Collections;
`
``
27
`+
import java.util.HashMap;
`
``
28
`+
import java.util.Map;
`
``
29
`+
import org.junit.Test;
`
``
30
`+
import org.junit.runner.RunWith;
`
``
31
`+
import org.junit.runners.JUnit4;
`
``
32
+
``
33
`+
@RunWith(JUnit4.class)
`
``
34
`+
public class HttpConnectProxiedSocketAddressTest {
`
``
35
+
``
36
`+
private final InetSocketAddress proxyAddress =
`
``
37
`+
new InetSocketAddress(InetAddress.getLoopbackAddress(), 8080);
`
``
38
`+
private final InetSocketAddress targetAddress =
`
``
39
`+
InetSocketAddress.createUnresolved("example.com", 443);
`
``
40
+
``
41
`+
@Test
`
``
42
`+
public void buildWithAllFields() {
`
``
43
`+
Map<String, String> headers = new HashMap<>();
`
``
44
`+
headers.put("X-Custom-Header", "custom-value");
`
``
45
`+
headers.put("Proxy-Authorization", "Bearer token");
`
``
46
+
``
47
`+
HttpConnectProxiedSocketAddress address = HttpConnectProxiedSocketAddress.newBuilder()
`
``
48
`+
.setProxyAddress(proxyAddress)
`
``
49
`+
.setTargetAddress(targetAddress)
`
``
50
`+
.setHeaders(headers)
`
``
51
`+
.setUsername("user")
`
``
52
`+
.setPassword("pass")
`
``
53
`+
.build();
`
``
54
+
``
55
`+
assertThat(address.getProxyAddress()).isEqualTo(proxyAddress);
`
``
56
`+
assertThat(address.getTargetAddress()).isEqualTo(targetAddress);
`
``
57
`+
assertThat(address.getHeaders()).hasSize(2);
`
``
58
`+
assertThat(address.getHeaders()).containsEntry("X-Custom-Header", "custom-value");
`
``
59
`+
assertThat(address.getHeaders()).containsEntry("Proxy-Authorization", "Bearer token");
`
``
60
`+
assertThat(address.getUsername()).isEqualTo("user");
`
``
61
`+
assertThat(address.getPassword()).isEqualTo("pass");
`
``
62
`+
}
`
``
63
+
``
64
`+
@Test
`
``
65
`+
public void buildWithoutOptionalFields() {
`
``
66
`+
HttpConnectProxiedSocketAddress address = HttpConnectProxiedSocketAddress.newBuilder()
`
``
67
`+
.setProxyAddress(proxyAddress)
`
``
68
`+
.setTargetAddress(targetAddress)
`
``
69
`+
.build();
`
``
70
+
``
71
`+
assertThat(address.getProxyAddress()).isEqualTo(proxyAddress);
`
``
72
`+
assertThat(address.getTargetAddress()).isEqualTo(targetAddress);
`
``
73
`+
assertThat(address.getHeaders()).isEmpty();
`
``
74
`+
assertThat(address.getUsername()).isNull();
`
``
75
`+
assertThat(address.getPassword()).isNull();
`
``
76
`+
}
`
``
77
+
``
78
`+
@Test
`
``
79
`+
public void buildWithEmptyHeaders() {
`
``
80
`+
HttpConnectProxiedSocketAddress address = HttpConnectProxiedSocketAddress.newBuilder()
`
``
81
`+
.setProxyAddress(proxyAddress)
`
``
82
`+
.setTargetAddress(targetAddress)
`
``
83
`+
.setHeaders(Collections.emptyMap())
`
``
84
`+
.build();
`
``
85
+
``
86
`+
assertThat(address.getHeaders()).isEmpty();
`
``
87
`+
}
`
``
88
+
``
89
`+
@Test
`
``
90
`+
public void headersAreImmutable() {
`
``
91
`+
Map<String, String> headers = new HashMap<>();
`
``
92
`+
headers.put("key1", "value1");
`
``
93
+
``
94
`+
HttpConnectProxiedSocketAddress address = HttpConnectProxiedSocketAddress.newBuilder()
`
``
95
`+
.setProxyAddress(proxyAddress)
`
``
96
`+
.setTargetAddress(targetAddress)
`
``
97
`+
.setHeaders(headers)
`
``
98
`+
.build();
`
``
99
+
``
100
`+
headers.put("key2", "value2");
`
``
101
+
``
102
`+
assertThat(address.getHeaders()).hasSize(1);
`
``
103
`+
assertThat(address.getHeaders()).containsEntry("key1", "value1");
`
``
104
`+
assertThat(address.getHeaders()).doesNotContainKey("key2");
`
``
105
`+
}
`
``
106
+
``
107
`+
@Test
`
``
108
`+
public void returnedHeadersAreUnmodifiable() {
`
``
109
`+
Map<String, String> headers = new HashMap<>();
`
``
110
`+
headers.put("key", "value");
`
``
111
+
``
112
`+
HttpConnectProxiedSocketAddress address = HttpConnectProxiedSocketAddress.newBuilder()
`
``
113
`+
.setProxyAddress(proxyAddress)
`
``
114
`+
.setTargetAddress(targetAddress)
`
``
115
`+
.setHeaders(headers)
`
``
116
`+
.build();
`
``
117
+
``
118
`+
assertThrows(UnsupportedOperationException.class,
`
``
119
`+
() -> address.getHeaders().put("newKey", "newValue"));
`
``
120
`+
}
`
``
121
+
``
122
`+
@Test
`
``
123
`+
public void nullHeadersThrowsException() {
`
``
124
`+
assertThrows(NullPointerException.class,
`
``
125
`+
() -> HttpConnectProxiedSocketAddress.newBuilder()
`
``
126
`+
.setProxyAddress(proxyAddress)
`
``
127
`+
.setTargetAddress(targetAddress)
`
``
128
`+
.setHeaders(null)
`
``
129
`+
.build());
`
``
130
`+
}
`
``
131
+
``
132
`+
@Test
`
``
133
`+
public void equalsAndHashCode() {
`
``
134
`+
Map<String, String> headers1 = new HashMap<>();
`
``
135
`+
headers1.put("header", "value");
`
``
136
+
``
137
`+
Map<String, String> headers2 = new HashMap<>();
`
``
138
`+
headers2.put("header", "value");
`
``
139
+
``
140
`+
Map<String, String> differentHeaders = new HashMap<>();
`
``
141
`+
differentHeaders.put("different", "header");
`
``
142
+
``
143
`+
new EqualsTester()
`
``
144
`+
.addEqualityGroup(
`
``
145
`+
HttpConnectProxiedSocketAddress.newBuilder()
`
``
146
`+
.setProxyAddress(proxyAddress)
`
``
147
`+
.setTargetAddress(targetAddress)
`
``
148
`+
.setHeaders(headers1)
`
``
149
`+
.setUsername("user")
`
``
150
`+
.setPassword("pass")
`
``
151
`+
.build(),
`
``
152
`+
HttpConnectProxiedSocketAddress.newBuilder()
`
``
153
`+
.setProxyAddress(proxyAddress)
`
``
154
`+
.setTargetAddress(targetAddress)
`
``
155
`+
.setHeaders(headers2)
`
``
156
`+
.setUsername("user")
`
``
157
`+
.setPassword("pass")
`
``
158
`+
.build())
`
``
159
`+
.addEqualityGroup(
`
``
160
`+
HttpConnectProxiedSocketAddress.newBuilder()
`
``
161
`+
.setProxyAddress(proxyAddress)
`
``
162
`+
.setTargetAddress(targetAddress)
`
``
163
`+
.setHeaders(differentHeaders)
`
``
164
`+
.setUsername("user")
`
``
165
`+
.setPassword("pass")
`
``
166
`+
.build())
`
``
167
`+
.addEqualityGroup(
`
``
168
`+
HttpConnectProxiedSocketAddress.newBuilder()
`
``
169
`+
.setProxyAddress(proxyAddress)
`
``
170
`+
.setTargetAddress(targetAddress)
`
``
171
`+
.build())
`
``
172
`+
.testEquals();
`
``
173
`+
}
`
``
174
+
``
175
`+
@Test
`
``
176
`+
public void toStringContainsHeaders() {
`
``
177
`+
Map<String, String> headers = new HashMap<>();
`
``
178
`+
headers.put("X-Test", "test-value");
`
``
179
+
``
180
`+
HttpConnectProxiedSocketAddress address = HttpConnectProxiedSocketAddress.newBuilder()
`
``
181
`+
.setProxyAddress(proxyAddress)
`
``
182
`+
.setTargetAddress(targetAddress)
`
``
183
`+
.setHeaders(headers)
`
``
184
`+
.setUsername("user")
`
``
185
`+
.setPassword("secret")
`
``
186
`+
.build();
`
``
187
+
``
188
`+
String toString = address.toString();
`
``
189
`+
assertThat(toString).contains("headers");
`
``
190
`+
assertThat(toString).contains("X-Test");
`
``
191
`+
assertThat(toString).contains("hasPassword=true");
`
``
192
`+
assertThat(toString).doesNotContain("secret");
`
``
193
`+
}
`
``
194
+
``
195
`+
@Test
`
``
196
`+
public void toStringWithoutPassword() {
`
``
197
`+
HttpConnectProxiedSocketAddress address = HttpConnectProxiedSocketAddress.newBuilder()
`
``
198
`+
.setProxyAddress(proxyAddress)
`
``
199
`+
.setTargetAddress(targetAddress)
`
``
200
`+
.build();
`
``
201
+
``
202
`+
String toString = address.toString();
`
``
203
`+
assertThat(toString).contains("hasPassword=false");
`
``
204
`+
}
`
``
205
+
``
206
`+
@Test
`
``
207
`+
public void hashCodeDependsOnHeaders() {
`
``
208
`+
Map<String, String> headers1 = new HashMap<>();
`
``
209
`+
headers1.put("header", "value1");
`
``
210
+
``
211
`+
Map<String, String> headers2 = new HashMap<>();
`
``
212
`+
headers2.put("header", "value2");
`
``
213
+
``
214
`+
HttpConnectProxiedSocketAddress address1 = HttpConnectProxiedSocketAddress.newBuilder()
`
``
215
`+
.setProxyAddress(proxyAddress)
`
``
216
`+
.setTargetAddress(targetAddress)
`
``
217
`+
.setHeaders(headers1)
`
``
218
`+
.build();
`
``
219
+
``
220
`+
HttpConnectProxiedSocketAddress address2 = HttpConnectProxiedSocketAddress.newBuilder()
`
``
221
`+
.setProxyAddress(proxyAddress)
`
``
222
`+
.setTargetAddress(targetAddress)
`
``
223
`+
.setHeaders(headers2)
`
``
224
`+
.build();
`
``
225
+
``
226
`+
assertNotEquals(address1.hashCode(), address2.hashCode());
`
``
227
`+
}
`
``
228
+
``
229
`+
@Test
`
``
230
`+
public void multipleHeadersSupported() {
`
``
231
`+
Map<String, String> headers = new HashMap<>();
`
``
232
`+
headers.put("X-Header-1", "value1");
`
``
233
`+
headers.put("X-Header-2", "value2");
`
``
234
`+
headers.put("X-Header-3", "value3");
`
``
235
+
``
236
`+
HttpConnectProxiedSocketAddress address = HttpConnectProxiedSocketAddress.newBuilder()
`
``
237
`+
.setProxyAddress(proxyAddress)
`
``
238
`+
.setTargetAddress(targetAddress)
`
``
239
`+
.setHeaders(headers)
`
``
240
`+
.build();
`
``
241
+
``
242
`+
assertThat(address.getHeaders()).hasSize(3);
`
``
243
`+
assertThat(address.getHeaders()).containsEntry("X-Header-1", "value1");
`
``
244
`+
assertThat(address.getHeaders()).containsEntry("X-Header-2", "value2");
`
``
245
`+
assertThat(address.getHeaders()).containsEntry("X-Header-3", "value3");
`
``
246
`+
}
`
``
247
`+
}
`
``
248
+