1 package de.juplo.httpresources;
2
3
4 import org.junit.jupiter.api.BeforeEach;
5 import org.junit.jupiter.api.Test;
6 import org.mockserver.integration.ClientAndServer;
7 import org.slf4j.Logger;
8 import org.slf4j.LoggerFactory;
9 import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.beans.factory.annotation.Value;
11 import org.springframework.boot.autoconfigure.SpringBootApplication;
12 import org.springframework.boot.test.context.SpringBootTest;
13 import org.springframework.cache.Cache;
14 import org.springframework.context.ApplicationContext;
15 import org.springframework.context.annotation.Bean;
16 import org.springframework.core.io.Resource;
17 import org.springframework.util.MimeType;
18
19 import java.time.Duration;
20
21 import static org.assertj.core.api.Assertions.assertThat;
22 import static org.junit.jupiter.api.Assertions.*;
23 import static org.mockserver.model.HttpRequest.request;
24 import static org.mockserver.verify.VerificationTimes.exactly;
25
26
27
28
29
30
31 @SpringBootTest({
32 "juplo.http-resources.protocol-resolver.enabled=true",
33 "juplo.http-resources.resolver.enabled=false",
34 "juplo.http-resources.resolver.exclusion-patterns=**.txt"
35 })
36 public class HttpResourceChainAwareResourceLoaderIT extends IntegrationTestBase
37 {
38 private final static Logger LOG =
39 LoggerFactory.getLogger(HttpResourceChainAwareResourceLoaderIT.class);
40
41
42 @Autowired
43 HttpResourceChainAwareResourceLoader loader;
44 @Autowired
45 Cache cache;
46 @Autowired
47 HttpResources resources;
48
49 @Value("classpath:static/foo")
50 Resource foo;
51 @Value("classpath:public/bar")
52 Resource bar;
53 @Value("classpath:remote/hello")
54 Resource hello;
55 @Value("classpath:remote/remote.html")
56 Resource remote;
57 @Value("classpath:remote/modified.html")
58 Resource modified;
59 @Value("classpath:fallback/foobar")
60 Resource foobar;
61 @Value("classpath:remote/hallo.txt")
62 Resource hallo;
63
64
65 @BeforeEach
66 public void setUp()
67 {
68 cache.clear();
69 }
70
71
72
73
74
75
76
77
78
79
80
81 @Test
82 public void testResourceHandling() throws Exception
83 {
84 LOG.info("<-- start of test-case");
85
86 Resource resource;
87
88 resource = loader.getResource("foo");
89 assertThat(resource.exists());
90 assertThat(resource.getInputStream()).hasSameContentAs(foo.getInputStream());
91
92 resource = loader.getResource("bar");
93 assertThat(resource.exists());
94 assertThat(resource.getInputStream()).hasSameContentAs(bar.getInputStream());
95
96 resource = loader.getResource("hello");
97 assertThat(resource.exists());
98 assertThat(resource).isInstanceOf(HttpResource.class);
99 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("application/octet-stream"));
100 assertThat(resource.getInputStream()).hasSameContentAs(hello.getInputStream());
101
102 resource = loader.getResource("remote.html");
103 assertThat(resource.exists());
104 assertThat(resource).isInstanceOf(HttpResource.class);
105 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
106 assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
107
108 resource = loader.getResource("foobar");
109 assertThat(resource.exists());
110 assertThat(resource.getInputStream()).hasSameContentAs(foobar.getInputStream());
111
112 resource = loader.getResource("/hello");
113 assertThat(resource.exists());
114 assertThat(resource).isInstanceOf(HttpResource.class);
115 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("application/octet-stream"));
116 assertThat(resource.getInputStream()).hasSameContentAs(hello.getInputStream());
117
118 resource = loader.getResource("hello");
119 assertThat(resource.exists());
120 assertThat(resource).isInstanceOf(HttpResource.class);
121 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("application/octet-stream"));
122 assertThat(resource.getInputStream()).hasSameContentAs(hello.getInputStream());
123
124 resource = loader.getResource("/hello");
125 assertThat(resource.exists());
126 assertThat(resource).isInstanceOf(HttpResource.class);
127 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("application/octet-stream"));
128 assertThat(resource.getInputStream()).hasSameContentAs(hello.getInputStream());
129
130 resource = loader.getResource("hallo.txt");
131 assertThat(resource.exists());
132 assertThat(resource).isInstanceOf(HttpResource.class);
133 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/plain"));
134 assertThat(resource.getInputStream()).hasSameContentAs(hallo.getInputStream());
135
136 server.verify(FETCH("/foo"), exactly(0));
137 server.verify(FETCH("/bar"), exactly(0));
138 server.verify(FETCH("/hello"), exactly(1));
139 server.verify(FETCH("/hallo.txt"), exactly(1));
140 server.verify(FETCH("/remote.html"), exactly(1));
141 server.verify(FETCH("/foobar"), exactly(2));
142 }
143
144 @Test
145 public void testFetchExistent() throws Exception
146 {
147 LOG.info("<-- Start of test-case");
148
149 getRemoteHtml();
150
151 server.verify(FETCH("/remote.html"), exactly(1));
152 }
153
154 @Test
155 public void testCachingOfExisting() throws Exception
156 {
157 LOG.info("<-- Start of test-case");
158
159 getRemoteHtml();
160 getRemoteHtml();
161 getRemoteHtml();
162 getRemoteHtml();
163 getRemoteHtml();
164 getRemoteHtml();
165 getRemoteHtml();
166 getRemoteHtml();
167
168 server.verify(FETCH("/remote.html"), exactly(1));
169 }
170
171 private void getRemoteHtml() throws Exception
172 {
173 Resource resource = loader.getResource("/remote.html");
174 assertNotNull(resource);
175 assertTrue(resource.exists());
176 assertThat(resource).isInstanceOf(HttpResource.class);
177 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
178 assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
179 }
180
181 @Test
182 public void testCachingOfModified() throws Exception
183 {
184 LOG.info("<-- Start of test-case");
185
186 Resource resource;
187
188 resource = loader.getResource("/remote.html");
189 assertNotNull(resource);
190 assertTrue(resource.exists());
191 assertThat(resource).isInstanceOf(HttpResource.class);
192 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
193 assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
194
195 CLOCK.timetravel(Duration.ofSeconds(10));
196 server.when(FETCH("/remote.html")).forward(NGINX("/modified.html"));
197
198 resource = loader.getResource("/remote.html");
199 assertNotNull(resource);
200 assertTrue(resource.exists());
201 assertThat(resource).isInstanceOf(HttpResource.class);
202 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
203 assertThat(resource.getInputStream()).hasSameContentAs(modified.getInputStream());
204
205 server.verify(FETCH("/remote.html"), exactly(2));
206 server.verify(
207 request()
208 .withPath("/remote.html")
209 .withHeader("If-Modified-Since")
210 .withHeader("If-None-Match"),
211 exactly(1));
212 }
213
214 @Test
215 public void testFetchNonExistent() throws Exception
216 {
217 LOG.info("<-- Start of test-case");
218
219 getNonExistingHtml();
220
221 server.verify(FETCH("/peter.html"), exactly(2));
222 }
223
224 @Test
225 public void testCachingOfNonExistent() throws Exception
226 {
227 LOG.info("<-- Start of test-case");
228
229 getNonExistingHtml();
230 getNonExistingHtml();
231 getNonExistingHtml();
232 getNonExistingHtml();
233 getNonExistingHtml();
234 getNonExistingHtml();
235 getNonExistingHtml();
236
237
238 server.verify(FETCH("/peter.html"), exactly(14));
239 }
240
241 private void getNonExistingHtml() throws Exception
242 {
243 Resource resource = loader.getResource("/peter.html");
244 assertNotNull(resource);
245 assertFalse(resource.exists());
246 }
247
248 @Test
249 public void testFetchServerResponse500() throws Exception
250 {
251 LOG.info("<-- Start of test-case");
252
253 server.when(FETCH("/peter.html")).respond(INTERNAL_SERVER_ERROR());
254
255 Resource resource = loader.getResource(address("/peter.html"));
256
257 assertThat(resource.exists()).isFalse();
258 server.verify(FETCH("/peter.html"), exactly(2));
259 }
260
261 @Test
262 public void testCaching() throws Exception
263 {
264 LOG.info("<-- Start of test-case");
265
266 Resource resource;
267
268 LOG.debug("First access /remote.html");
269 resource= loader.getResource("/remote.html");
270 assertThat(resource.exists()).isTrue();
271 assertThat(resource).isInstanceOf(HttpResource.class);
272 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
273 assertThat(((HttpResource)resource).isModified()).isFalse();
274 assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
275
276 LOG.debug("Second access to /remote.html");
277 resource= loader.getResource("/remote.html");
278 assertThat(resource.exists()).isTrue();
279 assertThat(resource).isInstanceOf(HttpResource.class);
280 assertThat(((HttpResource)resource).isModified()).isFalse();
281 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
282 assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
283
284 LOG.debug("Third access /remote.html");
285 resource= loader.getResource("/remote.html");
286 assertThat(resource.exists()).isTrue();
287 assertThat(resource).isInstanceOf(HttpResource.class);
288 assertThat(((HttpResource)resource).isModified()).isFalse();
289 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
290 assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
291
292 LOG.debug("First access to remote.html");
293 resource= loader.getResource("remote.html");
294 assertThat(resource.exists()).isTrue();
295 assertThat(resource).isInstanceOf(HttpResource.class);
296 assertThat(((HttpResource)resource).isModified()).isFalse();
297 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
298 assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
299
300 LOG.debug("Fourth access to /remote.html after a pause of 10 seconds -- resource should be expired");
301 CLOCK.timetravel(Duration.ofSeconds(10));
302 assertThat(resource.exists()).isTrue();
303 assertThat(resource).isInstanceOf(HttpResource.class);
304 assertThat(((HttpResource)resource).isModified()).isFalse();
305 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
306 assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
307
308 LOG.debug("Fifth access /remote.html");
309 resource= loader.getResource("/remote.html");
310 assertThat(resource.exists()).isTrue();
311 assertThat(resource).isInstanceOf(HttpResource.class);
312 assertThat(((HttpResource)resource).isModified()).isFalse();
313 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
314 assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
315
316 LOG.debug("Sixth access /remote.html");
317 resource= loader.getResource("/remote.html");
318 assertThat(resource.exists()).isTrue();
319 assertThat(resource).isInstanceOf(HttpResource.class);
320 assertThat(((HttpResource)resource).isModified()).isFalse();
321 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
322 assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
323
324 LOG.debug("Second access /remote.html");
325 resource= loader.getResource("remote.html");
326 assertThat(resource.exists()).isTrue();
327 assertThat(resource).isInstanceOf(HttpResource.class);
328 assertThat(((HttpResource)resource).isModified()).isFalse();
329 assertThat(((HttpResource)resource).contentType()).isEqualTo(MimeType.valueOf("text/html"));
330 assertThat(resource.getInputStream()).hasSameContentAs(remote.getInputStream());
331
332 server.verify(FETCH("/remote.html"), exactly(2));
333 server.verify(
334 request()
335 .withPath("/remote.html")
336 .withHeader("If-Modified-Since")
337 .withHeader("If-None-Match"),
338 exactly(1));
339 }
340
341
342 @SpringBootApplication
343 static class Application
344 {
345 @Bean
346 public HttpResourceChainAwareResourceLoader httpResourceChainAwareResourceLoader(
347 ClientAndServer server,
348 ApplicationContext context)
349 {
350 return new HttpResourceChainAwareResourceLoader(
351 context,
352 new String[]
353 {
354 "classpath:/static/",
355 "classpath:/public/",
356 "http://localhost:" + server.getLocalPort(),
357 "classpath:/fallback/"
358 });
359 }
360 }
361 }