1 /**
2 * Copyright 2003-2007 Greg Luck
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package net.sf.ehcache.constructs.web.filter;
18
19 import com.meterware.httpunit.HttpInternalErrorException;
20 import com.meterware.httpunit.WebResponse;
21 import junit.framework.AssertionFailedError;
22 import net.sf.ehcache.constructs.web.AbstractWebTest;
23 import net.sf.ehcache.constructs.web.PageInfo;
24 import org.apache.commons.httpclient.Header;
25 import org.apache.commons.httpclient.HttpClient;
26 import org.apache.commons.httpclient.HttpMethod;
27 import org.apache.commons.httpclient.methods.GetMethod;
28 import org.xml.sax.SAXException;
29
30 import java.io.IOException;
31 import java.net.HttpURLConnection;
32 import java.util.ArrayList;
33 import java.util.List;
34
35 /**
36 * @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
37 * @version $Id: CachingFilterTest.java 512 2007-07-10 09:18:45Z gregluck $
38 */
39 public class CachingFilterTest extends AbstractWebTest {
40
41 private String cachedPageUrl = "/CachedPage.jsp";
42
43 private void resetCachedPageUrl() {
44 cachedPageUrl = "/CachedPage.jsp";
45 }
46
47
48 /**
49 * Tests that a page which should be cached is cached.
50 * Also, check that the pages returned are good.
51 */
52 public void testCachedPageIsCached() throws Exception {
53 assertResponseGoodAndCached(cachedPageUrl, true);
54 }
55
56 /**
57 * Tests whether the page is gzipped using the rawer HttpClient library.
58 * Lets us check that the responseBody is really gzipped.
59 */
60 public void testCachedPageIsGzippedWhenEncodingHeaderSet() throws IOException {
61 HttpClient httpClient = new HttpClient();
62 HttpMethod httpMethod = new GetMethod(buildUrl(cachedPageUrl));
63 httpMethod.setRequestHeader(new Header("Accept-encoding", "gzip"));
64 httpClient.executeMethod(httpMethod);
65 byte[] responseBody = httpMethod.getResponseBody();
66 assertTrue(PageInfo.isGzipped(responseBody));
67 }
68
69 /**
70 * Tests whether the page is gzipped using the rawer HttpClient library.
71 * Lets us check that the responseBody is really not gzipped.
72 */
73 public void testCachedPageIsNotGzippedWhenEncodingHeaderNotSet() throws IOException {
74 HttpClient httpClient = new HttpClient();
75 HttpMethod httpMethod = new GetMethod(buildUrl(cachedPageUrl));
76 //httpMethod.setRequestHeader(new Header("Accept-encoding", "gzip"));
77 httpClient.executeMethod(httpMethod);
78 byte[] responseBody = httpMethod.getResponseBody();
79 assertFalse(PageInfo.isGzipped(responseBody));
80 }
81
82 /**
83 * Checks sequence
84 *
85 * @throws IOException
86 */
87 public void testSequenceNonGzipThenGzipThenNonGzip() throws IOException {
88 testCachedPageIsNotGzippedWhenEncodingHeaderNotSet();
89 testCachedPageIsGzippedWhenEncodingHeaderSet();
90 testCachedPageIsNotGzippedWhenEncodingHeaderNotSet();
91 }
92
93 /**
94 * Checks sequence
95 *
96 * @throws IOException
97 */
98 public void testSequenceGzipThenNonGzipThenGzip() throws IOException {
99 testCachedPageIsGzippedWhenEncodingHeaderSet();
100 testCachedPageIsNotGzippedWhenEncodingHeaderNotSet();
101 testCachedPageIsGzippedWhenEncodingHeaderSet();
102 }
103
104
105 /**
106 * The reentry check instruments the thread name to indicate it has entered a CachingFilter.
107 * The name is reset at the end. Check that this works for concurrent situations.
108 */
109 public void testCachedPageConcurrent() throws Exception {
110
111 final List executables = new ArrayList();
112 for (int i = 0; i < 40; i++) {
113 final AbstractWebTest.Executable executable = new AbstractWebTest.Executable() {
114 public void execute() throws Exception {
115 testCachedPageIsCached();
116 }
117 };
118 executables.add(executable);
119 }
120 runThreads(executables);
121 }
122
123
124 /**
125 * Tests that a cached full page can be included in another page which calls it with
126 * a jsp:include
127 * <p/>
128 * Fails when client request gzip
129 * wget -S --header='Accept-encoding: gzip' http://localhost:9080/legaldispatchtocachedpage/Include.jsp
130 * --15:01:49-- http://localhost:9080/legaldispatchtocachedpage/Include.jsp
131 * => `Include.jsp.9'
132 * Resolving localhost... done.
133 * Connecting to localhost[127.0.0.1]:9080... connected.
134 * HTTP request sent, awaiting response...
135 * 1 HTTP/1.1 200 OK
136 * 2 Date: Mon, 22 Nov 2004 05:01:48 GMT
137 * 3 Server: Orion/2.0.3
138 * 4 Set-Cookie: JSESSIONID=EFEIDKLICCGB; Path=/
139 * 5 Cache-Control: private
140 * 6 Connection: Close
141 * 7 Content-Type: text/html
142 * <p/>
143 * [ <=> ] 734 716.80K/s
144 * <p/>
145 * 15:01:49 (716.80 KB/s) - `Include.jsp.9' saved [734]
146 *
147 * @throws Exception
148 */
149 public void testFromJSPInclude() throws Exception {
150 try {
151 WebResponse response = getResponseFromAcceptGzipRequest("/legaldispatchtocachedpage/Include.jsp");
152 assertPropertlyFormed(response);
153 fail();
154 } catch (AssertionFailedError e) {
155 //noop Page is actually an error message in Orion
156 } catch (HttpInternalErrorException e) {
157 //noop 500 error in Tomcat. The log shows ResponseHeadersNotModifiableException when we try to set Gzip
158 //because it is already gzipped.
159 }
160
161 //No point doing the rest of the tests because the page is always blank
162 }
163
164 /**
165 * Tests that a cached full page can be included in another page which calls it with
166 * a jsp:include, where the client does not accept gzip encoding.
167 *
168 * @throws Exception
169 */
170 public void testFromJSPIncludeNoGzip() throws Exception {
171 try {
172 WebResponse response = getResponseFromNonAcceptGzipRequest("/legaldispatchtocachedpage/Include.jsp");
173 assertPropertlyFormed(response);
174 fail();
175 } catch (AssertionFailedError e) {
176 //noop Page is actually an error message
177 } catch (HttpInternalErrorException e) {
178 //noop 500 error in Tomcat. The log shows ResponseHeadersNotModifiableException when we try to set Gzip
179 //because it is already gzipped.
180 }
181
182 //No point doing the rest of the tests because the page is always blank
183 }
184
185 /**
186 * Tests that a cached full page included using the RequestDispatcher in a servlet
187 * throws a Server 500 error. In the server log a ResponseHeadersNotModifiableException
188 * with the message "Failure when attempting to set Content-Encoding: gzip" will be thrown.
189 * <p/>
190 * If we did not throw an error the client would get a strange result:
191 * <pre>
192 * wget -S --header='Accept-encoding: gzip' http://localhost:9080/servletdispatchtocachedpage/IncludeCachedPageServlet
193 * --20:43:29-- http://localhost:9080/servletdispatchtocachedpage/IncludeCachedPageServlet
194 * => `IncludeCachedPageServlet.2'
195 * Resolving localhost... done.
196 * Connecting to localhost[127.0.0.1]:9080... connected.
197 * HTTP request sent, awaiting response...
198 * 1 HTTP/1.1 200 OK
199 * 2 Date: Mon, 22 Nov 2004 10:43:28 GMT
200 * 3 Server: Orion/2.0.3
201 * 4 Connection: Close
202 * 5 Content-Type: text/plain
203 * </pre>
204 * Though gzip, the header is not set. The content-type is text/plain, because in our case the
205 * test servlet did not set a type.
206 *
207 * @see {@link net.sf.ehcache.constructs.web.ResponseHeadersNotModifiableException}
208 */
209 public void testFromServletInclude() {
210 String url = "/servletdispatchtocachedpage/IncludeCachedPageServlet";
211 try {
212 getResponseFromAcceptGzipRequest(url);
213 fail();
214 } catch (HttpInternalErrorException e) {
215 //this is what should happen.
216 } catch (IOException e) {
217 fail();
218 } catch (SAXException e) {
219 fail();
220 }
221
222 }
223
224 /**
225 * Tests that a cached full page can be forwarded to using the RequestDispatcher in a servlet.
226 *
227 * @throws Exception
228 */
229 public void testFromServletForward() throws Exception {
230 String url = "/servletdispatchtocachedpage/ForwardToCachedPageServlet";
231 WebResponse response = getResponseFromAcceptGzipRequest(url);
232 assertResponseOk(response);
233 assertHeadersSane(response);
234 assertPageNotBlank(response);
235 assertPropertlyFormed(response);
236
237 //runStandardTestsOnUrl(url);
238 }
239
240 /**
241 * This sequence returns a valid page with 0 bytes!
242 * This will be seen as an empty page in the browser.
243 * wget response looks like:
244 * wget -S --header='Accept-encoding: gzip' http://localhost:9080//blankpageproblem/Include.jsp
245 * --10:13:00-- http://localhost:9080/legaldispatchtocachedpage/Include.jsp
246 * => `Include.jsp.2'
247 * Resolving localhost... done.
248 * Connecting to localhost[127.0.0.1]:9080... connected.
249 * HTTP request sent, awaiting response...
250 * 1 HTTP/1.1 200 OK
251 * 2 Date: Mon, 22 Nov 2004 00:13:00 GMT
252 * 3 Server: Orion/2.0.3
253 * 4 Set-Cookie: JSESSIONID=JAPIBBCHOFKO; Path=/
254 * 5 Cache-Control: private
255 * 6 Connection: Close
256 * 7 Content-Type: text/html
257 * <p/>
258 * [ <=> ] 0 --.--K/s
259 * <p/>
260 * 10:13:03 (0.00 B/s) - `Include.jsp.2' saved [0]
261 * <p/>
262 * The problem is a filter inplementation which does not write the response wrapper content
263 * when it detects a response already committed so nothing is writen by the filter.
264 *
265 * @throws Exception
266 */
267 public void testBlankPageProblemFilterFromJSPInclude() throws Exception {
268 WebResponse response = getResponseFromAcceptGzipRequest("/blankpageproblem/Include.jsp");
269 assertResponseOk(response);
270 assertHeadersSane(response);
271 try {
272 assertPageNotBlank(response);
273 fail();
274 } catch (AssertionFailedError e) {
275 //noop Page is blank. Should fail.
276 }
277
278 //No point doing the rest of the tests because the page is always blank
279 }
280
281
282 /**
283 * Tests including a page cached with SimplePageCachingFilter, after setting Filter.noFilter(true)
284 * This works. It does not produce a blank page.
285 * wget response looks like:
286 * wget -S --header='Accept-encoding: gzip' http://localhost:9080/legaldispatchtocachedpage/IncludeWithNoFilter.jsp
287 * --13:19:12-- http://localhost:9080/legaldispatchtocachedpage/IncludeWithNoFilter.jsp
288 * => `IncludeWithNoFilter.jsp'
289 * Resolving localhost... done.
290 * Connecting to localhost[127.0.0.1]:9080... connected.
291 * HTTP request sent, awaiting response...
292 * 1 HTTP/1.1 200 OK
293 * 2 Date: Mon, 22 Nov 2004 03:19:12 GMT
294 * 3 Server: Orion/2.0.3
295 * 4 Set-Cookie: JSESSIONID=LCIFMEDBEJKP; Path=/
296 * 5 Cache-Control: private
297 * 6 Connection: Close
298 * 7 Content-Type: text/html
299 * <p/>
300 * [ <=> ] 1,846 1.76M/s
301 * <p/>
302 * 13:19:12 (1.76 MB/s) - `IncludeWithNoFilter.jsp' saved [1846]
303 * The problem is that the response is already committed so nothing is writen by the filter.
304 *
305 * @throws Exception
306 */
307 public void testFromJSPIncludeWithNoFilter() throws Exception {
308 WebResponse response = getResponseFromAcceptGzipRequest("/legaldispatchtocachedpage/IncludeWithNoFilter.jsp");
309 assertResponseOk(response);
310 assertHeadersSane(response);
311 assertPageNotBlank(response);
312 //Delay because of Windows timer inaccuracy
313 Thread.sleep(20);
314 assertResponseGoodAndNotCached("/legaldispatchtocachedpage/IncludeWithNoFilter.jsp", true);
315
316 //No point doing the rest of the tests because the page is not cached
317 }
318
319 /**
320 * This test calls a page which forwards to the cached page using jsp:forward.
321 * <p/>
322 * This works fine.
323 * <p/>
324 * wget response looks like:
325 * wget -S --header='Accept-encoding: gzip' http://localhost:9080/legaldispatchtocachedpage/Forward.jsp
326 * --10:17:43-- http://localhost:9080/legaldispatchtocachedpage/Forward.jsp
327 * => `Forward.jsp'
328 * Resolving localhost... done.
329 * Connecting to localhost[127.0.0.1]:9080... connected.
330 * HTTP request sent, awaiting response...
331 * 1 HTTP/1.1 200 OK
332 * 2 Date: Mon, 22 Nov 2004 00:17:56 GMT
333 * 3 Server: Orion/2.0.3
334 * 4 Content-Location: http://localhost:9080/CachedPage.jsp
335 * 5 Content-Length: 735
336 * 6 Set-Cookie: JSESSIONID=ICDJCBCHOFKO; Path=/
337 * 7 Cache-Control: private
338 * 8 Connection: Close
339 * 9 Content-Type: text/html
340 * 10 Content-Encoding: gzip
341 * <p/>
342 * 10:17:56 (717.77 KB/s) - `Forward.jsp' saved [735/735]
343 *
344 * @throws Exception
345 */
346 public void testFromJSPForward() throws Exception {
347 WebResponse response = getResponseFromAcceptGzipRequest("/legaldispatchtocachedpage/Forward.jsp");
348 assertResponseGood(response, true);
349 runStandardTestsOnUrl("/legaldispatchtocachedpage/Forward.jsp");
350 }
351
352 /**
353 * This test calls a page which forwards to the cached page using jsp:forward.
354 * <p/>
355 * This works fine.
356 * <p/>
357 * wget response looks like:
358 * wget -S --header='Accept-encoding: gzip' http://localhost:9080/legaldispatchtocachedpage/Forward.jsp
359 * --10:17:43-- http://localhost:9080/legaldispatchtocachedpage/Forward.jsp
360 * => `Forward.jsp'
361 * Resolving localhost... done.
362 * Connecting to localhost[127.0.0.1]:9080... connected.
363 * HTTP request sent, awaiting response...
364 * 1 HTTP/1.1 200 OK
365 * 2 Date: Mon, 22 Nov 2004 00:17:56 GMT
366 * 3 Server: Orion/2.0.3
367 * 4 Content-Location: http://localhost:9080/CachedPage.jsp
368 * 5 Content-Length: 735
369 * 6 Set-Cookie: JSESSIONID=ICDJCBCHOFKO; Path=/
370 * 7 Cache-Control: private
371 * 8 Connection: Close
372 * 9 Content-Type: text/html
373 * 10 Content-Encoding: gzip
374 * <p/>
375 * 10:17:56 (717.77 KB/s) - `Forward.jsp' saved [735/735]
376 *
377 * @throws Exception
378 */
379 public void testCallCachedPageFromRedirect() throws Exception {
380 WebResponse response = getResponseFromAcceptGzipRequest("/legaldispatchtocachedpage/Redirect.jsp");
381 assertResponseGood(response, true);
382
383 runStandardTestsOnUrl("/legaldispatchtocachedpage/Redirect.jsp");
384 }
385
386
387 /**
388 * @throws Exception Always reset the URL so we don't get dependencies
389 */
390 protected void tearDown() throws Exception {
391 resetCachedPageUrl();
392 }
393
394 /**
395 * Some standard tests that should work.
396 *
397 * @param url
398 * @throws Exception
399 */
400 private void runStandardTestsOnUrl(String url) throws Exception {
401 cachedPageUrl = url;
402 testCachedPageIsCached();
403
404 cachedPageUrl = url;
405 testCachedPageIsGzippedWhenEncodingHeaderSet();
406
407 cachedPageUrl = url;
408 testCachedPageIsNotGzippedWhenEncodingHeaderNotSet();
409
410 cachedPageUrl = url;
411 testSequenceGzipThenNonGzipThenGzip();
412
413 cachedPageUrl = url;
414 testSequenceNonGzipThenGzipThenNonGzip();
415
416 cachedPageUrl = url;
417 testCachedPageConcurrent();
418 }
419
420
421 /**
422 * A 0 length body should give a 0 length nongzipped body and content length
423 * Manual Test: wget -d --server-response --timestamping --header='If-modified-Since: Fri, 13 May 3006 23:54:18 GMT' --header='Accept-Encoding: gzip' http://localhost:9080/empty_caching_filter/empty.html
424 */
425 public void testIfModifiedZeroLengthHTML() throws Exception {
426
427 String url = "http://localhost:9080/empty_caching_filter/empty.html";
428 HttpClient httpClient = new HttpClient();
429 HttpMethod httpMethod = new GetMethod(url);
430 httpMethod.addRequestHeader("If-modified-Since", "Fri, 13 May 3006 23:54:18 GMT");
431 httpMethod.addRequestHeader("Accept-Encoding", "gzip");
432 int responseCode = httpClient.executeMethod(httpMethod);
433 assertTrue(HttpURLConnection.HTTP_OK == responseCode || HttpURLConnection.HTTP_NOT_MODIFIED == responseCode);
434 String responseBody = httpMethod.getResponseBodyAsString();
435 assertTrue("".equals(responseBody) || null == responseBody);
436 checkNullOrZeroContentLength(httpMethod);
437 }
438
439 /**
440 * Servlets and JSPs can send content even when the response is set to no content.
441 * In this case there should not be a body but there is. Orion seems to kill the body
442 * after is has left the Servlet filter chain. To avoid wget going into an inifinite
443 * retry loop, and presumably some other web clients, the content length should be 0
444 * and the body 0.
445 * <p/>
446 * wget -d --server-response --timestamping --header='If-modified-Since: Fri, 13 May 3006 23:54:18 GMT' --header='Accept-Encoding: gzip' http://localhost:9080/empty_caching_filter/SC_NOT_MODIFIED.jsp
447 */
448 public void testNotModifiedJSPGzipFilter() throws Exception {
449
450 String url = "http://localhost:9080/empty_caching_filter/SC_NOT_MODIFIED.jsp";
451 HttpClient httpClient = new HttpClient();
452 HttpMethod httpMethod = new GetMethod(url);
453 httpMethod.addRequestHeader("If-modified-Since", "Fri, 13 May 3006 23:54:18 GMT");
454 httpMethod.addRequestHeader("Accept-Encoding", "gzip");
455 int responseCode = httpClient.executeMethod(httpMethod);
456 assertEquals(HttpURLConnection.HTTP_NOT_MODIFIED, responseCode);
457 String responseBody = httpMethod.getResponseBodyAsString();
458 assertEquals(null, responseBody);
459 assertNull(httpMethod.getResponseHeader("Content-Encoding"));
460 assertNotNull(httpMethod.getResponseHeader("Last-Modified").getValue());
461 checkNullOrZeroContentLength(httpMethod);
462
463 }
464
465 /**
466 * Servlets and JSPs can send content even when the response is set to no content.
467 * In this case there should not be a body but there is. Orion seems to kill the body
468 * after is has left the Servlet filter chain. To avoid wget going into an inifinite
469 * retry loop, and presumably some other web clients, the content length should be 0
470 * and the body 0.
471 * <p/>
472 * Manual Test: wget -d --server-response --timestamping --header='If-modified-Since: Fri, 13 May 3006 23:54:18 GMT' --header='Accept-Encoding: gzip' http://localhost:9080/empty_caching_filter/SC_NO_CONTENT.jsp
473 */
474 public void testNoContentJSPGzipFilter() throws Exception {
475
476 String url = "http://localhost:9080/empty_caching_filter/SC_NO_CONTENT.jsp";
477 HttpClient httpClient = new HttpClient();
478 HttpMethod httpMethod = new GetMethod(url);
479 httpMethod.addRequestHeader("If-modified-Since", "Fri, 13 May 3006 23:54:18 GMT");
480 httpMethod.addRequestHeader("Accept-Encoding", "gzip");
481 int responseCode = httpClient.executeMethod(httpMethod);
482 assertEquals(HttpURLConnection.HTTP_NO_CONTENT, responseCode);
483 String responseBody = httpMethod.getResponseBodyAsString();
484 assertEquals(null, responseBody);
485 assertNull(httpMethod.getResponseHeader("Content-Encoding"));
486 assertNotNull(httpMethod.getResponseHeader("Last-Modified").getValue());
487 checkNullOrZeroContentLength(httpMethod);
488
489 }
490
491 /**
492 * When the servlet container generates a 404 page not found, we want to pass
493 * it through without caching and without adding anything to it.
494 * <p/>
495 * Manual Test: wget -d --server-response --header='Accept-Encoding: gzip' http://localhost:9080/non_ok/PageNotFound.jsp
496 */
497 public void testNotFound() throws Exception {
498
499 String url = "http://localhost:9080/non_ok/PageNotFound.jsp";
500 HttpClient httpClient = new HttpClient();
501 HttpMethod httpMethod = new GetMethod(url);
502 httpMethod.addRequestHeader("If-modified-Since", "Fri, 13 May 3006 23:54:18 GMT");
503 httpMethod.addRequestHeader("Accept-Encoding", "gzip");
504 int responseCode = httpClient.executeMethod(httpMethod);
505 assertEquals(HttpURLConnection.HTTP_NOT_FOUND, responseCode);
506 String responseBody = httpMethod.getResponseBodyAsString();
507 assertNotNull(responseBody);
508 assertNull(httpMethod.getResponseHeader("Content-Encoding"));
509 }
510
511 /**
512 * When the servlet container generates a 404 page not found, we want to pass
513 * it through without caching and without adding anything to it.
514 * <p/>
515 * Manual Test: wget -d --server-response --header='Accept-Encoding: gzip' http://localhost:9080/non_ok/SendRedirect.jsp
516 */
517 public void testRedirect() throws Exception {
518
519 String url = "http://localhost:9080/non_ok/SendRedirect.jsp";
520 HttpClient httpClient = new HttpClient();
521 HttpMethod httpMethod = new GetMethod(url);
522 httpMethod.addRequestHeader("Accept-Encoding", "gzip");
523 int responseCode = httpClient.executeMethod(httpMethod);
524 //httpclient follows redirects, so gets the home page.
525 assertEquals(HttpURLConnection.HTTP_OK, responseCode);
526 String responseBody = httpMethod.getResponseBodyAsString();
527 assertNotNull(responseBody);
528 assertNull(httpMethod.getResponseHeader("Content-Encoding"));
529 }
530
531
532 }