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 package org.apache.hc.client5.http.impl.cache;
28
29 import java.io.InputStream;
30 import java.time.Duration;
31 import java.time.Instant;
32 import java.util.Collection;
33 import java.util.Iterator;
34 import java.util.Objects;
35 import java.util.Random;
36 import java.util.concurrent.CountDownLatch;
37 import java.util.function.Consumer;
38
39 import org.apache.hc.client5.http.cache.HttpCacheEntry;
40 import org.apache.hc.client5.http.cache.Resource;
41 import org.apache.hc.client5.http.utils.DateUtils;
42 import org.apache.hc.core5.concurrent.FutureCallback;
43 import org.apache.hc.core5.http.ClassicHttpRequest;
44 import org.apache.hc.core5.http.ClassicHttpResponse;
45 import org.apache.hc.core5.http.Header;
46 import org.apache.hc.core5.http.HttpEntity;
47 import org.apache.hc.core5.http.HttpMessage;
48 import org.apache.hc.core5.http.HttpRequest;
49 import org.apache.hc.core5.http.HttpResponse;
50 import org.apache.hc.core5.http.HttpStatus;
51 import org.apache.hc.core5.http.HttpVersion;
52 import org.apache.hc.core5.http.Method;
53 import org.apache.hc.core5.http.ProtocolVersion;
54 import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
55 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
56 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
57 import org.apache.hc.core5.http.message.BasicHeader;
58 import org.apache.hc.core5.http.message.HeaderGroup;
59 import org.apache.hc.core5.http.message.MessageSupport;
60 import org.apache.hc.core5.util.ByteArrayBuffer;
61 import org.junit.jupiter.api.Assertions;
62
63 public class HttpTestUtils {
64
65
66
67
68 public static boolean equivalent(final HttpEntity e1, final HttpEntity e2) throws Exception {
69 final InputStream i1 = e1.getContent();
70 final InputStream i2 = e2.getContent();
71 if (i1 == null && i2 == null) {
72 return true;
73 }
74 if (i1 == null || i2 == null) {
75 return false;
76 }
77 int b1 = -1;
78 while ((b1 = i1.read()) != -1) {
79 if (b1 != i2.read()) {
80 return false;
81 }
82 }
83 return (-1 == i2.read());
84 }
85
86
87
88
89
90 public static String getCanonicalHeaderValue(final HttpMessage r, final String name) {
91 final int n = r.countHeaders(name);
92 r.getFirstHeader(name);
93 if (n == 0) {
94 return null;
95 } else if (n == 1) {
96 final Header h = r.getFirstHeader(name);
97 return h != null ? h.getValue() : null;
98 } else {
99 final StringBuilder buf = new StringBuilder();
100 for (final Iterator<Header> it = r.headerIterator(name); it.hasNext(); ) {
101 if (buf.length() > 0) {
102 buf.append(", ");
103 }
104 final Header header = it.next();
105 if (header != null) {
106 buf.append(header.getValue().trim());
107 }
108 }
109 return buf.toString();
110 }
111 }
112
113
114
115
116
117 public static boolean isEndToEndHeaderSubset(final HttpMessage r1, final HttpMessage r2) {
118 for (final Header h : r1.getHeaders()) {
119 if (!MessageSupport.isHopByHop(h.getName())) {
120 final String r1val = getCanonicalHeaderValue(r1, h.getName());
121 final String r2val = getCanonicalHeaderValue(r2, h.getName());
122 if (!Objects.equals(r1val, r2val)) {
123 return false;
124 }
125 }
126 }
127 return true;
128 }
129
130
131
132
133
134
135
136
137 public static boolean semanticallyTransparent(
138 final ClassicHttpResponse r1, final ClassicHttpResponse r2) throws Exception {
139 final boolean statusLineEquivalent = Objects.equals(r1.getReasonPhrase(), r2.getReasonPhrase())
140 && r1.getCode() == r2.getCode();
141 if (!statusLineEquivalent) {
142 return false;
143 }
144 final boolean headerEquivalent = isEndToEndHeaderSubset(r1, r2);
145 if (!headerEquivalent) {
146 return false;
147 }
148 final boolean entityEquivalent = equivalent(r1.getEntity(), r2.getEntity());
149 if (!entityEquivalent) {
150 return false;
151 }
152 return true;
153 }
154
155
156 public static boolean equivalent(final ProtocolVersion v1, final ProtocolVersion v2) {
157 return Objects.equals(v1 != null ? v1 : HttpVersion.DEFAULT, v2 != null ? v2 : HttpVersion.DEFAULT );
158 }
159
160
161 public static boolean equivalent(final HttpRequest r1, final HttpRequest r2) {
162 return equivalent(r1.getVersion(), r2.getVersion()) &&
163 Objects.equals(r1.getMethod(), r2.getMethod()) &&
164 Objects.equals(r1.getRequestUri(), r2.getRequestUri()) &&
165 isEndToEndHeaderSubset(r1, r2);
166 }
167
168
169 public static boolean equivalent(final HttpResponse r1, final HttpResponse r2) {
170 return equivalent(r1.getVersion(), r2.getVersion()) &&
171 r1.getCode() == r2.getCode() &&
172 Objects.equals(r1.getReasonPhrase(), r2.getReasonPhrase()) &&
173 isEndToEndHeaderSubset(r1, r2);
174 }
175
176 public static byte[] makeRandomBytes(final int nbytes) {
177 final byte[] bytes = new byte[nbytes];
178 new Random().nextBytes(bytes);
179 return bytes;
180 }
181
182 public static Resource makeRandomResource(final int nbytes) {
183 final byte[] bytes = new byte[nbytes];
184 new Random().nextBytes(bytes);
185 return new HeapResource(bytes);
186 }
187
188 public static Resource makeNullResource() {
189 return null;
190 }
191
192 public static ByteArrayBuffer makeRandomBuffer(final int nbytes) {
193 final ByteArrayBuffer buf = new ByteArrayBuffer(nbytes);
194 buf.setLength(nbytes);
195 new Random().nextBytes(buf.array());
196 return buf;
197 }
198
199
200
201
202
203 public static HttpEntity makeBody(final int nbytes) {
204 return new ByteArrayEntity(makeRandomBytes(nbytes), null);
205 }
206
207 public static HeaderGroup headers(final Header... headers) {
208 final HeaderGroup headerGroup = new HeaderGroup();
209 if (headers != null && headers.length > 0) {
210 headerGroup.setHeaders(headers);
211 }
212 return headerGroup;
213 }
214
215 public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
216 final Instant responseDate,
217 final Method method,
218 final String requestUri,
219 final Header[] requestHeaders,
220 final int status,
221 final Header[] responseHeaders,
222 final Collection<String> variants) {
223 return new HttpCacheEntry(
224 requestDate,
225 responseDate,
226 method.name(), requestUri, headers(requestHeaders),
227 status, headers(responseHeaders),
228 null,
229 variants);
230 }
231
232 public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
233 final Instant responseDate,
234 final Method method,
235 final String requestUri,
236 final Header[] requestHeaders,
237 final int status,
238 final Header[] responseHeaders,
239 final Resource resource) {
240 return new HttpCacheEntry(
241 requestDate,
242 responseDate,
243 method.name(), requestUri, headers(requestHeaders),
244 status, headers(responseHeaders),
245 resource,
246 null);
247 }
248
249 public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
250 final Instant responseDate,
251 final int status,
252 final Header[] responseHeaders,
253 final Collection<String> variants) {
254 return makeCacheEntry(
255 requestDate,
256 responseDate,
257 Method.GET, "/", null,
258 status, responseHeaders,
259 variants);
260 }
261
262 public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
263 final Instant responseDate,
264 final int status,
265 final Header[] responseHeaders,
266 final Resource resource) {
267 return makeCacheEntry(
268 requestDate,
269 responseDate,
270 Method.GET, "/", null,
271 status, responseHeaders,
272 resource);
273 }
274
275 public static Header[] getStockHeaders(final Instant when) {
276 return new Header[] {
277 new BasicHeader("Date", DateUtils.formatStandardDate(when)),
278 new BasicHeader("Server", "MockServer/1.0")
279 };
280 }
281
282 public static HttpCacheEntry makeCacheEntry(final Instant requestDate, final Instant responseDate) {
283 final Duration diff = Duration.between(requestDate, responseDate);
284 final Instant when = requestDate.plusMillis(diff.toMillis() / 2);
285 return makeCacheEntry(requestDate, responseDate, getStockHeaders(when));
286 }
287
288 public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
289 final Instant responseDate,
290 final Header[] headers,
291 final byte[] bytes) {
292 return makeCacheEntry(requestDate, responseDate, HttpStatus.SC_OK, headers, new HeapResource(bytes));
293 }
294
295 public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
296 final Instant responseDate,
297 final Header... headers) {
298 return makeCacheEntry(requestDate, responseDate, headers, makeRandomBytes(128));
299 }
300
301 public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
302 final Instant responseDate,
303 final Header[] headers,
304 final Collection<String> variants) {
305 return makeCacheEntry(requestDate, responseDate, Method.GET, "/", null, HttpStatus.SC_OK, headers, variants);
306 }
307
308 public static HttpCacheEntry makeCacheEntry(final Collection<String> variants) {
309 final Instant now = Instant.now();
310 return makeCacheEntry(now, now, new Header[] {}, variants);
311 }
312
313 public static HttpCacheEntry makeCacheEntry(final Header[] headers, final byte[] bytes) {
314 final Instant now = Instant.now();
315 return makeCacheEntry(now, now, headers, bytes);
316 }
317
318 public static HttpCacheEntry makeCacheEntry(final byte[] bytes) {
319 final Instant now = Instant.now();
320 return makeCacheEntry(getStockHeaders(now), bytes);
321 }
322
323 public static HttpCacheEntry makeCacheEntry(final Header... headers) {
324 return makeCacheEntry(headers, makeRandomBytes(128));
325 }
326
327 public static HttpCacheEntry makeCacheEntry() {
328 final Instant now = Instant.now();
329 return makeCacheEntry(now, now);
330 }
331
332 public static ClassicHttpResponse make200Response() {
333 final ClassicHttpResponse out = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
334 out.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
335 out.setHeader("Server", "MockOrigin/1.0");
336 out.setHeader("Content-Length", "128");
337 out.setEntity(makeBody(128));
338 return out;
339 }
340
341 public static final ClassicHttpResponse make200Response(final Instant date, final String cacheControl) {
342 final ClassicHttpResponse response = HttpTestUtils.make200Response();
343 response.setHeader("Date", DateUtils.formatStandardDate(date));
344 response.setHeader("Cache-Control",cacheControl);
345 response.setHeader("Etag","\"etag\"");
346 return response;
347 }
348
349 public static ClassicHttpResponse make304Response() {
350 return new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not modified");
351 }
352
353 public static ClassicHttpRequest makeDefaultRequest() {
354 return new BasicClassicHttpRequest(Method.GET.toString(), "/");
355 }
356
357 public static ClassicHttpRequest makeDefaultHEADRequest() {
358 return new BasicClassicHttpRequest(Method.HEAD.toString(), "/");
359 }
360
361 public static ClassicHttpResponse make500Response() {
362 return new BasicClassicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
363 }
364
365 public static <T> FutureCallback<T> countDown(final CountDownLatch latch, final Consumer<T> consumer) {
366 return new FutureCallback<T>() {
367
368 @Override
369 public void completed(final T result) {
370 if (consumer != null) {
371 consumer.accept(result);
372 }
373 latch.countDown();
374 }
375
376 @Override
377 public void failed(final Exception ex) {
378 latch.countDown();
379 Assertions.fail(ex);
380 }
381
382 @Override
383 public void cancelled() {
384 latch.countDown();
385 Assertions.fail("Unexpected cancellation");
386 }
387
388 };
389
390 }
391
392 public static <T> FutureCallback<T> countDown(final CountDownLatch latch) {
393 return countDown(latch, null);
394 }
395
396 }