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 2005/03/23 23:14:52 UTC
svn commit: r158844 - in jakarta/httpclient/trunk/http-common/src:
java/org/apache/http/impl/ java/org/apache/http/impl/NIOHttpDataReceiver.java
java/org/apache/http/impl/NIOHttpDataTransmitter.java
test/org/apache/http/impl/
test/org/apache/http/impl/TestNIOHttpTransmitterAndReceiver.java
Author: olegk
Date: Wed Mar 23 14:14:51 2005
New Revision: 158844
URL: http://svn.apache.org/viewcvs?view=rev&rev=158844
Log:
NIO based implementation of HttpDataReceiver and HttpDataTransmitter interfaces (initial import)
Added:
jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/
jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataReceiver.java (with props)
jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataTransmitter.java (with props)
jakarta/httpclient/trunk/http-common/src/test/org/apache/http/impl/
jakarta/httpclient/trunk/http-common/src/test/org/apache/http/impl/TestNIOHttpTransmitterAndReceiver.java (with props)
Added: jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataReceiver.java
URL: http://svn.apache.org/viewcvs/jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataReceiver.java?view=auto&rev=158844
==============================================================================
--- jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataReceiver.java (added)
+++ jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataReceiver.java Wed Mar 23 14:14:51 2005
@@ -0,0 +1,204 @@
+/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
+ *
+ * ====================================================================
+ *
+ * Copyright 1999-2004 The Apache Software Foundation
+ *
+ * Licensed 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.impl;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CodingErrorAction;
+
+import org.apache.http.HttpDataReceiver;
+
+/**
+ * <p>
+ * </p>
+ * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
+ *
+ * @version $Revision$
+ *
+ * @since 4.0
+ */
+public class NIOHttpDataReceiver implements HttpDataReceiver {
+
+ private static final int CR = 13;
+ private static final int LF = 10;
+
+ private final ReadableByteChannel channel;
+ private final ByteBuffer buffer;
+ private final Charset charset;
+
+ protected NIOHttpDataReceiver(final ReadableByteChannel channel, int buffersize) {
+ if (channel == null) {
+ throw new IllegalArgumentException("Channel may not be null");
+ }
+ this.channel = channel;
+
+ if ((buffersize > 2048) || (buffersize <= 0)) {
+ buffersize = 2048;
+ }
+ this.buffer = ByteBuffer.allocateDirect(buffersize);
+
+ this.charset = Charset.forName("US-ASCII");
+ }
+
+ private CharsetDecoder createCharDecoder() {
+ CharsetDecoder chardecoder = this.charset.newDecoder();
+ chardecoder.onMalformedInput(CodingErrorAction.REPLACE);
+ chardecoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+ return chardecoder;
+ }
+
+ private int fillBuffer() throws IOException {
+ int i = this.channel.read(this.buffer);
+ this.buffer.flip();
+ return i;
+ }
+
+ public int read(final byte[] b, int off, int len) throws IOException {
+ if (b == null) {
+ return 0;
+ }
+ if (!this.buffer.hasRemaining()) {
+ this.buffer.clear();
+ int noRead = fillBuffer();
+ if (noRead == -1) {
+ return noRead;
+ }
+ }
+ int chunk = len;
+ if (chunk > this.buffer.remaining()) {
+ chunk = this.buffer.remaining();
+ }
+ this.buffer.get(b, off, chunk);
+ return chunk;
+ }
+
+ public int read(final byte[] b) throws IOException {
+ if (b == null) {
+ return 0;
+ }
+ return read(b, 0, b.length);
+ }
+
+ private int locateLF() {
+ for (int i = this.buffer.position(); i < this.buffer.limit(); i++) {
+ int b = this.buffer.get(i);
+ if (b == LF) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public String readLine() throws IOException {
+ int noRead = 0;
+ // prefill the buffer if necessary
+ if (this.buffer.position() == 0) {
+ noRead = fillBuffer();
+ if (noRead == -1) {
+ // end of stream
+ return null;
+ }
+ }
+ CharsetDecoder chardecoder = createCharDecoder();
+ StringBuffer line = new StringBuffer();
+ CharBuffer tmp = CharBuffer.allocate(128);
+ boolean retry = true;
+ while (retry) {
+ // attempt to find end of line (LF)
+ int i = locateLF();
+ if (i != -1) {
+ // end of line found.
+ retry = false;
+ // read up to the end of line
+ int origLimit = this.buffer.limit();
+ this.buffer.limit(i + 1);
+ chardecoder.decode(this.buffer, tmp, true);
+ this.buffer.limit(origLimit);
+ } else {
+ // end of line not found
+ if (this.buffer.hasRemaining()) {
+ // decode the entire buffer content
+ chardecoder.decode(this.buffer, tmp, false);
+ }
+ // discard the decoded content
+ this.buffer.compact();
+ noRead = fillBuffer();
+ if (noRead == -1) {
+ retry = false;
+ // terminate the decoding process
+ chardecoder.decode(this.buffer, tmp, true);
+ this.buffer.clear();
+ }
+ }
+ // append the decoded content to the line buffer
+ tmp.flip();
+ if (tmp.hasRemaining()) {
+ line.append(tmp.array(), tmp.position(), tmp.remaining());
+ }
+ tmp.clear();
+ }
+ // flush the decoder
+ chardecoder.flush(tmp);
+ tmp.flip();
+ // append the decoded content to the line buffer
+ if (tmp.hasRemaining()) {
+ line.append(tmp.array(), tmp.position(), tmp.remaining());
+ }
+ if (noRead == -1 && line.length() == 0) {
+ // indicate the end of stream
+ return null;
+ }
+ // discard LF if found
+ int l = line.length();
+ if (l > 0) {
+ if (line.charAt(l - 1) == LF) {
+ l--;
+ line.setLength(l);
+ }
+ // discard CR if found
+ if (l > 0) {
+ if (line.charAt(l - 1) == CR) {
+ l--;
+ line.setLength(l);
+ }
+ }
+ }
+ return line.toString();
+ }
+
+ public boolean isDataAvailable(int timeout) throws IOException {
+ return this.channel.isOpen();
+ }
+
+}
Propchange: jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataReceiver.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataReceiver.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange: jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataReceiver.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataTransmitter.java
URL: http://svn.apache.org/viewcvs/jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataTransmitter.java?view=auto&rev=158844
==============================================================================
--- jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataTransmitter.java (added)
+++ jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataTransmitter.java Wed Mar 23 14:14:51 2005
@@ -0,0 +1,184 @@
+/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
+ *
+ * ====================================================================
+ *
+ * Copyright 1999-2004 The Apache Software Foundation
+ *
+ * Licensed 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.impl;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.WritableByteChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+
+import org.apache.http.HttpDataTransmitter;
+
+/**
+ * <p>
+ * </p>
+ * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
+ *
+ * @version $Revision$
+ *
+ * @since 4.0
+ */
+public class NIOHttpDataTransmitter implements HttpDataTransmitter {
+
+ private static final int CR = 13;
+ private static final int LF = 10;
+ private static final byte[] CRLF = new byte[] {CR, LF};
+
+ private final WritableByteChannel channel;
+ private final ByteBuffer buffer;
+ private final Charset charset;
+
+ protected NIOHttpDataTransmitter(final WritableByteChannel channel, int buffersize) {
+ super();
+ if (channel == null) {
+ throw new IllegalArgumentException("Channel may not be null");
+ }
+ this.channel = channel;
+
+ if ((buffersize > 2048) || (buffersize <= 0)) {
+ buffersize = 2048;
+ }
+ this.buffer = ByteBuffer.allocateDirect(buffersize);
+ this.charset = Charset.forName("US-ASCII");
+ }
+
+ private void flushBuffer() throws IOException {
+ this.buffer.flip();
+ this.channel.write(this.buffer);
+ this.buffer.compact();
+ }
+
+ public void flush() throws IOException {
+ this.buffer.flip();
+ while (this.buffer.hasRemaining()) {
+ this.channel.write(this.buffer);
+ }
+ this.buffer.clear();
+ }
+
+ private CharsetEncoder createCharEncoder() {
+ CharsetEncoder charencoder = this.charset.newEncoder();
+ charencoder.onMalformedInput(CodingErrorAction.REPLACE);
+ charencoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
+ return charencoder;
+ }
+
+ private void write(
+ final CharsetEncoder charencoder,
+ final CharBuffer charbuffer,
+ boolean endOfInput) throws IOException {
+ boolean retry = true;
+ while (retry) {
+ CoderResult result = charencoder.encode(charbuffer, this.buffer, endOfInput);
+ if (result.isOverflow()) {
+ flushBuffer();
+ }
+ retry = !result.isUnderflow();
+ }
+ }
+
+ public void write(final byte[] b, int off, int len) throws IOException {
+ if (b == null) {
+ return;
+ }
+ int i = off;
+ while (i < len) {
+ if (this.buffer.hasRemaining()) {
+ int chunk = len - i;
+ if (chunk > this.buffer.remaining()) {
+ chunk = this.buffer.remaining();
+ }
+ this.buffer.put(b, i, chunk);
+ i += chunk;
+ } else {
+ flushBuffer();
+ }
+ }
+ }
+
+ public void write(final byte[] b) throws IOException {
+ if (b == null) {
+ return;
+ }
+ write(b, 0, b.length);
+ }
+
+ private void writeCRLF() throws IOException {
+ write(CRLF);
+ }
+
+ private void flushCharEncoder(final CharsetEncoder charencoder) throws IOException {
+ boolean retry = true;
+ while (retry) {
+ CoderResult result = charencoder.flush(this.buffer);
+ if (result.isOverflow()) {
+ flushBuffer();
+ }
+ retry = !result.isUnderflow();
+ }
+ }
+
+ public void writeLine(final String s) throws IOException {
+ if (s == null) {
+ return;
+ }
+ // Do not bother if the string is empty
+ if (s.length() > 0 ) {
+ CharsetEncoder charencoder = createCharEncoder();
+ CharBuffer tmp = CharBuffer.allocate(128);
+ // transfer the string in small chunks
+ int remaining = s.length();
+ int offset = 0;
+ while (remaining > 0) {
+ int l = tmp.remaining();
+ boolean eol = false;
+ if (remaining < l) {
+ l = remaining;
+ // terminate the encoding process
+ eol = true;
+ }
+ tmp.put(s, offset, offset + l);
+ tmp.flip();
+ write(charencoder, tmp, eol);
+ tmp.compact();
+ offset += l;
+ remaining -= l;
+ }
+ // flush the encoder
+ flushCharEncoder(charencoder);
+ }
+ writeCRLF();
+ }
+
+}
Propchange: jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataTransmitter.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataTransmitter.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange: jakarta/httpclient/trunk/http-common/src/java/org/apache/http/impl/NIOHttpDataTransmitter.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: jakarta/httpclient/trunk/http-common/src/test/org/apache/http/impl/TestNIOHttpTransmitterAndReceiver.java
URL: http://svn.apache.org/viewcvs/jakarta/httpclient/trunk/http-common/src/test/org/apache/http/impl/TestNIOHttpTransmitterAndReceiver.java?view=auto&rev=158844
==============================================================================
--- jakarta/httpclient/trunk/http-common/src/test/org/apache/http/impl/TestNIOHttpTransmitterAndReceiver.java (added)
+++ jakarta/httpclient/trunk/http-common/src/test/org/apache/http/impl/TestNIOHttpTransmitterAndReceiver.java Wed Mar 23 14:14:51 2005
@@ -0,0 +1,155 @@
+/*
+ * $HeadURL$
+ * $Revision$
+ * $Date$
+ * ====================================================================
+ *
+ * Copyright 1999-2004 The Apache Software Foundation
+ *
+ * Licensed 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.impl;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.nio.channels.Channels;
+
+import org.apache.http.HttpDataReceiver;
+import org.apache.http.HttpDataTransmitter;
+
+import junit.framework.*;
+
+/**
+ * Simple tests for {@link NIOHttpDataTransmitter} and {@link NIOHttpDataReceiver}.
+ *
+ * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a>
+ *
+ * @version $Id$
+ */
+public class TestNIOHttpTransmitterAndReceiver extends TestCase {
+
+ // ------------------------------------------------------------ Constructor
+ public TestNIOHttpTransmitterAndReceiver(String testName) {
+ super(testName);
+ }
+
+ // ------------------------------------------------------------------- Main
+ public static void main(String args[]) {
+ String[] testCaseName = { TestNIOHttpTransmitterAndReceiver.class.getName() };
+ junit.textui.TestRunner.main(testCaseName);
+ }
+
+ // ------------------------------------------------------- TestCase Methods
+
+ public static Test suite() {
+ return new TestSuite(TestNIOHttpTransmitterAndReceiver.class);
+ }
+
+ public void testBasicReadWriteLine() throws Exception {
+
+ String[] teststrs = new String[5];
+ teststrs[0] = "Hello";
+ teststrs[1] = "This string should be much longer than the size of the output buffer " +
+ "which is only 16 bytes for this test";
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < 15; i++) {
+ buffer.append("123456789 ");
+ }
+ buffer.append("and stuff like that");
+ teststrs[2] = buffer.toString();
+ teststrs[3] = "";
+ teststrs[4] = "And goodbye";
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ HttpDataTransmitter transmitter =
+ new NIOHttpDataTransmitter(Channels.newChannel(out), 16);
+ for (int i = 0; i < teststrs.length; i++) {
+ transmitter.writeLine(teststrs[i]);
+ }
+ transmitter.flush();
+
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ HttpDataReceiver receiver =
+ new NIOHttpDataReceiver(Channels.newChannel(in), 16);
+
+ for (int i = 0; i < teststrs.length; i++) {
+ assertEquals(teststrs[i], receiver.readLine());
+ }
+ assertNull(receiver.readLine());
+ assertNull(receiver.readLine());
+ }
+
+ public void testComplexReadWriteLine() throws Exception {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ HttpDataTransmitter transmitter =
+ new NIOHttpDataTransmitter(Channels.newChannel(out), 16);
+ transmitter.write(new byte[] {'a', '\n'});
+ transmitter.write(new byte[] {'\r', '\n'});
+ transmitter.write(new byte[] {'\r', '\r', '\n'});
+ transmitter.write(new byte[] {'\n'});
+ transmitter.flush();
+
+ StringBuffer buffer = new StringBuffer();
+ for (int i = 0; i < 14; i++) {
+ buffer.append("a");
+ }
+ String s1 = buffer.toString();
+ buffer.append("\r\n");
+ transmitter.write(buffer.toString().getBytes("US-ASCII"));
+ transmitter.flush();
+
+ buffer.setLength(0);
+ for (int i = 0; i < 15; i++) {
+ buffer.append("a");
+ }
+ String s2 = buffer.toString();
+ buffer.append("\r\n");
+ transmitter.write(buffer.toString().getBytes("US-ASCII"));
+ transmitter.flush();
+
+ buffer.setLength(0);
+ for (int i = 0; i < 16; i++) {
+ buffer.append("a");
+ }
+ String s3 = buffer.toString();
+ buffer.append("\r\n");
+ transmitter.write(buffer.toString().getBytes("US-ASCII"));
+ transmitter.flush();
+
+ transmitter.write(new byte[] {'a'});
+ transmitter.flush();
+
+ ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+ HttpDataReceiver receiver =
+ new NIOHttpDataReceiver(Channels.newChannel(in), 16);
+
+ assertEquals("a", receiver.readLine());
+ assertEquals("", receiver.readLine());
+ assertEquals("\r", receiver.readLine());
+ assertEquals("", receiver.readLine());
+ assertEquals(s1, receiver.readLine());
+ assertEquals(s2, receiver.readLine());
+ assertEquals(s3, receiver.readLine());
+ assertEquals("a", receiver.readLine());
+ assertNull(receiver.readLine());
+ assertNull(receiver.readLine());
+ }
+}
Propchange: jakarta/httpclient/trunk/http-common/src/test/org/apache/http/impl/TestNIOHttpTransmitterAndReceiver.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jakarta/httpclient/trunk/http-common/src/test/org/apache/http/impl/TestNIOHttpTransmitterAndReceiver.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange: jakarta/httpclient/trunk/http-common/src/test/org/apache/http/impl/TestNIOHttpTransmitterAndReceiver.java
------------------------------------------------------------------------------
svn:mime-type = text/plain