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