You are viewing a plain text version of this content. The canonical link for it is here.
Posted to awf-commits@incubator.apache.org by el...@apache.org on 2011/07/26 21:45:19 UTC

svn commit: r1151259 - in /incubator/deft/sandbox: ./ src/main/java/org/deftserver/example/ src/main/java/org/deftserver/example/kv/ src/main/java/org/deftserver/io/ src/main/java/org/deftserver/io/buffer/ src/main/java/org/deftserver/util/ src/main/ja...

Author: elecharny
Date: Tue Jul 26 21:45:16 2011
New Revision: 1151259

URL: http://svn.apache.org/viewvc?rev=1151259&view=rev
Log:
Applied patch for DEFT-117

Added:
    incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientGetExample.java
    incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientPostExample.java
    incubator/deft/sandbox/src/main/java/org/deftserver/util/KnuthMorrisPrattAlgorithm.java
    incubator/deft/sandbox/src/main/java/org/deftserver/web/http/ContentType.java
    incubator/deft/sandbox/src/test/java/org/deftserver/web/HttpVerbTest.java
    incubator/deft/sandbox/src/test/java/org/deftserver/web/http/ContentTypeTest.java
    incubator/deft/sandbox/src/test/java/org/deftserver/web/http/client/
    incubator/deft/sandbox/src/test/java/org/deftserver/web/http/client/AsynchronousHttpClientTest.java
    incubator/deft/sandbox/src/test/java/org/deftserver/web/http/client/RequestTest.java
Modified:
    incubator/deft/sandbox/NEWS.txt
    incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientExample.java
    incubator/deft/sandbox/src/main/java/org/deftserver/example/kv/KeyValueStoreClient.java
    incubator/deft/sandbox/src/main/java/org/deftserver/example/kv/KeyValueStoreExample.java
    incubator/deft/sandbox/src/main/java/org/deftserver/io/AsynchronousSocket.java
    incubator/deft/sandbox/src/main/java/org/deftserver/io/buffer/DynamicByteBuffer.java
    incubator/deft/sandbox/src/main/java/org/deftserver/web/HttpVerb.java
    incubator/deft/sandbox/src/main/java/org/deftserver/web/http/client/AsynchronousHttpClient.java
    incubator/deft/sandbox/src/main/java/org/deftserver/web/http/client/Request.java
    incubator/deft/sandbox/src/test/java/org/deftserver/io/AsynchronousSocketTest.java
    incubator/deft/sandbox/src/test/java/org/deftserver/web/DeftSystemTest.java

Modified: incubator/deft/sandbox/NEWS.txt
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/NEWS.txt?rev=1151259&r1=1151258&r2=1151259&view=diff
==============================================================================
--- incubator/deft/sandbox/NEWS.txt (original)
+++ incubator/deft/sandbox/NEWS.txt Tue Jul 26 21:45:16 2011
@@ -13,6 +13,7 @@ Features / Improvements
     - AsynchronousSocket accepts SocketChannel instead of SelectableChannel (DEFT-162)
     - Improved error handling in AsynchronousSocket (DEFT-154)
     - Bug fix. NPE when using AsynchronousSocket.write (DEFT-155)
+    - Asynchronous HTTP client supports POST and PUT (DEFT-117)
     
 Configuration
 -------------

Modified: incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientExample.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientExample.java?rev=1151259&r1=1151258&r2=1151259&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientExample.java (original)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientExample.java Tue Jul 26 21:45:16 2011
@@ -1,51 +0,0 @@
-/*
- *  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. 
- *  
- */
-package org.deftserver.example;
-
-import static java.lang.System.out;
-
-import org.deftserver.io.IOLoop;
-import org.deftserver.io.timeout.Timeout;
-import org.deftserver.web.AsyncCallback;
-import org.deftserver.web.AsyncResult;
-import org.deftserver.web.http.client.AsynchronousHttpClient;
-import org.deftserver.web.http.client.Response;
-
-public class AsynchronousHttpClientExample {
-
-	public static void main(String[] args) {
-		AsynchronousHttpClient client = new AsynchronousHttpClient();
-		client.fetch("http://sunet.se/", 
-				new AsyncResult<Response>() {
-					public void onFailure(Throwable caught) { out.println("exception:\n" + caught);} 
-					public void onSuccess(Response response) { out.println("http resonse:\n" + response);} 
-				}
-		);
-		IOLoop.INSTANCE.addTimeout(
-				new Timeout(
-						System.currentTimeMillis() + 1000, 
-						new AsyncCallback() { public void onCallback() { IOLoop.INSTANCE.stop(); }}
-				)
-		);
-		IOLoop.INSTANCE.start();
-	}
-
-
-}

Added: incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientGetExample.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientGetExample.java?rev=1151259&view=auto
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientGetExample.java (added)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientGetExample.java Tue Jul 26 21:45:16 2011
@@ -0,0 +1,61 @@
+package org.deftserver.example;
+ 
+import org.deftserver.io.IOLoop;
+import org.deftserver.io.timeout.Timeout;
+import org.deftserver.web.AsyncCallback;
+import org.deftserver.web.AsyncResult;
+import org.deftserver.web.http.client.AsynchronousHttpClient;
+import org.deftserver.web.http.client.Response;
+ 
+/**
+ * Example class to demonstrate usage of <code>AsynchronousHttpClient</code>.
+ */
+public class AsynchronousHttpClientGetExample {
+    /**
+     * A simple example of how to use the <code>AsynchronousHttpClient</code>,
+     * taking the following steps:
+     * <ol>
+     * <li>Create an instance of <code>AsyncResult&lt;Response&gt;</code> and
+     * define the methods <code>onSuccess</code> and <code>onFailure</code>.
+     * This type will become the callback for our operation, and the actions
+     * defined in the method will be executed when that operation succeeds or
+     * fails respectively. In this case, we will detail the exception on failure
+     * and show the response body on success.
+     * <li>Create an instance of <code>AsynchronousHttpClient</code> and perform
+     * a GET action passing the URL to hit and our previously defined callback
+     * type.
+     * <li>Add time-out value to the current <code>IOLoop</code> of one second,
+     * and in the definition of the <code>onCallback</code> method tell the
+     * instance to stop listening when this time limit has been reached.
+     * <li>Start the instance.
+     * </ol>
+     * 
+     * @param args
+     *            the arguments to pass; unused.
+     */
+    public static void main(final String[] args) {
+ 
+        final AsyncResult<Response> callback = new AsyncResult<Response>() {
+ 
+            @Override
+            public void onSuccess(final Response response) { System.out.println("RESPONSE: " + response); }
+
+            @Override
+            public void onFailure(final Throwable caught) { System.out.println("EXCEPTION: " + caught); }
+        };
+
+        final AsynchronousHttpClient client = new AsynchronousHttpClient();
+        client.get("http://incubator.apache.org/deft/", callback);
+
+        IOLoop.INSTANCE.addTimeout(new Timeout(System.currentTimeMillis() + 1000, new AsyncCallback() {
+            @Override
+            public void onCallback() {
+                System.out.println("INSTANCE.stop()");
+                IOLoop.INSTANCE.stop();
+            }
+        }));
+
+        System.out.println("INSTANCE.start()");
+        IOLoop.INSTANCE.start();
+    }
+ }
\ No newline at end of file

Added: incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientPostExample.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientPostExample.java?rev=1151259&view=auto
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientPostExample.java (added)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/example/AsynchronousHttpClientPostExample.java Tue Jul 26 21:45:16 2011
@@ -0,0 +1,89 @@
+/*
+ *  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. 
+ *  
+ */
+package org.deftserver.example;
+
+import org.deftserver.io.IOLoop;
+import org.deftserver.io.timeout.Timeout;
+import org.deftserver.web.AsyncCallback;
+import org.deftserver.web.AsyncResult;
+import org.deftserver.web.HttpVerb;
+import org.deftserver.web.http.ContentType;
+import org.deftserver.web.http.client.AsynchronousHttpClient;
+import org.deftserver.web.http.client.Request;
+import org.deftserver.web.http.client.Response;
+
+/**
+ * Example class to demonstrate usage of <code>AsynchronousHttpClient</code>.
+ */
+public class AsynchronousHttpClientPostExample {
+
+    /**
+     * A simple example of how to use the <code>AsynchronousHttpClient</code>,
+     * taking the following steps:
+     * <ol>
+     * <li>Create an instance of <code>AsyncResult&lt;Response&gt;</code> and
+     * define the methods <code>onSuccess</code> and <code>onFailure</code>.
+     * This type will become the callback for our operation, and the actions
+     * defined in the method will be executed when that operation succeeds or
+     * fails respectively. In this case, we will detail the exception on failure
+     * and show the response body on success.
+     * <li>Create a <code>Request</code>, providing target URL and HTTP method
+     * as well as body and content type information.
+     * <li>Create an instance of <code>AsynchronousHttpClient</code> and pass
+     * this <code>Request</code> with our previously defined callback.
+     * <li>Add time-out value to the current <code>IOLoop</code> of one second,
+     * and in the definition of the <code>onCallback</code> method tell the
+     * instance to stop listening when this time limit has been reached.
+     * <li>Start the instance.
+     * </ol>
+     * 
+     * @param args
+     *            the arguments to pass; unused.
+     */
+    public static void main(final String[] args) {
+
+        final AsyncResult<Response> callback = new AsyncResult<Response>() {
+
+            @Override
+            public void onSuccess(final Response response) { System.out.println("RESPONSE: " + response); }
+
+            @Override
+            public void onFailure(final Throwable caught) { System.out.println("EXCEPTION: " + caught); }
+        };
+
+        Request request = new Request("http://incubator.apache.org/deft/", HttpVerb.POST);
+        request.setBody("paramName=paramValue");
+        request.setContentType(ContentType.APPLICATION_FORM_URLENCODED);
+
+        final AsynchronousHttpClient client = new AsynchronousHttpClient();
+        client.fetch(request, callback);
+
+        IOLoop.INSTANCE.addTimeout(new Timeout(System.currentTimeMillis() + 1000, new AsyncCallback() {
+            @Override
+            public void onCallback() {
+                System.out.println("INSTANCE.stop()");
+                IOLoop.INSTANCE.stop();
+            }
+        }));
+
+        System.out.println("INSTANCE.start()");
+        IOLoop.INSTANCE.start();
+    }
+}

Modified: incubator/deft/sandbox/src/main/java/org/deftserver/example/kv/KeyValueStoreClient.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/example/kv/KeyValueStoreClient.java?rev=1151259&r1=1151258&r2=1151259&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/example/kv/KeyValueStoreClient.java (original)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/example/kv/KeyValueStoreClient.java Tue Jul 26 21:45:16 2011
@@ -55,15 +55,15 @@ public class KeyValueStoreClient {
 		IOLoop.INSTANCE.addHandler(channel, socket, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
 	}
 	
-	public void get(String value, AsyncResult<String> cb) {
-		socket.write("GET deft\r\n", new WriteCallback(cb));
+	public void get(String value, AsyncResult<byte[]> cb) {
+		socket.write("GET deft\r\n".getBytes(), new WriteCallback(cb));
 	}
 	
 	private class WriteCallback implements AsyncCallback {
 
-		private final AsyncResult<String> cb;
+		private final AsyncResult<byte[]> cb;
 		
-		public WriteCallback(AsyncResult<String> cb) { 
+		public WriteCallback(AsyncResult<byte[]> cb) { 
 			this.cb = cb; 
 		}
 		
@@ -71,7 +71,7 @@ public class KeyValueStoreClient {
 		public void onCallback() {
 			// write is finished. read response from server
 			logger.debug("readUntil: \r\n");
-			socket.readUntil("\r\n", cb);
+			socket.readUntil("\r\n".getBytes(), cb);
 		}
 		
 	}

Modified: incubator/deft/sandbox/src/main/java/org/deftserver/example/kv/KeyValueStoreExample.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/example/kv/KeyValueStoreExample.java?rev=1151259&r1=1151258&r2=1151259&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/example/kv/KeyValueStoreExample.java (original)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/example/kv/KeyValueStoreExample.java Tue Jul 26 21:45:16 2011
@@ -50,9 +50,9 @@ public class KeyValueStoreExample {
 		@Override
 		@Asynchronous
 		public void get(HttpRequest request, final HttpResponse response) {
-			client.get("deft", new AsyncResult<String>() {
+			client.get("deft", new AsyncResult<byte[]>() {
 				@Override public void onFailure(Throwable caught) { /* ignore */}
-				@Override public void onSuccess(String result) { response.write(result).finish(); }
+				@Override public void onSuccess(byte[] result) { response.write(new String(result)).finish(); }
 			});
 		}
 

Modified: incubator/deft/sandbox/src/main/java/org/deftserver/io/AsynchronousSocket.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/io/AsynchronousSocket.java?rev=1151259&r1=1151258&r2=1151259&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/io/AsynchronousSocket.java (original)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/io/AsynchronousSocket.java Tue Jul 26 21:45:16 2011
@@ -30,7 +30,9 @@ import java.nio.channels.ServerSocketCha
 import java.nio.channels.SocketChannel;
 import java.nio.channels.UnresolvedAddressException;
 
+import org.deftserver.io.buffer.DynamicByteBuffer;
 import org.deftserver.util.Closeables;
+import org.deftserver.util.KnuthMorrisPrattAlgorithm;
 import org.deftserver.util.NopAsyncResult;
 import org.deftserver.web.AsyncCallback;
 import org.deftserver.web.AsyncResult;
@@ -45,24 +47,26 @@ public class AsynchronousSocket implemen
 	
 	private final IOLoop ioLoop;
 	
-	private final int DEFAULT_BYTEBUFFER_SIZE = 1024;
+	private static final int DEFAULT_BYTEBUFFER_SIZE = 1024;
+	private static final int DEFAULT_INITIAL_READ_BYTEBUFFER_SIZE = 1024;
+	private static final int DEFAULT_INITIAL_WRITE_BYTEBUFFER_SIZE = 1024;
 	
-	private final AsyncResult<String> nopAsyncStringResult = NopAsyncResult.of(String.class).nopAsyncResult;
+	private final AsyncResult<byte[]> nopAsyncByteArrayResult = NopAsyncResult.of(byte[].class).nopAsyncResult;
 	private final AsyncResult<Boolean> nopAsyncBooleanResult = NopAsyncResult.of(Boolean.class).nopAsyncResult;
 	
 	private final SocketChannel channel;
 	private int interestOps;
 	
-	private String readDelimiter = "";
+	private byte[] readDelimiter = "".getBytes();
 	private int readBytes = Integer.MAX_VALUE;
 	
 	private AsyncResult<Boolean> connectCallback = nopAsyncBooleanResult;
 	private AsyncCallback closeCallback = AsyncCallback.nopCb;
-	private AsyncResult<String> readCallback = nopAsyncStringResult;
+	private AsyncResult<byte[]> readCallback = nopAsyncByteArrayResult;
 	private AsyncCallback writeCallback = AsyncCallback.nopCb;
 	
-	private final StringBuilder readBuffer = new StringBuilder();
-	private final StringBuilder writeBuffer = new StringBuilder();
+	private final DynamicByteBuffer readBuffer = DynamicByteBuffer.allocate(DEFAULT_INITIAL_READ_BYTEBUFFER_SIZE);
+	private final DynamicByteBuffer writeBuffer = DynamicByteBuffer.allocate(DEFAULT_INITIAL_WRITE_BYTEBUFFER_SIZE);
 	
 	private boolean reachedEOF = false;
 	
@@ -185,7 +189,7 @@ public class AsynchronousSocket implemen
 	@Override
 	public void handleRead(SelectionKey key) throws IOException {
 		logger.debug("handle read...");
-		ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_BYTEBUFFER_SIZE);
+		ByteBuffer buffer = ByteBuffer.allocate(DEFAULT_BYTEBUFFER_SIZE);	// TODO RS 110723 reuse byte buffers
 		int read = 0;
 		try {
 		    read = channel.read(buffer);
@@ -201,8 +205,9 @@ public class AsynchronousSocket implemen
 			ioLoop.updateHandler(channel, interestOps &= ~SelectionKey.OP_READ);
 			return;
 		}
-		readBuffer.append(new String(buffer.array(), 0, buffer.position(), Charsets.ISO_8859_1));
-		logger.debug("readBuffer size: {}", readBuffer.length());
+		buffer.flip();
+		readBuffer.put(buffer);
+		logger.debug("readBuffer size: {}", readBuffer.position());
 		checkReadState();
 	}
 
@@ -219,8 +224,8 @@ public class AsynchronousSocket implemen
 	 * Reads from the underlaying SelectableChannel until delimiter is reached. When it its, the given
 	 * AsyncResult will be invoked.
 	 */
-	public void readUntil(String delimiter, AsyncResult<String> rcb) {
-		logger.debug("readUntil delimiter: {}", delimiter);
+	public void readUntil(byte[] delimiter, AsyncResult<byte[]> rcb) {
+		logger.debug("readUntil delimiter: {}", new String(delimiter));
 		readDelimiter = delimiter;
 		readCallback = rcb;
 		checkReadState();
@@ -230,7 +235,7 @@ public class AsynchronousSocket implemen
 	 * Reads from the underlaying SelectableChannel until n bytes are read. When it its, the given
 	 * AsyncResult will be invoked.
 	 */
-	public void readBytes(int n, AsyncResult<String> rcb) {
+	public void readBytes(int n, AsyncResult<byte[]> rcb) {
 		logger.debug("readBytes #bytes: {}", n);
 		readBytes = n;
 		readCallback = rcb;
@@ -247,31 +252,44 @@ public class AsynchronousSocket implemen
 			invokeReadFailureCallback(new EOFException("Reached end-of-stream"));
 			return;
 		}
-		int index = readBuffer.indexOf(readDelimiter);
-		if (index != -1 && !readDelimiter.isEmpty()) {
-			String result = readBuffer.substring(0, index /*+ readDelimiter.length()*/);
-			readBuffer.delete(0, index + readDelimiter.length());
-			logger.debug("readBuffer size: {}", readBuffer.length());
-			readDelimiter = "";
+		int index = KnuthMorrisPrattAlgorithm.indexOf(readBuffer.array(), 0, readBuffer.position(), readDelimiter);
+		if (index != -1 && readDelimiter.length > 0) {
+			byte[] result = getResult(index, readDelimiter.length);
+			readDelimiter = "".getBytes();
 			invokeReadSuccessfulCallback(result);
-		} else if (readBuffer.length() >= readBytes) {
-			String result = readBuffer.substring(0, readBytes);
-			readBuffer.delete(0, readBytes);
-			logger.debug("readBuffer size: {}", readBuffer.length());
+		} else if (readBuffer.position() >= readBytes) {
+			byte[] result = getResult(readBytes, 0);
 			readBytes = Integer.MAX_VALUE;
 			invokeReadSuccessfulCallback(result);
 		}
 	}
-
-	private void invokeReadSuccessfulCallback(String result) {
-		AsyncResult<String> cb = readCallback;
-		readCallback = nopAsyncStringResult;
+	
+	/**
+	 * Returns the resulting byte[] data that was requested by the client through readUntil(..) or readBytes(..)
+	 * 
+	 * @param size Number of bytes to fetch and remove from the read buffer.
+	 * @param advance The number of bytes the read buffer's position should move forward after the data has been fetched. 
+	 *        (To ignore the readDelimiter.)
+	 */
+	private byte[] getResult(int size, int advance) {
+		readBuffer.flip();
+		byte[] result = new byte[size]; 
+		readBuffer.get(result, 0, size);
+		readBuffer.position(readBuffer.position() + advance);	// ignore the delimiter (if it was a readUntil(..) call)
+		readBuffer.compact();	// "delete" the result data (data after result is left intact and will not be overwritten)
+		logger.debug("readBuffer size: {}", readBuffer.position());
+		return result;
+	}
+
+	private void invokeReadSuccessfulCallback(byte[] result) {
+		AsyncResult<byte[]> cb = readCallback;
+		readCallback = nopAsyncByteArrayResult;
 		cb.onSuccess(result);
 	}
 	
 	private void invokeReadFailureCallback(Exception e) {
-		AsyncResult<String> cb = readCallback;
-		readCallback = nopAsyncStringResult;
+		AsyncResult<byte[]> cb = readCallback;
+		readCallback = nopAsyncByteArrayResult;
 		cb.onFailure(e);
 	}
 	
@@ -303,10 +321,10 @@ public class AsynchronousSocket implemen
 	 * Writes the given data to the underlaying SelectableChannel. When all data is successfully transmitted, the given 
 	 * AsyncCallback will be invoked 
 	 */
-	public void write(String data, AsyncCallback wcb) {
-		logger.debug("write data: {}", data);
-		writeBuffer.append(data);
-		logger.debug("writeBuffer size: {}", writeBuffer.length());
+	public void write(byte[] data, AsyncCallback wcb) {
+		logger.debug("write data: {}", new String(data));
+		writeBuffer.put(data);
+		logger.debug("writeBuffer size: {}", writeBuffer.position());
 		writeCallback = wcb;
 		doWrite();
 	}
@@ -318,7 +336,9 @@ public class AsynchronousSocket implemen
 		int written = 0;
 		try {
 			if (channel.isConnected()) {
-				written = channel.write(ByteBuffer.wrap(writeBuffer.toString().getBytes()));
+				writeBuffer.flip(); 	// prepare for write
+				written = channel.write(writeBuffer.getByteBuffer());
+				writeBuffer.compact();	// // make room for more data be "read" in
 			}
 		} catch (IOException e) {
 			logger.error("IOException during write: {}", e.getMessage());
@@ -326,10 +346,9 @@ public class AsynchronousSocket implemen
 			Closeables.closeQuietly(ioLoop, channel);
 			return;
 		}
-		writeBuffer.delete(0, written);
 		logger.debug("wrote: {} bytes", written);
-		logger.debug("writeBuffer size: {}", writeBuffer.length());
-		if (writeBuffer.length() > 0) {
+		logger.debug("writeBuffer size: {}", writeBuffer.position());
+		if (writeBuffer.position() > 0) {
 			ioLoop.updateHandler(channel, interestOps |= SelectionKey.OP_WRITE);
 		} else {
 			ioLoop.updateHandler(channel, interestOps &= ~SelectionKey.OP_WRITE);

Modified: incubator/deft/sandbox/src/main/java/org/deftserver/io/buffer/DynamicByteBuffer.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/io/buffer/DynamicByteBuffer.java?rev=1151259&r1=1151258&r2=1151259&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/io/buffer/DynamicByteBuffer.java (original)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/io/buffer/DynamicByteBuffer.java Tue Jul 26 21:45:16 2011
@@ -51,6 +51,14 @@ public class DynamicByteBuffer {
 		ensureCapacity(src.length);
 		backend.put(src);
 	}
+	
+	/**
+	 * Append the bytes from the given src. Will reallocate if needed.
+	 */
+	public void put(ByteBuffer src) {
+		ensureCapacity(src.limit());
+		backend.put(src);
+	}
 
 	
 	/**
@@ -97,6 +105,17 @@ public class DynamicByteBuffer {
 	public ByteBuffer getByteBuffer() {
 		return backend;
 	}
+	
+	/**
+	 * See {@link ByteBuffer#get(byte[], int, int)}
+	 */
+	public void get(byte[] dst, int offset, int length) {
+		backend.get(dst, offset, length);
+	}
+	
+	public void position(int newPosition) {
+		backend.position(newPosition);
+	}
 
 	/**
 	 * See {@link ByteBuffer#flip}

Added: incubator/deft/sandbox/src/main/java/org/deftserver/util/KnuthMorrisPrattAlgorithm.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/util/KnuthMorrisPrattAlgorithm.java?rev=1151259&view=auto
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/util/KnuthMorrisPrattAlgorithm.java (added)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/util/KnuthMorrisPrattAlgorithm.java Tue Jul 26 21:45:16 2011
@@ -0,0 +1,76 @@
+/*
+ *  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. 
+ *  
+ */
+package org.deftserver.util;
+
+/**
+ * The Knuth Morris Pratt string searching algorithm (or KMP algorithm) searches for occurrences of 
+ * a "word" W within a main "text string" S by employing the observation that when a mismatch occurs, 
+ * the word itself embodies sufficient information to determine where the next match could begin, 
+ * thus bypassing re-examination of previously matched characters.
+ * 
+ * The algorithm was conceived by Donald Knuth and Vaughan Pratt and independently by James H. Morris 
+ * in 1977, but the three published it jointly.
+ *
+ */
+
+public class KnuthMorrisPrattAlgorithm {
+
+	/**
+	 * Search for pattern in data, [start, end). Returns -1 if no match is found or if pattern is of length 0. 
+	 */
+	public static int indexOf(byte[] data, int start, int end, byte[] pattern) {
+		if (pattern.length == 0) {
+			return -1;
+		}
+		int[] failure = failure(pattern);
+
+		int j = 0;
+
+		for (int i = 0; i < end; i++) {
+			while (j > 0 && pattern[j] != data[i]) {
+				j = failure[j - 1];
+			}
+			if (pattern[j] == data[i]) {
+				j++;
+			}
+			if (j == pattern.length) {
+				return i - pattern.length + 1;
+			}
+		}
+		return -1;
+	}
+
+	private static int[] failure(byte[] pattern) {
+		int[] failure = new int[pattern.length];
+
+		int j = 0;
+		for (int i = 1; i < pattern.length; i++) {
+			while (j>0 && pattern[j] != pattern[i]) {
+				j = failure[j - 1];
+			}
+			if (pattern[j] == pattern[i]) {
+				j++;
+			}
+			failure[i] = j;
+		}
+
+		return failure;
+	}
+}

Modified: incubator/deft/sandbox/src/main/java/org/deftserver/web/HttpVerb.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/web/HttpVerb.java?rev=1151259&r1=1151258&r2=1151259&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/web/HttpVerb.java (original)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/web/HttpVerb.java Tue Jul 26 21:45:16 2011
@@ -19,14 +19,19 @@
  */
 package org.deftserver.web;
 
+/**
+ * <code>Enumeration</code> of all available HTTP verbs, as defined by <a
+ * href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html">Hypertext
+ * Transfer Protocol -- HTTP/1.1 (RFC 2616)</a>.
+ */
 public enum HttpVerb {
-	
-	GET,
-	HEAD,
-	POST,
-	PUT,
-	DELETE,
-	OPTIONS,
-	TRACE,
-	CONNECT
+
+    GET,
+    HEAD,
+    POST,
+    PUT,
+    DELETE,
+    OPTIONS,
+    TRACE,
+    CONNECT;
 }

Added: incubator/deft/sandbox/src/main/java/org/deftserver/web/http/ContentType.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/web/http/ContentType.java?rev=1151259&view=auto
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/web/http/ContentType.java (added)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/web/http/ContentType.java Tue Jul 26 21:45:16 2011
@@ -0,0 +1,51 @@
+/*
+ *  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. 
+ *  
+ */
+package org.deftserver.web.http;
+
+/**
+ * A collection of known content types, for convenience.
+ */
+public class ContentType {
+
+    /** application/xml */
+    public static final String APPLICATION_XML = "application/xml";
+
+    /** application/json */
+    public static final String APPLICATION_JSON = "application/json";
+
+    /** application/x-www-form-urlencoded */
+    public static final String APPLICATION_FORM_URLENCODED = "application/x-www-form-urlencoded";
+
+    /** application/octet-stream */
+    public static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
+
+    /** multipart/form-data */
+    public static final String MULTIPART_FORM_DATA = "multipart/form-data";
+
+    /** text/html */
+    public static final String TEXT_HTML = "text/html";
+
+    /** text/plain */
+    public static final String TEXT_PLAIN = "text/plain";
+
+    /** text/xml */
+    public static final String TEXT_XML = "text/xml";
+
+}

Modified: incubator/deft/sandbox/src/main/java/org/deftserver/web/http/client/AsynchronousHttpClient.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/web/http/client/AsynchronousHttpClient.java?rev=1151259&r1=1151258&r2=1151259&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/web/http/client/AsynchronousHttpClient.java (original)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/web/http/client/AsynchronousHttpClient.java Tue Jul 26 21:45:16 2011
@@ -34,251 +34,363 @@ import org.deftserver.web.HttpVerb;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Charsets;
+
 /**
-* This class implements a simple HTTP 1.1 client on top of Deft's {@code AsynchronousSocket}.
-* It does not currently implement all applicable parts of the HTTP
-* specification.
-* <pre>
-* E.g the following is not supported.
-*  - POST and PUT
-*  
-* </pre>
-* This class has not been tested extensively in production and
-* should be considered experimental as of the release of
-* Deft 0.3.
-* 
-* This http client is inspired by https://github.com/facebook/tornado/blob/master/tornado/simple_httpclient.py
-* and part of the documentation is simply copy pasted.
-*/
+ * This class implements a simple HTTP 1.1 client on top of Deft's {@code
+ * AsynchronousSocket}. It does not currently implement all applicable parts of
+ * the HTTP specification.
+ * 
+ * <pre>
+ * E.g the following is not supported.
+ *  - keep-alive
+ * 
+ * </pre>
+ * 
+ * This class has not been tested extensively in production and should be
+ * considered experimental as of the release of Deft 0.3. This http client is
+ * inspired by
+ * https://github.com/facebook/tornado/blob/master/tornado/simple_httpclient.py
+ * and part of the documentation is simply copy pasted.
+ */
 public class AsynchronousHttpClient {
 
-	private static final Logger logger = LoggerFactory.getLogger(AsynchronousHttpClient.class);
+    /** The <code>Logger</code>. */
+    private static final Logger logger = LoggerFactory.getLogger(AsynchronousHttpClient.class);
+
+    private static final long TIMEOUT = 15 * 1000; // 15s
+
+    private static final AsyncResult<Response> nopAsyncResult = NopAsyncResult.of(Response.class).nopAsyncResult;
+
+    private AsynchronousSocket socket;
+
+    private Request request;
+    private long requestStarted;
+    private Response response;
+    private AsyncResult<Response> responseCallback;
+
+    private Timeout timeout;
+
+    private final IOLoop ioLoop;
+
+    static final String HTTP_VERSION = "HTTP/1.1\r\n";
+    static final String USER_AGENT_HEADER = "User-Agent: Deft AsynchronousHttpClient/0.2-SNAPSHOT\r\n";
+    static final String NEWLINE = "\r\n";
+
+    /**
+     * Create an instance of this type.
+     */
+    public AsynchronousHttpClient() {
+        this(IOLoop.INSTANCE);
+    }
+
+    /**
+     * Create an instance of this type, utilising the given <code>IOLoop</code>.
+     * 
+     * @param ioLoop
+     *            the <code>IOLoop</code> to use.
+     */
+    public AsynchronousHttpClient(final IOLoop ioLoop) {
+        this.ioLoop = ioLoop;
+    }
+
+    /**
+     * Make an asynchronous call as per the given <code>Request</code>, invoking
+     * the passed callback upon completion.
+     * 
+     * @param request
+     *            the definition of the request to make.
+     * @param callback
+     *            the callback to execute when the response is received.
+     */
+    public void fetch(final Request request, final AsyncResult<Response> callback) {
+        this.request = request;
+        doFetch(callback, System.currentTimeMillis());
+    }
+
+    /**
+     * Make an asynchronous HTTP GET request against the specified URL, and
+     * invoke the given callback when the response upon completion.
+     * 
+     * @param url
+     *            the URL from which to request, e.g.
+     *            <em>http://incubator.apache.org/deft/</em>.
+     * @param callback
+     *            the callback to execute when the response is received.
+     */
+    public void get(final String url, final AsyncResult<Response> callback) {
+        request = new Request(url, HttpVerb.GET);
+        doFetch(callback, System.currentTimeMillis());
+    }
+
+    /**
+     * Make an asynchronous HTTP POST request against the specified URL, and
+     * invoke the given callback when the response upon completion.
+     * 
+     * @param url
+     *            the URL from which to request, e.g.
+     *            <em>http://incubator.apache.org/deft/</em>.
+     * @param body
+     *            the message body to pass.
+     * @param callback
+     *            the callback to execute when the response is received.
+     */
+    public void post(final String url, final String body, final AsyncResult<Response> callback) {
+        request = new Request(url, HttpVerb.POST);
+        request.setBody(body);
+        doFetch(callback, System.currentTimeMillis());
+    }
+
+    public void post(final String url, final byte[] body, final AsyncResult<Response> callback) {
+        request = new Request(url, HttpVerb.POST);
+        request.setBody(body);
+        doFetch(callback, System.currentTimeMillis());
+    }
+
+    /**
+     * Make an asynchronous HTTP PUT request against the specified URL, and
+     * invoke the given callback when the response upon completion.
+     * 
+     * @param url
+     *            the URL from which to request, e.g.
+     *            <em>http://incubator.apache.org/deft/</em>.
+     * @param body
+     *            the message body to pass.
+     * @param callback
+     *            the callback to execute when the response is received.
+     */
+    public void put(final String url, final String body, final AsyncResult<Response> callback) {
+        request = new Request(url, HttpVerb.PUT);
+        request.setBody(body);
+        doFetch(callback, System.currentTimeMillis());
+    }
+
+    /**
+     * Perform the action of making a request against a known URL, before
+     * invoking the given callback type.
+     * 
+     * @param callback
+     *            the callback to execute when the response is received.
+     * @param requestStarted
+     *            the current time in milliseconds at which the request was
+     *            begun.
+     */
+    protected void doFetch(final AsyncResult<Response> callback, final long requestStarted) {
+
+        this.requestStarted = requestStarted;
+        try {
+            socket = new AsynchronousSocket(SocketChannel.open());
+        } catch (final IOException e) {
+            logger.error("Error opening SocketChannel: {}" + e.getMessage());
+        }
+
+        responseCallback = callback;
+        int port = request.getURL().getPort();
+        port = port == -1 ? 80 : port;
+
+        startTimeout();
+        socket.connect(
+        		request.getURL().getHost(), 
+        		port, 
+        		new AsyncResult<Boolean>() {
+                    public void onFailure(final Throwable t) { onConnectFailure(t); }
+                    public void onSuccess(final Boolean result) { onConnect(); }
+        });
+    }
+
+    /**
+     * Close the underlaying {@code AsynchronousSocket}.
+     */
+    public void close() {
+        logger.debug("Closing http client connection...");
+        socket.close();
+    }
+
+    private void startTimeout() {
+        logger.debug("start timeout...");
+        timeout = new Timeout(
+                System.currentTimeMillis() + TIMEOUT, 
+                new AsyncCallback() { public void onCallback() { onTimeout(); }}
+        );
+        ioLoop.addTimeout(timeout);
+    }
+
+    private void cancelTimeout() {
+        logger.debug("cancel timeout...");
+        timeout.cancel();
+        timeout = null;
+    }
+
+    private void onTimeout() {
+        logger.debug("Pending operation (connect, read or write) timed out...");
+        final AsyncResult<Response> cb = responseCallback;
+        responseCallback = nopAsyncResult;
+        cb.onFailure(new TimeoutException("Connection timed out"));
+        close();
+    }
+
+    private void onConnect() {
+        logger.debug("Connected...");
+        cancelTimeout();
+        startTimeout();
+        socket.write(
+        		makeRequestLineAndHeaders().getBytes(), 
+        		new AsyncCallback() { public void onCallback() { onWriteComplete(); }
+        });
+    }
+
+    private void onConnectFailure(final Throwable t) {
+        logger.debug("Connect failed...");
+        cancelTimeout();
+        final AsyncResult<Response> cb = responseCallback;
+        responseCallback = nopAsyncResult;
+        cb.onFailure(t);
+        close();
+    }
+
+    /**
+     * Create the request lines and header, populating the body where not
+     * <code>null</code>. Ignoring origin of inputs, the output for a POST
+     * request might resemble:
+     * <p>
+     * <code>
+     * POST /path/to/target HTTP/1.1<br/>
+     * Host: hostname<br/>
+     * User-Agent: Deft AsynchronousHttpClient/0.x-SNAPSHOT<br/>
+     * Content-Type: application/x-www-form-urlencoded<br/>
+     * Content-Length: 20<br/>
+     * <br/>
+     * paramName=paramValue<br/>
+	 * </code>
+     * </p>
+     * 
+     * @see HttpVerb#hasRequestBody(HttpVerb)
+     */
+    String makeRequestLineAndHeaders() {
+
+        int length = request.getBody() == null ? 0 : request.getBody().length;
+        final StringBuilder builder = new StringBuilder(length + 1024);
+        builder.append(request.getVerb()).append(" ").append(request.getURL().getPath()).append(" ");
+        builder.append(HTTP_VERSION).append("Host: ").append(request.getURL().getHost()).append(NEWLINE);
+        builder.append(USER_AGENT_HEADER);
+
+        if (request.getBody() != null) {
+            builder.append("Content-Type: ").append(request.getContentType().toString()).append(NEWLINE);
+            builder.append("Content-Length: ").append(length);
+            builder.append(NEWLINE).append(NEWLINE);
+            builder.append(new String(request.getBody(), Charsets.ISO_8859_1));
+        }
+
+        builder.append(NEWLINE);
+        return builder.toString();
+    }
+
+    private void onWriteComplete() {
+        logger.debug("onWriteComplete...");
+        cancelTimeout();
+        startTimeout();
+        socket.readUntil(
+        		"\r\n\r\n".getBytes(), /* header delimiter */
+        		new NaiveAsyncResult() { public void onSuccess(final byte[] headers) { onHeaders(headers); }}
+        );
+    }
+
+    private void onHeaders(final byte[] rawResult) {
+    	String result = new String(rawResult, Charsets.ISO_8859_1);
+        logger.debug("headers: {}", result);
+        cancelTimeout();
+        response = new Response(requestStarted);
+        final String[] headers = result.split("\r\n");
+        response.setStatuLine(headers[0]); // first entry contains status
+        // line
+        // (e.g. HTTP/1.1 200 OK)
+        for (int i = 1; i < headers.length; i++) {
+            final String[] header = headers[i].split(": ");
+            response.setHeader(header[0], header[1]);
+        }
+
+        final String contentLength = response.getHeader("Content-Length");
+        startTimeout();
+        if (contentLength != null) {
+        	socket.readBytes(
+        			Integer.parseInt(contentLength), 
+        			new NaiveAsyncResult() { public void onSuccess(byte[] body) { onBody(body);}
+        	});
+        } else { // Transfer-Encoding: chunked
+            socket.readUntil(
+            		NEWLINE.getBytes(), /* chunk delimiter */
+            		new NaiveAsyncResult() { public void onSuccess(byte[] octet) { onChunkOctet(octet);
+                }
+            });
+        }
+    }
+
+    /**
+     * JM: TODO, especially noting the redirects we follow....
+     */
+    private void onBody(final byte[] rawBody) {
+    	final String body = new String(rawBody, Charsets.ISO_8859_1);
+        logger.debug("body size: {}", body.length());
+        cancelTimeout();
+        response.setBody(body);
+        if ((response.getStatusLine().contains("301") || response.getStatusLine().contains("302"))
+                && request.isFollowingRedirects() && request.getMaxRedirects() > 0) {
+            final String newUrl = UrlUtil.urlJoin(request.getURL(), response.getHeader("Location"));
+            request = new Request(newUrl, request.getVerb(), true, request.getMaxRedirects() - 1);
+            logger.debug("Following redirect, new url: {}, redirects left: {}", newUrl, request.getMaxRedirects());
+            doFetch(responseCallback, requestStarted);
+        } else {
+            close();
+            invokeResponseCallback();
+        }
+    }
+
+    private void onChunk(final byte[] rawChunk) {
+    	final String chunk = new String(rawChunk, Charsets.ISO_8859_1);
+        logger.debug("chunk size: {}", chunk.length());
+        cancelTimeout();
+        response.addChunk(chunk.substring(0, chunk.length() - NEWLINE.length()));
+        startTimeout();
+        socket.readUntil(
+        		NEWLINE.getBytes(), /* chunk delimiter */
+        		new NaiveAsyncResult() {public void onSuccess(final byte[] octet) { onChunkOctet(octet);
+            }
+        });
+    }
+
+    private void onChunkOctet(final byte[] rawOctet) {
+    	final String octet = new String(rawOctet, Charsets.ISO_8859_1);
+        final int readBytes = Integer.parseInt(octet, 16);
+        logger.debug("chunk octet: {} (decimal: {})", octet, readBytes);
+        cancelTimeout();
+        startTimeout();
+        if (readBytes != 0) {
+            socket.readBytes(
+            		readBytes + NEWLINE.length(), // chunk delimiter is \r\n
+                    new NaiveAsyncResult() { public void onSuccess(final byte[] chunk) { onChunk(chunk); }}
+            );
+        } else {
+            onBody(response.getBody().getBytes(Charsets.ISO_8859_1));
+        }
+    }
+
+    private void invokeResponseCallback() {
+        final AsyncResult<Response> cb = responseCallback;
+        responseCallback = nopAsyncResult;
+        cb.onSuccess(response);
+    }
+
+    /**
+     * Naive because all it does when an exception is thrown is log the
+     * exception.
+     */
+    private abstract class NaiveAsyncResult implements AsyncResult<byte[]> {
+
+        @Override
+        public void onFailure(final Throwable caught) {
+            logger.debug("onFailure: {}", caught);
+        }
 
-	private static final long TIMEOUT = 15 * 1000;	// 15s
-	
-	private static final AsyncResult<Response> nopAsyncResult = NopAsyncResult.of(Response.class).nopAsyncResult;
-
-	private AsynchronousSocket socket;
-	
-	private Request request;
-	private long requestStarted;
-	private Response response;
-	private AsyncResult<Response> responseCallback;
-	
-	private Timeout timeout;
-	
-	private final IOLoop ioLoop;
-	
-	private static final String HTTP_VERSION = "HTTP/1.1\r\n";
-	private static final String USER_AGENT_HEADER = "User-Agent: Deft AsynchronousHttpClient/0.2-SNAPSHOT\r\n";
-	private static final String NEWLINE = "\r\n";
-	
-	public AsynchronousHttpClient() { 
-		this(IOLoop.INSTANCE);
-	}
-	
-	public AsynchronousHttpClient(IOLoop ioLoop) {
-		this.ioLoop = ioLoop;
-	}
-
-	/**
-	 * Makes an asynchronous HTTP GET request against the specified url and invokes the given 
-	 * callback when the response is fetched.
-	 * 
-	 * @param url e.g "http://tt.se:80/start/"
-	 * @param cb callback that will be executed when the response is received.
-	 */
-	public void fetch(String url, AsyncResult<Response> cb) {
-		request = new Request(url, HttpVerb.GET);
-		doFetch(cb, System.currentTimeMillis());
-	}
-	
-	public void fetch(Request request, AsyncResult<Response> cb) {
-		this.request = request;
-		doFetch(cb, System.currentTimeMillis());
-	}
-	
-	private void doFetch(AsyncResult<Response> cb, long requestStarted) {
-		this.requestStarted = requestStarted;
-		try {
-			socket = new AsynchronousSocket(SocketChannel.open());
-		} catch (IOException e) {
-			logger.error("Error opening SocketChannel: {}" + e.getMessage());
-		}
-		responseCallback = cb;
-		int port = request.getURL().getPort();
-		port = port == -1 ? 80 : port;
-		startTimeout();
-		socket.connect(
-				request.getURL().getHost(), 
-				port,
-				new AsyncResult<Boolean>() {
-					public void onFailure(Throwable t) { onConnectFailure(t); }
-					public void onSuccess(Boolean result) { onConnect(); }
-				}
-		);
-	}
-	
-	/**
-	 * Close the underlaying {@code AsynchronousSocket}.
-	 */
-	public void close() {
-		logger.debug("Closing http client connection...");
-		socket.close(); 
-	}
-	
-	private void startTimeout() {
-		logger.debug("start timeout...");
-		timeout = new Timeout(
-				System.currentTimeMillis() + TIMEOUT, 
-				new AsyncCallback() { public void onCallback() { onTimeout(); } }
-		);
-		ioLoop.addTimeout(timeout);		
-	}
-	
-	private void cancelTimeout() {
-		logger.debug("cancel timeout...");
-		timeout.cancel();
-		timeout = null;
-	}
-	
-	private void onTimeout() {
-		logger.debug("Pending operation (connect, read or write) timed out...");
-		AsyncResult<Response> cb = responseCallback;
-		responseCallback = nopAsyncResult;
-		cb.onFailure(new TimeoutException("Connection timed out"));
-		close();
-	}
-
-	private void onConnect() {
-		logger.debug("Connected...");
-		cancelTimeout();
-		startTimeout();
-		socket.write(
-				makeRequestLineAndHeaders(), 
-				new AsyncCallback() { public void onCallback() { onWriteComplete(); }}
-		);
-	}
-	
-	private void onConnectFailure(Throwable t) {
-		logger.debug("Connect failed...");
-		cancelTimeout();
-		AsyncResult<Response> cb = responseCallback;
-		responseCallback = nopAsyncResult;
-		cb.onFailure(t);
-		close();
-	}
-
-	/**
-	 * 
-	 * @return Eg. 
-	 * 				GET /path/to/file/index.html HTTP/1.0
-	 * 				From: a@b.com
-	 * 				User-Agent: HTTPTool/1.0
-	 * 
-	 */
-	private String makeRequestLineAndHeaders() {
-		return request.getVerb() + " " + request.getURL().getPath() + " " + HTTP_VERSION +
-				"Host: " + request.getURL().getHost() + "\r\n" +
-				USER_AGENT_HEADER +
-				NEWLINE;
-	}
-	
-	private void onWriteComplete() {
-		logger.debug("onWriteComplete...");
-		cancelTimeout();
-		startTimeout();
-		socket.readUntil(
-				"\r\n\r\n", 	/* header delimiter */
-				new NaiveAsyncResult() { public void onSuccess(String headers) { onHeaders(headers); }
-		});
-	}
-	
-	private void onHeaders(String result) {
-		logger.debug("headers: {}", result);
-		cancelTimeout();
-		response = new Response(requestStarted);
-		String[] headers = result.split("\r\n");
-		response.setStatuLine(headers[0]);	// first entry contains status line (e.g. HTTP/1.1 200 OK)
-		for (int i = 1; i < headers.length; i++) {
-			String[] header = headers[i].split(": ");
-			response.setHeader(header[0], header[1]);
-		}
-		
-		String contentLength = response.getHeader("Content-Length");
-		startTimeout();
-		if (contentLength != null) {
-			socket.readBytes(
-					Integer.parseInt(contentLength), 
-					new NaiveAsyncResult() { public void onSuccess(String body) { onBody(body); } }
-			);
-		} else {  // Transfer-Encoding: chunked
-			socket.readUntil(
-					NEWLINE, 	/* chunk delimiter*/
-					new NaiveAsyncResult() { public void onSuccess(String octet) { onChunkOctet(octet); } }
-			);
-		}
-	}
-				
-	private void onBody(String body) {
-		logger.debug("body size: {}", body.length());
-		cancelTimeout();
-		response.setBody(body);
-		if ((response.getStatusLine().contains("301") || response.getStatusLine().contains("302")) && 
-			request.isFollowingRedirects() && 
-			request.getMaxRedirects() > 0) {
-				String newUrl = UrlUtil.urlJoin(request.getURL(), response.getHeader("Location"));
-				request = new Request(newUrl, HttpVerb.valueOf(request.getVerb()), true, request.getMaxRedirects() - 1);
-				logger.debug("Following redirect, new url: {}, redirects left: {}", newUrl, request.getMaxRedirects());
-				doFetch(responseCallback, requestStarted);
-		} else {
-			close();
-			invokeResponseCallback();
-		}
-	} 
-	
-	private void onChunk(String chunk) {
-		logger.debug("chunk size: {}", chunk.length());
-		cancelTimeout();
-		response.addChunk(chunk.substring(0, chunk.length() - NEWLINE.length()));
-		startTimeout();
-		socket.readUntil(
-				NEWLINE, 	/* chunk delimiter*/
-				new NaiveAsyncResult() { public void onSuccess(String octet) { onChunkOctet(octet); } }
-		);
-	}
-	
-	private void onChunkOctet(String octet) {
-		int readBytes = Integer.parseInt(octet, 16);
-		logger.debug("chunk octet: {} (decimal: {})", octet, readBytes);
-		cancelTimeout();
-		startTimeout();
-		if (readBytes != 0) {
-			socket.readBytes(
-					readBytes + NEWLINE.length(),	// chunk delimiter is \r\n
-					new NaiveAsyncResult() { public void onSuccess(String chunk) { onChunk(chunk); } }
-			);
-		} else {
-			onBody(response.getBody());
-		}
-	}
-	
-	private void invokeResponseCallback() {
-		AsyncResult<Response> cb = responseCallback;
-		responseCallback = nopAsyncResult;
-		cb.onSuccess(response);
-	}
-				
-	/**
-	 * Naive because all it does when an exception is thrown is log the exception.
-	 */
-	private abstract class NaiveAsyncResult implements AsyncResult<String> {
-		
-		@Override
-		public void onFailure(Throwable caught) {
-			logger.debug("onFailure: {}", caught);
-		}
-		
-	}
+    }
 
 }

Modified: incubator/deft/sandbox/src/main/java/org/deftserver/web/http/client/Request.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/deftserver/web/http/client/Request.java?rev=1151259&r1=1151258&r2=1151259&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/deftserver/web/http/client/Request.java (original)
+++ incubator/deft/sandbox/src/main/java/org/deftserver/web/http/client/Request.java Tue Jul 26 21:45:16 2011
@@ -23,60 +23,168 @@ import java.net.MalformedURLException;
 import java.net.URL;
 
 import org.deftserver.web.HttpVerb;
+import org.deftserver.web.http.ContentType;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class Request {
-	
-	private static final Logger logger = LoggerFactory.getLogger(Request.class);
 
-	private final URL url;
-	private final HttpVerb verb;
-	private boolean followRedirects = true;
-	private int maxRedirects = 7;
-	
-	/**
-	 * An HTTP {@code Request} that follows (at most 7) redirects (= HTTP status codes 301, 302)  
-	 */
-	public Request(String url, HttpVerb verb) {
-		try {
-			this.url = new URL(url);
-			this.verb = verb;
-		} catch (MalformedURLException e) {
-			logger.error("Malformed URL: {}", e.getMessage());
-			throw new RuntimeException(e);
-		}
-	}
-	
-	public Request(String url, HttpVerb verb, boolean followRedirects, int maxRedirects) {
-		this(url, verb);
-		this.followRedirects = followRedirects;
-		this.maxRedirects = maxRedirects;
-		
-	}
-
-	public URL getURL() {
-		return url;
-	}
-	
-	/**
-	 * 
-	 * @return The verb (method) name, e.g. "GET" or "POST"
-	 */
-	public String getVerb() {
-		return verb.name();
-	}
-	
-	/**
-	 * 
-	 * @return The maximum number of redirects this {@code Request} follows.
-	 */
-	public int getMaxRedirects() {
-		return maxRedirects;
-	}
-	
-	public boolean isFollowingRedirects() {
-		return followRedirects;
-	}
-	
+    /** The <code>Logger</code>. */
+    private static final Logger logger = LoggerFactory.getLogger(Request.class);
+
+    /** The <code>URL</code> associated with the current <code>Request</code>. */
+    private final URL url;
+
+    /** The request type. */
+    private final HttpVerb verb;
+
+    /**
+     * Indicates whether 3xx Redirection codes should be followed; defaults to
+     * <code>true</code>.
+     * 
+     * @see AsynchronousHttpClient#onBody
+     */
+    private boolean followRedirects = true;
+
+    /**
+     * The maximum number of redirects to follow, where enabled; defaults to 7.
+     * 
+     * @see #followRedirects
+     */
+    private int maxRedirects = 7;
+
+    /** The body associated with the request. */
+    private byte[] body;
+
+    /** The type of content represented by this request. */
+    private String contentType = ContentType.APPLICATION_FORM_URLENCODED;
+
+    /**
+     * Create an instance of this type with the given <code>URL</code> and
+     * <code>HttpVerb</code>. Follows redirects and to a count as specified by
+     * default.
+     * 
+     * @throws RuntimeException
+     *             where a {@link MalformedURLException} is caught.
+     * @see #Request(String, HttpVerb, boolean, int)
+     */
+    public Request(final String url, final HttpVerb verb) {
+        try {
+            this.url = new URL(url);
+            this.verb = verb;
+        } catch (final MalformedURLException e) {
+            logger.error("Malformed URL: {}", e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Create an instance of this type with the given <code>URL</code> and
+     * <code>HttpVerb</code>. Redirection behaviour and count are as specified.
+     * 
+     * @param url
+     *            the <code>URL</code> for the request.
+     * @param verb
+     *            the type of request to be made.
+     * @param followRedirects
+     *            <code>true</code> if redirects are to be followed;
+     *            <code>false</code> otherwise.
+     * @param maxRedirects
+     *            where redirects should be followed, the maximum number to
+     *            follow.
+     * @see #Request(String, HttpVerb)
+     * @see AsynchronousHttpClient#onBody
+     */
+    public Request(final String url, final HttpVerb verb, final boolean followRedirects, final int maxRedirects) {
+        this(url, verb);
+        this.followRedirects = followRedirects;
+        this.maxRedirects = maxRedirects;
+    }
+
+    /**
+     * Retrieve the <code>URL</code> associated with this <code>Request</code>.
+     * 
+     * @return the associated <code>URL</code>.
+     */
+    public URL getURL() {
+        return this.url;
+    }
+
+    /**
+     * Retrieve the current type of request.
+     * 
+     * @return the associated <code>HttpVerb</code>.
+     */
+    public HttpVerb getVerb() {
+        return this.verb;
+    }
+
+    /**
+     * Indicates whether 3xx Redirection codes should be followed.
+     * 
+     * @return <code>true</code> if redirects should be followed;
+     *         <code>false</code> otherwise.
+     * @see #followRedirects
+     */
+    public boolean isFollowingRedirects() {
+        return this.followRedirects;
+    }
+
+    /**
+     * Retrieve the maximum number of redirects this <code>Request</code> will
+     * follow.
+     * 
+     * @return an <code>int</code> representing the number of redirects.
+     */
+    public int getMaxRedirects() {
+        return this.maxRedirects;
+    }
+
+    /**
+     * Retrieve the body associated with this <code>Request</code>.
+     * 
+     * @return the associated body; may be <code>null</code>.
+     */
+    public byte[] getBody() {
+        return this.body;
+    }
+
+    /**
+     * Set the body applicable to this <code>Request</code>.
+     * 
+     * @param body
+     *            the body to apply.
+     */
+    public void setBody(final byte[] body) {
+        this.body = body;
+    }
+
+    /**
+     * Set the body applicable to this <code>Request</code>.
+     * 
+     * @param body
+     *            the body to apply.
+     */
+    public void setBody(final String body) {
+        this.body = body.getBytes();
+    }
+
+    /**
+     * Retrieve the content type associated with this <code>Request</code>.
+     * 
+     * @return the associated content type.
+     */
+    public String getContentType() {
+        return contentType;
+    }
+
+    /**
+     * Set the content type associated with this <code>Request</code>.
+     * 
+     * @param contentType
+     *            the content type to apply.
+     */
+    public void setContentType(String contentType) {
+        this.contentType = contentType;
+    }
 }

Modified: incubator/deft/sandbox/src/test/java/org/deftserver/io/AsynchronousSocketTest.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/test/java/org/deftserver/io/AsynchronousSocketTest.java?rev=1151259&r1=1151258&r2=1151259&view=diff
==============================================================================
--- incubator/deft/sandbox/src/test/java/org/deftserver/io/AsynchronousSocketTest.java (original)
+++ incubator/deft/sandbox/src/test/java/org/deftserver/io/AsynchronousSocketTest.java Tue Jul 26 21:45:16 2011
@@ -126,16 +126,16 @@ public class AsynchronousSocketTest {
 	private void onConnect() {
 		latch.countDown();
 		AsyncCallback wcb = new AsyncCallback() { @Override public void onCallback() { onWriteComplete(); }};
-		socket.write("roger|\r\n", wcb);
+		socket.write("roger|\r\n".getBytes(), wcb);
 	}
 	
 	private void onWriteComplete() {
 		latch.countDown();
-		AsyncResult<String> rcb = new AsyncResult<String>() { 
+		AsyncResult<byte[]> rcb = new AsyncResult<byte[]>() { 
 			@Override public void onFailure(Throwable caught) { assertTrue(false); }
-			@Override public void onSuccess(String result) { onReadComplete(result); }
+			@Override public void onSuccess(byte[] result) { onReadComplete(new String(result)); }
 		};
-		socket.readUntil("|", rcb);
+		socket.readUntil("|".getBytes(), rcb);
 	}
 	
 	private void onReadComplete(String result) {

Modified: incubator/deft/sandbox/src/test/java/org/deftserver/web/DeftSystemTest.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/test/java/org/deftserver/web/DeftSystemTest.java?rev=1151259&r1=1151258&r2=1151259&view=diff
==============================================================================
--- incubator/deft/sandbox/src/test/java/org/deftserver/web/DeftSystemTest.java (original)
+++ incubator/deft/sandbox/src/test/java/org/deftserver/web/DeftSystemTest.java Tue Jul 26 21:45:16 2011
@@ -224,9 +224,9 @@ public class DeftSystemTest {
 		@Override
 		@Asynchronous
 		public void get(HttpRequest request, final org.deftserver.web.http.HttpResponse response) {
-			client.get("deft", new AsyncResult<String>() {
+			client.get("deft", new AsyncResult<byte[]>() {
 				@Override public void onFailure(Throwable caught) { /* ignore */}
-				@Override public void onSuccess(String result) { response.write(result).finish(); }
+				@Override public void onSuccess(byte[] result) { response.write(new String(result)).finish(); }
 			});
 		}
 
@@ -1109,7 +1109,7 @@ public class DeftSystemTest {
 		final AsyncCallback runByIOLoop = new AsyncCallback() {
 
 			public void onCallback() {
-				client.fetch(unresolvableAddress, new AsyncResult<org.deftserver.web.http.client.Response>() {
+				client.get(unresolvableAddress, new AsyncResult<org.deftserver.web.http.client.Response>() {
 
 					public void onSuccess(org.deftserver.web.http.client.Response result) { client.close(); }
 
@@ -1134,7 +1134,7 @@ public class DeftSystemTest {
 		final AsyncCallback runByIOLoop = new AsyncCallback() {
 
 			public void onCallback() {
-				client.fetch(unconnectableAddress, new AsyncResult<org.deftserver.web.http.client.Response>() {
+				client.get(unconnectableAddress, new AsyncResult<org.deftserver.web.http.client.Response>() {
 
 					public void onSuccess(org.deftserver.web.http.client.Response result) { client.close(); }
 
@@ -1170,7 +1170,7 @@ public class DeftSystemTest {
 				public void onFailure(Throwable ignore) { }
 			};
 			// make sure that the http.fetch(..) is invoked from the ioloop thread
-			IOLoop.INSTANCE.addCallback(new AsyncCallback() { public void onCallback() { http.fetch(url, cb); }});
+			IOLoop.INSTANCE.addCallback(new AsyncCallback() { public void onCallback() { http.get(url, cb); }});
 			latch.await(15, TimeUnit.SECONDS);
 			assertEquals(0, latch.getCount());
 			assertEquals("hello test", result[0]);
@@ -1191,7 +1191,7 @@ public class DeftSystemTest {
 			public void onFailure(Throwable e) { if (e instanceof ConnectException) latch.countDown(); }
 		};
 		// make sure that the http.fetch(..) is invoked from the ioloop thread
-		IOLoop.INSTANCE.addCallback(new AsyncCallback() { public void onCallback() { http.fetch(url, cb); }});
+		IOLoop.INSTANCE.addCallback(new AsyncCallback() { public void onCallback() { http.get(url, cb); }});
 		latch.await(5, TimeUnit.SECONDS);
 		assertEquals(0, latch.getCount());
 	}
@@ -1215,7 +1215,7 @@ public class DeftSystemTest {
 		
 		};
 		// make sure that the http.fetch(..) is invoked from the ioloop thread
-		IOLoop.INSTANCE.addCallback(new AsyncCallback() { public void onCallback() { http.fetch(url, cb); }});
+		IOLoop.INSTANCE.addCallback(new AsyncCallback() { public void onCallback() { http.get(url, cb); }});
 		latch.await(5, TimeUnit.SECONDS);
 		assertEquals(0, latch.getCount());
 	}
@@ -1240,7 +1240,7 @@ public class DeftSystemTest {
 		
 		};
 		// make sure that the http.fetch(..) is invoked from the ioloop thread
-		IOLoop.INSTANCE.addCallback(new AsyncCallback() { public void onCallback() { http.fetch(url, cb); }});
+		IOLoop.INSTANCE.addCallback(new AsyncCallback() { public void onCallback() { http.get(url, cb); }});
 		latch.await(5, TimeUnit.SECONDS);
 		assertEquals(0, latch.getCount());		
 	}

Added: incubator/deft/sandbox/src/test/java/org/deftserver/web/HttpVerbTest.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/test/java/org/deftserver/web/HttpVerbTest.java?rev=1151259&view=auto
==============================================================================
--- incubator/deft/sandbox/src/test/java/org/deftserver/web/HttpVerbTest.java (added)
+++ incubator/deft/sandbox/src/test/java/org/deftserver/web/HttpVerbTest.java Tue Jul 26 21:45:16 2011
@@ -0,0 +1,36 @@
+/*
+ *  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. 
+ *  
+ */
+package org.deftserver.web;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+/**
+ * Test cases for {@link HttpVerb}.
+ */
+public class HttpVerbTest {
+
+    @Test
+    public void testHttpVerb() {
+
+        assertEquals(8, HttpVerb.values().length);
+    }
+}

Added: incubator/deft/sandbox/src/test/java/org/deftserver/web/http/ContentTypeTest.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/test/java/org/deftserver/web/http/ContentTypeTest.java?rev=1151259&view=auto
==============================================================================
--- incubator/deft/sandbox/src/test/java/org/deftserver/web/http/ContentTypeTest.java (added)
+++ incubator/deft/sandbox/src/test/java/org/deftserver/web/http/ContentTypeTest.java Tue Jul 26 21:45:16 2011
@@ -0,0 +1,35 @@
+/*
+ *  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. 
+ *  
+ */
+package org.deftserver.web.http;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * Test cases for {@link ContentType}.
+ */
+public class ContentTypeTest {
+
+    @Test
+    public void testname() throws Exception {
+        assertTrue("No functionality in class to test.", true);
+    }
+}

Added: incubator/deft/sandbox/src/test/java/org/deftserver/web/http/client/AsynchronousHttpClientTest.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/test/java/org/deftserver/web/http/client/AsynchronousHttpClientTest.java?rev=1151259&view=auto
==============================================================================
--- incubator/deft/sandbox/src/test/java/org/deftserver/web/http/client/AsynchronousHttpClientTest.java (added)
+++ incubator/deft/sandbox/src/test/java/org/deftserver/web/http/client/AsynchronousHttpClientTest.java Tue Jul 26 21:45:16 2011
@@ -0,0 +1,105 @@
+/*
+ *  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. 
+ *  
+ */
+package org.deftserver.web.http.client;
+
+import static org.junit.Assert.*;
+import static org.deftserver.web.http.client.AsynchronousHttpClient.HTTP_VERSION;
+import static org.deftserver.web.http.client.AsynchronousHttpClient.USER_AGENT_HEADER;
+import static org.deftserver.web.http.client.AsynchronousHttpClient.NEWLINE;
+
+import org.deftserver.web.AsyncResult;
+import org.deftserver.web.HttpVerb;
+import org.junit.Test;
+
+/**
+ * Test cases for {@link AsynchronousHttpClient}.
+ */
+public class AsynchronousHttpClientTest {
+
+    @Test
+    public void testMakeRequestLineAndHeaders() {
+
+        AsynchronousHttpClient client = new AsynchronousHttpClient() {
+            @Override
+            protected void doFetch(AsyncResult<Response> callback, long requestStarted) {
+                // Do nothing.
+            }
+        };
+
+        client.get("http://testurl.com/path/", null);
+
+        String expected = HttpVerb.GET + " /path/ " + HTTP_VERSION;
+        expected += "Host: testurl.com" + NEWLINE + USER_AGENT_HEADER;
+        expected += NEWLINE;
+        
+        String actual = client.makeRequestLineAndHeaders();
+
+        assertEquals(expected, actual);
+    }
+
+    @Test
+    public void testMakeRequestLineAndHeadersWithBody() {
+
+        AsynchronousHttpClient client = new AsynchronousHttpClient() {
+            @Override
+            protected void doFetch(AsyncResult<Response> callback, long requestStarted) {
+                // Do nothing.
+            }
+        };
+
+        client.post("http://testurl.com/path/", "name=value", null);
+
+        String expected = HttpVerb.POST + " /path/ " + HTTP_VERSION;
+        expected += "Host: testurl.com" + NEWLINE + USER_AGENT_HEADER;
+        expected += "Content-Type: application/x-www-form-urlencoded" + NEWLINE;
+        expected += "Content-Length: 10";
+        expected += NEWLINE + NEWLINE;
+        expected += "name=value";
+        expected += NEWLINE;
+        
+        String actual = client.makeRequestLineAndHeaders();
+
+        assertEquals(expected, actual);
+    }
+    
+    @Test
+    public void testMakeRequestLineAndHeadersWithZeroLengthBody() {
+        
+        AsynchronousHttpClient client = new AsynchronousHttpClient() {
+            @Override
+            protected void doFetch(AsyncResult<Response> callback, long requestStarted) {
+                // Do nothing.
+            }
+        };
+        
+        client.post("http://testurl.com/path/", "", null);
+        
+        String expected = HttpVerb.POST + " /path/ " + HTTP_VERSION;
+        expected += "Host: testurl.com" + NEWLINE + USER_AGENT_HEADER;
+        expected += "Content-Type: application/x-www-form-urlencoded" + NEWLINE;
+        expected += "Content-Length: 0";
+        expected += NEWLINE + NEWLINE;
+        expected += NEWLINE;
+        
+        String actual = client.makeRequestLineAndHeaders();
+        
+        assertEquals(expected, actual);
+    }
+}

Added: incubator/deft/sandbox/src/test/java/org/deftserver/web/http/client/RequestTest.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/test/java/org/deftserver/web/http/client/RequestTest.java?rev=1151259&view=auto
==============================================================================
--- incubator/deft/sandbox/src/test/java/org/deftserver/web/http/client/RequestTest.java (added)
+++ incubator/deft/sandbox/src/test/java/org/deftserver/web/http/client/RequestTest.java Tue Jul 26 21:45:16 2011
@@ -0,0 +1,80 @@
+/*
+ *  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. 
+ *  
+ */
+package org.deftserver.web.http.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.deftserver.web.HttpVerb;
+import org.deftserver.web.http.ContentType;
+import org.junit.Test;
+
+import com.google.common.base.Charsets;
+
+/**
+ * Test cases for {@link Request}.
+ */
+public class RequestTest {
+
+    @Test
+    public void testRequest() {
+
+        final Request request = new Request("http://testurl.com:8080", HttpVerb.POST, false, 99);
+
+        assertEquals(request.getVerb(), HttpVerb.POST);
+
+        assertEquals("http", request.getURL().getProtocol());
+        assertEquals("testurl.com", request.getURL().getHost());
+        assertEquals(8080, request.getURL().getPort());
+
+        assertEquals(false, request.isFollowingRedirects());
+        assertEquals(99, request.getMaxRedirects());
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void testRequestForMalformedUrl() {
+
+        new Request("malformed", HttpVerb.POST);
+    }
+
+    @Test
+    public void testSetGetBody() {
+
+        final Request request = new Request("http://unimportant-value.com", HttpVerb.POST);
+
+        request.setBody("testContent1");
+        assertTrue(Arrays.equals("testContent1".getBytes(), request.getBody()));
+
+        request.setBody("testContent2".getBytes());
+        assertEquals("testContent2", new String(request.getBody(), Charsets.ISO_8859_1));
+    }
+
+    @Test
+    public void testSetGetContentType() {
+
+        final Request request = new Request("http://unimportant-value.com", HttpVerb.POST);
+        assertEquals(ContentType.APPLICATION_FORM_URLENCODED, request.getContentType());
+
+        request.setContentType(ContentType.MULTIPART_FORM_DATA);
+        assertEquals(ContentType.MULTIPART_FORM_DATA, request.getContentType());
+    }
+}