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"));
+ }
+
+}