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 sl...@apache.org on 2011/09/19 23:38:17 UTC

svn commit: r1172899 - in /incubator/deft/sandbox/src: main/java/org/apache/deft/io/buffer/ main/java/org/apache/deft/web/http/ test/java/org/apache/deft/web/http/

Author: slemesle
Date: Mon Sep 19 23:38:16 2011
New Revision: 1172899

URL: http://svn.apache.org/viewvc?rev=1172899&view=rev
Log:
DEFT-186 - New buffer oriented parser

Added:
    incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpBufferedLexer.java
    incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpParsingContext.java
    incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpRequestParser.java
    incubator/deft/sandbox/src/test/java/org/apache/deft/web/http/HttpBufferedLexerTest.java
    incubator/deft/sandbox/src/test/java/org/apache/deft/web/http/HttpRequestParserTest.java
Modified:
    incubator/deft/sandbox/src/main/java/org/apache/deft/io/buffer/DynamicByteBuffer.java
    incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpProtocol.java
    incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpRequestImpl.java
    incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/MalFormedHttpRequest.java

Modified: incubator/deft/sandbox/src/main/java/org/apache/deft/io/buffer/DynamicByteBuffer.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/apache/deft/io/buffer/DynamicByteBuffer.java?rev=1172899&r1=1172898&r2=1172899&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/apache/deft/io/buffer/DynamicByteBuffer.java (original)
+++ incubator/deft/sandbox/src/main/java/org/apache/deft/io/buffer/DynamicByteBuffer.java Mon Sep 19 23:38:16 2011
@@ -60,7 +60,18 @@ public class DynamicByteBuffer {
 		backend.put(src);
 	}
 
-	
+
+    /**
+     * Append count bytes in the given byte array start at array position
+     * @param array     byte array to copy
+     * @param position  start position
+     * @param count     bytes to copy count
+     */
+    public void put(byte[] array, int position, int count) {
+        ensureCapacity(count);
+		backend.put(array, position, count);
+    }
+
 	/**
 	 * Prepend the data. Will reallocate if needed.
 	 */
@@ -73,9 +84,9 @@ public class DynamicByteBuffer {
 		backend = ByteBuffer.wrap(newBuffer);
 		backend.position(newSize);
 	}
-	
+
 	/**
-	 * Ensures that its safe to append size data to backend. 
+	 * Ensures that its safe to append size data to backend.
 	 * @param size The size of the data that is about to be appended.
 	 */
 	private void ensureCapacity(int size) {
@@ -87,7 +98,7 @@ public class DynamicByteBuffer {
 			reallocate(newSize);
 		}
 	}
-	
+
 	// Preserves position.
 	private void reallocate(int newCapacity) {
 		int oldPosition = backend.position();
@@ -97,7 +108,7 @@ public class DynamicByteBuffer {
 		backend.position(oldPosition);
 		logger.debug("allocated new DynamicByteBufer, new capacity: {}", backend.capacity());
 	}
-	
+
 	/**
 	 * Returns the {@code ByteBuffer} that is used internally by this {@DynamicByteBufer}.
 	 * Changes made to the returned {@code ByteBuffer} will be incur modifications in this {@DynamicByteBufer}.
@@ -105,14 +116,14 @@ 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);
 	}
@@ -140,7 +151,7 @@ public class DynamicByteBuffer {
 
 	/**
 	 * See {@link ByteBuffer#array}
-	 */	
+	 */
 	public byte[] array() {
 		return backend.array();
 	}

Added: incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpBufferedLexer.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpBufferedLexer.java?rev=1172899&view=auto
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpBufferedLexer.java (added)
+++ incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpBufferedLexer.java Mon Sep 19 23:38:16 2011
@@ -0,0 +1,209 @@
+/*
+ *  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.apache.deft.web.http;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Lexer class responsible for lexing an HTTP stream into tokens.
+ * The tokens are composed of Method, URI, Protocol version, Header name and header value.
+ *
+ */
+public class HttpBufferedLexer {
+
+
+    static final int LF     = (int)'\n';
+    static final int CR     = (int)'\r';
+    static final int SP     = (int)' ';
+    static final int TAB    = (int)'\t';
+    static final int COLON  = (int)':';
+
+    static final int LINE_MAX_SIZE = 500;
+
+
+    static final StopChars SP_SEPARATOR = new StopChars() {
+
+        public boolean isSeparator(int ptr) {
+            return ptr == SP;
+        }
+
+
+        public boolean isForbidden(int ptr) {
+            return ptr == CR || ptr == LF;
+        }
+    };
+
+    static final StopChars CRLF_SEPARATOR = new StopChars() {
+
+        public boolean isSeparator(int ptr) {
+            return ptr == CR || ptr == LF;
+        }
+
+        public boolean isForbidden(int ptr) {
+            return false;
+        }
+    };
+
+    static final StopChars HEADER_NAME_SEPARATOR = new StopChars() {
+
+        public boolean isSeparator(int ptr) {
+            return ptr == COLON;
+        }
+
+        public boolean isForbidden(int ptr) {
+            return ptr == CR || ptr == LF;
+        }
+    };
+	
+	private ErrorStatus status = ErrorStatus.OK;
+	
+	enum ErrorStatus {
+		OK,
+		TOO_LONG_REQUEST_LINE, 
+		BAD_HEADER_NAME_FORMAT,
+		BAD_REQUEST,
+		
+	}
+
+    /**
+     * Reads the next HTTP token from context buffer
+     * @param context Context object holding parsing data
+     * @return -1 on errors, 0 if not complete and 1 on success
+     */
+	public int nextToken(HttpParsingContext context){
+        int res = -1;
+
+        switch (context.currentType){
+            case REQUEST_LINE: { // read the first token of the request line METHOD
+                if (skipWhiteSpaceAndLine(context)){
+                    // Get method token
+                    res = nextWord(context, HttpParsingContext.TokenType.REQUEST_METHOD, SP_SEPARATOR);
+                    
+                }else{ // EOS reached with no data 
+                    return 0;
+                }
+                break;
+            }
+            case REQUEST_METHOD:{
+                // Get URI token
+                res =  nextWord(context, HttpParsingContext.TokenType.REQUEST_URI, SP_SEPARATOR);
+                break;
+            }
+            case REQUEST_URI:{ // request version
+                res = nextWord(context, HttpParsingContext.TokenType.HTTP_VERSION, CRLF_SEPARATOR);
+                break;
+            }
+            case HTTP_VERSION:{ // First header line
+                context.skips = 0;
+                if (!skipEndOfLine(context)){
+                    res = nextWord(context, HttpParsingContext.TokenType.HEADER_NAME, HEADER_NAME_SEPARATOR);
+                }else {
+                    context.setBodyFound();
+                    res = 1;
+                }
+                break;
+            }
+            case HEADER_NAME:{ // header value
+               res = nextWord(context, HttpParsingContext.TokenType.HEADER_VALUE, CRLF_SEPARATOR);
+               break;
+            }case HEADER_VALUE:{ // Might be a header value for multiline headers, a header name, or Body
+                context.skips = 0;
+                if (!skipEndOfLine(context)){
+                    if (context.currentPointer == SP || context.currentPointer == TAB){
+                        res = nextWord(context, HttpParsingContext.TokenType.HEADER_VALUE,CRLF_SEPARATOR );
+                    }else {
+                        res = nextWord(context, HttpParsingContext.TokenType.HEADER_NAME, HEADER_NAME_SEPARATOR);
+                    }
+                }else {
+                    context.setBodyFound();
+                    res = 1;
+                }
+                break;
+            }
+            default:{ // If BODY or other nothing todo
+                res = 0;
+            }
+        }
+        return res;
+
+    }
+
+	
+	public boolean skipWhiteSpaceAndLine(HttpParsingContext context){
+
+		while(context.hasRemaining()){
+			if (context.incrementAndGetPointer() != CR && context.currentPointer != LF && context.currentPointer != SP){
+                context.startPreviousPosition();
+				return true;
+			}
+		}
+		return false;
+	}
+
+    public int nextWord(HttpParsingContext context, HttpParsingContext.TokenType type, StopChars stopChars){
+        int currentChar = 0;
+   
+		while(context.buffer.hasRemaining()){
+			currentChar = context.buffer.get();
+			if (stopChars.isForbidden(currentChar)){
+				return -1; // Bad format Request should not contain this char at this point
+			} else if (stopChars.isSeparator(currentChar)){
+                context.storeCompleteToken(type);
+                return 1;
+            }
+		}
+        // No errors but the token is not complete
+        context.storeIncompleteToken();
+		return 0;
+    }
+
+
+    /**
+     * Skips all end of line characters.
+     * @return true if body was found starting
+     */
+    public boolean skipEndOfLine(HttpParsingContext context){
+
+        while(context.hasRemaining()){
+
+            if (context.incrementAndGetPointer() != CR && context.currentPointer != LF){
+                context.startPreviousPosition();
+                return false;
+            }else if (context.skips >= 2){ // Here we got CRLFCRLF combination so rest is the body
+                return true;
+            }
+
+            context.skips++;
+        }
+
+        return false;
+    }
+
+
+
+    private interface StopChars {
+
+        boolean isSeparator(int ptr);
+
+        boolean isForbidden(int ptr);
+
+    }
+	
+}
\ No newline at end of file

Added: incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpParsingContext.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpParsingContext.java?rev=1172899&view=auto
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpParsingContext.java (added)
+++ incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpParsingContext.java Mon Sep 19 23:38:16 2011
@@ -0,0 +1,123 @@
+/*
+ *  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.apache.deft.web.http;
+
+import com.google.common.base.Charsets;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Context object holding data of the currently or last parser execution.
+ * Used to maintain buffer position, last Token,
+ */
+public class HttpParsingContext {
+
+
+
+    enum TokenType{
+        REQUEST_LINE,
+        REQUEST_METHOD,
+        REQUEST_URI,
+        HTTP_VERSION,
+        HEADER_NAME,
+        HEADER_VALUE,
+        BODY;
+    }
+    
+    ByteBuffer buffer;
+
+    int startPosition = 0;
+
+    TokenType currentType = TokenType.REQUEST_LINE;
+
+    int skips = 0;
+
+    StringBuilder tokenValue = new StringBuilder(255);
+
+    boolean complete = false;
+
+    int currentPointer = 0;
+
+    String lastHeaderName = null;
+
+    int incrementAndGetPointer(){
+        currentPointer = buffer.get();
+        return currentPointer;
+    }
+
+    void setBuffer(ByteBuffer buffer){
+        this.buffer = buffer;
+        if (buffer != null){
+            startPosition = buffer.position();
+        } else {
+            startPosition = 0;
+        }
+    }
+
+    boolean hasRemaining(){
+        return buffer.hasRemaining();
+    }
+
+    void setBodyFound(){
+        currentType = TokenType.BODY;
+        tokenValue.delete(0, Integer.MAX_VALUE);
+    }
+
+    public boolean isbodyFound() {
+        return TokenType.BODY.equals(currentType);
+    }
+
+    void startPreviousPosition(){
+        startPosition = buffer.position()-1;
+    }
+
+    /**
+     * Stores the token value and define the completeness
+     */
+    void storeIncompleteToken(){
+        storeTokenValue(currentType, false, buffer.position() - startPosition);
+    }
+
+    void storeCompleteToken(TokenType type){
+        storeTokenValue(type, true, buffer.position() - startPosition -1);
+    }
+
+    private void storeTokenValue(TokenType type, boolean _complete, int endPosition){
+        if (complete){ // Free buffer when last was complete
+            tokenValue.delete(0, Integer.MAX_VALUE);
+        }
+        tokenValue.append(new String (buffer.array(), startPosition, endPosition, Charsets.ISO_8859_1));
+        startPosition = buffer.position();
+        currentType = type;
+        complete = _complete;
+    }
+
+    String getTokenValue(){
+        return tokenValue.toString();
+    }
+
+    public void persistHeaderName() {
+        lastHeaderName = tokenValue.toString();
+    }
+
+    public String getLastHeaderName() {
+        return lastHeaderName;
+    }
+}

Modified: incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpProtocol.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpProtocol.java?rev=1172899&r1=1172898&r2=1172899&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpProtocol.java (original)
+++ incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpProtocol.java Mon Sep 19 23:38:16 2011
@@ -48,8 +48,10 @@ public class HttpProtocol implements IOH
     private final IOLoop ioLoop;
     private final Application application;
 
+    private final HttpRequestParser parser;
+
     // a queue of half-baked (pending/unfinished) HTTP post request
-    private final Map<SelectableChannel, PartialHttpRequest> partials = Maps.newHashMap();
+    private final Map<SelectableChannel, HttpRequestImpl> partials = Maps.newHashMap();
 
     public HttpProtocol(Application app) {
         this(IOLoop.INSTANCE, app);
@@ -58,6 +60,7 @@ public class HttpProtocol implements IOH
     public HttpProtocol(IOLoop ioLoop, Application app) {
         this.ioLoop = ioLoop;
         application = app;
+        parser = new HttpRequestParser();
     }
 
     @Override
@@ -244,24 +247,31 @@ public class HttpProtocol implements IOH
     private HttpRequest doGetHttpRequest(SelectionKey key, SocketChannel clientChannel, ByteBuffer buffer) {
         // do we have any unfinished http post requests for this channel?
         HttpRequestImpl request = null;
-        if (partials.containsKey(clientChannel)) {
-            request = HttpRequestImpl.continueParsing(buffer, partials.get(clientChannel));
-            if (!(request instanceof PartialHttpRequest)) {
-                // received the entire payload/body
-                partials.remove(clientChannel);
-            }
-        } else {
-            request = HttpRequestImpl.of(buffer);
-            if (request instanceof PartialHttpRequest) {
-                partials.put(key.channel(), (PartialHttpRequest) request);
+        try {
+            if (partials.containsKey(clientChannel)) {
+                request = parser.parseRequestBuffer(buffer, partials.get(clientChannel));
+                if (request.isFinished()) {
+                    // received the entire payload/body
+                    partials.remove(clientChannel);
+                }
+            } else {
+                request = parser.parseRequestBuffer(buffer);
+                if (!request.isFinished()) {
+                    partials.put(key.channel(), request);
+                }
             }
+
+
+            // set extra request info
+            request.setRemoteHost(clientChannel.socket().getInetAddress());
+            request.setRemotePort(clientChannel.socket().getPort());
+            request.setServerHost(clientChannel.socket().getLocalAddress());
+            request.setServerPort(clientChannel.socket().getLocalPort());
+            return (request.isFinished() ? request : null);
+        } catch (Exception e) {
+            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
         }
-        // set extra request info
-        request.setRemoteHost(clientChannel.socket().getInetAddress());
-        request.setRemotePort(clientChannel.socket().getPort());
-        request.setServerHost(clientChannel.socket().getLocalAddress());
-        request.setServerPort(clientChannel.socket().getLocalPort());
-        return request;
+        return null;
     }
 
     @Override

Modified: incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpRequestImpl.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpRequestImpl.java?rev=1172899&r1=1172898&r2=1172899&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpRequestImpl.java (original)
+++ incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpRequestImpl.java Mon Sep 19 23:38:16 2011
@@ -28,6 +28,7 @@ import java.util.Map;
 import java.util.regex.Pattern;
 
 import org.apache.deft.io.IOLoop;
+import org.apache.deft.io.buffer.DynamicByteBuffer;
 import org.apache.deft.util.ArrayUtil;
 import org.apache.deft.web.http.protocol.HttpVerb;
 
@@ -40,10 +41,10 @@ public class HttpRequestImpl implements 
 
     private IOLoop ioLoop;
 
-    private final String requestLine;
-    private final HttpVerb method;
-    private final String requestedPath; // correct name?
-    private final String version;
+    private  String requestLine;
+    private  HttpVerb method;
+    private  String requestedPath; // correct name?
+    private  String version;
     private Map<String, String> headers;
     private ImmutableMultimap<String, String> parameters;
     private String body;
@@ -53,6 +54,10 @@ public class HttpRequestImpl implements 
     private int remotePort;
     private int serverPort;
     private Map<String, String> cookies = null;
+    private final HttpParsingContext context = new HttpParsingContext();
+    private int contentLength = -1;
+    private DynamicByteBuffer bodyBuffer;
+
 
     /** Regex to parse HttpRequest Request Line */
     public static final Pattern REQUEST_LINE_PATTERN = Pattern.compile(" ");
@@ -72,6 +77,11 @@ public class HttpRequestImpl implements 
     /** Regex to split cookie header following RFC6265 Section 5.4 */
     public static final Pattern COOKIE_SEPARATOR_PATTERN = Pattern.compile(";");
 
+
+    public HttpRequestImpl(){
+        headers = Maps.newHashMap();
+    }
+
     /**
      * Creates a new HttpRequest
      * 
@@ -88,7 +98,8 @@ public class HttpRequestImpl implements 
         this.headers = headers;
         body = null;
         initKeepAlive();
-        parameters = parseParameters(elements[1]);
+        parameters = parseParameters((pathFrags.length>1 ? pathFrags[1]: ""));
+
     }
 
     public HttpRequestImpl(String method, String fullUrl, String version, Map<String, String> headers,
@@ -223,9 +234,16 @@ public class HttpRequestImpl implements 
 
     @Override
     public String getBody() {
-        return body;
+
+        if(bodyBuffer != null){
+            return new String (bodyBuffer.array(), Charsets.ISO_8859_1);
+        }else {
+            return body;
+        }
     }
 
+
+
     @Override
     public InetAddress getRemoteHost() {
         return remoteHost;
@@ -328,13 +346,10 @@ public class HttpRequestImpl implements 
         return result;
     }
 
-    private ImmutableMultimap<String, String> parseParameters(String requestLine) {
+    private ImmutableMultimap<String, String> parseParameters(String params) {
         ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder();
-        String[] str = QUERY_STRING_PATTERN.split(requestLine);
 
-        // Parameters exist
-        if (str.length > 1) {
-            String[] paramArray = PARAM_STRING_PATTERN.split(str[1]);
+            String[] paramArray = PARAM_STRING_PATTERN.split(params);
             for (String keyValue : paramArray) {
                 String[] keyValueArray = KEY_VALUE_PATTERN.split(keyValue);
                 // We need to check if the parameter has a value associated with
@@ -344,7 +359,7 @@ public class HttpRequestImpl implements 
                     // value
                 }
             }
-        }
+
         return builder.build();
     }
 
@@ -363,8 +378,9 @@ public class HttpRequestImpl implements 
         }
     }
 
-    private void initKeepAlive() {
+    protected void initKeepAlive() {
         String connection = getHeader("Connection");
+
         if ("keep-alive".equalsIgnoreCase(connection)) {
             keepAlive = true;
         } else if ("close".equalsIgnoreCase(connection) || requestLine.contains("1.0")) {
@@ -373,4 +389,76 @@ public class HttpRequestImpl implements 
             keepAlive = true;
         }
     }
+
+
+    protected HttpParsingContext getContext(){
+        return this.context;
+    }
+
+    protected void setMethod(HttpVerb method) {
+        this.method = method;
+    }
+
+    /**
+     * Sets the requestedPath and parse parameters using the received complete URI
+     * @param uri
+     */
+    protected void setURI(String uri) {
+        String[] pathFrags = QUERY_STRING_PATTERN.split(uri);
+        requestedPath = pathFrags[0];
+        parameters = parseParameters((pathFrags.length > 1 ? pathFrags[1] : ""));
+
+        requestLine = method.toString() + " "+ uri;
+
+    }
+
+    protected void setVersion(String version) {
+        this.version = version;
+        requestLine += " " + version;
+    }
+
+    /**
+     * Append the given value to the specified header.
+     * If the header does not exist it will be added to the header map.
+     */
+    protected void pushToHeaders(String name, String value) {
+        if (name != null){
+			name = name.toLowerCase();
+			// Handle repeated header-name like Cookies
+			if (headers.containsKey(name)){
+				value = new StringBuilder(headers.get(name)).append(';').append(value.trim()).toString();
+			}
+			headers.put(name, value.trim());
+		}
+    }
+
+    /**
+     * compute contentLength with header content-length when needed.
+     * Please notice that it will also allocate the body buffer to the appropriate size.
+     * @return actual content length or 0 if not specified
+     */
+    public int getContentLength(){
+        if (contentLength < 0 ){
+            if (headers.containsKey("content-length")){
+                contentLength = Integer.parseInt(headers.get("content-length"));
+                bodyBuffer = DynamicByteBuffer.allocate(contentLength);
+            }else {
+                contentLength = 0;
+            }
+        }
+        return contentLength;
+    }
+
+    protected DynamicByteBuffer getBodyBuffer(){
+        return bodyBuffer;
+    }
+
+    protected boolean isFinished(){
+        boolean res = context.isbodyFound();
+        if (res && contentLength > 0){
+            res = contentLength <= bodyBuffer.position();
+        }
+        return res;
+    }
+
 }

Added: incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpRequestParser.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpRequestParser.java?rev=1172899&view=auto
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpRequestParser.java (added)
+++ incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/HttpRequestParser.java Mon Sep 19 23:38:16 2011
@@ -0,0 +1,124 @@
+/*
+ *  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.apache.deft.web.http;
+
+import java.nio.ByteBuffer;
+
+import com.google.common.base.Charsets;
+import org.apache.deft.io.buffer.DynamicByteBuffer;
+import org.apache.deft.web.http.protocol.HttpVerb;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+
+/**
+ * Builds HttpRequest using a given ByteBuffer and already existing request object (unfinished).
+ */
+public class HttpRequestParser {
+	
+	private static final Logger LOG = LoggerFactory.getLogger(HttpRequestParser.class);
+
+    private final HttpBufferedLexer lexer;
+
+    public HttpRequestParser(){
+        lexer = new HttpBufferedLexer();
+    }
+
+
+    public HttpRequestImpl parseRequestBuffer(ByteBuffer buffer){
+
+        return parseRequestBuffer(buffer, null);
+    }
+
+    /**
+     *
+     * @param buffer
+     * @param result
+     * @return
+     */
+	public HttpRequestImpl parseRequestBuffer(ByteBuffer buffer,HttpRequestImpl result){
+
+
+		if (result == null){
+            result = new HttpRequestImpl();
+        }
+        int status = 1;
+        HttpParsingContext context = result.getContext();
+        context.setBuffer(buffer);
+
+//        LOG.warn("Request buffer is {}", new String (buffer.array(), Charsets.ISO_8859_1));
+        // while no errors and buffer not finished
+        while ((status = lexer.nextToken(context)) > 0){
+           switch (context.currentType){
+               case REQUEST_METHOD: {
+                   result.setMethod(HttpVerb.valueOf(context.getTokenValue()));break;
+               }
+               case REQUEST_URI:{
+                   result.setURI(context.getTokenValue());
+                   break;
+               }
+               case HTTP_VERSION:{
+                   result.setVersion(context.getTokenValue());break;
+               }
+               case HEADER_NAME:{
+                   context.persistHeaderName();break;
+               }
+               case HEADER_VALUE:{
+                   result.pushToHeaders(context.getLastHeaderName(), context.getTokenValue());break;
+               }
+           }
+        }
+
+        // There was an error while parsing request
+        if (status < 0){
+            result = MalFormedHttpRequest.instance;
+        } else if (context.currentType == HttpParsingContext.TokenType.BODY){
+            result.initKeepAlive();
+            // Copy body data to the request bodyBuffer
+            if (result.getContentLength() > 0){
+                pushRemainingToBody(context.buffer, result.getBodyBuffer(), result.getContentLength());
+        //        LOG.warn("Body size is {} and contentLength is {}", result.getBodyBuffer().position(), result.getContentLength());
+            }
+            // TODO: Implement chunked encoding here
+        }
+
+
+        // release the context buffer
+        context.setBuffer(null);
+        buffer.clear();
+        return result;
+	}
+
+	private void pushRemainingToBody(ByteBuffer buffer, DynamicByteBuffer body, int size){
+		// If buffer is empty or there is no clength then skip this
+		if (size == 0 || !buffer.hasRemaining()){
+			return;
+		}
+
+		if (body.position() + buffer.remaining() > size){
+			body.put(buffer.array(),  buffer.position(), size - body.position());
+		}
+		else {
+			body.put(buffer.array(),  buffer.position(), buffer.remaining());
+		}
+	}
+
+}
\ No newline at end of file

Modified: incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/MalFormedHttpRequest.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/MalFormedHttpRequest.java?rev=1172899&r1=1172898&r2=1172899&view=diff
==============================================================================
--- incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/MalFormedHttpRequest.java (original)
+++ incubator/deft/sandbox/src/main/java/org/apache/deft/web/http/MalFormedHttpRequest.java Mon Sep 19 23:38:16 2011
@@ -29,4 +29,10 @@ public class MalFormedHttpRequest extend
     private MalFormedHttpRequest() {
         super("GET / Mal formed request\r\n", Maps.<String, String> newHashMap());
     }
+
+    protected boolean isFinished(){
+        return true;
+    }
+
+
 }

Added: incubator/deft/sandbox/src/test/java/org/apache/deft/web/http/HttpBufferedLexerTest.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/test/java/org/apache/deft/web/http/HttpBufferedLexerTest.java?rev=1172899&view=auto
==============================================================================
--- incubator/deft/sandbox/src/test/java/org/apache/deft/web/http/HttpBufferedLexerTest.java (added)
+++ incubator/deft/sandbox/src/test/java/org/apache/deft/web/http/HttpBufferedLexerTest.java Mon Sep 19 23:38:16 2011
@@ -0,0 +1,240 @@
+/*
+ *  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.apache.deft.web.http;
+
+
+import junit.framework.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+
+import java.nio.ByteBuffer;
+
+/**
+ *
+ * User: slm
+ * Date: 13/09/11
+ * Time: 00:11
+ */
+public class HttpBufferedLexerTest {
+
+    HttpBufferedLexer   lexer;
+    HttpParsingContext  context;
+
+    @Before
+    public  void init(){
+        lexer   = new HttpBufferedLexer();
+        context = new HttpParsingContext();
+    }
+
+    @Test
+    public void testNextTokenSimpleGet() throws Exception {
+        String request = "GET /path/script.cgi HTTP/1.1\r\n\r\n";
+
+        context.setBuffer(ByteBuffer.wrap(request.getBytes()));
+
+        int res = lexer.nextToken(context);
+        Assert.assertEquals("Token GET should be found with no errors", 1,res);
+        Assert.assertEquals("GET", context.getTokenValue());
+        Assert.assertEquals(HttpParsingContext.TokenType.REQUEST_METHOD, context.currentType);
+
+        res = lexer.nextToken(context);
+        Assert.assertEquals("Token uri should be found with no errors", 1,res);
+        Assert.assertEquals("/path/script.cgi", context.getTokenValue());
+        Assert.assertEquals(HttpParsingContext.TokenType.REQUEST_URI, context.currentType);
+
+        res = lexer.nextToken(context);
+        Assert.assertEquals("Token protocol version should be found with no errors", 1,res);
+        Assert.assertEquals("HTTP/1.1", context.getTokenValue());
+        Assert.assertEquals(HttpParsingContext.TokenType.HTTP_VERSION, context.currentType);
+
+        res = lexer.nextToken(context);
+        Assert.assertEquals("Token body should be found with no errors", 1,res);
+        Assert.assertEquals(HttpParsingContext.TokenType.BODY, context.currentType);
+        Assert.assertEquals("TokenValue should be null for body","", context.getTokenValue());
+
+    }
+
+    @Test
+    public void requestLineContainingCRLF(){
+        String request = "GET\r\n /path/script.cgi HTTP/1.1\r\n\r\n";
+
+        context.setBuffer(ByteBuffer.wrap(request.getBytes()));
+
+        int res = lexer.nextToken(context);
+        Assert.assertEquals("Token GET should be found with error", -1,res);
+        Assert.assertEquals("", context.getTokenValue());
+    }
+
+    @Test
+    public void requestWithHeadersParsing(){
+
+        String request = "POST /path/script.cgi HTTP/1.0\r\n"
+                + "Host: localhost\r\n"
+                + "From: frog@jmarshall.com\r\n"
+                + "User-Agent: HTTPTool/1.0\r\n"
+                + "Content-Type: application/x-www-form-urlencoded\r\n"
+                + "Content-Length: 32\r\n\r\n";
+
+        context.setBuffer( ByteBuffer.wrap(request.getBytes()));
+        String [][] headers = new String[][]{{"Host", " localhost"},
+                {"From", " frog@jmarshall.com"},
+                {"User-Agent", " HTTPTool/1.0"},
+                {"Content-Type", " application/x-www-form-urlencoded"},
+                {"Content-Length", " 32"}};
+
+        int res = lexer.nextToken(context);
+        String method = context.getTokenValue();
+        res = lexer.nextToken(context);
+        String path = context.getTokenValue();
+        res = lexer.nextToken(context);
+        String protocol = context.getTokenValue();
+
+        Assert.assertEquals("POST", method);
+        Assert.assertEquals("/path/script.cgi", path);
+        Assert.assertEquals("HTTP/1.0", protocol);
+
+        for (String [] header : headers){
+            res = lexer.nextToken(context);
+            Assert.assertEquals(1, res);
+            Assert.assertEquals(HttpParsingContext.TokenType.HEADER_NAME, context.currentType);
+            Assert.assertEquals(header[0], context.getTokenValue());
+            res = lexer.nextToken(context);
+            Assert.assertEquals(1, res);
+            Assert.assertEquals(HttpParsingContext.TokenType.HEADER_VALUE, context.currentType);
+            Assert.assertEquals(header[1], context.getTokenValue());
+        }
+
+        res = lexer.nextToken(context);
+        Assert.assertEquals("Token body should be found with no errors", 1,res);
+        Assert.assertEquals(HttpParsingContext.TokenType.BODY, context.currentType);
+        Assert.assertEquals("TokenValue should be null for body","", context.getTokenValue());
+
+    }
+
+    @Test
+    public void requestLineWithTrailingHeaders(){
+        String request = " \r\n \r\nPOST ";
+        context.setBuffer(ByteBuffer.wrap(request.getBytes()));
+
+        int res = lexer.nextToken(context);
+        Assert.assertEquals(1, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.REQUEST_METHOD,context.currentType);
+        Assert.assertEquals("POST", context.getTokenValue());
+    }
+
+    @Test
+    public void incompleteHeaderAfterRequestLine(){
+        String requestPart1 = "Content-";
+        String requestPart2 = "Type: application";
+        String requestPart3 = "/x-www-form-";
+        String requestPart4 = "urlencoded\r\n";
+        context.currentType = HttpParsingContext.TokenType.HTTP_VERSION;
+        context.setBuffer(ByteBuffer.wrap(requestPart1.getBytes()));
+
+        int res = lexer.nextToken(context);
+        Assert.assertEquals(0, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HTTP_VERSION,context.currentType);
+        Assert.assertEquals("Content-", context.getTokenValue());
+
+        context.setBuffer(ByteBuffer.wrap(requestPart2.getBytes()));
+        res = lexer.nextToken(context);
+        Assert.assertEquals(1, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_NAME,context.currentType);
+        Assert.assertEquals("Content-Type", context.getTokenValue());
+
+        res = lexer.nextToken(context);
+        Assert.assertEquals(0, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_NAME,context.currentType);
+        Assert.assertEquals(" application", context.getTokenValue());
+
+        context.setBuffer(ByteBuffer.wrap(requestPart3.getBytes()));
+        res = lexer.nextToken(context);
+        Assert.assertEquals(0, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_NAME,context.currentType);
+        Assert.assertEquals(" application/x-www-form-", context.getTokenValue());
+
+        context.setBuffer(ByteBuffer.wrap(requestPart4.getBytes()));
+        res = lexer.nextToken(context);
+        Assert.assertEquals(1, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_VALUE,context.currentType);
+        Assert.assertEquals(" application/x-www-form-urlencoded", context.getTokenValue());
+    }
+
+    @Test
+    public void incompleteHeaderAfterHeader(){
+        String requestPart1 = "Content-";
+        String requestPart2 = "Type: application";
+        String requestPart3 = "/x-www-form-";
+        String requestPart4 = "urlencoded\r\n";
+        context.currentType = HttpParsingContext.TokenType.HEADER_VALUE;
+        context.setBuffer(ByteBuffer.wrap(requestPart1.getBytes()));
+
+        int res = lexer.nextToken(context);
+        Assert.assertEquals(0, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_VALUE,context.currentType);
+        Assert.assertEquals("Content-", context.getTokenValue());
+
+        context.setBuffer(ByteBuffer.wrap(requestPart2.getBytes()));
+        res = lexer.nextToken(context);
+        Assert.assertEquals(1, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_NAME,context.currentType);
+        Assert.assertEquals("Content-Type", context.getTokenValue());
+
+        res = lexer.nextToken(context);
+        Assert.assertEquals(0, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_NAME,context.currentType);
+        Assert.assertEquals(" application", context.getTokenValue());
+
+        context.setBuffer(ByteBuffer.wrap(requestPart3.getBytes()));
+        res = lexer.nextToken(context);
+        Assert.assertEquals(0, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_NAME,context.currentType);
+        Assert.assertEquals(" application/x-www-form-", context.getTokenValue());
+
+        context.setBuffer(ByteBuffer.wrap(requestPart4.getBytes()));
+        res = lexer.nextToken(context);
+        Assert.assertEquals(1, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_VALUE,context.currentType);
+        Assert.assertEquals(" application/x-www-form-urlencoded", context.getTokenValue());
+    }
+
+    @Test
+    public void parseMultiLineHeaders(){
+        String request = "my headervalue\r\n and so on\r\n\tand so on\r\n";
+        context.currentType = HttpParsingContext.TokenType.HEADER_NAME;
+        context.setBuffer(ByteBuffer.wrap(request.getBytes()));
+
+        int res = lexer.nextToken(context);
+        Assert.assertEquals(1, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_VALUE,context.currentType);
+        Assert.assertEquals("my headervalue", context.getTokenValue());
+
+        res = lexer.nextToken(context);
+        Assert.assertEquals(1, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_VALUE,context.currentType);
+        Assert.assertEquals(" and so on", context.getTokenValue());
+
+        res = lexer.nextToken(context);
+        Assert.assertEquals(1, res);
+        Assert.assertEquals(HttpParsingContext.TokenType.HEADER_VALUE,context.currentType);
+        Assert.assertEquals("\tand so on", context.getTokenValue());
+    }
+}

Added: incubator/deft/sandbox/src/test/java/org/apache/deft/web/http/HttpRequestParserTest.java
URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/src/test/java/org/apache/deft/web/http/HttpRequestParserTest.java?rev=1172899&view=auto
==============================================================================
--- incubator/deft/sandbox/src/test/java/org/apache/deft/web/http/HttpRequestParserTest.java (added)
+++ incubator/deft/sandbox/src/test/java/org/apache/deft/web/http/HttpRequestParserTest.java Mon Sep 19 23:38:16 2011
@@ -0,0 +1,401 @@
+/*
+ *  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.apache.deft.web.http;
+
+import org.apache.deft.util.ArrayUtil;
+import org.apache.deft.util.HttpRequestHelper;
+import org.apache.deft.util.HttpUtil;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.*;
+
+import static org.junit.Assert.*;
+
+public class HttpRequestParserTest {
+
+    
+    private HttpRequestParser parser;
+
+    @Before
+    public void init(){
+        parser = new HttpRequestParser();
+    }
+
+    @Test
+    public void testDeserializeHttpGetRequest() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addHeader("Host", "127.0.0.1:8080");
+        helper.addHeader("User-Agent", "curl/7.19.5 (i386-apple-darwin10.0.0) libcurl/7.19.5 zlib/1.2.3");
+        helper.addHeader("Accept", "*/*");
+        ByteBuffer bb1 = helper.getRequestAsByteBuffer();
+
+        helper = new HttpRequestHelper();
+        helper.addHeader("Host", "127.0.0.1:8080");
+        helper.addHeader("User-Agent",
+                "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; sv-SE; rv:1.9.2.2) Gecko/20100316 Firefox/3.6.2");
+        helper.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
+        helper.addHeader("Accept-Language", "sv-se,sv;q=0.8,en-us;q=0.5,en;q=0.3");
+        helper.addHeader("Accept-Encoding", "gzip,deflate");
+        helper.addHeader("Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
+        helper.addHeader("Keep-Alive", "115");
+        helper.addHeader("Connection", "keep-alve");
+        ByteBuffer bb2 = helper.getRequestAsByteBuffer();
+
+        HttpRequest request1 = parser.parseRequestBuffer(bb1);
+        HttpRequest request2 = parser.parseRequestBuffer(bb2);
+
+        assertEquals("GET / HTTP/1.1", request1.getRequestLine());
+        assertEquals("GET / HTTP/1.1", request2.getRequestLine());
+
+        assertEquals(4, request1.getHeaders().size());
+        assertEquals(9, request2.getHeaders().size());
+
+        List<String> expectedHeaderNamesInRequest1 = Arrays.asList(new String[] { "User-Agent", "Host", "Accept",
+                "From" });
+        for (String expectedHeaderName : expectedHeaderNamesInRequest1) {
+            assertTrue(request1.getHeaders().containsKey(expectedHeaderName.toLowerCase()));
+        }
+
+        List<String> expectedHeaderNamesInRequest2 = Arrays.asList(new String[] { "Host", "User-Agent", "Accept",
+                "From", "Accept-Language", "Accept-Encoding", "Accept-Charset", "Keep-Alive", "Connection" });
+        for (String expectedHeaderName : expectedHeaderNamesInRequest2) {
+            assertTrue(request2.getHeaders().containsKey(expectedHeaderName.toLowerCase()));
+        }
+
+        // TODO RS 100920 verify that the headers exist
+    }
+
+
+    @Test
+    public void testSingleGetParameter() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addGetParameter("firstname", "jim");
+
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+
+        assertEquals(1, request.getParameters().size());
+        assertEquals("jim", request.getParameter("firstname"));
+    }
+
+    @Test
+    public void testMultipleGetParameter() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addGetParameter("firstname", "jim");
+        helper.addGetParameter("lastname", "petersson");
+        helper.addGetParameter("city", "stockholm");
+
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        Map<String, Collection<String>> params = request.getParameters();
+
+        assertEquals(3, getSize(params));
+        assertEquals("jim", request.getParameter("firstname"));
+        assertEquals("petersson", request.getParameter("lastname"));
+        assertEquals("stockholm", request.getParameter("city"));
+    }
+
+    private int getSize(Map<String, Collection<String>> mmap) {
+        int size = 0;
+        for (Collection<String> values : mmap.values()) {
+            size += values.size();
+        }
+        return size;
+    }
+
+    @Test
+    public void testSingleParameterWithoutValue() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addGetParameter("firstname", null);
+
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        Map<String, Collection<String>> params = request.getParameters();
+        assertEquals(0, getSize(params));
+        assertEquals(null, request.getParameter("firstname"));
+    }
+
+    @Test
+    public void testMultipleParametersWithoutValue() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addGetParameter("firstname", null);
+        helper.addGetParameter("lastName", "");
+
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        Map<String, Collection<String>> params = request.getParameters();
+
+        assertEquals(0, getSize(params));
+        assertEquals(null, request.getParameter("firstname"));
+        assertEquals(null, request.getParameter("lastName"));
+    }
+
+    @Test
+    public void testMultipleParametersWithAndWithoutValue() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addGetParameter("firstname", null);
+        helper.addGetParameter("lastName", "petersson");
+        helper.addGetParameter("city", "");
+        helper.addGetParameter("phoneno", "12345");
+        helper.addGetParameter("age", "30");
+
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        Map<String, Collection<String>> params = request.getParameters();
+
+        assertEquals(3, getSize(params));
+        assertEquals(null, request.getParameter("firstname"));
+        assertEquals("petersson", request.getParameter("lastName"));
+        assertEquals(null, request.getParameter("city"));
+        assertEquals("12345", request.getParameter("phoneno"));
+        assertEquals("30", request.getParameter("age"));
+    }
+
+    @Test
+    public void testSingleGetParameterMultipleValues() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addGetParameter("letters", "x");
+        helper.addGetParameter("letters", "y");
+        helper.addGetParameter("letters", "z");
+
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        Map<String, Collection<String>> params = request.getParameters();
+
+        assertEquals(3, getSize(params));
+        Collection<String> values = params.get("letters");
+        assertEquals(3, values.size());
+        assertTrue(values.contains("x"));
+        assertTrue(values.contains("y"));
+        assertTrue(values.contains("z"));
+    }
+
+    @Test
+    public void testMultipleGetParametersMultipleValues() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addGetParameter("letters", "x");
+        helper.addGetParameter("letters", "y");
+        helper.addGetParameter("letters", "z");
+        helper.addGetParameter("numbers", "23");
+        helper.addGetParameter("numbers", "54");
+        helper.addGetParameter("country", "swe");
+
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        Map<String, Collection<String>> params = request.getParameters();
+
+        assertEquals(6, getSize(params));
+        Collection<String> letters = params.get("letters");
+        Collection<String> numbers = params.get("numbers");
+        Collection<String> country = params.get("country");
+
+        assertEquals(3, letters.size());
+        assertEquals(2, numbers.size());
+        assertEquals(1, country.size());
+
+        assertTrue(letters.contains("x"));
+        assertTrue(letters.contains("y"));
+        assertTrue(letters.contains("z"));
+
+        assertTrue(numbers.contains("23"));
+        assertTrue(numbers.contains("54"));
+
+        assertTrue(country.contains("swe"));
+    }
+
+    @Test
+    public void testSingleGetParameterMultipleValuesIncludingNull() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addGetParameter("letters", "x");
+        helper.addGetParameter("letters", "y");
+        helper.addGetParameter("letters", null);
+        helper.addGetParameter("letters", "z");
+
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        Map<String, Collection<String>> params = request.getParameters();
+
+        assertEquals(3, getSize(params));
+        Collection<String> values = params.get("letters");
+        assertEquals(3, values.size());
+        assertTrue(values.contains("x"));
+        assertTrue(values.contains("y"));
+        assertTrue(values.contains("z"));
+    }
+
+    @Test
+    public void testEmptyParameters() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        Map<String, Collection<String>> params = request.getParameters();
+        assertNotNull(params);
+        assertEquals(0, getSize(params));
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void testImmutableParameters() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addGetParameter("letter", "x");
+
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        Map<String, Collection<String>> params = request.getParameters();
+        params.put("not", new ArrayList<String>());
+    }
+
+    @Test
+    public void testHostVerification_exists_HTTP_1_0() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.setVersion("1.0");
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        boolean requestOk = HttpUtil.verifyRequest(request);
+        assertTrue(requestOk);
+    }
+
+    @Test
+    public void testHostVerification_nonExisting_HTTP_1_0() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.setVersion("1.0");
+        helper.removeHeader("Host");
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        boolean requestOk = HttpUtil.verifyRequest(request);
+        assertTrue(requestOk);
+    }
+
+    @Test
+    public void testHostVerification_exists_HTTP_1_1() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        boolean requestOk = HttpUtil.verifyRequest(request);
+        assertTrue(requestOk);
+    }
+
+    @Test
+    public void testHostVerification_nonExisting_HTTP_1_1() {
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.removeHeader("Host");
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+        boolean requestOk = HttpUtil.verifyRequest(request);
+        assertFalse(requestOk);
+    }
+
+    @Test
+    public void testGarbageRequest() {
+        HttpRequest request =  parser.parseRequestBuffer(ByteBuffer.wrap(new byte[] { 1, 1, 1, 1 } // garbage
+                ));
+    }
+
+    /**
+     * Ensure that header keys are converted to lower case, to facilitate
+     * case-insensitive retrieval through
+     * {@link org.apache.deft.web.http.HttpRequestImpl#getHeader(String)}.
+     */
+    @Test
+    public void testOfConvertsHeaderKeysToLowerCase() {
+
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addHeader("TESTKEY", "unimportant");
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+
+        assertFalse(request.getHeaders().containsKey("TESTKEY"));
+        assertTrue(request.getHeaders().containsKey("testkey"));
+    }
+
+    /**
+     * Ensure that the case of any header values is correctly maintained.
+     */
+    @Test
+    public void testOfMaintainsHeaderValueCase() {
+
+        String expected = "vAlUe";
+
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addHeader("TESTKEY", expected);
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+
+        String actual = request.getHeader("TESTKEY");
+        assertEquals(expected, actual);
+    }
+
+    /**
+     * Ensure that case for any key passed to the method is unimportant for its
+     * retrieval.
+     */
+    @Test
+    public void testGetHeader() {
+
+        String expected = "value";
+
+        HttpRequestHelper helper = new HttpRequestHelper();
+        helper.addHeader("TESTKEY", expected);
+        HttpRequest request = parser.parseRequestBuffer(helper.getRequestAsByteBuffer());
+
+        assertEquals(expected, request.getHeader("TESTKEY"));
+        assertEquals(expected, request.getHeader("testkey"));
+    }
+
+    @Test
+    public void testHttpRequestNoQueryString() {
+        String requestLine = "GET /foobar HTTP/1.1 ";
+        HttpRequest request = new HttpRequestImpl(requestLine, new HashMap<String, String>());
+        Assert.assertEquals("/foobar", request.getRequestedPath());
+    }
+
+    @Test
+    public void testHttpRequestNullQueryString() {
+        String requestLine = "GET /foobar? HTTP/1.1 ";
+        HttpRequest request = new HttpRequestImpl(requestLine, new HashMap<String, String>());
+        Assert.assertEquals("/foobar", request.getRequestedPath());
+    }
+
+    @Test
+    public void testHttpRequestNullQueryStringTrailingSlash() {
+        String requestLine = "GET /foobar/? HTTP/1.1 ";
+        HttpRequest request = new HttpRequestImpl(requestLine, new HashMap<String, String>());
+        Assert.assertEquals("/foobar/", request.getRequestedPath());
+    }
+
+    @Test
+    public void testNoCookies() {
+        HttpRequestHelper hrh = new HttpRequestHelper();
+        HttpRequest hr = parser.parseRequestBuffer(hrh.getRequestAsByteBuffer());
+        Assert.assertEquals(0, hr.getCookies().size());
+    }
+
+    @Test
+    public void testOneCookie() {
+        HttpRequestHelper hrh = new HttpRequestHelper();
+        hrh.addHeader("Cookie", "one=value");
+        HttpRequest hr = parser.parseRequestBuffer(hrh.getRequestAsByteBuffer());
+        Assert.assertEquals("value", hr.getCookie("one"));
+    }
+
+    @Test
+    public void testOneCookieWithoutValue() {
+        HttpRequestHelper hrh = new HttpRequestHelper();
+        hrh.addHeader("Cookie", "one=");
+        HttpRequest hr = parser.parseRequestBuffer(hrh.getRequestAsByteBuffer());
+        Assert.assertEquals("", hr.getCookie("one"));
+    }
+
+    @Test
+    public void testMultipleCookies() {
+        HttpRequestHelper hrh = new HttpRequestHelper();
+        hrh.addHeader("Cookie", "one=value;two=value2");
+        HttpRequest hr = parser.parseRequestBuffer(hrh.getRequestAsByteBuffer());
+        Assert.assertEquals("value", hr.getCookie("one"));
+        Assert.assertEquals("value2", hr.getCookie("two"));
+    }
+
+}