blob: b37ca43306d66e296ca125d45881485793238118 [file] [log] [blame]
/*
* 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.addURLPatternDecoded("*");
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.addURLPatternDecoded("*");
root.addFilterMap(filterMap);
Tomcat.addServlet(root, servlet.getClass().getName(), servlet);
root.addServletMappingDecoded("/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);
}
}