From e6d44a6199ef7832c9ca386d74b090ec825a7de7 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Mon, 19 Sep 2011 23:50:25 +0000
Subject: [PATCH]   * I2PTunnelHTTPServer: Don't compress small responses or
 images

---
 .../i2ptunnel/HTTPResponseOutputStream.java   | 17 +++++++-
 .../i2p/i2ptunnel/I2PTunnelHTTPServer.java    | 41 +++++++++++++++++--
 2 files changed, 52 insertions(+), 6 deletions(-)

diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java
index 7be986cc86..441c863f9a 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/HTTPResponseOutputStream.java
@@ -46,6 +46,9 @@ class HTTPResponseOutputStream extends FilterOutputStream {
     private final byte _buf1[];
     protected boolean _gzip;
     private long _dataWritten;
+    protected long _dataExpected;
+    protected String _contentType;
+
     private static final int CACHE_SIZE = 8*1024;
     private static final ByteCache _cache = ByteCache.getInstance(8, CACHE_SIZE);
     // OOM DOS prevention
@@ -130,10 +133,11 @@ class HTTPResponseOutputStream extends FilterOutputStream {
     }
     
     /**
-     * Tweak that first HTTP response line (HTTP 200 OK, etc)
+     * Possibly tweak that first HTTP response line (HTTP/1.0 200 OK, etc).
+     * Overridden on server side.
      *
      */
-    protected static String filterResponseLine(String line) {
+    protected String filterResponseLine(String line) {
         return line;
     }
     
@@ -184,6 +188,15 @@ class HTTPResponseOutputStream extends FilterOutputStream {
                             } else if ("Proxy-Authenticate".equalsIgnoreCase(key)) {
                                 // filter this hop-by-hop header; outproxy authentication must be configured in I2PTunnelHTTPClient
                             } else {
+                                if ("Content-Length".equalsIgnoreCase(key)) {
+                                    // save for compress decision on server side
+                                    try {
+                                        _dataExpected = Long.parseLong(val);
+                                    } catch (NumberFormatException nfe) {}
+                                } else if ("Content-Type".equalsIgnoreCase(key)) {
+                                    // save for compress decision on server side
+                                    _contentType = val;
+                                }
                                 out.write((key.trim() + ": " + val.trim() + "\r\n").getBytes());
                             }
                             break;
diff --git a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java
index d44f5539a9..ea665e398e 100644
--- a/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java
+++ b/apps/i2ptunnel/java/src/net/i2p/i2ptunnel/I2PTunnelHTTPServer.java
@@ -303,19 +303,48 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
         }
     }
 
+    /**
+     *  This plus a typ. HTTP response header will fit into a 1730-byte streaming message.
+     */
+    private static final int MIN_TO_COMPRESS = 1300;
+
     private static class CompressedResponseOutputStream extends HTTPResponseOutputStream {
         private InternalGZIPOutputStream _gzipOut;
+
         public CompressedResponseOutputStream(OutputStream o) {
             super(o);
+            _dataExpected = -1;
         }
         
+        /**
+         * Overridden to peek at response code. Always returns line.
+         */
+        @Override
+        protected String filterResponseLine(String line) {
+            String[] s = line.split(" ", 3);
+            if (s.length > 1 &&
+                (s[1].startsWith("3") || s[1].startsWith("5")))
+                _dataExpected = 0;
+            return line;
+        }
+    
+        /**
+         *  Don't compress small responses or images.
+         *  Compression is inline but decompression on the client side
+         *  creates a new thread.
+         */
         @Override
-        protected boolean shouldCompress() { return true; }
+        protected boolean shouldCompress() {
+            return (_dataExpected < 0 || _dataExpected >= MIN_TO_COMPRESS) &&
+                   (_contentType == null || !_contentType.startsWith("image/"));
+        }
+
         @Override
         protected void finishHeaders() throws IOException {
             //if (_log.shouldLog(Log.INFO))
             //    _log.info("Including x-i2p-gzip as the content encoding in the response");
-            out.write("Content-encoding: x-i2p-gzip\r\n".getBytes());
+            if (shouldCompress())
+                out.write("Content-encoding: x-i2p-gzip\r\n".getBytes());
             super.finishHeaders();
         }
 
@@ -324,9 +353,12 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
             //if (_log.shouldLog(Log.INFO))
             //    _log.info("Beginning compression processing");
             //out.flush();
-            _gzipOut = new InternalGZIPOutputStream(out);
-            out = _gzipOut;
+            if (shouldCompress()) {
+                _gzipOut = new InternalGZIPOutputStream(out);
+                out = _gzipOut;
+            }
         }
+
         public long getTotalRead() { 
             InternalGZIPOutputStream gzipOut = _gzipOut;
             if (gzipOut != null)
@@ -334,6 +366,7 @@ public class I2PTunnelHTTPServer extends I2PTunnelServer {
             else
                 return 0;
         }
+
         public long getTotalCompressed() { 
             InternalGZIPOutputStream gzipOut = _gzipOut;
             if (gzipOut != null)
-- 
GitLab