HttpTestCase in das Modul test verschoben und schweren Fehler korrigiert
authorKai Moritz <kai@coolibri.de>
Thu, 8 Mar 2012 21:24:03 +0000 (22:24 +0100)
committerKai Moritz <kai@coolibri.de>
Thu, 2 Aug 2012 07:03:25 +0000 (09:03 +0200)
Schweren Denkfehler in der Klasse HttpTestCase und dem zugehörigen Response-
Wrapper behoben:
Der Servlet-Container ruft flush erst auf, wenn die Filter-Kette fertig
abgearbeitet wurde. Da hat er aber wieder nur den ursprünglichen Response
zur Verfügung und nicht mehr den Response-Wrapper, in den der Response hier
zu Test-Zwecken verpackt wurde. Dementsprechend kann der Servlet-Container
die Methode flush während der Finalisierung des Requests nur auf dem
ursprünglichen Response-Objekt aufrufen -- nicht aber auf dem Response-
Wrapper!
Wenn der Response-Wrapper also (wie hier der Fall) ein spezielles flush
benötigt, kann man sich dafür nicht auf den Servlet-Container verlassen!!
Der Flush muss explizit auf angestoßen werden, wenn der Response-Wrapper
keine Daten verschlucken soll.

Dabei:
 * Den bisher als innere Klasse implementierten Response-Wrapper in
   LoggingHttpServletResponseWrapper umbenannt und in eine eigenständige
   Klasse umgewandelt.
 * LoggingHttpServletResponseFilter implementiert, mit dem sich der
   Response-Wrapper auch in enier normalen Java-Webanwendung verwenden
   lässt, um zu Protokollieren, wie ein Response-Objekt benutzt wird.

cachecontrol/pom.xml
cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/HttpTestCase.java [deleted file]
cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/ParameterGuessingTest.java
cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/RequestSizeTest.java
pom.xml
test/pom.xml
test/src/main/java/de/halbekunst/juplo/test/HttpTestCase.java [new file with mode: 0644]
test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseFilter.java [new file with mode: 0644]
test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseWrapper.java [new file with mode: 0644]

index 9fd31eb..cc38b34 100644 (file)
       <version>${jpa.version}</version>
       <scope>provided</scope>
     </dependency>
-    <dependency>
-      <groupId>httpunit</groupId>
-      <artifactId>httpunit</artifactId>
-      <version>${httpunit.version}</version>
-      <scope>test</scope>
-      <exclusions>
-        <exclusion>
-          <groupId>javax.servlet</groupId>
-          <artifactId>servlet-api</artifactId>
-        </exclusion>
-      </exclusions>
-    </dependency>
     <dependency>
       <groupId>${pom.parent.groupId}</groupId>
       <artifactId>${pom.parent.artifactId}-test</artifactId>
diff --git a/cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/HttpTestCase.java b/cachecontrol/src/test/java/de/halbekunst/juplo/cachecontrol/HttpTestCase.java
deleted file mode 100644 (file)
index bd28c91..0000000
+++ /dev/null
@@ -1,191 +0,0 @@
-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 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);
-      }
-    }
-  }
-}
-
index 4d0ce9a..e7b64f6 100644 (file)
@@ -1,6 +1,7 @@
 package de.halbekunst.juplo.cachecontrol;
 
 import com.meterware.httpunit.WebResponse;
+import de.halbekunst.juplo.test.HttpTestCase;
 import java.net.URLEncoder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,6 +31,11 @@ public class ParameterGuessingTest extends HttpTestCase {
   private final static Logger log = LoggerFactory.getLogger(ParameterGuessingTest.class);
 
 
+  public ParameterGuessingTest() {
+    super("src/test/resources/web.xml");
+  }
+
+
   @Test
   public void testNothingSet() throws Exception {
 
index 172e3ba..2b3fec8 100644 (file)
@@ -1,6 +1,8 @@
 package de.halbekunst.juplo.cachecontrol;
 
 import com.meterware.httpunit.WebResponse;
+import de.halbekunst.juplo.test.HttpTestCase;
+import de.halbekunst.juplo.test.LoggingHttpServletResponseWrapper;
 import java.net.URLEncoder;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -26,6 +28,11 @@ public class RequestSizeTest extends HttpTestCase {
   private final static Logger log = LoggerFactory.getLogger(RequestSizeTest.class);
 
 
+  public RequestSizeTest() {
+    super("src/test/resources/web.xml");
+  }
+
+
   @Test
   public void testSimpleRequestWithGzip() throws Exception {
 
@@ -138,7 +145,7 @@ public class RequestSizeTest extends HttpTestCase {
       uri.append(URLEncoder.encode("/forwarded?n=" + i*128, "UTF-8"));
       try {
         WebResponse response = executeRequest(uri.toString());
-        if (i*128 > 2048)
+        if (i*128 > LoggingHttpServletResponseWrapper.DEFAULT_BUFFER_SIZE)
           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));
@@ -153,7 +160,7 @@ public class RequestSizeTest extends HttpTestCase {
         Assert.assertTrue("Expires-Header passt nicht zum Date-Header! Unterschied: " + (expires-date)/1000 + " Sekunden.", date + 3600000 == expires);
       }
       catch (IllegalStateException e) {
-        if (i*128 > 2048)
+        if (i*128 > LoggingHttpServletResponseWrapper.DEFAULT_BUFFER_SIZE)
           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());
diff --git a/pom.xml b/pom.xml
index 4583d12..5e4bba1 100644 (file)
--- a/pom.xml
+++ b/pom.xml
 
     <!-- Verwendete Versionen -->
     <aspectj.version>1.6.11</aspectj.version>
-    <httpunit.version>1.7</httpunit.version>
+    <httpunit.version>1.7.1</httpunit.version>
     <jpa.version>1.0</jpa.version>
     <junit.version>4.8.1</junit.version>
-    <log4j.version>1.2.15</log4j.version>
+    <log4j.version>1.2.16</log4j.version>
     <servlet-api.version>2.5</servlet-api.version>
     <slf4j.binding>slf4j-log4j12</slf4j.binding>
     <slf4j.version>1.6.1</slf4j.version>
index 5fc6b1c..d13cdbf 100644 (file)
   <name>Juplo - Test</name>
 
   <dependencies>
+
+    <!-- public dependencies -->
+    <dependency>
+      <groupId>httpunit</groupId>
+      <artifactId>httpunit</artifactId>
+      <version>${httpunit.version}</version>
+      <exclusions>
+        <exclusion>
+          <groupId>junit</groupId>
+          <artifactId>junit</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>javax.servlet</groupId>
+          <artifactId>servlet-api</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>${junit.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework</groupId>
+      <artifactId>spring-test</artifactId>
+      <version>${springframework.version}</version>
+      <scope>provided</scope>
+    </dependency>
+
+    <!-- hidden dependencies -->
     <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-beans</artifactId>
       <version>${springframework.version}</version>
+      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-context</artifactId>
       <version>${springframework.version}</version>
+      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>javax.servlet</groupId>
       <version>${servlet-api.version}</version>
       <scope>provided</scope>
     </dependency>
-    <dependency>
-      <groupId>org.springframework</groupId>
-      <artifactId>spring-test</artifactId>
-      <version>${springframework.version}</version>
-    </dependency>
   </dependencies>
 
 </project>
diff --git a/test/src/main/java/de/halbekunst/juplo/test/HttpTestCase.java b/test/src/main/java/de/halbekunst/juplo/test/HttpTestCase.java
new file mode 100644 (file)
index 0000000..76c4646
--- /dev/null
@@ -0,0 +1,84 @@
+package de.halbekunst.juplo.test;
+
+import com.meterware.httpunit.WebResponse;
+import com.meterware.servletunit.InvocationContext;
+import com.meterware.servletunit.ServletRunner;
+import com.meterware.servletunit.ServletUnitClient;
+import java.io.File;
+import java.util.Enumeration;
+import javax.servlet.http.HttpServletRequest;
+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;
+  private File config;
+  public ServletUnitClient client;
+
+
+  public HttpTestCase(String config) {
+    this(new File(config));
+  }
+
+  public HttpTestCase(File config) {
+    if (!config.exists())
+      throw new RuntimeException("web.xml is missing: " + config + " does not exist.");
+    this.config = config;
+  }
+
+
+  @Before
+  public void init() throws Exception {
+    sr = new ServletRunner(config, ""); // Dies ist der einzige Konstruktor, der die Context-Root der Webapp im Testfall korrekt initialisiert :/
+    client = sr.newClient();
+  }
+
+  public 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.
+     */
+    LoggingHttpServletResponseWrapper wrappedResponse =
+        new LoggingHttpServletResponseWrapper(uri, invocation.getResponse());
+    if (invocation.isFilterActive()) {
+      invocation.getFilter().doFilter(invocation.getRequest(), wrappedResponse, invocation.getFilterChain());
+    }
+    else {
+      invocation.getServlet().service(invocation.getRequest(), wrappedResponse);
+    }
+    long count = wrappedResponse.close();
+
+    WebResponse response = invocation.getServletResponse();
+    log.debug("Response - {}: {}", response.getResponseCode(), response.getResponseMessage());
+    log.debug("Response - {}, {} bytes", response.getContentType(), count);
+    for (String header : response.getHeaderFieldNames()) {
+      for (String value : response.getHeaderFields(header)) {
+        log.debug("Response - {}: {}", header, value);
+      }
+    }
+    return response;
+  }
+}
+
diff --git a/test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseFilter.java b/test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseFilter.java
new file mode 100644 (file)
index 0000000..b9dad51
--- /dev/null
@@ -0,0 +1,42 @@
+package de.halbekunst.juplo.test;
+
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author kai
+ */
+public class LoggingHttpServletResponseFilter implements Filter {
+
+  private final static Logger log = LoggerFactory.getLogger(LoggingHttpServletResponseWrapper.class);
+
+
+  @Override
+  public void init(FilterConfig filterConfig) throws ServletException {
+  }
+
+  @Override
+  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
+    HttpServletRequest request = (HttpServletRequest)req;
+    HttpServletResponse response = (HttpServletResponse)res;
+    log.info("counting request {}", request.getRequestURI());
+    LoggingHttpServletResponseWrapper wrappedResponse =
+        new LoggingHttpServletResponseWrapper(request.getRequestURI(), response);
+    chain.doFilter(request, wrappedResponse);
+    log.info("response-size: {}", wrappedResponse.close());
+  }
+
+  @Override
+  public void destroy() {
+  }
+}
diff --git a/test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseWrapper.java b/test/src/main/java/de/halbekunst/juplo/test/LoggingHttpServletResponseWrapper.java
new file mode 100644 (file)
index 0000000..f654f2a
--- /dev/null
@@ -0,0 +1,310 @@
+package de.halbekunst.juplo.test;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.Date;
+import java.util.Locale;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author kai
+ */
+public class LoggingHttpServletResponseWrapper implements HttpServletResponse {
+
+  private final static Logger log = LoggerFactory.getLogger(LoggingHttpServletResponseWrapper.class);
+
+  public final static int DEFAULT_BUFFER_SIZE = 1024;
+
+  private static long count = 0;
+
+  private final Long no;
+  private final HttpServletResponse response;
+  private CountingServletOutputStream out;
+  private ServletOutputStream stream;
+  private PrintWriter writer;
+  private int buffer = DEFAULT_BUFFER_SIZE;
+  private boolean committed = false;
+
+
+  LoggingHttpServletResponseWrapper(String name, HttpServletResponse response) {
+    no = ++count;
+    log.debug("New request {}: {}", no, name);
+    this.response = response;
+  }
+
+
+  public long close() throws IOException {
+    if (out == null) {
+      return -1l;
+    }
+    else {
+      if (writer != null)
+        writer.close();
+      else
+        stream.close();
+      long result = out.count;
+      out = null;
+      buffer = DEFAULT_BUFFER_SIZE;
+      return result;
+    }
+  }
+
+  @Override
+  public void flushBuffer() throws IOException {
+    log.debug("{} -- flushing buffer", no);
+    committed = true;
+    response.flushBuffer();
+  }
+
+  @Override
+  public int getBufferSize() {
+    log.trace("{} -- getting buffer size: {}", no, buffer);
+    return buffer;
+  }
+
+  @Override
+  public boolean isCommitted() {
+    Boolean result = committed || response.isCommitted();
+    log.trace("{} -- commited? {}", no, result);
+    return result;
+  }
+
+  @Override
+  public void reset() {
+    log.debug("{} -- reset!", no);
+    if (committed)
+      throw new IllegalStateException("call to reset() after response has been commited!");
+    if (out != null)
+      out.count = 0;
+    response.reset();
+  }
+
+  @Override
+  public void resetBuffer() {
+    log.debug("{} -- resetting buffer", no);
+    if (committed)
+      throw new IllegalStateException("call to resetBuffer() after response has been commited!");
+    if (out != null)
+      out.count = 0;
+    response.resetBuffer();
+  }
+
+  @Override
+  public void setBufferSize(int size) {
+    log.debug("{} -- setting buffer size to {}", no, size);
+    if (out != null && out.count > 0)
+      throw new IllegalStateException("call to setBuffer() after content has been written!");
+    response.setBufferSize(size);
+    buffer = size;
+  }
+
+  @Override
+  public ServletOutputStream getOutputStream() throws IOException {
+    log.debug("{} -- getting output stream", no);
+
+    if (writer != null)
+      throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!");
+
+    if (stream == null) {
+      log.debug("{} -- creating new servlet output stream", no);
+      out = new CountingServletOutputStream(response.getOutputStream());
+      stream = out;
+    }
+
+    return stream;
+  }
+
+  @Override
+  public PrintWriter getWriter() throws IOException {
+    log.debug("{} -- getting print writer", no);
+
+    if (stream != null)
+      throw new IllegalStateException("ServletOutputStream and PrintWriter cannot be requested both!");
+
+    if (writer == null) {
+      log.debug("{} -- creating new print writer", no);
+      out = new CountingServletOutputStream(response.getOutputStream());
+      OutputStreamWriter streamWriter = new OutputStreamWriter(out, response.getCharacterEncoding());
+      writer = new PrintWriter(streamWriter);
+    }
+
+    return writer;
+  }
+
+  @Override
+  public void addCookie(Cookie cookie) {
+    log.debug("{} -- adding cookie: {}", no, cookie);
+    response.addCookie(cookie);
+  }
+
+  @Override
+  public boolean containsHeader(String name) {
+    Boolean result = response.containsHeader(name);
+    log.trace("{} -- contains header {}? {}", new Object[] { no, name, result });
+    return result;
+  }
+
+  @Override
+  public String encodeURL(String url) {
+    log.trace("{} -- encoding url {}", no, url);
+    return response.encodeURL(url);
+  }
+
+  @Override
+  public String encodeRedirectURL(String url) {
+    log.trace("{} -- encoding redirect url {}", no, url);
+    return response.encodeRedirectURL(url);
+  }
+
+  @Override
+  public String encodeUrl(String url) {
+    log.trace("{} -- encoding url {}", no, url);
+    return response.encodeUrl(url);
+  }
+
+  @Override
+  public String encodeRedirectUrl(String url) {
+    log.trace("{} -- encoding redirect url {}", no, url);
+    return response.encodeRedirectUrl(url);
+  }
+
+  @Override
+  public void sendError(int sc, String msg) throws IOException {
+    log.debug("{} -- sending error: {}. {}", new Object[] { no, sc, msg });
+    response.sendError(sc, msg);
+  }
+
+  @Override
+  public void sendError(int sc) throws IOException {
+    log.debug("{} -- sending error: {}", no, sc);
+  }
+
+  @Override
+  public void sendRedirect(String location) throws IOException {
+    log.debug("{} -- sending redirect: {}", no, location);
+    response.sendRedirect(location);
+  }
+
+  @Override
+  public void setDateHeader(String name, long date) {
+    log.debug("{} -- setting date header {} to {}", new Object[] { no, name, new Date(date) });
+    response.setDateHeader(name, date);
+  }
+
+  @Override
+  public void addDateHeader(String name, long date) {
+    log.debug("{} -- adding date header {}: {}", new Object[] { no, name, new Date(date) });
+    response.addDateHeader(name, date);
+  }
+
+  @Override
+  public void setHeader(String name, String value) {
+    log.debug("{} -- setting header {} to {}", new Object[] { no, name, value });
+    response.setHeader(name, value);
+  }
+
+  @Override
+  public void addHeader(String name, String value) {
+    log.debug("{} -- adding header {}: {}", new Object[] { no, name, value });
+    response.addHeader(name, value);
+  }
+
+  @Override
+  public void setIntHeader(String name, int value) {
+    log.debug("{} -- seting int header {} to {}", new Object[] { no, name, value });
+    response.setIntHeader(name, value);
+  }
+
+  @Override
+  public void addIntHeader(String name, int value) {
+    log.debug("{} -- adding int header {}: {}", new Object[] { no, name, value });
+    response.addIntHeader(name, value);
+  }
+
+  @Override
+  public void setStatus(int sc) {
+    log.debug("{} -- setting status to {}", no, sc);
+    response.setStatus(sc);
+  }
+
+  @Override
+  public void setStatus(int sc, String sm) {
+    log.debug("{} -- setting status to {} (message: {})", new Object[] { no, sc, sm });
+    response.setStatus(sc, sm);
+  }
+
+  @Override
+  public String getCharacterEncoding() {
+    String result = response.getCharacterEncoding();
+    log.trace("{} -- character encoding: {}", no, result);
+    return result;
+  }
+
+  @Override
+  public String getContentType() {
+    String result = response.getContentType();
+    log.trace("{} -- content type: {}", no, result);
+    return result;
+  }
+
+  @Override
+  public void setCharacterEncoding(String charset) {
+    log.debug("{} -- setting character encoding to {}", no, charset);
+    response.setCharacterEncoding(charset);
+  }
+
+  @Override
+  public void setContentLength(int len) {
+    log.debug("{} -- setting content length to {}", no, len);
+    response.setContentLength(len);
+  }
+
+  @Override
+  public void setContentType(String type) {
+    log.debug("{} -- setting content type to {}", no, type);
+    response.setContentType(type);
+  }
+
+  @Override
+  public void setLocale(Locale loc) {
+    log.debug("{} -- setting locale to {}", no, loc);
+    response.setLocale(loc);
+  }
+
+  @Override
+  public Locale getLocale() {
+    Locale locale = response.getLocale();
+    log.trace("{} -- locale: {}", no, locale);
+    return locale;
+  }
+
+
+  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.info("{} -- simulating commit because buffer overflow! buffer: {}, count: {}", new Object[] { no, buffer, count });
+        committed = true;
+      }
+      log.trace("{} -- writing byte {}: {}", new Object[] { no, count, (char)i });
+      out.write(i);
+    }
+  }
+}