| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.catalina.filters; |
| |
| import java.io.IOException; |
| import java.net.HttpURLConnection; |
| import java.net.URL; |
| import java.util.Calendar; |
| import java.util.List; |
| import java.util.Map.Entry; |
| import java.util.StringTokenizer; |
| import java.util.TimeZone; |
| |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.junit.Assert; |
| |
| import org.junit.Test; |
| |
| import org.apache.catalina.Context; |
| import org.apache.catalina.filters.ExpiresFilter.Duration; |
| import org.apache.catalina.filters.ExpiresFilter.DurationUnit; |
| import org.apache.catalina.filters.ExpiresFilter.ExpiresConfiguration; |
| import org.apache.catalina.filters.ExpiresFilter.StartingPoint; |
| import org.apache.catalina.startup.Tomcat; |
| import org.apache.catalina.startup.TomcatBaseTest; |
| import org.apache.tomcat.util.descriptor.web.FilterDef; |
| import org.apache.tomcat.util.descriptor.web.FilterMap; |
| |
| public class TestExpiresFilter extends TomcatBaseTest { |
| public static final String TEMP_DIR = System.getProperty("java.io.tmpdir"); |
| |
| @Test |
| public void testConfiguration() throws Exception { |
| |
| Tomcat tomcat = getTomcatInstance(); |
| Context root = tomcat.addContext("", TEMP_DIR); |
| |
| FilterDef filterDef = new FilterDef(); |
| filterDef.addInitParameter("ExpiresDefault", "access plus 1 month"); |
| filterDef.addInitParameter("ExpiresByType text/html", |
| "access plus 1 month 15 days 2 hours"); |
| filterDef.addInitParameter("ExpiresByType image/gif", |
| "modification plus 5 hours 3 minutes"); |
| filterDef.addInitParameter("ExpiresByType image/jpg", "A10000"); |
| filterDef.addInitParameter("ExpiresByType video/mpeg", "M20000"); |
| filterDef.addInitParameter("ExpiresExcludedResponseStatusCodes", |
| "304, 503"); |
| |
| ExpiresFilter expiresFilter = new ExpiresFilter(); |
| |
| filterDef.setFilter(expiresFilter); |
| filterDef.setFilterClass(ExpiresFilter.class.getName()); |
| filterDef.setFilterName(ExpiresFilter.class.getName()); |
| |
| root.addFilterDef(filterDef); |
| |
| FilterMap filterMap = new FilterMap(); |
| filterMap.setFilterName(ExpiresFilter.class.getName()); |
| filterMap.addURLPattern("*"); |
| |
| tomcat.start(); |
| try { |
| // VERIFY EXCLUDED RESPONSE STATUS CODES |
| int[] excludedResponseStatusCodes = expiresFilter.getExcludedResponseStatusCodesAsInts(); |
| Assert.assertEquals(2, excludedResponseStatusCodes.length); |
| Assert.assertEquals(304, excludedResponseStatusCodes[0]); |
| Assert.assertEquals(503, excludedResponseStatusCodes[1]); |
| |
| // VERIFY DEFAULT CONFIGURATION |
| ExpiresConfiguration expiresConfigurationDefault = |
| expiresFilter.getDefaultExpiresConfiguration(); |
| Assert.assertEquals(StartingPoint.ACCESS_TIME, |
| expiresConfigurationDefault.getStartingPoint()); |
| Assert.assertEquals(1, expiresConfigurationDefault.getDurations().size()); |
| Assert.assertEquals(DurationUnit.MONTH, |
| expiresConfigurationDefault.getDurations().get(0).getUnit()); |
| Assert.assertEquals(1, expiresConfigurationDefault.getDurations().get(0).getAmount()); |
| |
| // VERIFY TEXT/HTML |
| ExpiresConfiguration expiresConfigurationTextHtml = |
| expiresFilter.getExpiresConfigurationByContentType().get("text/html"); |
| Assert.assertEquals(StartingPoint.ACCESS_TIME, |
| expiresConfigurationTextHtml.getStartingPoint()); |
| |
| Assert.assertEquals(3, expiresConfigurationTextHtml.getDurations().size()); |
| |
| Duration oneMonth = expiresConfigurationTextHtml.getDurations().get(0); |
| Assert.assertEquals(DurationUnit.MONTH, oneMonth.getUnit()); |
| Assert.assertEquals(1, oneMonth.getAmount()); |
| |
| Duration fifteenDays = expiresConfigurationTextHtml.getDurations().get(1); |
| Assert.assertEquals(DurationUnit.DAY, fifteenDays.getUnit()); |
| Assert.assertEquals(15, fifteenDays.getAmount()); |
| |
| Duration twoHours = expiresConfigurationTextHtml.getDurations().get(2); |
| Assert.assertEquals(DurationUnit.HOUR, twoHours.getUnit()); |
| Assert.assertEquals(2, twoHours.getAmount()); |
| |
| // VERIFY IMAGE/GIF |
| ExpiresConfiguration expiresConfigurationImageGif = |
| expiresFilter.getExpiresConfigurationByContentType().get("image/gif"); |
| Assert.assertEquals(StartingPoint.LAST_MODIFICATION_TIME, |
| expiresConfigurationImageGif.getStartingPoint()); |
| |
| Assert.assertEquals(2, expiresConfigurationImageGif.getDurations().size()); |
| |
| Duration fiveHours = expiresConfigurationImageGif.getDurations().get(0); |
| Assert.assertEquals(DurationUnit.HOUR, fiveHours.getUnit()); |
| Assert.assertEquals(5, fiveHours.getAmount()); |
| |
| Duration threeMinutes = expiresConfigurationImageGif.getDurations().get(1); |
| Assert.assertEquals(DurationUnit.MINUTE, threeMinutes.getUnit()); |
| Assert.assertEquals(3, threeMinutes.getAmount()); |
| |
| // VERIFY IMAGE/JPG |
| ExpiresConfiguration expiresConfigurationImageJpg = |
| expiresFilter.getExpiresConfigurationByContentType().get("image/jpg"); |
| Assert.assertEquals(StartingPoint.ACCESS_TIME, |
| expiresConfigurationImageJpg.getStartingPoint()); |
| |
| Assert.assertEquals(1, expiresConfigurationImageJpg.getDurations().size()); |
| |
| Duration tenThousandSeconds = expiresConfigurationImageJpg.getDurations().get(0); |
| Assert.assertEquals(DurationUnit.SECOND, tenThousandSeconds.getUnit()); |
| Assert.assertEquals(10000, tenThousandSeconds.getAmount()); |
| |
| // VERIFY VIDEO/MPEG |
| ExpiresConfiguration expiresConfiguration = |
| expiresFilter.getExpiresConfigurationByContentType().get("video/mpeg"); |
| Assert.assertEquals(StartingPoint.LAST_MODIFICATION_TIME, |
| expiresConfiguration.getStartingPoint()); |
| |
| Assert.assertEquals(1, expiresConfiguration.getDurations().size()); |
| |
| Duration twentyThousandSeconds = expiresConfiguration.getDurations().get(0); |
| Assert.assertEquals(DurationUnit.SECOND, twentyThousandSeconds.getUnit()); |
| Assert.assertEquals(20000, twentyThousandSeconds.getAmount()); |
| } finally { |
| tomcat.stop(); |
| } |
| } |
| |
| /* |
| * Test that a resource with empty content is also processed |
| */ |
| @Test |
| public void testEmptyContent() throws Exception { |
| HttpServlet servlet = new HttpServlet() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void service(HttpServletRequest request, |
| HttpServletResponse response) throws ServletException, |
| IOException { |
| response.setContentType("text/plain"); |
| // no content is written in the response |
| } |
| }; |
| |
| validate(servlet, Integer.valueOf(7 * 60)); |
| } |
| |
| @Test |
| public void testParseExpiresConfigurationCombinedDuration() { |
| ExpiresFilter expiresFilter = new ExpiresFilter(); |
| ExpiresConfiguration actualConfiguration = expiresFilter.parseExpiresConfiguration("access plus 1 month 15 days 2 hours"); |
| |
| Assert.assertEquals(StartingPoint.ACCESS_TIME, |
| actualConfiguration.getStartingPoint()); |
| |
| Assert.assertEquals(3, actualConfiguration.getDurations().size()); |
| |
| } |
| |
| @Test |
| public void testParseExpiresConfigurationMonoDuration() { |
| ExpiresFilter expiresFilter = new ExpiresFilter(); |
| ExpiresConfiguration actualConfiguration = expiresFilter.parseExpiresConfiguration("access plus 2 hours"); |
| |
| Assert.assertEquals(StartingPoint.ACCESS_TIME, |
| actualConfiguration.getStartingPoint()); |
| |
| Assert.assertEquals(1, actualConfiguration.getDurations().size()); |
| Assert.assertEquals(2, |
| actualConfiguration.getDurations().get(0).getAmount()); |
| Assert.assertEquals(DurationUnit.HOUR, |
| actualConfiguration.getDurations().get(0).getUnit()); |
| |
| } |
| |
| @Test |
| public void testSkipBecauseCacheControlMaxAgeIsDefined() throws Exception { |
| HttpServlet servlet = new HttpServlet() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void service(HttpServletRequest request, |
| HttpServletResponse response) throws ServletException, |
| IOException { |
| response.setContentType("text/xml; charset=utf-8"); |
| response.addHeader("Cache-Control", "private, max-age=232"); |
| response.getWriter().print("Hello world"); |
| } |
| }; |
| |
| validate(servlet, Integer.valueOf(232)); |
| } |
| |
| @Test |
| public void testExcludedResponseStatusCode() throws Exception { |
| HttpServlet servlet = new HttpServlet() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void service(HttpServletRequest request, |
| HttpServletResponse response) throws ServletException, |
| IOException { |
| response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); |
| response.addHeader("ETag", "W/\"1934-1269208821000\""); |
| response.addDateHeader("Date", System.currentTimeMillis()); |
| } |
| }; |
| |
| validate(servlet, null, HttpServletResponse.SC_NOT_MODIFIED); |
| } |
| |
| @Test |
| public void testNullContentType() throws Exception { |
| HttpServlet servlet = new HttpServlet() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void service(HttpServletRequest request, |
| HttpServletResponse response) throws ServletException, |
| IOException { |
| response.setContentType(null); |
| } |
| }; |
| |
| validate(servlet, Integer.valueOf(1 * 60)); |
| } |
| |
| @Test |
| public void testSkipBecauseExpiresIsDefined() throws Exception { |
| HttpServlet servlet = new HttpServlet() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void service(HttpServletRequest request, |
| HttpServletResponse response) throws ServletException, |
| IOException { |
| response.setContentType("text/xml; charset=utf-8"); |
| response.addDateHeader("Expires", System.currentTimeMillis()); |
| response.getWriter().print("Hello world"); |
| } |
| }; |
| |
| validate(servlet, null); |
| } |
| |
| @Test |
| public void testUseContentTypeExpiresConfiguration() throws Exception { |
| HttpServlet servlet = new HttpServlet() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void service(HttpServletRequest request, |
| HttpServletResponse response) throws ServletException, |
| IOException { |
| response.setContentType("text/xml; charset=utf-8"); |
| response.getWriter().print("Hello world"); |
| } |
| }; |
| |
| validate(servlet, Integer.valueOf(3 * 60)); |
| } |
| |
| @Test |
| public void testUseContentTypeWithoutCharsetExpiresConfiguration() |
| throws Exception { |
| HttpServlet servlet = new HttpServlet() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void service(HttpServletRequest request, |
| HttpServletResponse response) throws ServletException, |
| IOException { |
| response.setContentType("text/xml; charset=iso-8859-1"); |
| response.getWriter().print("Hello world"); |
| } |
| }; |
| |
| validate(servlet, Integer.valueOf(5 * 60)); |
| } |
| |
| @Test |
| public void testUseDefaultConfiguration1() throws Exception { |
| HttpServlet servlet = new HttpServlet() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void service(HttpServletRequest request, |
| HttpServletResponse response) throws ServletException, |
| IOException { |
| response.setContentType("image/jpeg"); |
| response.getWriter().print("Hello world"); |
| } |
| }; |
| |
| validate(servlet, Integer.valueOf(1 * 60)); |
| } |
| |
| @Test |
| public void testUseDefaultConfiguration2() throws Exception { |
| HttpServlet servlet = new HttpServlet() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void service(HttpServletRequest request, |
| HttpServletResponse response) throws ServletException, |
| IOException { |
| response.setContentType("image/jpeg"); |
| response.addHeader("Cache-Control", "private"); |
| |
| response.getWriter().print("Hello world"); |
| } |
| }; |
| |
| validate(servlet, Integer.valueOf(1 * 60)); |
| } |
| |
| @Test |
| public void testUseMajorTypeExpiresConfiguration() throws Exception { |
| HttpServlet servlet = new HttpServlet() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| protected void service(HttpServletRequest request, |
| HttpServletResponse response) throws ServletException, |
| IOException { |
| response.setContentType("text/json; charset=iso-8859-1"); |
| response.getWriter().print("Hello world"); |
| } |
| }; |
| |
| validate(servlet, Integer.valueOf(7 * 60)); |
| } |
| |
| protected void validate(HttpServlet servlet, Integer expectedMaxAgeInSeconds) |
| throws Exception { |
| validate(servlet, expectedMaxAgeInSeconds, HttpURLConnection.HTTP_OK); |
| } |
| |
| protected void validate(HttpServlet servlet, |
| Integer expectedMaxAgeInSeconds, int expectedResponseStatusCode) |
| throws Exception { |
| // SETUP |
| |
| Tomcat tomcat = getTomcatInstance(); |
| Context root = tomcat.addContext("", TEMP_DIR); |
| |
| FilterDef filterDef = new FilterDef(); |
| filterDef.addInitParameter("ExpiresDefault", "access plus 1 minute"); |
| filterDef.addInitParameter("ExpiresByType text/xml;charset=utf-8", |
| "access plus 3 minutes"); |
| filterDef.addInitParameter("ExpiresByType text/xml", |
| "access plus 5 minutes"); |
| filterDef.addInitParameter("ExpiresByType text", |
| "access plus 7 minutes"); |
| filterDef.addInitParameter("ExpiresExcludedResponseStatusCodes", |
| "304, 503"); |
| |
| filterDef.setFilterClass(ExpiresFilter.class.getName()); |
| filterDef.setFilterName(ExpiresFilter.class.getName()); |
| |
| root.addFilterDef(filterDef); |
| |
| FilterMap filterMap = new FilterMap(); |
| filterMap.setFilterName(ExpiresFilter.class.getName()); |
| filterMap.addURLPattern("*"); |
| root.addFilterMap(filterMap); |
| |
| Tomcat.addServlet(root, servlet.getClass().getName(), servlet); |
| root.addServletMapping("/test", servlet.getClass().getName()); |
| |
| tomcat.start(); |
| |
| try { |
| Calendar.getInstance(TimeZone.getTimeZone("GMT")); |
| long timeBeforeInMillis = System.currentTimeMillis(); |
| |
| // TEST |
| HttpURLConnection httpURLConnection = (HttpURLConnection) new URL( |
| "http://localhost:" + tomcat.getConnector().getLocalPort() + |
| "/test").openConnection(); |
| |
| // VALIDATE |
| Assert.assertEquals(expectedResponseStatusCode, |
| httpURLConnection.getResponseCode()); |
| |
| StringBuilder msg = new StringBuilder(); |
| for (Entry<String, List<String>> field : httpURLConnection.getHeaderFields().entrySet()) { |
| for (String value : field.getValue()) { |
| msg.append((field.getKey() == null ? "" : field.getKey() + |
| ": ") + |
| value + "\n"); |
| } |
| } |
| System.out.println(msg); |
| |
| Integer actualMaxAgeInSeconds; |
| |
| String cacheControlHeader = httpURLConnection.getHeaderField("Cache-Control"); |
| if (cacheControlHeader == null) { |
| actualMaxAgeInSeconds = null; |
| } else { |
| actualMaxAgeInSeconds = null; |
| StringTokenizer cacheControlTokenizer = new StringTokenizer( |
| cacheControlHeader, ","); |
| while (cacheControlTokenizer.hasMoreTokens() && |
| actualMaxAgeInSeconds == null) { |
| String cacheDirective = cacheControlTokenizer.nextToken(); |
| StringTokenizer cacheDirectiveTokenizer = new StringTokenizer( |
| cacheDirective, "="); |
| if (cacheDirectiveTokenizer.countTokens() == 2) { |
| String key = cacheDirectiveTokenizer.nextToken().trim(); |
| String value = cacheDirectiveTokenizer.nextToken().trim(); |
| if (key.equalsIgnoreCase("max-age")) { |
| actualMaxAgeInSeconds = Integer.valueOf(value); |
| } |
| } |
| } |
| } |
| |
| if (expectedMaxAgeInSeconds == null) { |
| Assert.assertNull("actualMaxAgeInSeconds '" + |
| actualMaxAgeInSeconds + "' should be null", |
| actualMaxAgeInSeconds); |
| return; |
| } |
| |
| Assert.assertNotNull(actualMaxAgeInSeconds); |
| |
| int deltaInSeconds = Math.abs(actualMaxAgeInSeconds.intValue() - |
| expectedMaxAgeInSeconds.intValue()); |
| Assert.assertTrue("actualMaxAgeInSeconds: " + |
| actualMaxAgeInSeconds + ", expectedMaxAgeInSeconds: " + |
| expectedMaxAgeInSeconds + ", request time: " + |
| timeBeforeInMillis + " for content type " + |
| httpURLConnection.getContentType(), deltaInSeconds < 3); |
| |
| } finally { |
| tomcat.stop(); |
| } |
| } |
| |
| @Test |
| public void testIntsToCommaDelimitedString() { |
| String actual = ExpiresFilter.intsToCommaDelimitedString(new int[] { |
| 500, 503 }); |
| String expected = "500, 503"; |
| |
| Assert.assertEquals(expected, actual); |
| } |
| } |