GZIPServletOutputStream puffert, um Ausgabefehler zu verhindern
authorKai Moritz <kai@coolibri.de>
Sun, 25 Dec 2011 12:16:07 +0000 (13:16 +0100)
committerKai Moritz <kai@coolibri.de>
Thu, 2 Aug 2012 07:03:25 +0000 (09:03 +0200)
TODO:
Eventuell reicht es auch, den Buffer gezielt zu leeren, anstatt hier stets
eine neue Instanz zu erzeugen!

Noch zu klären: sind die im folgenden vermerkten Fehler inzwischen
korrigiert, so dass die Anmerkungen jetzt nach dem Zusammenlegen der
Commits gelöscht werden können?!?
----------

Buggy Commit!
Die in CountingServletOutputStream eingeführte Methode finish(),
die wahrscheinlich von der finish()-Methode des Wrappers aus
aufgerufen werden sollte, wird nur von der Unterklasse
GZipServletOutputstream aufgerufen.
Der im Wrapper entfernte Aufruf der Dekoration nach der Ausführung
des Filters wird also durch diese Maßnahme gar nicht ersetzt!

BUGGY: gzip-Header wird nicht mehr unterdrückt, wenn noch Ausgabe folgen könnte

Die zur Sicherstellung der Kompression eingeführte Variable forceCompression
wird nie ausgewertet. Eventuell ein Fehler, der erst nachträglich durch das
Rebasen beim aufräumen produziert wurde.
Wenn die Variable forceCompression korrekt in den Scope private gesetzt
wäre, wäre der Fehler früher aufgefallen, da Netbeans ihn dann angemeckert
hätte...

cachecontrol/src/main/java/de/halbekunst/juplo/cachecontrol/AcceleratorFilter.java

index 9125554..5fee736 100644 (file)
@@ -1,6 +1,8 @@
 package de.halbekunst.juplo.cachecontrol;
 
 package de.halbekunst.juplo.cachecontrol;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.IOException;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.io.PrintWriter;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
@@ -71,8 +73,7 @@ public class AcceleratorFilter implements Filter {
     httpRequest.setAttribute(RESPONSE_WRAPPER, wrapper);
     cacheControl.init(wrapper);
     chain.doFilter(request, wrapper);
     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();
   }
 
   @Override
   }
 
   @Override
@@ -90,6 +91,7 @@ public class AcceleratorFilter implements Filter {
     private final HttpServletResponse response;
 
     boolean zipped; // CacheControll greift direkt auf dieses Flag zu!
     private final HttpServletResponse response;
 
     boolean zipped; // CacheControll greift direkt auf dieses Flag zu!
+    boolean forceCompression = false;
 
     private CountingServletOutputStream out;
     private ServletOutputStream stream;
 
     private CountingServletOutputStream out;
     private ServletOutputStream stream;
@@ -133,7 +135,11 @@ public class AcceleratorFilter implements Filter {
       if (zipped)
         out = new GZIPServletOutputStream();
       else
       if (zipped)
         out = new GZIPServletOutputStream();
       else
-        out = new WrappedServletOutputStream();
+        out = new CountingServletOutputStream();
+    }
+
+    public void finish() throws IOException {
+      out.close();
     }
 
 
     }
 
 
@@ -309,6 +315,7 @@ public class AcceleratorFilter implements Filter {
     @Override
     public void flushBuffer() throws IOException {
 
     @Override
     public void flushBuffer() throws IOException {
 
+      forceCompression = true;
       cacheControl.decorate(request, response, this);
       response.flushBuffer();
     }
       cacheControl.decorate(request, response, this);
       response.flushBuffer();
     }
@@ -317,6 +324,15 @@ public class AcceleratorFilter implements Filter {
     public void resetBuffer() {
 
       response.resetBuffer();
     public void resetBuffer() {
 
       response.resetBuffer();
+      try {
+        if (zipped)
+          out = new GZIPServletOutputStream();
+        else
+          out = new CountingServletOutputStream();
+      }
+      catch (IOException e) {
+        throw new IllegalStateException(e);
+      }
       stream = null;
       writer = null;
     }
       stream = null;
       writer = null;
     }
@@ -329,7 +345,7 @@ public class AcceleratorFilter implements Filter {
         if (zipped)
           out = new GZIPServletOutputStream();
         else
         if (zipped)
           out = new GZIPServletOutputStream();
         else
-          out = new WrappedServletOutputStream();
+          out = new CountingServletOutputStream();
       }
       catch (IOException e) {
         throw new IllegalStateException(e);
       }
       catch (IOException e) {
         throw new IllegalStateException(e);
@@ -338,6 +354,7 @@ public class AcceleratorFilter implements Filter {
       writer = null;
 
       /** Cookies has been cleared! Reinitialize decorator... */
       writer = null;
 
       /** Cookies has been cleared! Reinitialize decorator... */
+      forceCompression = false;
       cacheControl.init(this);
     }
 
       cacheControl.init(this);
     }
 
@@ -537,50 +554,31 @@ public class AcceleratorFilter implements Filter {
     }
 
 
     }
 
 
-    abstract class CountingServletOutputStream extends ServletOutputStream {
-
-      abstract void setBuffer(int size) throws IllegalStateException;
-      abstract boolean isZipped();
-    }
+    class CountingServletOutputStream extends ServletOutputStream {
 
 
+      private OutputStream out;
+      int left;
+      boolean empty;
 
 
-    final class GZIPServletOutputStream extends CountingServletOutputStream {
-
-      final static int MINMAL_BUFFER_SIZE = 128;
 
 
-
-      private final GZIPOutputStream out;
-      private int buffer, left;
-      private boolean empty;
-
-
-      public GZIPServletOutputStream() throws IOException {
-        this.out = new GZIPOutputStream(response.getOutputStream());
-        empty = true;
-        setBuffer(AcceleratorFilter.this.buffer);
+      CountingServletOutputStream() throws IOException {
+        out = response.getOutputStream();
         left = buffer;
         left = buffer;
+        empty = true;
       }
 
 
       }
 
 
-      @Override
+      void setBuffer(int size) throws IllegalStateException {}
+
       boolean isZipped() {
       boolean isZipped() {
-        return !empty;
+        return false;
       }
 
       }
 
-      @Override
-      void setBuffer(int size) {
-        if (!empty)
-          throw new IllegalStateException("attemp to change buffer size after writing data to response!");
-
-        if (size > MINMAL_BUFFER_SIZE) {
-          buffer = size;
-          left = buffer;
-        }
+      void finish() throws IOException {
+        decorate();
       }
 
       }
 
-
-      @Override
-      public void close() throws IOException {
+      void decorate() throws IOException {
         try {
           AcceleratorFilter.this.cacheControl.decorate(AccelerationWrapper.this.request, response, AccelerationWrapper.this);
         }
         try {
           AcceleratorFilter.this.cacheControl.decorate(AccelerationWrapper.this.request, response, AccelerationWrapper.this);
         }
@@ -588,79 +586,113 @@ public class AcceleratorFilter implements Filter {
           log.error("Error while guessing Cache-Header's", e);
           response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
         }
           log.error("Error while guessing Cache-Header's", e);
           response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
         }
+      }
 
 
+
+      @Override
+      public void close() throws IOException {
+        decorate();
         out.close();
       }
 
       @Override
       public void flush() throws IOException {
         out.close();
       }
 
       @Override
       public void flush() throws IOException {
-        try {
-          AcceleratorFilter.this.cacheControl.decorate(AccelerationWrapper.this.request, response, AccelerationWrapper.this);
-        }
-        catch (Exception e) {
-          log.error("Error while guessing Cache-Header's", e);
-          response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-        }
-
+        decorate();
         out.flush();
       }
 
       @Override
       public void write(int i) throws IOException {
         out.flush();
       }
 
       @Override
       public void write(int i) throws IOException {
-        if (left == 0) {
-          try {
-            AcceleratorFilter.this.cacheControl.decorate(AccelerationWrapper.this.request, response, AccelerationWrapper.this);
-          }
-          catch (Exception e) {
-            log.error("Error while guessing Cache-Header's", e);
-            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
-          }
-        }
         empty = false;
         empty = false;
+        if (left == 0)
+          decorate();
         left--;
         out.write(i);
       }
     }
 
 
         left--;
         out.write(i);
       }
     }
 
 
-    final class WrappedServletOutputStream extends CountingServletOutputStream {
+    final class GZIPServletOutputStream extends CountingServletOutputStream {
 
 
-      private final ServletOutputStream out;
-      private boolean empty;
+      final static int MINIMAL_BUFFER_SIZE = 128;
 
 
+      private ByteArrayOutputStream buffer;
+      private OutputStream out;
+      private GZIPOutputStream zout;
+      private int bufferSize;
+      private boolean decorated = false;
 
 
-      public WrappedServletOutputStream() throws IOException {
-        this.out = response.getOutputStream();
-        empty = true;
+
+      public GZIPServletOutputStream() throws IOException {
+        setBuffer(AcceleratorFilter.this.buffer);
       }
 
 
       }
 
 
+      @Override
+      void finish() throws IOException {
+        decorate();
+        zout.finish();
+        super.out.write(buffer.toByteArray());
+      }
+
       @Override
       boolean isZipped() {
       @Override
       boolean isZipped() {
-        return false;
+        if (decorated)
+          return true;
+        return !empty;
       }
 
       @Override
       }
 
       @Override
-      void setBuffer(int size) {
+      void setBuffer(int size) throws IllegalStateException {
         if (!empty)
           throw new IllegalStateException("attemp to change buffer size after writing data to response!");
         if (!empty)
           throw new IllegalStateException("attemp to change buffer size after writing data to response!");
+
+        if (size > MINIMAL_BUFFER_SIZE) {
+          left = size;
+          try {
+            bufferSize = size;
+            out = response.getOutputStream();
+            buffer = new ByteArrayOutputStream(size);
+            zout = new GZIPOutputStream(buffer, size);
+            super.out = zout;
+          }
+          catch (IOException e) {
+            throw new IllegalStateException(e);
+          }
+        }
       }
 
 
       }
 
 
+
       @Override
       public void close() throws IOException {
       @Override
       public void close() throws IOException {
+        decorate();
+        zout.close();
+        out.write(buffer.toByteArray());
         out.close();
       }
 
       @Override
       public void flush() throws IOException {
         out.close();
       }
 
       @Override
       public void flush() throws IOException {
+        decorate();
+        out.write(buffer.toByteArray());
         out.flush();
       }
 
       @Override
       public void write(int i) throws IOException {
         empty = false;
         out.flush();
       }
 
       @Override
       public void write(int i) throws IOException {
         empty = false;
-        out.write(i);
+        if (left == 0) {
+          if (!decorated) {
+            decorate();
+            decorated = true;
+          }
+          out.write(buffer.toByteArray());
+          buffer.reset();
+          left = bufferSize;
+        }
+        left--;
+        zout.write(i);
       }
     }
   }
       }
     }
   }