<artifactId>spring-webmvc</artifactId>
<version>${springframework.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-aspects</artifactId>
+ <version>${springframework.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-tx</artifactId>
+ <version>${springframework.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.aspectj</groupId>
+ <artifactId>aspectjrt</artifactId>
+ <version>${aspectj.version}</version>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>${servlet-api.version}</version>
<scope>provided</scope>
</dependency>
+ <dependency>
+ <groupId>javax.persistence</groupId>
+ <artifactId>persistence-api</artifactId>
+ <version>${jpa.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>httpunit</groupId>
+ <artifactId>httpunit</artifactId>
+ <version>${httpunit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-test</artifactId>
+ <version>${springframework.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>${slf4j.binding}</artifactId>
+ <version>${slf4j.version}</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>aspectj-maven-plugin</artifactId>
+ <configuration>
+ <complianceLevel>1.6</complianceLevel>
+ <aspectLibraries>
+ <aspectLibrary>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-aspects</artifactId>
+ </aspectLibrary>
+ </aspectLibraries>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
</project>
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
import java.util.zip.GZIPOutputStream;
import javax.servlet.Filter;
import javax.servlet.http.HttpServletResponseWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
*
* @author kai
*/
-@Configurable
+@Configurable(autowire=Autowire.BY_NAME)
public class AcceleratorFilter implements Filter {
private final static Logger log = LoggerFactory.getLogger(AcceleratorFilter.class);
+ private final static Map<String,String> ADDITIONAL_HEADERS_NONE = Collections.unmodifiableMap(new HashMap<String,String>());
+ private final static Map<String,String> ADDITIONAL_HEADERS_GZIP;
+
public final static String REQUEST_URI_ATTRIBUTE = "javax.servlet.include.request_uri";
+ public final static String RESPONSE_WRAPPER = AcceleratorFilter.class.getName() + ".RESPONSE_WRAPPER";
+
+ static {
+ Map<String,String> map = new HashMap<String,String>(1);
+ map.put(Headers.HEADER_CONTENT_ENCODING, "gzip");
+ ADDITIONAL_HEADERS_GZIP = Collections.unmodifiableMap(map);
+ }
@Autowired CacheControl cacheControl;
return;
}
- AccelerationWrapper wrapper = new AccelerationWrapper((HttpServletRequest)request, (HttpServletResponse)response);
+ HttpServletRequest httpRequest = (HttpServletRequest)request;
+ HttpServletResponse httpResponse = (HttpServletResponse)response;
+ AccelerationWrapper wrapper = new AccelerationWrapper(httpRequest, httpResponse);
+ httpRequest.setAttribute(RESPONSE_WRAPPER, wrapper);
cacheControl.init(wrapper);
chain.doFilter(request, wrapper);
+ /** Dekoration auslösen, falls sie bisher nicht ausgelöst wurde... */
+ cacheControl.decorate(httpRequest, httpResponse, wrapper);
wrapper.finish();
}
private final HttpServletResponse response;
private boolean zipped;
+ private int buffer;
private GZIPServletOutputStream out;
+ private ServletOutputStream stream;
+ private PrintWriter writer;
+
+ private boolean guessing = true;
private long now;
- private int buffer;
private int status;
- private String type;
+ private int cacheSeconds;
+ private boolean cacheSecondsSet = false;
+ private long lastModified, expires = 0l;
+ private String eTag;
+ private boolean weak;
+ private Map<String,String> cacheParams;
AccelerationWrapper(HttpServletRequest request, HttpServletResponse response) throws IOException {
this.response = response;
now = System.currentTimeMillis();
- buffer = AcceleratorFilter.this.buffer;
status = HttpServletResponse.SC_OK;
+ cacheSeconds = AcceleratorFilter.this.cacheSeconds;
+ lastModified = AcceleratorFilter.this.lastModified;
+ eTag = AcceleratorFilter.this.eTag;
+ weak = AcceleratorFilter.this.weak;
+ cacheParams = new HashMap<String,String>();
+ buffer = AcceleratorFilter.this.buffer;
zipped = false;
- Enumeration values = request.getHeaders(HeaderNames.HEADER_ACCEPT_ENCODING);
+ Enumeration values = request.getHeaders(Headers.HEADER_ACCEPT_ENCODING);
while (values.hasMoreElements()) {
String value = (String) values.nextElement();
if (value.indexOf("gzip") != -1) {
zipped = true;
- response.addHeader(HeaderNames.HEADER_CONTENT_ENCODING, "gzip");
break;
}
}
}
}
+ @Override
+ public void addDateHeader(String name, long value) {
+
+ if (!guessing) {
+ super.addDateHeader(name, value);
+ return;
+ }
+
+ if (Headers.HEADER_DATE.equalsIgnoreCase(name)) {
+ now = value;
+ calculateCacheSeconds();
+ return;
+ }
+
+ if (Headers.HEADER_EXPIRES.equalsIgnoreCase(name)) {
+ expires = value;
+ calculateCacheSeconds();
+ return;
+ }
+
+ if (Headers.HEADER_LAST_MODIFIED.equalsIgnoreCase(name)) {
+ lastModified = value;
+ return;
+ }
+
+ /** Unknown header: pass throug! */
+ super.addDateHeader(name, value);
+ }
+
+ @Override
+ public void addHeader(String name, String value) {
+
+ if (!guessing) {
+ super.addHeader(name, value);
+ return;
+ }
+
+ if (value == null)
+ return;
+ analyzeHeader(name, value, false);
+ }
+
+ @Override
+ public void addIntHeader(String name, int value) {
+
+ if (!guessing) {
+ super.addIntHeader(name, value);
+ return;
+ }
+
+ analyzeHeader(name, Integer.toString(value), false);
+ }
+
+ @Override
+ public void setDateHeader(String name, long value) {
+
+ if (!guessing) {
+ super.setDateHeader(name, value);
+ return;
+ }
+
+ if (Headers.HEADER_DATE.equalsIgnoreCase(name)) {
+ now = value;
+ calculateCacheSeconds();
+ return;
+ }
+
+ if (Headers.HEADER_EXPIRES.equalsIgnoreCase(name)) {
+ expires = value;
+ calculateCacheSeconds();
+ return;
+ }
+
+ if (Headers.HEADER_LAST_MODIFIED.equalsIgnoreCase(name)) {
+ lastModified = value;
+ return;
+ }
+
+ /** Unknown header: pass throug! */
+ super.setDateHeader(name, value);
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+
+ if (!guessing) {
+ super.setHeader(name, value);
+ return;
+ }
+
+ analyzeHeader(name, value, true);
+ }
+
+ @Override
+ public void setIntHeader(String name, int value) {
+
+ if (!guessing) {
+ super.setIntHeader(name, value);
+ return;
+ }
+
+ analyzeHeader(name, Integer.toString(value), true);
+ }
+
@Override
public ServletOutputStream getOutputStream() throws IOException {
- if (out == null)
- out = new GZIPServletOutputStream(response.getOutputStream(), zipped);
+
+ if (writer != null)
+ throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!");
+
+ if (stream == null) {
+ out = new GZIPServletOutputStream();
+ stream = out;
+ }
+
return out;
}
@Override
public PrintWriter getWriter() throws IOException {
- return new PrintWriter(getOutputStream());
+
+ if (stream != null)
+ throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!");
+
+ if (writer == null) {
+ out = new GZIPServletOutputStream();
+ writer = new PrintWriter(out);
+ }
+
+ return writer;
}
@Override
- public void setContentType(String type) {
- this.type = type;
- response.setContentType(type);
+ public void setContentLength(int len) {
+ if (zipped)
+ log.info("Supressing explicit content-length {} for request {}, because content will be zipped!", len, request.getRequestURI());
+ else
+ response.setContentLength(len);
}
@Override
public void setBufferSize(int size) {
+
+ if (out != null && this.buffer != out.left)
+ throw new IllegalStateException("setBufferSize() cannot be called after content has been written!");
+
+ if (size < 0)
+ size = 0;
+
this.buffer = size;
+ if (out != null)
+ out.left = size;
response.setBufferSize(size);
}
- @Override
- public int getBufferSize() {
- return buffer;
- }
-
@Override
public void flushBuffer() throws IOException {
+
cacheControl.decorate(request, response, this);
- if (zipped && out != null) {
- out.zout.finish();
- }
response.flushBuffer();
}
@Override
public void resetBuffer() {
- response.resetBuffer();
- }
- @Override
- public boolean isCommitted() {
- return response.isCommitted();
+ response.resetBuffer();
+ stream = null;
+ writer = null;
}
@Override
public void reset() {
+
response.reset();
+ out = null;
+ stream = null;
+ writer = null;
+
+ /** Cookies has been cleared! Reinitialize decorator... */
+ cacheControl.init(this);
}
@Override
public void cacheControl(HttpServletRequest request, Map<String, String> cacheControlMap) {
+ cacheControlMap.putAll(cacheParams);
+ }
+
+ @Override
+ public Map<String,String> getAdditionalHeaders(HttpServletRequest request) {
+ if (zipped && !out.empty)
+ return ADDITIONAL_HEADERS_GZIP;
+ else
+ return ADDITIONAL_HEADERS_NONE;
+ }
+
+ public void guessingFinished() {
+ guessing = false;
+ }
+
+
+ private void analyzeHeader(String name, String value, boolean overwrite) {
+ if (name == null)
+ return;
+ name = name.trim();
+
+ if (name.equalsIgnoreCase(Headers.HEADER_DATE)) {
+ if (value == null) {
+ if (overwrite) {
+ now = System.currentTimeMillis();
+ cacheSeconds = AcceleratorFilter.this.cacheSeconds;
+ }
+ return;
+ }
+ try {
+ SimpleDateFormat parser = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ now = parser.parse(value).getTime();
+ calculateCacheSeconds();
+ }
+ catch (ParseException e) {
+ log.warn("ignoring date for header \"Date\" in invalid format: {}", value);
+ }
+ return;
+ }
+
+ if (name.equalsIgnoreCase(Headers.HEADER_EXPIRES)) {
+ if (value == null) {
+ if (overwrite) {
+ expires = 0;
+ cacheSeconds = AcceleratorFilter.this.cacheSeconds;
+ }
+ return;
+ }
+ try {
+ SimpleDateFormat parser = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ expires = parser.parse(value).getTime();
+ calculateCacheSeconds();
+ }
+ catch (ParseException e) {
+ log.warn("ignoring date for header \"Expires\" in invalid format: {}", value);
+ }
+ return;
+ }
+
+ if (name.equalsIgnoreCase(Headers.HEADER_LAST_MODIFIED)) {
+ if (value == null) {
+ if (overwrite)
+ lastModified = AcceleratorFilter.this.lastModified;
+ return;
+ }
+ try {
+ SimpleDateFormat parser = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ lastModified = parser.parse(value).getTime();
+ }
+ catch (ParseException e) {
+ log.warn("ignoring date for header \"Last-Modified\" in invalid format: {}", value);
+ }
+ return;
+ }
+
+ if (name.equalsIgnoreCase(Headers.HEADER_ETAG)) {
+ if (value == null) {
+ if (overwrite) {
+ eTag = AcceleratorFilter.this.eTag;
+ weak = AcceleratorFilter.this.weak;
+ }
+ return;
+ }
+ value = value.trim();
+ int start = 0;
+ int end = value.length();
+ if (value.startsWith("W/")) {
+ weak = true;
+ start = 2;
+ }
+ else {
+ weak = false;
+ }
+ if (value.charAt(start) == '"')
+ start++;
+ else
+ log.warn("Quote at the beginning ov ETag is missing: {}", value);
+ if (value.charAt(end -1) == '"')
+ end--;
+ else
+ log.warn("Quote at the end of ETag is missing: {}", value);
+ eTag = value.substring(start, end);
+ String filtered = eTag.replaceAll("[^\\x00-\\x21\\x23-\\x7F]+","");
+ if (filtered.length() < eTag.length()) {
+ log.warn("filtering out illegal characters in ETag: \"{}\" -> \"{}\"", eTag, filtered);
+ eTag = filtered;
+ }
+ }
+
+ if (name.equalsIgnoreCase(Headers.HEADER_CACHE_CONTROL)) {
+ if (overwrite)
+ cacheParams.clear();
+ if (value == null)
+ return;
+ for (String param : value.split(",")) {
+ param = param.trim();
+ int pos = param.indexOf("=");
+ if (pos < 0) {
+ cacheParams.put(param, null);
+ }
+ else {
+ String paramName = param.substring(0, pos).trim();
+ if (paramName.equalsIgnoreCase("max-age")) {
+ try {
+ cacheSeconds = Integer.parseInt(param.substring(pos + 1));
+ cacheSecondsSet = true;
+ }
+ catch (NumberFormatException e) {
+ log.warn("illegal value for Header \"Cache-Control\":", param);
+ }
+ }
+ else {
+ cacheParams.put(paramName, param.substring(pos + 1));
+ }
+ }
+ }
+ return;
+ }
+
+ if (name.equalsIgnoreCase(Headers.HEADER_PRAGMA)) {
+ if (value != null && value.trim().equalsIgnoreCase("no-cache"))
+ cacheSeconds = 0;
+ return;
+ }
+
+ /** Pass header through, if no value from intrest was found */
+ if (overwrite)
+ super.setHeader(name, value);
+ else
+ super.addHeader(name, value);
+ }
+
+ private void calculateCacheSeconds() {
+ if (!cacheSecondsSet && expires >= now) {
+ cacheSeconds = (int)(expires/1000 - now/1000);
+ log.debug("calculating cache-seconds from DATE and EXPIRES: {}", cacheSeconds);
+ }
}
private final OutputStream out;
private final GZIPOutputStream zout;
- private boolean untouched = true;
+ int left;
+ boolean empty;
- public GZIPServletOutputStream(ServletOutputStream out, boolean zipped) throws IOException {
+ public GZIPServletOutputStream() throws IOException {
if (zipped) {
- this.zout = new GZIPOutputStream(out, buffer);
+ this.zout = new GZIPOutputStream(response.getOutputStream(), buffer);
this.out = this.zout;
}
else {
- this.out = out;
this.zout = null;
+ this.out = response.getOutputStream();
+ }
+ empty = true;
+ left = buffer;
+ }
+
+
+ @Override
+ public void close() throws IOException {
+ try {
+ AcceleratorFilter.this.cacheControl.decorate(AccelerationWrapper.this.request, response, buffer);
+ }
+ catch (Exception e) {
+ log.error("Error while guessing Cache-Header's", e);
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
+
+ if (!empty && zout != null)
+ zout.finish();
+ out.close();
}
+ @Override
+ public void flush() throws IOException {
+ try {
+ AcceleratorFilter.this.cacheControl.decorate(AccelerationWrapper.this.request, response, buffer);
+ }
+ catch (Exception e) {
+ log.error("Error while guessing Cache-Header's", e);
+ response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+ }
+
+ out.flush();
+ }
@Override
public void write(int i) throws IOException {
- if (untouched) {
- untouched = false;
+ if (left == 0) {
try {
AcceleratorFilter.this.cacheControl.decorate(AccelerationWrapper.this.request, response, buffer);
}
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
+ empty = false;
+ left--;
out.write(i);
}
}
import de.halbekunst.juplo.cachecontrol.annotations.CacheSeconds;
import de.halbekunst.juplo.cachecontrol.annotations.Accepts;
+import de.halbekunst.juplo.cachecontrol.annotations.AdditionalHeaders;
import de.halbekunst.juplo.cachecontrol.annotations.LastModified;
import de.halbekunst.juplo.cachecontrol.annotations.ETag;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Date;
+import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.stereotype.Component;
/**
*
* @author kai
*/
+@Component
public class CacheControl {
private final static Logger log = LoggerFactory.getLogger(CacheControl.class);
private static final ThreadLocal<CacheMethodHandle> tl = new ThreadLocal<CacheMethodHandle>();
- private Integer defaultCacheSeconds;
- private Long defaultLastModified;
+ @Autowired @Qualifier("cacheSeconds") private Integer defaultCacheSeconds;
+ @Autowired @Qualifier("lastModified") private Long defaultLastModified;
public void init(CacheMethodHandle handle) {
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.18 RFC 2616,
* Abschnitt 14.18} einen Date-Header enthalten
*/
- response.setDateHeader(HeaderNames.HEADER_DATE, controller.getTimestamp());
+ response.setDateHeader(Headers.HEADER_DATE, controller.getTimestamp());
/** Besondere Maßnahmen für besondere HTTP-Status-Codes ?!? */
int status = controller.accepts(request);
return false;
}
+ Map<String,String> headers = controller.getAdditionalHeaders(request);
+ for (String name : headers.keySet())
+ response.addHeader(name, headers.get(name));
+
String url = null;
if (log.isDebugEnabled()) {
if (request.getQueryString() == null) {
int cacheSeconds = controller.getCacheSeconds(request);
if (cacheSeconds < 1) {
log.debug("{}: caching disabled!", url);
- response.setDateHeader(HeaderNames.HEADER_DATE, controller.getTimestamp());
- response.setDateHeader(HeaderNames.HEADER_EXPIRES, 0);
- response.addHeader(HeaderNames.HEADER_PRAGMA, "no-cache");
- response.addHeader(HeaderNames.HEADER_CACHE_CONTROL, "private");
- response.addHeader(HeaderNames.HEADER_CACHE_CONTROL, "no-cache");
- response.addHeader(HeaderNames.HEADER_CACHE_CONTROL, "no-store");
- response.addHeader(HeaderNames.HEADER_CACHE_CONTROL, "max-age=0");
- response.addHeader(HeaderNames.HEADER_CACHE_CONTROL, "s-max-age=0");
+ response.setDateHeader(Headers.HEADER_DATE, controller.getTimestamp());
+ response.setDateHeader(Headers.HEADER_EXPIRES, 0);
+ response.addHeader(Headers.HEADER_PRAGMA, "no-cache");
+ response.addHeader(Headers.HEADER_CACHE_CONTROL, "private");
+ response.addHeader(Headers.HEADER_CACHE_CONTROL, "no-cache");
+ response.addHeader(Headers.HEADER_CACHE_CONTROL, "no-store");
+ response.addHeader(Headers.HEADER_CACHE_CONTROL, "max-age=0");
+ response.addHeader(Headers.HEADER_CACHE_CONTROL, "s-max-age=0");
return true;
}
long ifModifiedSince = -1;
try {
- ifModifiedSince = request.getDateHeader(HeaderNames.HEADER_IF_MODIFIED_SINCE);
+ ifModifiedSince = request.getDateHeader(Headers.HEADER_IF_MODIFIED_SINCE);
}
catch (Exception e) {
log.error("Exception while fetching If-Modified-Since: {}", e);
*/
lastModified = lastModified - (lastModified % 1000);
- String ifNoneMatch = request.getHeader(HeaderNames.HEADER_IF_NONE_MATCH);
+ String ifNoneMatch = request.getHeader(Headers.HEADER_IF_NONE_MATCH);
String eTag = controller.getETag(request);
/**
builder.append('"');
builder.append(eTag);
builder.append('"');
- response.setHeader(HeaderNames.HEADER_ETAG, builder.toString());
+ response.setHeader(Headers.HEADER_ETAG, builder.toString());
}
ifNoneMatch = ifNoneMatch.substring(1, ifNoneMatch.length() - 1);
}
- if (!weak || (request.getMethod().equals("GET") && request.getHeader(HeaderNames.HEADER_RANGE) == null)) {
+ if (!weak || (request.getMethod().equals("GET") && request.getHeader(Headers.HEADER_RANGE) == null)) {
/**
* Die Gleichheit gilt nur, wenn die ETag's der Anfrage _und_ der
* Antwort stark sind (starke Gleichheit!), oder wenn die Antwort nur
log.debug("{}: first up!", url);
/** HTTP/1.1-Caching-Header richtig setzen!! */
- response.setDateHeader(HeaderNames.HEADER_LAST_MODIFIED, lastModified);
+ response.setDateHeader(Headers.HEADER_LAST_MODIFIED, lastModified);
/** Cache-Control für HTTP/1.1-Clients generieren */
Map<String, String> cacheControl = new TreeMap<String, String>();
* <code>Expires</code>-Header für HTTP/1.0-Clients setzen.
*/
cacheControl.put("max-age", Integer.toString(cacheSeconds));
- response.setDateHeader(HeaderNames.HEADER_EXPIRES, (controller.getTimestamp() + (long) cacheSeconds * 1000));
+ response.setDateHeader(Headers.HEADER_EXPIRES, (controller.getTimestamp() + (long) cacheSeconds * 1000));
}
/** Dem Handler die Gelegenheit geben, den Cache-Controll-Header anzupassen */
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.32
* Abschnitt 14.32})
*/
- response.setDateHeader(HeaderNames.HEADER_EXPIRES, 0l);
- response.addHeader(HeaderNames.HEADER_PRAGMA, "no-cache");
+ response.setDateHeader(Headers.HEADER_EXPIRES, 0l);
+ response.addHeader(Headers.HEADER_PRAGMA, "no-cache");
}
StringBuilder builder = new StringBuilder();
builder.append('=');
builder.append(entry.getValue());
}
- response.addHeader(HeaderNames.HEADER_CACHE_CONTROL, builder.toString());
+ response.addHeader(Headers.HEADER_CACHE_CONTROL, builder.toString());
}
return true;
String getETag(HttpServletRequest request);
boolean isETagWeak();
void cacheControl(HttpServletRequest request, Map<String, String> cacheControlMap);
+ Map<String,String> getAdditionalHeaders(HttpServletRequest request);
}
private Integer cacheSeconds;
private Long lastModified;
private String eTag;
+ private Map<String,String> additionalHeaders;
private Method acceptsMethod;
private Method cacheSecondsMethod;
private Method lastModifiedMethod;
private Method eTagMethod;
private Method cacheControlMethod;
+ private Method additionalHeadersMethod;
private boolean isAcceptsMethodDefined;
private boolean isCacheSecondsMethodDefined;
private boolean isLastModifiedMethodDefined;
private boolean isETagMethodDefined;
private boolean isCacheControlMethodDefined;
+ private boolean isAdditionalHeadersMethodDefined;
private boolean weak;
isETagMethodDefined = true;
continue;
}
+ if (annotation.annotationType().equals(AdditionalHeaders.class)) {
+ AdditionalHeaders additionalHeadersAnnotation = (AdditionalHeaders)annotation;
+ additionalHeaders = new HashMap<String,String>();
+ for (String header : additionalHeadersAnnotation.value()) {
+ int i = header.indexOf(':');
+ if (i < 0) {
+ log.error("invalid header: [{}]", header);
+ }
+ else {
+ String name = header.substring(0,i).trim();
+ String value = header.substring(i+1,header.length()).trim();
+ additionalHeaders.put(name, value);
+ }
+ }
+ isAdditionalHeadersMethodDefined = true;
+ continue;
+ }
}
/** Method-Level-Annotations auslesen */
isCacheControlMethodDefined = true;
continue;
}
+ if (annotation.annotationType().equals(AdditionalHeaders.class)) {
+ if (isAdditionalHeadersMethodDefined)
+ throw new IllegalArgumentException("Die Annotation @AdditionalHeaders wurde in der Klasse " + handler.getClass().getSimpleName() + " mehrfach verwendet!");
+ additionalHeadersMethod = method;
+ isAdditionalHeadersMethodDefined = true;
+ continue;
+ }
}
}
+
+ if (!isAdditionalHeadersMethodDefined)
+ additionalHeaders = new HashMap<String,String>();
}
}
}
}
- }
-
-
- public void setDefaultCacheSeconds(Integer defaultCacheSeconds) {
- this.defaultCacheSeconds = defaultCacheSeconds;
- }
- public void setDefaultLastModified(Long defaultLastModified) {
- this.defaultLastModified = defaultLastModified;
+ @Override
+ public Map<String,String> getAdditionalHeaders(HttpServletRequest request) throws IllegalArgumentException {
+ if (additionalHeadersMethod == null) {
+ return additionalHeaders;
+ }
+ else {
+ try {
+ return (Map<String,String>)additionalHeadersMethod.invoke(handler, request);
+ }
+ catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ }
}
}
package de.halbekunst.juplo.cachecontrol;
+import de.halbekunst.juplo.cachecontrol.AcceleratorFilter.AccelerationWrapper;
import de.halbekunst.juplo.cachecontrol.annotations.Cacheable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** CacheControll initialisieren (Handler nach annotierte Methoden scannen etc.) */
cacheControl.init(handler);
+ AccelerationWrapper wrapper = (AccelerationWrapper)request.getAttribute(AcceleratorFilter.RESPONSE_WRAPPER);
+ if (wrapper != null) {
+ wrapper.guessingFinished();
+ if (log.isInfoEnabled()) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("request should not be filtered, because it is intercepted: ");
+ builder.append(request.getRequestURI());
+ if (request.getQueryString() == null) {
+ builder.append ("?");
+ builder.append(request.getQueryString());
+ }
+ log.info(builder.toString());
+ }
+ }
+
if (cacheable.eager()) {
return cacheControl.decorate(request, response, handler);
}
+++ /dev/null
-package de.halbekunst.juplo.cachecontrol;
-
-/**
- *
- * @author kai
- */
-public abstract class HeaderNames {
-
- public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
- public static final String HEADER_CACHE_CONTROL = "Cache-Control";
- public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
- public static final String HEADER_DATE = "Date";
- public static final String HEADER_ETAG = "ETag";
- public static final String HEADER_EXPIRES = "Expires";
- public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
- public static final String HEADER_IF_NONE_MATCH = "If-None-Match";
- public static final String HEADER_LAST_MODIFIED = "Last-Modified";
- public static final String HEADER_PRAGMA = "Pragma";
- public static final String HEADER_RANGE = "Range";
-
-}
--- /dev/null
+package de.halbekunst.juplo.cachecontrol;
+
+/**
+ *
+ * @author kai
+ */
+public abstract class Headers {
+
+ public static final String RFC_1123_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss z";
+
+ public static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding";
+ public static final String HEADER_CACHE_CONTROL = "Cache-Control";
+ public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
+ public static final String HEADER_CONTENT_TYPE = "Content-Type";
+ public static final String HEADER_DATE = "Date";
+ public static final String HEADER_ETAG = "ETag";
+ public static final String HEADER_EXPIRES = "Expires";
+ public static final String HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
+ public static final String HEADER_IF_NONE_MATCH = "If-None-Match";
+ public static final String HEADER_LAST_MODIFIED = "Last-Modified";
+ public static final String HEADER_PRAGMA = "Pragma";
+ public static final String HEADER_RANGE = "Range";
+
+}
* Anfrage verarbeitet hat.
* Wenn die Cache-Dekoration im Modus <code>eager=true</code> betrieben wird
* und keine Methode mit dieser Annotation annotiert ist, geht {@link CacheControl}
- * davn aus, dass die verarbeitende Klasse alle Anfragen annimmt.
+ * davon aus, dass die verarbeitende Klasse alle Anfragen annimmt.
*
* @author kai
*/
--- /dev/null
+package de.halbekunst.juplo.cachecontrol.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * @author kai
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.TYPE, ElementType.METHOD })
+public @interface AdditionalHeaders {
+
+ String[] value() default {};
+}
--- /dev/null
+package com.meterware.servletunit;
+/********************************************************************************************************************
+* $Id: ServletUnitHttpResponse.java 751 2006-03-24 19:59:12Z russgold $
+*
+* Copyright (c) 2000-2004,2006, Russell Gold
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
+* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
+* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
+* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in all copies or substantial portions
+* of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
+* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+* DEALINGS IN THE SOFTWARE.
+*
+*******************************************************************************************************************/
+import com.meterware.httpunit.HttpUnitUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+
+import java.util.*;
+import java.text.SimpleDateFormat;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+
+
+class ServletUnitHttpResponse implements HttpServletResponse {
+
+ // rfc1123-date is "Sun, 06 Nov 1994 08:49:37 GMT"
+ private static final String RFC1123_DATE_SPEC = "EEE, dd MMM yyyy HH:mm:ss z";
+ private boolean _committed;
+ private Locale _locale = Locale.getDefault();
+
+ private static final Hashtable ENCODING_MAP = new Hashtable();
+
+ /**
+ * @deprecated Use encodeURL(String url)
+ */
+ public String encodeUrl( String url ) {
+ return encodeURL( url );
+ }
+
+
+ /**
+ * Adds the specified cookie to the response. It can be called
+ * multiple times to set more than one cookie.
+ */
+ public void addCookie( Cookie cookie ) {
+ _cookies.addElement( cookie );
+ }
+
+
+ /**
+ * Checks whether the response message header has a field with
+ * the specified name.
+ */
+ public boolean containsHeader( String name ) {
+ return _headers.containsKey( name.toUpperCase() );
+ }
+
+
+ /**
+ * @deprecated Use encodeRedirectURL(String url)
+ **/
+ public String encodeRedirectUrl( String url ) {
+ return encodeRedirectURL( url );
+ }
+
+
+ /**
+ * Encodes the specified URL by including the session ID in it,
+ * or, if encoding is not needed, returns the URL unchanged.
+ * The implementation of this method should include the logic to
+ * determine whether the session ID needs to be encoded in the URL.
+ * For example, if the browser supports cookies, or session
+ * tracking is turned off, URL encoding is unnecessary.
+ **/
+ public String encodeURL( String url ) {
+ return url;
+ }
+
+
+ /**
+ * Encodes the specified URL for use in the
+ * <code>sendRedirect</code> method or, if encoding is not needed,
+ * returns the URL unchanged. The implementation of this method
+ * should include the logic to determine whether the session ID
+ * needs to be encoded in the URL. Because the rules for making
+ * this determination differ from those used to decide whether to
+ * encode a normal link, this method is seperate from the
+ * <code>encodeUrl</code> method.
+ **/
+ public String encodeRedirectURL( String url ) {
+ return url;
+ }
+
+
+ /**
+ * Sends a temporary redirect response to the client using the
+ * specified redirect location URL. The URL must be absolute (for
+ * example, <code><em>https://hostname/path/file.html</em></code>).
+ * Relative URLs are not permitted here.
+ */
+ public void sendRedirect( String location ) throws IOException {
+ setStatus( HttpServletResponse.SC_MOVED_TEMPORARILY );
+ setHeader( "Location", location );
+ }
+
+
+ /**
+ * Sends an error response to the client using the specified status
+ * code and descriptive message. If setStatus has previously been
+ * called, it is reset to the error status code. The message is
+ * sent as the body of an HTML page, which is returned to the user
+ * to describe the problem. The page is sent with a default HTML
+ * header; the message is enclosed in simple body tags
+ * (<body></body>).
+ **/
+ public void sendError( int sc ) throws IOException {
+ sendError( sc, "" );
+ }
+
+
+ /**
+ * Sends an error response to the client using the specified status
+ * code and descriptive message. If setStatus has previously been
+ * called, it is reset to the error status code. The message is
+ * sent as the body of an HTML page, which is returned to the user
+ * to describe the problem. The page is sent with a default HTML
+ * header; the message is enclosed in simple body tags
+ * (<body></body>).
+ **/
+ public void sendError(int sc, String msg) throws IOException {
+ setStatus( sc );
+ _statusMessage = msg;
+
+ _writer = null;
+ _servletStream = null;
+
+ setContentType( "text/html" );
+ getWriter().println( "<html><head><title>" + msg + "</title></head><body>" + msg + "</body></html>" );
+ }
+
+
+ /**
+ * Sets the status code for this response. This method is used to
+ * set the return status code when there is no error (for example,
+ * for the status codes SC_OK or SC_MOVED_TEMPORARILY). If there
+ * is an error, the <code>sendError</code> method should be used
+ * instead.
+ **/
+ public void setStatus( int sc ) {
+ _status = sc;
+ }
+
+
+ /**
+ * @deprecated As of version 2.1, due to ambiguous meaning of the message parameter.
+ * To set a status code use setStatus(int), to send an error with a description
+ * use sendError(int, String). Sets the status code and message for this response.
+ **/
+ public void setStatus( int sc, String msg ) {
+ setStatus( sc );
+ }
+
+
+ /**
+ * Adds a field to the response header with the given name and value.
+ * If the field had already been set, the new value overwrites the
+ * previous one. The <code>containsHeader</code> method can be
+ * used to test for the presence of a header before setting its
+ * value.
+ **/
+ public void setHeader( String name, String value ) {
+ ArrayList values = new ArrayList();
+ values.add( value );
+ synchronized (_headers) {
+ _headers.put( name.toUpperCase(), values );
+ }
+ }
+
+
+ /**
+ * Adds a field to the response header with the given name and
+ * integer value. If the field had already been set, the new value
+ * overwrites the previous one. The <code>containsHeader</code>
+ * method can be used to test for the presence of a header before
+ * setting its value.
+ **/
+ public void setIntHeader( String name, int value ) {
+ setHeader( name, asHeaderValue( value ) );
+ }
+
+
+ private String asHeaderValue( int value ) {
+ return Integer.toString( value );
+ }
+
+
+ /**
+ * Adds a field to the response header with the given name and
+ * date-valued field. The date is specified in terms of
+ * milliseconds since the epoch. If the date field had already
+ * been set, the new value overwrites the previous one. The
+ * <code>containsHeader</code> method can be used to test for the
+ * presence of a header before setting its value.
+ **/
+ public void setDateHeader( String name, long date ) {
+ setHeader( name, asDateHeaderValue( date ) );
+ }
+
+
+ private String asDateHeaderValue( long date ) {
+ Date value = new Date( date );
+ SimpleDateFormat formatter = new SimpleDateFormat( RFC1123_DATE_SPEC, Locale.US );
+ formatter.setTimeZone( TimeZone.getTimeZone( "Greenwich Mean Time" ) );
+ return formatter.format( value );
+ }
+
+
+ /**
+ * Returns the name of the character set encoding used for
+ * the MIME body sent by this response.
+ **/
+ public String getCharacterEncoding() {
+ return _encoding == null ? HttpUnitUtils.DEFAULT_CHARACTER_SET : _encoding;
+ }
+
+
+ /**
+ * Sets the content type of the response the server sends to
+ * the client. The content type may include the type of character
+ * encoding used, for example, <code>text/html; charset=ISO-8859-4</code>.
+ *
+ * <p>You can only use this method once, and you should call it
+ * before you obtain a <code>PrintWriter</code> or
+ * {@link ServletOutputStream} object to return a response.
+ **/
+ public void setContentType( String type ) {
+ String[] typeAndEncoding = HttpUnitUtils.parseContentTypeHeader( type );
+
+ _contentType = typeAndEncoding[0];
+ if (typeAndEncoding[1] != null) _encoding = typeAndEncoding[1];
+ }
+
+
+ /**
+ * Returns a {@link ServletOutputStream} suitable for writing binary
+ * data in the response. The servlet engine does not encode the
+ * binary data.
+ *
+ * @exception IllegalStateException if you have already called the <code>getWriter</code> method
+ **/
+ public ServletOutputStream getOutputStream() throws IOException {
+ if (_writer != null) throw new IllegalStateException( "Tried to create output stream; writer already exists" );
+ if (_servletStream == null) {
+ _outputStream = new ByteArrayOutputStream();
+ _servletStream = new ServletUnitOutputStream( _outputStream );
+ }
+ return _servletStream;
+ }
+
+
+ /**
+ * Returns a <code>PrintWriter</code> object that you
+ * can use to send character text to the client.
+ * The character encoding used is the one specified
+ * in the <code>charset=</code> property of the
+ * {@link #setContentType} method, which you must call
+ * <i>before</i> you call this method.
+ *
+ * <p>If necessary, the MIME type of the response is
+ * modified to reflect the character encoding used.
+ *
+ * <p> You cannot use this method if you have already
+ * called {@link #getOutputStream} for this
+ * <code>ServletResponse</code> object.
+ *
+ * @exception UnsupportedEncodingException if the character encoding specified in
+ * <code>setContentType</code> cannot be
+ * used
+ *
+ * @exception IllegalStateException if the <code>getOutputStream</code>
+ * method has already been called for this
+ * response object; in that case, you can't
+ * use this method
+ *
+ **/
+ public PrintWriter getWriter() throws UnsupportedEncodingException {
+ if (_servletStream != null) throw new IllegalStateException( "Tried to create writer; output stream already exists" );
+ if (_writer == null) {
+ _outputStream = new ByteArrayOutputStream();
+ _writer = new PrintWriter( new OutputStreamWriter( _outputStream, getCharacterEncoding() ) );
+ }
+ return _writer;
+ }
+
+
+ /**
+ * Sets the length of the content the server returns
+ * to the client. In HTTP servlets, this method sets the
+ * HTTP Content-Length header.
+ **/
+ public void setContentLength( int len ) {
+ setIntHeader( "Content-Length", len );
+ }
+
+
+//------------------------------- the following methods are new in JSDK 2.2 ----------------------
+
+
+ /**
+ * Adds a response header with the given name and value. This method allows response headers to have multiple values.
+ **/
+ public void addHeader( String name, String value ) {
+ synchronized (_headers) {
+ String key = name.toUpperCase();
+ ArrayList values = (ArrayList) _headers.get( key );
+ if (values == null) {
+ values = new ArrayList();
+ _headers.put( key, values );
+ }
+ values.add( value );
+ }
+ }
+
+
+ /**
+ * Adds a response header with the given name and value. This method allows response headers to have multiple values.
+ **/
+ public void addIntHeader( String name, int value ) {
+ addHeader( name, asHeaderValue( value ) );
+ }
+
+
+ /**
+ * Adds a response header with the given name and value. This method allows response headers to have multiple values.
+ **/
+ public void addDateHeader( String name, long value ) {
+ addHeader( name, asDateHeaderValue( value ) );
+ }
+
+
+ /**
+ * Sets the preferred buffer size for the body of the response. The servlet container
+ * will use a buffer at least as large as the size requested. The actual buffer size
+ * used can be found using getBufferSize.
+ **/
+ public void setBufferSize( int size ) {
+ if (getContents().length != 0) throw new IllegalStateException( "May not set buffer size after data is written" );
+ }
+
+
+ /**
+ * Returns the actual buffer size used for the response. If no buffering is used, this method returns 0.
+ **/
+ public int getBufferSize() {
+ return 0;
+ }
+
+
+ /**
+ * Returns a boolean indicating if the response has been committed. A committed response has
+ * already had its status code and headers written.
+ **/
+ public boolean isCommitted() {
+ return _committed;
+ }
+
+
+ /**
+ * Forces any content in the buffer to be written to the client. A call to this method automatically
+ * commits the response, meaning the status code and headers will be written.
+ **/
+ public void flushBuffer() throws IOException {
+ _committed = true;
+ }
+
+
+ /**
+ * Clears any data that exists in the buffer as well as the status code and headers.
+ * If the response has been committed, this method throws an IllegalStateException.
+ **/
+ public void reset() {
+ resetBuffer();
+ _headers.clear();
+ _headersComplete = false;
+ _status = SC_OK;
+ }
+
+
+ /**
+ * Sets the locale of the response, setting the headers (including the Content-Type's charset)
+ * as appropriate. This method should be called before a call to getWriter().
+ * By default, the response locale is the default locale for the server.
+ **/
+ public void setLocale( Locale locale ) {
+ _locale = locale;
+ if (_encoding == null) {
+ for (Iterator it = ENCODING_MAP.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry) it.next();
+ String locales = (String) entry.getValue();
+ if (locales.indexOf( locale.getLanguage() ) >= 0 || locales.indexOf( locale.toString() ) >= 0) {
+ _encoding = (String) entry.getKey();
+ return;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Returns the locale assigned to the response.
+ **/
+ public Locale getLocale() {
+ return _locale;
+ }
+
+
+//----------------------------- methods added to ServletResponse in JSDK 2.3 --------------------------------------
+
+
+ /**
+ * Clears the content of the underlying buffer in the response without clearing headers or status code.
+ * If the response has been committed, this method throws an IllegalStateException.
+ *
+ * @since 1.3
+ */
+ public void resetBuffer() {
+ if (_committed) throw new IllegalStateException( "May not resetBuffer after response is committed" );
+ _outputStream = null;
+ _servletStream = null;
+ _writer = null;
+ }
+
+
+//---------------------------------------------- package methods --------------------------------------------------
+
+ /**
+ * Returns the contents of this response.
+ **/
+ byte[] getContents() {
+ if (_outputStream == null) {
+ return new byte[0];
+ } else {
+ if (_writer != null) _writer.flush();
+ return _outputStream.toByteArray();
+ }
+ }
+
+
+ /**
+ * Returns the status of this response.
+ **/
+ int getStatus() {
+ return _status;
+ }
+
+
+ /**
+ * Returns the message associated with this response's status.
+ **/
+ String getMessage() {
+ return _statusMessage;
+ }
+
+
+ public String[] getHeaderFieldNames() {
+ if (!_headersComplete) completeHeaders();
+ Vector names = new Vector();
+ for (Enumeration e = _headers.keys(); e.hasMoreElements();) {
+ names.addElement( e.nextElement() );
+ }
+ String[] result = new String[ names.size() ];
+ names.copyInto( result );
+ return result;
+ }
+
+
+ /**
+ * Returns the headers defined for this response.
+ **/
+ String getHeaderField( String name ) {
+ if (!_headersComplete) completeHeaders();
+
+ ArrayList values;
+ synchronized (_headers) {
+ values = (ArrayList) _headers.get( name.toUpperCase() );
+ }
+
+ return values == null ? null : (String) values.get( 0 );
+ }
+
+
+ /**
+ * Return an array of all the header values associated with the
+ * specified header name, or an zero-length array if there are no such
+ * header values.
+ *
+ * @param name Header name to look up
+ */
+ public String[] getHeaderFields(String name) {
+ if (!_headersComplete) completeHeaders();
+ ArrayList values;
+ synchronized (_headers) {
+ values = (ArrayList) _headers.get(name.toUpperCase());
+ }
+ if (values == null)
+ return (new String[0]);
+ String results[] = new String[values.size()];
+ return ((String[]) values.toArray(results));
+
+ }
+
+//--------------------------------------- methods added to ServletRequest in Servlet API 2.4 ----------------------------
+
+ public void setCharacterEncoding(String string) {
+ _encoding = string;
+ }
+
+ /**
+ * Returns the content type defined for this response.
+ **/
+ public String getContentType() {
+ return _contentType;
+ }
+
+
+//------------------------------------------- private members ------------------------------------
+
+
+ private String _contentType = "text/plain";
+
+ private String _encoding;
+
+ private PrintWriter _writer;
+
+ private ServletOutputStream _servletStream;
+
+ private ByteArrayOutputStream _outputStream;
+
+ private int _status = SC_OK;
+
+ private String _statusMessage = "OK";
+
+ private final Hashtable _headers = new Hashtable();
+
+ private boolean _headersComplete;
+
+ private Vector _cookies = new Vector();
+
+
+ private void completeHeaders() {
+ if (_headersComplete) return;
+ addCookieHeader();
+ setHeader( "Content-Type", _contentType + "; charset=" + getCharacterEncoding() );
+ _headersComplete = true;
+ }
+
+
+ private void addCookieHeader() {
+ if (_cookies.isEmpty()) return;
+
+ StringBuffer sb = new StringBuffer();
+ for (Enumeration e = _cookies.elements(); e.hasMoreElements();) {
+ Cookie cookie = (Cookie) e.nextElement();
+ sb.append( cookie.getName() ).append( '=' ).append( cookie.getValue() );
+ if (cookie.getPath() != null) sb.append( ";path=" ).append( cookie.getPath() );
+ if (cookie.getDomain() != null) sb.append( ";domain=" ).append( cookie.getDomain() );
+ if (e.hasMoreElements()) sb.append( ',' );
+ }
+ setHeader( "Set-Cookie", sb.toString() );
+ }
+
+
+
+ static {
+ ENCODING_MAP.put( "iso-8859-1", "ca da de en es fi fr is it nl no pt sv " );
+ ENCODING_MAP.put( "iso-8859-2", "cs hr hu pl ro sh sk sl sq " );
+ ENCODING_MAP.put( "iso-8859-4", "et lt lv ");
+ ENCODING_MAP.put( "iso-8859-5", "be bg mk ru sr uk " );
+ ENCODING_MAP.put( "iso-8859-6", "ar " );
+ ENCODING_MAP.put( "iso-8859-7", "el " );
+ ENCODING_MAP.put( "iso-8859-8", "iw he " );
+ ENCODING_MAP.put( "iso-8859-9", "tr " );
+
+ ENCODING_MAP.put("Shift_JIS", "ja ");
+ ENCODING_MAP.put("EUC-KR", "ko ");
+ ENCODING_MAP.put("TIS-620", "th ");
+ ENCODING_MAP.put("GB2312", "zh " );
+ ENCODING_MAP.put("Big5", "zh_TW zh_HK " );
+ }
+
+}
+
+
+
+class ServletUnitOutputStream extends ServletOutputStream {
+
+ ServletUnitOutputStream( ByteArrayOutputStream stream ) {
+ _stream = stream;
+ }
+
+
+ public void write( int aByte ) throws IOException {
+ _stream.write( aByte );
+ }
+
+ private ByteArrayOutputStream _stream;
+}
--- /dev/null
+package de.halbekunst.juplo.cachecontrol;
+
+import com.meterware.httpunit.WebResponse;
+import com.meterware.servletunit.InvocationContext;
+import com.meterware.servletunit.ServletRunner;
+import com.meterware.servletunit.ServletUnitClient;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import org.junit.Before;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author kai
+ */
+public abstract class HttpTestCase {
+ private final static Logger log = LoggerFactory.getLogger(HttpTestCase.class);
+
+ private ServletRunner sr;
+ ServletUnitClient client;
+ int buffer = 2048;
+
+
+ @Before
+ public void init() throws Exception {
+ sr = new ServletRunner(ParameterGuessingTest.class.getResourceAsStream("/web.xml"));
+ client = sr.newClient();
+ }
+
+ protected WebResponse executeRequest(String uri) throws Exception {
+ log.debug("---------- GET: {}", uri);
+ InvocationContext invocation = client.newInvocation(uri);
+ HttpServletRequest request = invocation.getRequest();
+ log.debug("Request - {}: {}", request.getMethod(), request.getProtocol());
+ Enumeration<String> headers = request.getHeaderNames();
+ while (headers.hasMoreElements()) {
+ String header = headers.nextElement();
+ Enumeration<String> values = request.getHeaders(header);
+ while (values.hasMoreElements())
+ log.debug("Request - {}: {}", header, values.nextElement());
+ }
+ log.debug("Invocing service method.");
+
+ /**
+ * We cannot call invocation.service(), because we have to wrap the
+ * response. Therefore this was coppied from InvocationContextImpl.
+ */
+ TestHttpServletResponse wrappedResponse = new TestHttpServletResponse(invocation.getResponse());
+ if (invocation.isFilterActive()) {
+ invocation.getFilter().doFilter(invocation.getRequest(), wrappedResponse, invocation.getFilterChain());
+ }
+ else {
+ invocation.getServlet().service(invocation.getRequest(), wrappedResponse);
+ }
+
+ WebResponse response = client.getResponse(invocation);
+ log.debug("Response - {}: {}", response.getResponseCode(), response.getResponseMessage());
+ log.debug("Response - {}, {} bytes", response.getContentType(), wrappedResponse.getCount());
+ for (String header : response.getHeaderFieldNames()) {
+ for (String value : response.getHeaderFields(header)) {
+ log.debug("Response - {}: {}", header, value);
+ }
+ }
+ return response;
+ }
+
+
+ class TestHttpServletResponse extends HttpServletResponseWrapper {
+
+ private CountingServletOutputStream out;
+ private HttpServletResponse response;
+ private ServletOutputStream stream;
+ private PrintWriter writer;
+ private boolean committed = false;
+
+
+ TestHttpServletResponse(HttpServletResponse response) {
+ super(response);
+ this.response = response;
+ }
+
+
+ public long getCount() {
+ if (out == null)
+ return -1l;
+ else
+ return out.count;
+ }
+
+
+ @Override
+ public void flushBuffer() throws IOException {
+ committed = true;
+ super.flushBuffer();
+ }
+
+ @Override
+ public int getBufferSize() {
+ return buffer;
+ }
+
+ @Override
+ public boolean isCommitted() {
+ return committed;
+ }
+
+ @Override
+ public void reset() {
+ if (committed)
+ throw new IllegalStateException("call to reset() after response has been commited!");
+ if (out != null)
+ out.count = 0;
+ super.reset();
+ }
+
+ @Override
+ public void resetBuffer() {
+ if (committed)
+ throw new IllegalStateException("call to resetBuffer() after response has been commited!");
+ if (out != null)
+ out.count = 0;
+ super.resetBuffer();
+ }
+
+ @Override
+ public void setBufferSize(int size) {
+ if (out != null && out.count > 0)
+ throw new IllegalStateException("call to setBuffer() after content has been written!");
+ buffer = size;
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException {
+
+ if (writer != null)
+ throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!");
+
+ if (stream == null) {
+ out = new CountingServletOutputStream(response.getOutputStream());
+ stream = out;
+ }
+
+ return stream;
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException {
+
+ if (stream != null)
+ throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!");
+
+ if (writer == null) {
+ out = new CountingServletOutputStream(response.getOutputStream());
+ writer = new PrintWriter(out);
+ }
+
+ return writer;
+ }
+
+
+ class CountingServletOutputStream extends ServletOutputStream {
+
+ private ServletOutputStream out;
+ long count = 0l;
+
+
+ CountingServletOutputStream(ServletOutputStream out) {
+ this.out = out;
+ }
+
+
+ @Override
+ public void write(int i) throws IOException {
+ count++;
+ /** Simulate commit, when count is getting bigger then buffer */
+ if (count == buffer + 1) {
+ log.debug("simulating commit because buffer overflow! buffer: {}, count: {}", buffer, count);
+ committed = true;
+ }
+ out.write(i);
+ }
+ }
+ }
+}
+
--- /dev/null
+package de.halbekunst.juplo.cachecontrol;
+
+import com.meterware.httpunit.WebResponse;
+import java.net.URLEncoder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+import org.junit.Assert;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+
+
+/**
+ *
+ * @author kai
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {
+ "classpath:/config.xml"
+})
+public class ParameterGuessingTest extends HttpTestCase {
+ private final static Logger log = LoggerFactory.getLogger(ParameterGuessingTest.class);
+
+
+ @Test
+ public void testNothingSet() throws Exception {
+
+ log.info("-------- Test: Servlet does not implement getLastModified() and sets no Headers...");
+
+ WebResponse response = executeRequest("http://localhost/parameter-guessing?n=16");
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+ }
+
+ @Test
+ public void testSetUnfilteredHeaders() throws Exception {
+
+ log.info("-------- Test: Servlet sets unfiltered Headers...");
+
+ WebResponse response = executeRequest("http://localhost/parameter-guessing?n=16&X-Debug=bla&Age=34&Content-Language=de");
+ Assert.assertEquals("bla", response.getHeaderField("X-Debug"));
+ Assert.assertEquals("34", response.getHeaderField("Age"));
+ Assert.assertEquals("de", response.getHeaderField("Content-Language"));
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+ }
+
+ @Test
+ public void testETagSet() throws Exception {
+
+ log.info("-------- Test: Servlet sets Header \"ETag\"");
+
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ WebResponse response;
+ long date, expires;
+
+ response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("\"bla\"", "UTF-8"));
+ Assert.assertEquals("\"bla\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+ response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("\"bÄl\"a\"", "UTF-8"));
+ Assert.assertEquals("\"bla\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+ response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("bla", "UTF-8"));
+ Assert.assertEquals("\"bla\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+ response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("bÄl\"a", "UTF-8"));
+ Assert.assertEquals("\"bla\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+ response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("W/\"blub\"", "UTF-8"));
+ Assert.assertEquals("W/\"blub\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+ response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("W/\"bÄl\"ub\"", "UTF-8"));
+ Assert.assertEquals("W/\"blub\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+ response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("W/blub", "UTF-8"));
+ Assert.assertEquals("W/\"blub\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+
+ response = executeRequest("http://localhost/parameter-guessing?n=16&ETag=" + URLEncoder.encode("W/bÄl\"ub", "UTF-8"));
+ Assert.assertEquals("W/\"blub\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+ }
+
+ @Test
+ public void testLastModifiedImplemented() throws Exception {
+
+ log.info("-------- Test: Servlet implements getLastModified()");
+
+ WebResponse response = executeRequest("http://localhost/parameter-guessing?n=16&l=1324162929861");
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Sat, 17 Dec 2011 23:02:09 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+ }
+
+ @Test
+ public void testCacheControlSet() throws Exception {
+
+ log.info("-------- Test: Servlet sets Header \"Cache-Control\"");
+
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ StringBuilder uri;
+ WebResponse response;
+ Date date;
+ long expires;
+ Set<String> params;
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ /** max-age=120 */
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Cache-Control=");
+ uri.append(URLEncoder.encode("max-age=120", "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=120", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE));
+ expires = (date.getTime()/1000l + 120l) * 1000l;
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires, df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+ /** max-age=120, s-max-age=60, private, must-revalidate */
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Cache-Control=");
+ uri.append(URLEncoder.encode("max-age=120, s-max-age=60, must-revalidate", "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ params = new HashSet<String>();
+ for (String param : response.getHeaderFields(Headers.HEADER_CACHE_CONTROL))
+ for (String part : param.split(","))
+ params.add(part.trim());
+ Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"max-age=120\" nicht!", params.contains("max-age=120"));
+ Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"s-max-age=60\" nicht!", params.contains("s-max-age=60"));
+ Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"must-revalidate\" nicht!", params.contains("must-revalidate"));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE));
+ expires = (date.getTime()/1000l + 120l) * 1000l;
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires, df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+ /** max-age=120, s-max-age=60, private, must-revalidate, BUT: several other values are set before */
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Cache-Control=");
+ uri.append(URLEncoder.encode("no-store", "UTF-8"));
+ uri.append("&Cache-Control=");
+ uri.append(URLEncoder.encode("max-age=360, s-max-age=600, private", "UTF-8"));
+ uri.append("&Cache-Control=");
+ uri.append(URLEncoder.encode("public", "UTF-8"));
+ uri.append("&Cache-Control=");
+ uri.append(URLEncoder.encode("max-age=120, s-max-age=60, must-revalidate", "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ params = new HashSet<String>();
+ for (String param : response.getHeaderFields(Headers.HEADER_CACHE_CONTROL))
+ for (String part : param.split(","))
+ params.add(part.trim());
+ Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"max-age=120\" nicht!", params.contains("max-age=120"));
+ Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"s-max-age=60\" nicht!", params.contains("s-max-age=60"));
+ Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"must-revalidate\" nicht!", params.contains("must-revalidate"));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE));
+ expires = (date.getTime()/1000l + 120l) * 1000l;
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires, df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+ }
+
+ @Test
+ public void testDateSet() throws Exception {
+
+ log.info("-------- Test: Servlet sets Header \"Date\"");
+
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ StringBuilder uri;
+ WebResponse response;
+ Date date, expires;
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ /** Date ca NOW -1m */
+ calendar.add(Calendar.MINUTE, -1);
+ date = calendar.getTime();
+ calendar.add(Calendar.MINUTE, 60); /** default max-age=3600 yields 60m! */
+ expires = calendar.getTime();
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Date=");
+ uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+ /** Date ca NOW -1m, BUT: is set to some garbage values before */
+ calendar.add(Calendar.MINUTE, -1);
+ date = calendar.getTime();
+ calendar.add(Calendar.MINUTE, 60); /** default max-age=3600 yields 60m! */
+ expires = calendar.getTime();
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Date=");
+ calendar.add(Calendar.MINUTE, 10);
+ uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+ uri.append("&Date=");
+ calendar.add(Calendar.HOUR, -2);
+ uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+ uri.append("&Date=");
+ calendar.add(Calendar.DATE, 1);
+ uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+ uri.append("&Date=");
+ uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+ }
+
+ @Test
+ public void testExpiresSet() throws Exception {
+
+ log.info("-------- Test: Servlet sets Header \"Expires\"");
+
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ StringBuilder uri;
+ WebResponse response;
+ Date date, expires;
+ long age;
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ /** Expires ca. NOW + 10m */
+ calendar.add(Calendar.MINUTE, 10);
+ expires = calendar.getTime();
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Expires=");
+ uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE));
+ age = (expires.getTime() - date.getTime())/1000l;
+ Assert.assertEquals("max-age=" + age, response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+ /** Expires ca. NOW + 10m, BUT: is set to some garbage values before */
+ calendar.add(Calendar.MINUTE, 10);
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Expires=");
+ calendar.add(Calendar.MINUTE, 10);
+ uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+ uri.append("&Expires=");
+ calendar.add(Calendar.HOUR, -2);
+ uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+ uri.append("&Expires=");
+ calendar.add(Calendar.DATE, 1);
+ uri.append(URLEncoder.encode(df.format(calendar.getTime()), "UTF-8"));
+ uri.append("&Expires=");
+ uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ date = df.parse(response.getHeaderField(Headers.HEADER_DATE));
+ age = (expires.getTime() - date.getTime())/1000l;
+ Assert.assertEquals("max-age=" + age, response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+ }
+
+ @Test
+ public void testDateAndExpiresSet() throws Exception {
+
+ log.info("-------- Test: Servlet sets Header's \"Date\" and \"Expires\"");
+
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ StringBuilder uri;
+ WebResponse response;
+ Date date, expires, garbage;
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ /** Expires = Date + 30m */
+ date = calendar.getTime();
+ calendar.add(Calendar.MINUTE, 30);
+ expires = calendar.getTime();
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Date=");
+ uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+ uri.append("&Expires=");
+ uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertEquals("max-age=1800", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+ /** Expires = Date + 30m, BUT: Date is set to Date - 2h first and Expires to Date */
+ date = calendar.getTime();
+ calendar.add(Calendar.MINUTE, 30);
+ expires = calendar.getTime();
+ calendar.add(Calendar.HOUR, -2);
+ garbage = calendar.getTime();
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Date=");
+ uri.append(URLEncoder.encode(df.format(garbage), "UTF-8"));
+ uri.append("&Expires=");
+ uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+ uri.append("&Date=");
+ uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+ uri.append("&Expires=");
+ uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertEquals("max-age=1800", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+ /** Expires = Date - 1h --> will be ignored! */
+ date = calendar.getTime();
+ calendar.add(Calendar.MINUTE, -60);
+ garbage = calendar.getTime();
+ calendar.setTime(date);
+ calendar.add(Calendar.MINUTE, 60); /** default max-age=3600 yields 60m! */
+ expires = calendar.getTime();
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Date=");
+ uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+ uri.append("&Expires=");
+ uri.append(URLEncoder.encode(df.format(garbage), "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+ }
+
+ @Test
+ public void testCacheControlDateAndExpiresSet() throws Exception {
+
+ log.info("-------- Test: Servlet sets Header's \"Cache-Control\", \"Date\" and \"Expires\"");
+
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ StringBuilder uri;
+ WebResponse response;
+ Date date, expires, expected;
+ Set<String> params;
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(Calendar.MILLISECOND, 0);
+
+ /** Expires = Date + 30m, Cache-Control: must-revalidate, no-store */
+ date = calendar.getTime();
+ calendar.add(Calendar.MINUTE, 30);
+ expires = calendar.getTime();
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Cache-Control=");
+ uri.append(URLEncoder.encode("must-revalidate, no-store", "UTF-8"));
+ uri.append("&Date=");
+ uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+ uri.append("&Expires=");
+ uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ params = new HashSet<String>();
+ for (String param : response.getHeaderFields(Headers.HEADER_CACHE_CONTROL))
+ for (String part : param.split(","))
+ params.add(part.trim());
+ Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"max-age=1800\" nicht!", params.contains("max-age=1800"));
+ Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"must-revalidate\" nicht!", params.contains("must-revalidate"));
+ Assert.assertTrue(response.getHeaderField(Headers.HEADER_CACHE_CONTROL) + " enthält \"no-store\" nicht!", params.contains("no-store"));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expires.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+
+ /** Expires = Date + 30m, BUT: max-age is set to 600s */
+ date = calendar.getTime();
+ calendar.add(Calendar.MINUTE, 10);
+ expected = calendar.getTime();
+ calendar.add(Calendar.MINUTE, 20);
+ expires = calendar.getTime();
+ uri = new StringBuilder();
+ uri.append("http://localhost/parameter-guessing");
+ uri.append("?n=16");
+ uri.append("&Date=");
+ uri.append(URLEncoder.encode(df.format(date), "UTF-8"));
+ uri.append("&Expires=");
+ uri.append(URLEncoder.encode(df.format(expires), "UTF-8"));
+ uri.append("&Cache-Control=");
+ uri.append(URLEncoder.encode("max-age=600", "UTF-8"));
+ response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertEquals("max-age=600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertEquals("Unerwartetr Wert für den Date-Header!", date.getTime(), df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime());
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ Assert.assertEquals("Unerwartetr Wert für den Expires-Header!", expected.getTime(), df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime());
+ }
+}
--- /dev/null
+package de.halbekunst.juplo.cachecontrol;
+
+import com.meterware.httpunit.WebResponse;
+import java.net.URLEncoder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import org.junit.Assert;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+
+
+/**
+ *
+ * @author kai
+ */
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration(locations = {
+ "classpath:/config.xml"
+})
+public class RequestSizeTest extends HttpTestCase {
+ private final static Logger log = LoggerFactory.getLogger(RequestSizeTest.class);
+
+
+ @Test
+ public void testSimpleRequestWithGzip() throws Exception {
+
+ log.info("-------- Test: gzipped simple request");
+
+ client.getClientProperties().setAcceptGzip(true);
+
+ for (int i=0; i<33; i++) {
+ /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+ WebResponse response = executeRequest("http://localhost/request-size?n=" + i*128);
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ if (i==0)
+ Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ else
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+ }
+ }
+
+ @Test
+ public void testSimpleRequestWithoutGzip() throws Exception {
+
+ log.info("-------- Test: uncompressed simple request");
+
+ client.getClientProperties().setAcceptGzip(false);
+
+ for (int i=0; i<33; i++) {
+ /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+ WebResponse response = executeRequest("http://localhost/request-size?n=" + i*128);
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+ }
+ }
+
+ @Test
+ public void testForwardWithGzip() throws Exception {
+
+ log.info("-------- Test: gzipped request with forward");
+
+ client.getClientProperties().setAcceptGzip(true);
+
+ for (int i=0; i<33; i++) {
+ /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+ StringBuilder uri = new StringBuilder();
+ uri.append("http://localhost/request-size");
+ uri.append("?n=");
+ uri.append(i*128);
+ uri.append("&f=");
+ uri.append(URLEncoder.encode("/forwarded?n=" + i*128, "UTF-8"));
+ WebResponse response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ if (i==0)
+ Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ else
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+ }
+ }
+
+ @Test
+ public void testForwardWithoutGzip() throws Exception {
+
+ log.info("-------- Test: uncompressed request with forward");
+
+ client.getClientProperties().setAcceptGzip(false);
+
+ for (int i=0; i<33; i++) {
+ /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+ StringBuilder uri = new StringBuilder();
+ uri.append("http://localhost/request-size");
+ uri.append("?n=");
+ uri.append(i*128);
+ uri.append("&f=");
+ uri.append(URLEncoder.encode("/forwarded?n=" + i*128, "UTF-8"));
+ try {
+ WebResponse response = executeRequest(uri.toString());
+ if (i*128 > 2048)
+ Assert.fail("Error expected while forwarding after " + i*128 + " bytes written!");
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+ }
+ catch (IllegalStateException e) {
+ if (i*128 > 2048)
+ log.debug("Expected error while forwarding after {} bytes written: {}", i*128, e.getMessage());
+ else
+ Assert.fail("Unexpected error while forwarding after " + i*128 + " bytes written: " + e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ public void testIncludeWithGzip() throws Exception {
+
+ log.info("-------- Test: gzipped request with includes");
+
+ client.getClientProperties().setAcceptGzip(true);
+
+ for (int i=0; i<33; i++) {
+ /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+ StringBuilder uri = new StringBuilder();
+ uri.append("http://localhost/request-size");
+ uri.append("?n=");
+ uri.append(i*128);
+ for (int j=0; j < i%4+1; j++) {
+ uri.append("&i=");
+ uri.append(URLEncoder.encode("/included?n=" + i*32*(4-j), "UTF-8"));
+ }
+ WebResponse response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ if (i==0)
+ Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ else
+ Assert.assertEquals("gzip", response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+ }
+ }
+
+ @Test
+ public void testIncludeWithoutGzip() throws Exception {
+
+ log.info("-------- Test: uncompressed request with includes");
+
+ client.getClientProperties().setAcceptGzip(false);
+
+ for (int i=0; i<33; i++) {
+ /** 33 requests ranging from 0 B to 4 KB - response ist buffered up to 1 KB */
+ StringBuilder uri = new StringBuilder();
+ uri.append("http://localhost/request-size");
+ uri.append("?n=");
+ uri.append(i*128);
+ for (int j=0; j < i%4+1; j++) {
+ uri.append("&i=");
+ uri.append(URLEncoder.encode("/included?n=" + i*32*(4-j), "UTF-8"));
+ }
+ WebResponse response = executeRequest(uri.toString());
+ Assert.assertEquals("W/\"Hallo Welt!\"", response.getHeaderField(Headers.HEADER_ETAG));
+ Assert.assertEquals("Thu, 01 Jan 1970 00:00:00 GMT", response.getHeaderField(Headers.HEADER_LAST_MODIFIED));
+ Assert.assertNull(response.getHeaderField(Headers.HEADER_CONTENT_ENCODING));
+ Assert.assertEquals("max-age=3600", response.getHeaderField(Headers.HEADER_CACHE_CONTROL));
+ Assert.assertEquals("text/plain; charset=iso-8859-1", response.getHeaderField(Headers.HEADER_CONTENT_TYPE));
+ Assert.assertNotNull("Date-Header was not set!", response.getHeaderField(Headers.HEADER_DATE));
+ Assert.assertNotNull("Expires-Header was not set!", response.getHeaderField(Headers.HEADER_EXPIRES));
+ SimpleDateFormat df = new SimpleDateFormat(Headers.RFC_1123_DATE_FORMAT, Locale.US);
+ long date = df.parse(response.getHeaderField(Headers.HEADER_DATE)).getTime();
+ long expires = df.parse(response.getHeaderField(Headers.HEADER_EXPIRES)).getTime();
+ Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
+ }
+ }
+}
--- /dev/null
+package de.halbekunst.juplo.cachecontrol;
+
+import java.io.IOException;
+import java.util.Map;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.junit.Ignore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author kai
+ */
+@Ignore
+public class TestServlet extends HttpServlet {
+ private final static Logger log = LoggerFactory.getLogger(TestServlet.class);
+
+ private static final String FORWARDED = TestServlet.class.getName() + ".FORWARDED";
+ private static final String INCLUDED = TestServlet.class.getName() + ".INCLUDED";
+
+ @Override
+ protected long getLastModified(HttpServletRequest req) {
+ try {
+ /** Der Reqeust-Parameter "lm" wird als Wert für Last-Modified zurückgegeben */
+ return Long.parseLong(req.getParameter("l"));
+ }
+ catch (Exception e) {
+ return -1l;
+ }
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+ /** Angeforderte Header setzen */
+ Map<String, String[]> map = request.getParameterMap();
+ for (String param : map.keySet()) {
+ if (param.equals("n") || param.equals("l") || param.equals("f") || param.equals("i"))
+ continue;
+ /**
+ * Alle Request-Parameter außer die Sonder-Parameter "n" und "lm"
+ * werden als Schlüssel/Wert-Paare für die Antwort-Header interpretiert!
+ */
+ for (String value : map.get(param))
+ response.setHeader(param, value);
+ }
+
+ int n = 0;
+ try {
+ /**
+ * Wenn der Parameter n gesetzt ist, wird ein Antwort-Body erzeugt, der
+ * exakt die Anzahl der geforderten Bytes enthält.
+ */
+ n = Integer.parseInt(request.getParameter("n"));
+ }
+ catch(Exception e) {}
+ log.debug("GET {} bytes: {}", n, request.getRequestURI());
+ ServletOutputStream out = response.getOutputStream();
+ for (int i=0; i<n; i++)
+ out.write(i%2 + 48); /** ASCII-Codes für "0" und "1" */
+
+ if (request.getParameter("i") != null && request.getAttribute(INCLUDED) == null) {
+ for (String include : request.getParameterValues("i")) {
+ RequestDispatcher dispatcher = request.getRequestDispatcher(include);
+ request.setAttribute(INCLUDED, include);
+ dispatcher.include(request, response);
+ }
+ }
+
+ if (request.getParameter("f") != null && request.getAttribute(FORWARDED) == null) {
+ String forward = request.getParameter("f");
+ request.setAttribute(FORWARDED, forward);
+ RequestDispatcher dispatcher = request.getRequestDispatcher(forward);
+ dispatcher.forward(request, response);
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xsi:schemaLocation="
+ http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+ <!-- Activates the AspectJ-Weaver -->
+ <context:component-scan base-package="de.halbekunst"/>
+ <context:spring-configured/>
+ <!--<context:load-time-weaver/>-->
+
+ <bean id="buffer" class="java.lang.Integer">
+ <constructor-arg value="1024"/>
+ </bean>
+
+ <bean id="eTag" class="java.lang.String">
+ <constructor-arg value="Hallo Welt!"/>
+ </bean>
+
+ <bean id="weak" class="java.lang.Boolean">
+ <constructor-arg value="true"/>
+ </bean>
+
+ <bean id="lastModified" class="java.lang.Long">
+ <constructor-arg value="0"/>
+ </bean>
+
+ <bean id="cacheSeconds" class="java.lang.Integer">
+ <constructor-arg value="3600"/>
+ </bean>
+
+</beans>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+
+ <appender name="CONSOLE" class="org.apache.log4j.ConsoleAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern"
+ value="%p - %C{1}.%M(%L) | %m%n"/>
+ </layout>
+ </appender>
+
+ <logger name="de.halbekunst">
+ <level value="debug" />
+ </logger>
+
+ <root>
+ <level value="INFO"/>
+ <appender-ref ref="CONSOLE"/>
+ </root>
+
+</log4j:configuration>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+ <filter>
+ <filter-name>accelerator</filter-name>
+ <filter-class>de.halbekunst.juplo.cachecontrol.AcceleratorFilter</filter-class>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>accelerator</filter-name>
+ <url-pattern>/*</url-pattern>
+ </filter-mapping>
+
+ <servlet>
+ <servlet-name>test-servlet</servlet-name>
+ <servlet-class>de.halbekunst.juplo.cachecontrol.TestServlet</servlet-class>
+ </servlet>
+
+ <servlet-mapping>
+ <servlet-name>test-servlet</servlet-name>
+ <url-pattern>/*</url-pattern>
+ </servlet-mapping>
+
+</web-app>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- Verwendete Versionen -->
+ <aspectj.version>1.6.11</aspectj.version>
+ <httpunit.version>1.7</httpunit.version>
+ <jpa.version>1.0</jpa.version>
<junit.version>4.8.1</junit.version>
<log4j.version>1.2.15</log4j.version>
<servlet-api.version>2.5</servlet-api.version>
+ <slf4j.binding>slf4j-log4j12</slf4j.binding>
<slf4j.version>1.6.1</slf4j.version>
<springframework.version>3.0.6.RELEASE</springframework.version>