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