1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.client5.testing.sync;
29
30 import java.io.ByteArrayInputStream;
31 import java.io.ByteArrayOutputStream;
32 import java.io.IOException;
33 import java.io.OutputStream;
34 import java.nio.charset.StandardCharsets;
35 import java.util.ArrayList;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.concurrent.CountDownLatch;
39 import java.util.concurrent.ExecutorService;
40 import java.util.concurrent.Executors;
41 import java.util.function.Consumer;
42 import java.util.zip.Deflater;
43 import java.util.zip.GZIPOutputStream;
44
45 import org.apache.hc.client5.http.classic.methods.HttpGet;
46 import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler;
47 import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
48 import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
49 import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
50 import org.apache.hc.client5.testing.sync.extension.TestClientResources;
51 import org.apache.hc.core5.http.ClassicHttpRequest;
52 import org.apache.hc.core5.http.ClassicHttpResponse;
53 import org.apache.hc.core5.http.HeaderElement;
54 import org.apache.hc.core5.http.HttpException;
55 import org.apache.hc.core5.http.HttpHost;
56 import org.apache.hc.core5.http.HttpStatus;
57 import org.apache.hc.core5.http.URIScheme;
58 import org.apache.hc.core5.http.io.HttpRequestHandler;
59 import org.apache.hc.core5.http.io.entity.EntityUtils;
60 import org.apache.hc.core5.http.io.entity.InputStreamEntity;
61 import org.apache.hc.core5.http.io.entity.StringEntity;
62 import org.apache.hc.core5.http.message.MessageSupport;
63 import org.apache.hc.core5.http.protocol.HttpContext;
64 import org.apache.hc.core5.testing.classic.ClassicTestServer;
65 import org.apache.hc.core5.util.Timeout;
66 import org.junit.jupiter.api.Assertions;
67 import org.junit.jupiter.api.Test;
68 import org.junit.jupiter.api.extension.RegisterExtension;
69
70
71
72
73
74
75 public class TestContentCodings {
76
77 public static final Timeout TIMEOUT = Timeout.ofMinutes(1);
78
79 @RegisterExtension
80 private TestClientResources testResources = new TestClientResources(URIScheme.HTTP, TIMEOUT);
81
82 public ClassicTestServer startServer() throws IOException {
83 return testResources.startServer(null, null, null);
84 }
85
86 public CloseableHttpClient startClient(final Consumer<HttpClientBuilder> clientCustomizer) {
87 return testResources.startClient(clientCustomizer);
88 }
89
90 public CloseableHttpClient startClient() {
91 return testResources.startClient(builder -> {});
92 }
93
94 public HttpHost targetHost() {
95 return testResources.targetHost();
96 }
97
98
99
100
101
102
103
104
105 @Test
106 public void testResponseWithNoContent() throws Exception {
107 final ClassicTestServer server = startServer();
108 server.registerHandler("*", new HttpRequestHandler() {
109
110
111
112
113 @Override
114 public void handle(
115 final ClassicHttpRequest request,
116 final ClassicHttpResponse response,
117 final HttpContext context) throws HttpException, IOException {
118 response.setCode(HttpStatus.SC_NO_CONTENT);
119 }
120 });
121
122 final HttpHost target = targetHost();
123
124 final CloseableHttpClient client = startClient();
125
126 final HttpGet request = new HttpGet("/some-resource");
127 client.execute(target, request, response -> {
128 Assertions.assertEquals(HttpStatus.SC_NO_CONTENT, response.getCode());
129 Assertions.assertNull(response.getEntity());
130 return null;
131 });
132 }
133
134
135
136
137
138
139
140 @Test
141 public void testDeflateSupportForServerReturningRfc1950Stream() throws Exception {
142 final String entityText = "Hello, this is some plain text coming back.";
143
144 final ClassicTestServer server = startServer();
145 server.registerHandler("*", createDeflateEncodingRequestHandler(entityText, false));
146
147 final HttpHost target = targetHost();
148
149 final CloseableHttpClient client = startClient();
150
151 final HttpGet request = new HttpGet("/some-resource");
152 client.execute(target, request, response -> {
153 Assertions.assertEquals(entityText, EntityUtils.toString(response.getEntity()),
154 "The entity text is correctly transported");
155 return null;
156 });
157 }
158
159
160
161
162
163
164
165 @Test
166 public void testDeflateSupportForServerReturningRfc1951Stream() throws Exception {
167 final String entityText = "Hello, this is some plain text coming back.";
168
169 final ClassicTestServer server = startServer();
170 server.registerHandler("*", createDeflateEncodingRequestHandler(entityText, true));
171
172 final HttpHost target = targetHost();
173
174 final CloseableHttpClient client = startClient();
175
176 final HttpGet request = new HttpGet("/some-resource");
177 client.execute(target, request, response -> {
178 Assertions.assertEquals(entityText, EntityUtils.toString(response.getEntity()),
179 "The entity text is correctly transported");
180 return null;
181 });
182 }
183
184
185
186
187
188
189 @Test
190 public void testGzipSupport() throws Exception {
191 final String entityText = "Hello, this is some plain text coming back.";
192
193 final ClassicTestServer server = startServer();
194 server.registerHandler("*", createGzipEncodingRequestHandler(entityText));
195
196 final HttpHost target = targetHost();
197
198 final CloseableHttpClient client = startClient();
199
200 final HttpGet request = new HttpGet("/some-resource");
201 client.execute(target, request, response -> {
202 Assertions.assertEquals(entityText, EntityUtils.toString(response.getEntity()),
203 "The entity text is correctly transported");
204 return null;
205 });
206 }
207
208
209
210
211
212
213
214 @Test
215 public void testThreadSafetyOfContentCodings() throws Exception {
216 final String entityText = "Hello, this is some plain text coming back.";
217
218 final ClassicTestServer server = startServer();
219 server.registerHandler("*", createGzipEncodingRequestHandler(entityText));
220
221 final HttpHost target = targetHost();
222
223 final CloseableHttpClient client = startClient();
224 final PoolingHttpClientConnectionManager connManager = testResources.connManager();
225
226
227
228
229
230 final int clients = 100;
231
232 connManager.setMaxTotal(clients);
233
234 final ExecutorService executor = Executors.newFixedThreadPool(clients);
235
236 final CountDownLatch startGate = new CountDownLatch(1);
237 final CountDownLatch endGate = new CountDownLatch(clients);
238
239 final List<WorkerTask> workers = new ArrayList<>();
240
241 for (int i = 0; i < clients; ++i) {
242 workers.add(new WorkerTask(client, target, i % 2 == 0, startGate, endGate));
243 }
244
245 for (final WorkerTask workerTask : workers) {
246
247
248 executor.execute(workerTask);
249 }
250
251 startGate.countDown();
252
253
254 endGate.await();
255
256 for (final WorkerTask workerTask : workers) {
257 if (workerTask.isFailed()) {
258 Assertions.fail("A worker failed");
259 }
260 Assertions.assertEquals(entityText, workerTask.getText());
261 }
262 }
263
264 @Test
265 public void testHttpEntityWriteToForGzip() throws Exception {
266 final String entityText = "Hello, this is some plain text coming back.";
267
268 final ClassicTestServer server = startServer();
269 server.registerHandler("*", createGzipEncodingRequestHandler(entityText));
270
271 final HttpHost target = targetHost();
272
273 final CloseableHttpClient client = startClient();
274
275 final HttpGet request = new HttpGet("/some-resource");
276 client.execute(target, request, response -> {
277 final ByteArrayOutputStream out = new ByteArrayOutputStream();
278 response.getEntity().writeTo(out);
279 Assertions.assertEquals(entityText, out.toString("utf-8"));
280 return null;
281 });
282
283 }
284
285 @Test
286 public void testHttpEntityWriteToForDeflate() throws Exception {
287 final String entityText = "Hello, this is some plain text coming back.";
288
289 final ClassicTestServer server = startServer();
290 server.registerHandler("*", createDeflateEncodingRequestHandler(entityText, true));
291
292 final HttpHost target = targetHost();
293
294 final CloseableHttpClient client = startClient();
295
296 final HttpGet request = new HttpGet("/some-resource");
297 client.execute(target, request, response -> {
298 final ByteArrayOutputStream out = new ByteArrayOutputStream();
299 response.getEntity().writeTo(out);
300 Assertions.assertEquals(entityText, out.toString("utf-8"));
301 return out;
302 });
303 }
304
305 @Test
306 public void gzipResponsesWorkWithBasicResponseHandler() throws Exception {
307 final String entityText = "Hello, this is some plain text coming back.";
308
309 final ClassicTestServer server = startServer();
310 server.registerHandler("*", createGzipEncodingRequestHandler(entityText));
311
312 final HttpHost target = targetHost();
313
314 final CloseableHttpClient client = startClient();
315
316 final HttpGet request = new HttpGet("/some-resource");
317 final String response = client.execute(target, request, new BasicHttpClientResponseHandler());
318 Assertions.assertEquals(entityText, response, "The entity text is correctly transported");
319 }
320
321 @Test
322 public void deflateResponsesWorkWithBasicResponseHandler() throws Exception {
323 final String entityText = "Hello, this is some plain text coming back.";
324
325 final ClassicTestServer server = startServer();
326 server.registerHandler("*", createDeflateEncodingRequestHandler(entityText, false));
327
328 final HttpHost target = targetHost();
329
330 final CloseableHttpClient client = startClient();
331
332 final HttpGet request = new HttpGet("/some-resource");
333 final String response = client.execute(target, request, new BasicHttpClientResponseHandler());
334 Assertions.assertEquals(entityText, response, "The entity text is correctly transported");
335 }
336
337
338
339
340
341
342
343
344
345
346
347
348
349 private HttpRequestHandler createDeflateEncodingRequestHandler(
350 final String entityText, final boolean rfc1951) {
351 return new HttpRequestHandler() {
352
353
354
355
356 @Override
357 public void handle(
358 final ClassicHttpRequest request,
359 final ClassicHttpResponse response,
360 final HttpContext context) throws HttpException, IOException {
361 response.setEntity(new StringEntity(entityText));
362 response.addHeader("Content-Type", "text/plain");
363 final Iterator<HeaderElement> it = MessageSupport.iterate(request, "Accept-Encoding");
364 while (it.hasNext()) {
365 final HeaderElement element = it.next();
366 if ("deflate".equalsIgnoreCase(element.getName())) {
367 response.addHeader("Content-Encoding", "deflate");
368
369
370
371
372
373 final byte[] uncompressed = entityText.getBytes(StandardCharsets.UTF_8);
374 final Deflater compressor = new Deflater(Deflater.DEFAULT_COMPRESSION, rfc1951);
375 compressor.setInput(uncompressed);
376 compressor.finish();
377 final byte[] output = new byte[100];
378 final int compressedLength = compressor.deflate(output);
379 final byte[] compressed = new byte[compressedLength];
380 System.arraycopy(output, 0, compressed, 0, compressedLength);
381 response.setEntity(new InputStreamEntity(
382 new ByteArrayInputStream(compressed), compressedLength, null));
383 return;
384 }
385 }
386 }
387 };
388 }
389
390
391
392
393
394
395
396
397
398 private HttpRequestHandler createGzipEncodingRequestHandler(final String entityText) {
399 return new HttpRequestHandler() {
400
401
402
403
404 @Override
405 public void handle(
406 final ClassicHttpRequest request,
407 final ClassicHttpResponse response,
408 final HttpContext context) throws HttpException, IOException {
409 response.setEntity(new StringEntity(entityText));
410 response.addHeader("Content-Type", "text/plain");
411 response.addHeader("Content-Type", "text/plain");
412 final Iterator<HeaderElement> it = MessageSupport.iterate(request, "Accept-Encoding");
413 while (it.hasNext()) {
414 final HeaderElement element = it.next();
415 if ("gzip".equalsIgnoreCase(element.getName())) {
416 response.addHeader("Content-Encoding", "gzip");
417
418
419
420
421
422
423
424
425
426 final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
427 final OutputStream out = new GZIPOutputStream(bytes);
428
429 final ByteArrayInputStream uncompressed = new ByteArrayInputStream(
430 entityText.getBytes(StandardCharsets.UTF_8));
431
432 final byte[] buf = new byte[60];
433
434 int n;
435 while ((n = uncompressed.read(buf)) != -1) {
436 out.write(buf, 0, n);
437 }
438
439 out.close();
440
441 final byte[] arr = bytes.toByteArray();
442 response.setEntity(new InputStreamEntity(new ByteArrayInputStream(arr),
443 arr.length, null));
444
445 return;
446 }
447 }
448 }
449 };
450 }
451
452
453
454
455
456
457
458 class WorkerTask implements Runnable {
459
460 private final CloseableHttpClient client;
461 private final HttpHost target;
462 private final HttpGet request;
463 private final CountDownLatch startGate;
464 private final CountDownLatch endGate;
465
466 private boolean failed;
467 private String text;
468
469 WorkerTask(final CloseableHttpClient client, final HttpHost target, final boolean identity, final CountDownLatch startGate, final CountDownLatch endGate) {
470 this.client = client;
471 this.target = target;
472 this.request = new HttpGet("/some-resource");
473 if (identity) {
474 request.addHeader("Accept-Encoding", "identity");
475 }
476 this.startGate = startGate;
477 this.endGate = endGate;
478 }
479
480
481
482
483
484
485 public String getText() {
486 return this.text;
487 }
488
489
490
491
492 @Override
493 public void run() {
494 try {
495 startGate.await();
496 try {
497 text = client.execute(target, request, response ->
498 EntityUtils.toString(response.getEntity()));
499 } catch (final Exception e) {
500 failed = true;
501 } finally {
502 endGate.countDown();
503 }
504 } catch (final InterruptedException ignore) {
505 }
506 }
507
508
509
510
511
512
513 boolean isFailed() {
514 return this.failed;
515 }
516 }
517 }