You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2009/03/09 19:36:49 UTC

svn commit: r751788 - in /httpcomponents/httpcore/trunk: ./ httpcore/src/main/java/org/apache/http/impl/io/ httpcore/src/test/java/org/apache/http/impl/io/ httpcore/src/test/java/org/apache/http/mockup/

Author: olegk
Date: Mon Mar  9 18:36:49 2009
New Revision: 751788

URL: http://svn.apache.org/viewvc?rev=751788&view=rev
Log:
HTTPCORE-190: ChunkedInputStream is now capable of correctly preserving its internal state on SocketTimeoutExceptions, which makes it possible to continue reading from the stream after a socket timeout.

Added:
    httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/mockup/TimeoutByteArrayInputStream.java   (with props)
Modified:
    httpcomponents/httpcore/trunk/RELEASE_NOTES.txt
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/AbstractSessionInputBuffer.java
    httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java
    httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/impl/io/TestChunkCoding.java

Modified: httpcomponents/httpcore/trunk/RELEASE_NOTES.txt
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/RELEASE_NOTES.txt?rev=751788&r1=751787&r2=751788&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/RELEASE_NOTES.txt (original)
+++ httpcomponents/httpcore/trunk/RELEASE_NOTES.txt Mon Mar  9 18:36:49 2009
@@ -1,3 +1,11 @@
+Changes since 4.0
+-------------------
+
+* [HTTPCORE-190] ChunkedInputStream is now capable of correctly preserving its internal state on 
+  SocketTimeoutExceptions, which makes it possible to continue reading from the stream after a 
+  socket timeout.
+  Contributed by Oleg Kalnichevski <olegk at apache.org> 
+
 Release 4.0
 -------------------
 
@@ -13,10 +21,6 @@
 scenarios where raw data throughput is less important than the ability to handle thousands of 
 simultaneous HTTP connections in a resource efficient manner.
 
-
-Changes since 4.0 Beta 3
--------------------
-
 * [HTTPCORE-180] Fixed NPE in standard I/O event dispatchers when 
   IOEventDispatch#disconnected fires before the session was fully initialized 
   (IOEventDispatch#connected was not called).

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/AbstractSessionInputBuffer.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/AbstractSessionInputBuffer.java?rev=751788&r1=751787&r2=751788&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/AbstractSessionInputBuffer.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/AbstractSessionInputBuffer.java Mon Mar  9 18:36:49 2009
@@ -207,7 +207,6 @@
         if (charbuffer == null) {
             throw new IllegalArgumentException("Char array buffer may not be null");
         }
-        this.linebuffer.clear();
         int noRead = 0;
         boolean retry = true;
         while (retry) {
@@ -285,6 +284,7 @@
             String s = new String(this.linebuffer.buffer(), 0, l, this.charset);
             charbuffer.append(s);
         }
+        this.linebuffer.clear();
         return l;
     }
     

Modified: httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java?rev=751788&r1=751787&r2=751788&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java Mon Mar  9 18:36:49 2009
@@ -38,7 +38,6 @@
 import org.apache.http.HttpException;
 import org.apache.http.MalformedChunkCodingException;
 import org.apache.http.io.SessionInputBuffer;
-import org.apache.http.protocol.HTTP;
 import org.apache.http.util.CharArrayBuffer;
 import org.apache.http.util.ExceptionUtils;
 
@@ -60,10 +59,16 @@
  */
 public class ChunkedInputStream extends InputStream {
 
+    private static final int CHUNK_LEN               = 1;
+    private static final int CHUNK_DATA              = 2;
+    private static final int CHUNK_CRLF              = 3;
+    
     /** The session input buffer */
-    private SessionInputBuffer in;
+    private final SessionInputBuffer in;
 
     private final CharArrayBuffer buffer;
+
+    private int state;
     
     /** The chunk size */
     private int chunkSize;
@@ -71,9 +76,6 @@
     /** The current position within the current chunk */
     private int pos;
 
-    /** True if we'are at the beginning of stream */
-    private boolean bof = true;
-
     /** True if we've reached the end of stream */
     private boolean eof = false;
 
@@ -95,6 +97,7 @@
         this.in = in;
         this.pos = 0;
         this.buffer = new CharArrayBuffer(16);
+        this.state = CHUNK_LEN;
     }
 
     /**
@@ -102,7 +105,7 @@
      * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0
      * is detected.</p>
      * 
-     * <p> Trailer headers are read automcatically at the end of the stream and
+     * <p> Trailer headers are read automatically at the end of the stream and
      * can be obtained with the getResponseFooters() method.</p>
      *
      * @return -1 of the end of the stream has been reached or the next data
@@ -116,7 +119,7 @@
         if (this.eof) {
             return -1;
         } 
-        if (this.pos >= this.chunkSize) {
+        if (state != CHUNK_DATA) {
             nextChunk();
             if (this.eof) { 
                 return -1;
@@ -125,6 +128,9 @@
         int b = in.read();
         if (b != -1) {
             pos++;
+            if (pos >= chunkSize) {
+                state = CHUNK_CRLF;
+            }
         }
         return b;
     }
@@ -148,7 +154,7 @@
         if (eof) { 
             return -1;
         }
-        if (pos >= chunkSize) {
+        if (state != CHUNK_DATA) {
             nextChunk();
             if (eof) { 
                 return -1;
@@ -158,6 +164,9 @@
         int bytesRead = in.read(b, off, len);
         if (bytesRead != -1) {
             pos += bytesRead;
+            if (pos >= chunkSize) {
+                state = CHUNK_CRLF;
+            }
             return bytesRead;
         } else {
             throw new MalformedChunkCodingException("Truncated chunk");
@@ -184,7 +193,7 @@
         if (chunkSize < 0) {
             throw new MalformedChunkCodingException("Negative chunk size");
         }
-        bof = false;
+        state = CHUNK_DATA;
         pos = 0;
         if (chunkSize == 0) {
             eof = true;
@@ -206,29 +215,36 @@
      * @throws IOException when the chunk size could not be parsed
      */
     private int getChunkSize() throws IOException {
-        // skip CRLF
-        if (!bof) {
-            int cr = in.read();
-            int lf = in.read();
-            if ((cr != HTTP.CR) || (lf != HTTP.LF)) { 
+        int st = this.state;
+        switch (st) {
+        case CHUNK_CRLF:
+            this.buffer.clear();
+            int i = this.in.readLine(this.buffer);
+            if (i == -1) {
+                return 0;
+            }
+            if (!this.buffer.isEmpty()) { 
                 throw new MalformedChunkCodingException(
-                    "CRLF expected at end of chunk");
+                    "Unexpected content at the end of chunk");
             }
-        }
-        //parse data
-        this.buffer.clear();
-        int i = this.in.readLine(this.buffer);
-        if (i == -1) {
-            return 0;
-        }
-        int separator = this.buffer.indexOf(';');
-        if (separator < 0) {
-            separator = this.buffer.length();
-        }
-        try {
-            return Integer.parseInt(this.buffer.substringTrimmed(0, separator), 16);
-        } catch (NumberFormatException e) {
-            throw new MalformedChunkCodingException("Bad chunk header");
+            state = CHUNK_LEN; 
+        case CHUNK_LEN:
+            this.buffer.clear();
+            i = this.in.readLine(this.buffer);
+            if (i == -1) {
+                return 0;
+            }
+            int separator = this.buffer.indexOf(';');
+            if (separator < 0) {
+                separator = this.buffer.length();
+            }
+            try {
+                return Integer.parseInt(this.buffer.substringTrimmed(0, separator), 16);
+            } catch (NumberFormatException e) {
+                throw new MalformedChunkCodingException("Bad chunk header");
+            }
+        default:
+            throw new IllegalStateException("Incontent state");
         }
     }
 

Modified: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/impl/io/TestChunkCoding.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/impl/io/TestChunkCoding.java?rev=751788&r1=751787&r2=751788&view=diff
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/impl/io/TestChunkCoding.java (original)
+++ httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/impl/io/TestChunkCoding.java Mon Mar  9 18:36:49 2009
@@ -34,6 +34,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.net.SocketTimeoutException;
 
 import org.apache.http.Header;
 import org.apache.http.MalformedChunkCodingException;
@@ -42,6 +43,7 @@
 import org.apache.http.io.SessionInputBuffer;
 import org.apache.http.mockup.SessionInputBufferMockup;
 import org.apache.http.mockup.SessionOutputBufferMockup;
+import org.apache.http.mockup.TimeoutByteArrayInputStream;
 import org.apache.http.util.EncodingUtils;
 
 import junit.framework.Test;
@@ -430,5 +432,57 @@
         assertEquals('\n', rawdata[10]);
     }
 
+    public void testResumeOnSocketTimeoutInData() throws IOException {
+        String s = "5\r\n01234\r\n5\r\n5\0006789\r\na\r\n0123\000456789\r\n0\r\n";
+        SessionInputBuffer sessbuf = new SessionInputBufferMockup(
+                new TimeoutByteArrayInputStream(s.getBytes("ISO-8859-1")), 16);
+        InputStream in = new ChunkedInputStream(sessbuf);        
+        
+        byte[] tmp = new byte[3];
+        
+        int bytesRead = 0;
+        int timeouts = 0;
+        
+        int i = 0;
+        while (i != -1) {
+            try {
+                i = in.read(tmp);
+                if (i > 0) {
+                    bytesRead += i;
+                }
+            } catch (SocketTimeoutException ex) {
+                timeouts++;
+            }
+        }
+        assertEquals(20, bytesRead);
+        assertEquals(2, timeouts);
+    }
+    
+    public void testResumeOnSocketTimeoutInChunk() throws IOException {
+        String s = "5\000\r\000\n\00001234\r\n\0005\r\n56789\r\na\r\n0123456789\r\n\0000\r\n";
+        SessionInputBuffer sessbuf = new SessionInputBufferMockup(
+                new TimeoutByteArrayInputStream(s.getBytes("ISO-8859-1")), 16);
+        InputStream in = new ChunkedInputStream(sessbuf);        
+        
+        byte[] tmp = new byte[3];
+        
+        int bytesRead = 0;
+        int timeouts = 0;
+        
+        int i = 0;
+        while (i != -1) {
+            try {
+                i = in.read(tmp);
+                if (i > 0) {
+                    bytesRead += i;
+                }
+            } catch (SocketTimeoutException ex) {
+                timeouts++;
+            }
+        }
+        assertEquals(20, bytesRead);
+        assertEquals(5, timeouts);
+    }
+
 }
 

Added: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/mockup/TimeoutByteArrayInputStream.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/mockup/TimeoutByteArrayInputStream.java?rev=751788&view=auto
==============================================================================
--- httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/mockup/TimeoutByteArrayInputStream.java (added)
+++ httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/mockup/TimeoutByteArrayInputStream.java Mon Mar  9 18:36:49 2009
@@ -0,0 +1,123 @@
+/*
+ * $HeadURL:$
+ * $Revision:$
+ * $Date:$
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.mockup;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.SocketTimeoutException;
+
+/**
+ * Test class similar to {@link ByteArrayInputStream} that throws if encounters
+ * value zero '\000' in the source byte array. 
+ */
+public class TimeoutByteArrayInputStream extends InputStream {
+    
+    private final byte[] buf;
+    
+    private int pos;
+    protected int count;
+    
+    public TimeoutByteArrayInputStream(byte[] buf, int off, int len) {
+        super();
+        this.buf = buf;
+        this.pos = off;
+        this.count = Math.min(off + len, buf.length);
+    }
+    
+    public TimeoutByteArrayInputStream(byte[] buf) {
+        this(buf, 0, buf.length);
+    }
+    
+    public int read() throws IOException {
+        if (this.pos < this.count) {
+            return -1;
+        }
+        int v = this.buf[this.pos++] & 0xff;
+        if (v != 0) {
+            return v;
+        } else {
+            throw new SocketTimeoutException("Timeout");
+        }
+    }
+    
+    public int read(byte b[], int off, int len) throws IOException {
+        if (b == null) {
+            throw new NullPointerException();
+        } else if ((off < 0) || (off > b.length) || (len < 0) ||
+               ((off + len) > b.length) || ((off + len) < 0)) {
+            throw new IndexOutOfBoundsException();
+        }
+        if (this.pos >= this.count) {
+            return -1;
+        }
+        if (this.pos + len > this.count) {
+            len = this.count - this.pos;
+        }
+        if (len <= 0) {
+            return 0;
+        }
+        if ((this.buf[this.pos] & 0xff) == 0) {
+            this.pos++;
+            throw new SocketTimeoutException("Timeout");
+        }
+        for (int i = 0; i < len; i++) {
+            int v = this.buf[this.pos] & 0xff;
+            if (v == 0) {
+                return i;
+            } else {
+                b[off + i] = (byte) v;
+                this.pos++;
+            }
+        }
+        return len;
+    }
+ 
+    public long skip(long n) {
+        if (this.pos + n > this.count) {
+            n = this.count - this.pos;
+        }
+        if (n < 0) {
+            return 0;
+        }
+        this.pos += n;
+        return n;
+    }
+
+    public int available() {
+        return this.count - this.pos;
+    }
+    
+    public boolean markSupported() {
+        return false;
+    }
+    
+}

Propchange: httpcomponents/httpcore/trunk/httpcore/src/test/java/org/apache/http/mockup/TimeoutByteArrayInputStream.java
------------------------------------------------------------------------------
    svn:eol-style = native