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.HashMap;
33 import java.util.Iterator;
34 import java.util.Map;
35 import java.util.Objects;
36 import java.util.Random;
37
38 import org.apache.hc.client5.http.cache.HttpCacheEntry;
39 import org.apache.hc.client5.http.utils.DateUtils;
40 import org.apache.hc.core5.http.ClassicHttpRequest;
41 import org.apache.hc.core5.http.ClassicHttpResponse;
42 import org.apache.hc.core5.http.Header;
43 import org.apache.hc.core5.http.HeaderElement;
44 import org.apache.hc.core5.http.HttpEntity;
45 import org.apache.hc.core5.http.HttpHeaders;
46 import org.apache.hc.core5.http.HttpMessage;
47 import org.apache.hc.core5.http.HttpRequest;
48 import org.apache.hc.core5.http.HttpResponse;
49 import org.apache.hc.core5.http.HttpStatus;
50 import org.apache.hc.core5.http.HttpVersion;
51 import org.apache.hc.core5.http.Method;
52 import org.apache.hc.core5.http.ProtocolVersion;
53 import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
54 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
55 import org.apache.hc.core5.http.message.BasicClassicHttpResponse;
56 import org.apache.hc.core5.http.message.BasicHeader;
57 import org.apache.hc.core5.http.message.MessageSupport;
58 import org.apache.hc.core5.util.ByteArrayBuffer;
59 import org.junit.jupiter.api.Assertions;
60
61 public class HttpTestUtils {
62
63
64
65
66
67
68 private static final String[] HOP_BY_HOP_HEADERS = { "Connection", "Keep-Alive", "Proxy-Authenticate",
69 "Proxy-Authorization", "TE", "Trailers", "Transfer-Encoding", "Upgrade" };
70
71
72
73
74
75
76
77
78 private static final String[] MULTI_HEADERS = { "Accept", "Accept-Charset", "Accept-Encoding",
79 "Accept-Language", "Allow", "Cache-Control", "Connection", "Content-Encoding",
80 "Content-Language", "Expect", "Pragma", "Proxy-Authenticate", "TE", "Trailer",
81 "Transfer-Encoding", "Upgrade", "Via", HttpHeaders.WARNING, "WWW-Authenticate" };
82 private static final String[] SINGLE_HEADERS = { "Accept-Ranges", "Age", "Authorization",
83 "Content-Length", "Content-Location", "Content-MD5", "Content-Range", "Content-Type",
84 "Date", "ETag", "Expires", "From", "Host", "If-Match", "If-Modified-Since",
85 "If-None-Match", "If-Range", "If-Unmodified-Since", "Last-Modified", "Location",
86 "Max-Forwards", "Proxy-Authorization", "Range", "Referer", "Retry-After", "Server",
87 "User-Agent", "Vary" };
88
89
90
91
92
93
94
95 public static boolean isHopByHopHeader(final String name) {
96 for (final String s : HOP_BY_HOP_HEADERS) {
97 if (s.equalsIgnoreCase(name)) {
98 return true;
99 }
100 }
101 return false;
102 }
103
104
105
106
107 public static boolean isSingleHeader(final String name) {
108 for (final String s : SINGLE_HEADERS) {
109 if (s.equalsIgnoreCase(name)) {
110 return true;
111 }
112 }
113 return false;
114 }
115
116
117
118 public static boolean equivalent(final HttpEntity e1, final HttpEntity e2) throws Exception {
119 final InputStream i1 = e1.getContent();
120 final InputStream i2 = e2.getContent();
121 if (i1 == null && i2 == null) {
122 return true;
123 }
124 if (i1 == null || i2 == null) {
125 return false;
126 }
127 int b1 = -1;
128 while ((b1 = i1.read()) != -1) {
129 if (b1 != i2.read()) {
130 return false;
131 }
132 }
133 return (-1 == i2.read());
134 }
135
136
137
138
139
140
141
142 public static String getCanonicalHeaderValue(final HttpMessage r, final String name) {
143 if (isSingleHeader(name)) {
144 final Header h = r.getFirstHeader(name);
145 return (h != null) ? h.getValue() : null;
146 }
147 final StringBuilder buf = new StringBuilder();
148 boolean first = true;
149 for (final Header h : r.getHeaders(name)) {
150 if (!first) {
151 buf.append(", ");
152 }
153 buf.append(h.getValue().trim());
154 first = false;
155 }
156 return buf.toString();
157 }
158
159
160
161
162
163 public static boolean isEndToEndHeaderSubset(final HttpMessage r1, final HttpMessage r2) {
164 for (final Header h : r1.getHeaders()) {
165 if (!isHopByHopHeader(h.getName())) {
166 final String r1val = getCanonicalHeaderValue(r1, h.getName());
167 final String r2val = getCanonicalHeaderValue(r2, h.getName());
168 if (!r1val.equals(r2val)) {
169 return false;
170 }
171 }
172 }
173 return true;
174 }
175
176
177
178
179
180
181
182
183
184
185 public static boolean semanticallyTransparent(
186 final ClassicHttpResponse r1, final ClassicHttpResponse r2) throws Exception {
187 final boolean entitiesEquivalent = equivalent(r1.getEntity(), r2.getEntity());
188 if (!entitiesEquivalent) {
189 return false;
190 }
191 final boolean statusLinesEquivalent = Objects.equals(r1.getReasonPhrase(), r2.getReasonPhrase())
192 && r1.getCode() == r2.getCode();
193 if (!statusLinesEquivalent) {
194 return false;
195 }
196 return isEndToEndHeaderSubset(r1, r2);
197 }
198
199
200 public static boolean equivalent(final ProtocolVersion v1, final ProtocolVersion v2) {
201 return Objects.equals(v1 != null ? v1 : HttpVersion.DEFAULT, v2 != null ? v2 : HttpVersion.DEFAULT );
202 }
203
204
205 public static boolean equivalent(final HttpRequest r1, final HttpRequest r2) {
206 return equivalent(r1.getVersion(), r2.getVersion()) &&
207 Objects.equals(r1.getMethod(), r2.getMethod()) &&
208 Objects.equals(r1.getRequestUri(), r2.getRequestUri()) &&
209 isEndToEndHeaderSubset(r1, r2);
210 }
211
212
213 public static boolean equivalent(final HttpResponse r1, final HttpResponse r2) {
214 return equivalent(r1.getVersion(), r2.getVersion()) &&
215 r1.getCode() == r2.getCode() &&
216 Objects.equals(r1.getReasonPhrase(), r2.getReasonPhrase()) &&
217 isEndToEndHeaderSubset(r1, r2);
218 }
219
220 public static byte[] getRandomBytes(final int nbytes) {
221 final byte[] bytes = new byte[nbytes];
222 new Random().nextBytes(bytes);
223 return bytes;
224 }
225
226 public static ByteArrayBuffer getRandomBuffer(final int nbytes) {
227 final ByteArrayBuffer buf = new ByteArrayBuffer(nbytes);
228 buf.setLength(nbytes);
229 new Random().nextBytes(buf.array());
230 return buf;
231 }
232
233
234
235
236
237 public static HttpEntity makeBody(final int nbytes) {
238 return new ByteArrayEntity(getRandomBytes(nbytes), null);
239 }
240
241 public static HttpCacheEntry makeCacheEntry(final Instant requestDate, final Instant responseDate) {
242 final Duration diff = Duration.between(requestDate, responseDate);
243 final Instant when = requestDate.plusMillis(diff.toMillis() / 2);
244 return makeCacheEntry(requestDate, responseDate, getStockHeaders(when));
245 }
246
247 public static Header[] getStockHeaders(final Instant when) {
248 return new Header[] {
249 new BasicHeader("Date", DateUtils.formatStandardDate(when)),
250 new BasicHeader("Server", "MockServer/1.0")
251 };
252 }
253
254 public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
255 final Instant responseDate, final Header... headers) {
256 final byte[] bytes = getRandomBytes(128);
257 return makeCacheEntry(requestDate, responseDate, headers, bytes);
258 }
259
260 public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
261 final Instant responseDate, final Header[] headers, final byte[] bytes) {
262 return makeCacheEntry(requestDate, responseDate, headers, bytes, null);
263 }
264
265 public static HttpCacheEntry makeCacheEntry(final Map<String,String> variantMap) {
266 final Instant now = Instant.now();
267 return makeCacheEntry(now, now, getStockHeaders(now),
268 getRandomBytes(128), variantMap);
269 }
270 @SuppressWarnings("deprecation")
271 public static HttpCacheEntry makeCacheEntry(final Instant requestDate,
272 final Instant responseDate, final Header[] headers, final byte[] bytes,
273 final Map<String,String> variantMap) {
274 return new HttpCacheEntry(DateUtils.toDate(requestDate), DateUtils.toDate(responseDate),
275 HttpStatus.SC_OK, headers, new HeapResource(bytes), variantMap);
276 }
277
278 public static HttpCacheEntry makeCacheEntry(final Header[] headers, final byte[] bytes) {
279 final Instant now = Instant.now();
280 return makeCacheEntry(now, now, headers, bytes);
281 }
282
283 public static HttpCacheEntry makeCacheEntry(final byte[] bytes) {
284 final Instant now = Instant.now();
285 return makeCacheEntry(getStockHeaders(now), bytes);
286 }
287
288 public static HttpCacheEntry makeCacheEntry(final Header... headers) {
289 return makeCacheEntry(headers, getRandomBytes(128));
290 }
291
292 public static HttpCacheEntry makeCacheEntry() {
293 final Instant now = Instant.now();
294 return makeCacheEntry(now, now);
295 }
296
297 public static ClassicHttpResponse make200Response() {
298 final ClassicHttpResponse out = new BasicClassicHttpResponse(HttpStatus.SC_OK, "OK");
299 out.setHeader("Date", DateUtils.formatStandardDate(Instant.now()));
300 out.setHeader("Server", "MockOrigin/1.0");
301 out.setHeader("Content-Length", "128");
302 out.setEntity(makeBody(128));
303 return out;
304 }
305
306 public static final ClassicHttpResponse make200Response(final Instant date, final String cacheControl) {
307 final ClassicHttpResponse response = HttpTestUtils.make200Response();
308 response.setHeader("Date", DateUtils.formatStandardDate(date));
309 response.setHeader("Cache-Control",cacheControl);
310 response.setHeader("Etag","\"etag\"");
311 return response;
312 }
313
314 public static ClassicHttpResponse make304Response() {
315 return new BasicClassicHttpResponse(HttpStatus.SC_NOT_MODIFIED, "Not modified");
316 }
317
318 public static final void assert110WarningFound(final HttpResponse response) {
319 boolean found110Warning = false;
320 final Iterator<HeaderElement> it = MessageSupport.iterate(response, HttpHeaders.WARNING);
321 while (it.hasNext()) {
322 final HeaderElement elt = it.next();
323 final String[] parts = elt.getName().split("\\s");
324 if ("110".equals(parts[0])) {
325 found110Warning = true;
326 break;
327 }
328 }
329 Assertions.assertTrue(found110Warning);
330 }
331
332 public static ClassicHttpRequest makeDefaultRequest() {
333 return new BasicClassicHttpRequest(Method.GET.toString(), "/");
334 }
335
336 public static ClassicHttpRequest makeDefaultHEADRequest() {
337 return new BasicClassicHttpRequest(Method.HEAD.toString(), "/");
338 }
339
340 public static ClassicHttpResponse make500Response() {
341 return new BasicClassicHttpResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR, "Internal Server Error");
342 }
343
344 public static Map<String, String> makeDefaultVariantMap(final String key, final String value) {
345 final Map<String, String> variants = new HashMap<>();
346 variants.put(key, value);
347
348 return variants;
349 }
350
351
352 public static HttpCacheEntry makeCacheEntryWithNoRequestMethodOrEntity(final Header... headers) {
353 final Instant now = Instant.now();
354 return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, null, null);
355 }
356
357 public static HttpCacheEntry makeCacheEntryWithNoRequestMethod(final Header... headers) {
358 final Instant now = Instant.now();
359 return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, new HeapResource(getRandomBytes(128)), null);
360 }
361
362 public static HttpCacheEntry make204CacheEntryWithNoRequestMethod(final Header... headers) {
363 final Instant now = Instant.now();
364 return new HttpCacheEntry(now, now, HttpStatus.SC_NO_CONTENT, headers, null, null);
365 }
366
367 public static HttpCacheEntry makeHeadCacheEntry(final Header... headers) {
368 final Instant now = Instant.now();
369 return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, null, null);
370 }
371
372 public static HttpCacheEntry makeHeadCacheEntryWithNoRequestMethod(final Header... headers) {
373 final Instant now = Instant.now();
374 return new HttpCacheEntry(now, now, HttpStatus.SC_OK, headers, null, null);
375 }
376 }