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 jm...@apache.org on 2012/02/13 23:07:51 UTC

svn commit: r1243729 [3/7] - in /incubator/deft/trunk: ./ awf-core/ awf-core/src/ awf-core/src/main/ awf-core/src/main/assembly/ awf-core/src/main/java/ awf-core/src/main/java/org/ awf-core/src/main/java/org/apache/ awf-core/src/main/java/org/apache/aw...

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/Application.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/Application.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/Application.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/Application.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,196 @@
+/*
+ *  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.awf.web;
+
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.apache.awf.configuration.Configuration;
+import org.apache.awf.util.HttpUtil;
+import org.apache.awf.web.handler.BadRequestRequestHandler;
+import org.apache.awf.web.handler.ForbiddenRequestHandler;
+import org.apache.awf.web.handler.HttpContinueRequestHandler;
+import org.apache.awf.web.handler.NotFoundRequestHandler;
+import org.apache.awf.web.handler.RequestHandler;
+import org.apache.awf.web.handler.RequestHandlerFactory;
+import org.apache.awf.web.handler.StaticContentHandler;
+import org.apache.awf.web.http.HttpRequest;
+
+import com.google.common.collect.ImmutableMap;
+
+public class Application {
+
+    /**
+     * "Normal/Absolute" (non group capturing) RequestHandlers e.g. "/",
+     * "/persons"
+     */
+    private final ImmutableMap<String, RequestHandler> absoluteHandlers;
+
+    /**
+     * Group capturing RequestHandlers e.g. "/persons/([0-9]+)",
+     * "/persons/(\\d{1,3})"
+     */
+    private final ImmutableMap<String, RequestHandler> capturingHandlers;
+
+    /**
+     * A mapping between group capturing RequestHandlers and their corresponding
+     * pattern ( e.g. "([0-9]+)" )
+     */
+    private final ImmutableMap<RequestHandler, Pattern> patterns;
+
+    /**
+     * The directory where static content (files) will be served from.
+     */
+    private String staticContentDir;
+
+    /**
+     * A copy of the <code>Configuration</code> used to create this type.
+     */
+    private Configuration configuration;
+
+    public Application(Map<String, RequestHandler> handlers) {
+        ImmutableMap.Builder<String, RequestHandler> builder = new ImmutableMap.Builder<String, RequestHandler>();
+        ImmutableMap.Builder<String, RequestHandler> capturingBuilder = new ImmutableMap.Builder<String, RequestHandler>();
+        ImmutableMap.Builder<RequestHandler, Pattern> patternsBuilder = new ImmutableMap.Builder<RequestHandler, Pattern>();
+
+        for (String path : handlers.keySet()) {
+            int index = path.lastIndexOf("/");
+            String group = path.substring(index + 1, path.length());
+            if (containsCapturingGroup(group)) {
+                // path ends with capturing group, e.g path ==
+                // "/person/([0-9]+)"
+                capturingBuilder.put(path.substring(0, index + 1), handlers.get(path));
+                patternsBuilder.put(handlers.get(path), Pattern.compile(group));
+            } else {
+                // "normal" path, e.g. path == "/"
+                builder.put(path, handlers.get(path));
+            }
+        }
+        absoluteHandlers = builder.build();
+        capturingHandlers = capturingBuilder.build();
+        patterns = patternsBuilder.build();
+    }
+
+    /**
+     * 
+     * @param path Requested path
+     * @return Returns the {@link RequestHandler} associated with the given
+     *         path. If no mapping exists a {@link NotFoundRequestHandler} is
+     *         returned.
+     */
+    private RequestHandler getHandler(String path) {
+
+        RequestHandler rh = absoluteHandlers.get(path);
+        if (rh == null) {
+            rh = getCapturingHandler(path);
+            if (rh == null) {
+                rh = getStaticContentHandler(path);
+                if (rh != null) {
+                    return rh;
+                }
+            } else {
+                return RequestHandlerFactory.cloneHandler(rh);
+            }
+        } else {
+            return RequestHandlerFactory.cloneHandler(rh);
+        }
+
+        return NotFoundRequestHandler.getInstance();
+    }
+
+    public RequestHandler getHandler(HttpRequest request) {
+
+        if (!HttpUtil.verifyRequest(request)) {
+            return BadRequestRequestHandler.getInstance();
+        }
+        // if @Authenticated annotation is present, make sure that the
+        // request/user is authenticated
+        // (i.e RequestHandler.getCurrentUser() != null).
+        RequestHandler rh = getHandler(request.getRequestedPath());
+        if (rh.isMethodAuthenticated(request.getMethod()) && rh.getCurrentUser(request) == null) {
+            return ForbiddenRequestHandler.getInstance();
+        }
+
+        if (request.expectContinue()) {
+            return HttpContinueRequestHandler.getInstance();
+        }
+
+        return rh;
+    }
+
+    private boolean containsCapturingGroup(String group) {
+        boolean containsGroup = group.matches("^\\(.*\\)$");
+        Pattern.compile(group); // throws PatternSyntaxException if group is
+        // malformed regular expression
+        return containsGroup;
+    }
+
+    private RequestHandler getCapturingHandler(String path) {
+        int index = path.lastIndexOf("/");
+        if (index != -1) {
+            String init = path.substring(0, index + 1); // path without its last
+            // segment
+            String group = path.substring(index + 1, path.length());
+            RequestHandler handler = capturingHandlers.get(init);
+            if (handler != null) {
+                Pattern regex = patterns.get(handler);
+                if (regex.matcher(group).matches()) {
+                    return handler;
+                }
+            }
+        }
+        return null;
+    }
+
+    private RequestHandler getStaticContentHandler(String path) {
+        if (staticContentDir == null || path.length() <= staticContentDir.length()) {
+            return null; // quick reject (no static dir or simple contradiction)
+        }
+
+        if (path.substring(1).startsWith(staticContentDir)) {
+            return StaticContentHandler.getInstance();
+        } else {
+            return null;
+        }
+    }
+
+    void setStaticContentDir(String scd) {
+        staticContentDir = scd;
+    }
+
+    /**
+     * Set the <code>Configuration</code> for use with this type.
+     * 
+     * @param configuration the <code>Configuration</code> to apply.
+     */
+    public void setConfiguration(Configuration configuration) {
+
+        this.configuration = configuration;
+    }
+
+    /**
+     * Retrieve the <code>Configuration</code> used by this type.
+     * 
+     * @return the current <code>Configuration</code>.
+     */
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/AsyncCallback.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/AsyncCallback.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/AsyncCallback.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/AsyncCallback.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,36 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.awf.web;
+
+/**
+ * The generic interface a caller must implement to receive an arbitrary
+ * callback from an async call (similar to {@link AsyncResult}).
+ */
+public interface AsyncCallback {
+
+    public static final AsyncCallback nopCb = new AsyncCallback() {
+        @Override
+        public void onCallback() { /* nop */
+        }
+    };
+
+    void onCallback();
+
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/AsyncResult.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/AsyncResult.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/AsyncResult.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/AsyncResult.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,42 @@
+/*
+ *  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.awf.web;
+
+/**
+ * The generic interface a caller must implement to receive a result from an
+ * async call
+ */
+public interface AsyncResult<T> {
+
+    /**
+     * The asynchronous call failed to complete normally.
+     * 
+     * @param caught failure encountered while executing an async operation
+     */
+    void onFailure(Throwable caught);
+
+    /**
+     * Called when an asynchronous call completes successfully.
+     * 
+     * @param result return value of the async call
+     */
+    void onSuccess(T result);
+
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/HttpServer.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/HttpServer.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/HttpServer.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/HttpServer.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,168 @@
+/*
+ *  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.awf.web;
+
+import com.google.common.collect.Lists;
+
+import org.apache.awf.configuration.AnnotationsScanner;
+import org.apache.awf.configuration.Configuration;
+import org.apache.awf.io.IOLoop;
+import org.apache.awf.util.Closeables;
+import org.apache.awf.web.handler.RequestHandler;
+import org.apache.awf.web.http.HttpProtocol;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.ServerSocketChannel;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+public class HttpServer {
+
+    private final Logger logger = LoggerFactory.getLogger(HttpServer.class);
+
+    private static final int MIN_PORT_NUMBER = 1;
+    private static final int MAX_PORT_NUMBER = 65535;
+
+    private ServerSocketChannel serverChannel;
+    private final List<IOLoop> ioLoops = Lists.newLinkedList();
+
+    private final Application application;
+
+    public HttpServer(Configuration configuration) {
+
+        application = createApplication(configuration.getHandlerPackage());
+        application.setStaticContentDir(configuration.getStaticDirectory());
+        application.setConfiguration(configuration);
+
+    }
+
+    protected Application createApplication(String packageName) {
+
+        Map<String, RequestHandler> handlers = new AnnotationsScanner().findHandlers(packageName);
+        return new Application(handlers);
+    }
+
+    /**
+     * If you want to run AWF with multiple threads first invoke
+     * {@link #bind(int)} then {@link #start(int)} instead of
+     * {@link #listen(int)} (listen starts an HTTP server on a single thread
+     * with the default IOLoop instance: {@code IOLoop.INSTANCE}).
+     */
+    public void listen(int port) {
+        bind(port);
+        ioLoops.add(IOLoop.INSTANCE);
+        registerHandler(IOLoop.INSTANCE, new HttpProtocol(application));
+    }
+
+    public void bind(int port) {
+        if (port <= MIN_PORT_NUMBER || port > MAX_PORT_NUMBER) {
+            throw new IllegalArgumentException("Invalid port number. Valid range: [" + MIN_PORT_NUMBER + ", "
+                    + MAX_PORT_NUMBER + ")");
+        }
+        try {
+            serverChannel = ServerSocketChannel.open();
+
+            boolean reuse = serverChannel.socket().getReuseAddress();
+            if (!reuse) {
+                logger.info("Enabling SO_REUSEADDR (was disabled)");
+                serverChannel.socket().setReuseAddress(true);
+            }
+            serverChannel.configureBlocking(false);
+        } catch (IOException e) {
+            logger.error("Error creating ServerSocketChannel: {}", e);
+        }
+
+        InetSocketAddress endpoint = new InetSocketAddress(port);
+        try {
+            serverChannel.socket().bind(endpoint);
+        } catch (IOException e) {
+            logger.error("Could not bind socket: {}", e);
+        }
+    }
+
+    public void start(int numThreads) {
+
+        final CountDownLatch loopsLatch = new CountDownLatch(numThreads);
+        for (int i = 0; i < numThreads; i++) {
+            final IOLoop ioLoop = new IOLoop();
+            ioLoops.add(ioLoop);
+            final HttpProtocol protocol = new HttpProtocol(ioLoop, application);
+            new Thread(new Runnable() {
+
+                @Override
+                public void run() {
+                    registerHandler(ioLoop, protocol);
+                    loopsLatch.countDown();
+                    ioLoop.start();
+                }
+            }).start();
+        }
+        try {
+            loopsLatch.await();
+        } catch (InterruptedException e) {
+            logger.error("Interrupted while waiting for all IOLoop to start", e);
+        }
+    }
+
+    /**
+     * Unbinds the port and shutdown the HTTP server using a callback to execute
+     * the stop from the IOLoop thread
+     * 
+     */
+    public void stop() {
+        logger.debug("Stopping HTTP server");
+        final CountDownLatch loopsLatch = new CountDownLatch(ioLoops.size());
+        for (final IOLoop ioLoop : ioLoops) {
+            // Use a callback to stop the loops from theire Threads
+            ioLoop.addCallback(new AsyncCallback() {
+                @Override
+                public void onCallback() {
+                    Closeables.closeQuietly(ioLoop, serverChannel);
+                    ioLoop.stop();
+                    loopsLatch.countDown();
+                }
+            });
+        }
+        try {
+            loopsLatch.await();
+        } catch (InterruptedException e) {
+            logger.error("Interrupted while waiting for all IOLoop to stop", e);
+        }
+    }
+
+    private void registerHandler(IOLoop ioLoop, HttpProtocol protocol) {
+        ioLoop.addHandler(serverChannel, protocol, SelectionKey.OP_ACCEPT, null /* attachment */
+        );
+    }
+
+    /**
+     * Added for test purposes.
+     * 
+     * @return a <code>List</code> of current <code>IOLoop</code>s.
+     */
+    protected List<IOLoop> getIoLoops() {
+        return ioLoops;
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/BadRequestRequestHandler.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/BadRequestRequestHandler.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/BadRequestRequestHandler.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/BadRequestRequestHandler.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,43 @@
+/*
+ *  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.awf.web.handler;
+
+import org.apache.awf.web.http.HttpRequest;
+import org.apache.awf.web.http.HttpResponse;
+import org.apache.awf.web.http.protocol.HttpStatus;
+
+public class BadRequestRequestHandler extends RequestHandler {
+
+    private final static BadRequestRequestHandler instance = new BadRequestRequestHandler();
+
+    private BadRequestRequestHandler() {
+    }
+
+    public static final BadRequestRequestHandler getInstance() {
+        return instance;
+    }
+
+    @Override
+    public void get(HttpRequest request, HttpResponse response) {
+        response.setStatus(HttpStatus.CLIENT_ERROR_BAD_REQUEST);
+        response.setHeader("Connection", "close");
+        response.write("HTTP 1.1 requests must include the Host: header");
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/ForbiddenRequestHandler.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/ForbiddenRequestHandler.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/ForbiddenRequestHandler.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/ForbiddenRequestHandler.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,43 @@
+/*
+ *  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.awf.web.handler;
+
+import org.apache.awf.web.http.HttpRequest;
+import org.apache.awf.web.http.HttpResponse;
+import org.apache.awf.web.http.protocol.HttpStatus;
+
+public class ForbiddenRequestHandler extends RequestHandler {
+
+    private final static ForbiddenRequestHandler instance = new ForbiddenRequestHandler();
+
+    private ForbiddenRequestHandler() {
+    }
+
+    public static final ForbiddenRequestHandler getInstance() {
+        return instance;
+    }
+
+    @Override
+    public void get(HttpRequest request, HttpResponse response) {
+        response.setStatus(HttpStatus.CLIENT_ERROR_FORBIDDEN);
+        response.setHeader("Connection", "close");
+        response.write("Authentication failed");
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/HttpContinueRequestHandler.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/HttpContinueRequestHandler.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/HttpContinueRequestHandler.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/HttpContinueRequestHandler.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,47 @@
+/*
+ *  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.awf.web.handler;
+
+import org.apache.awf.web.http.HttpRequest;
+import org.apache.awf.web.http.HttpResponse;
+import org.apache.awf.web.http.protocol.HttpStatus;
+
+public class HttpContinueRequestHandler extends RequestHandler {
+
+	private final static HttpContinueRequestHandler instance = new HttpContinueRequestHandler();
+
+	private HttpContinueRequestHandler() {
+	}
+
+	public static final HttpContinueRequestHandler getInstance() {
+		return instance;
+	}
+
+	@Override
+	public void post(HttpRequest request, HttpResponse response) {
+		response.setStatus(HttpStatus.SUCCESS_CONTINUE);
+	}
+
+	@Override
+	public void put(HttpRequest request, HttpResponse response) {
+		response.setStatus(HttpStatus.SUCCESS_CONTINUE);
+	}
+
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/NotFoundRequestHandler.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/NotFoundRequestHandler.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/NotFoundRequestHandler.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/NotFoundRequestHandler.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,43 @@
+/*
+ *  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.awf.web.handler;
+
+import org.apache.awf.web.http.HttpRequest;
+import org.apache.awf.web.http.HttpResponse;
+import org.apache.awf.web.http.protocol.HttpStatus;
+
+public class NotFoundRequestHandler extends RequestHandler {
+
+    private final static NotFoundRequestHandler instance = new NotFoundRequestHandler();
+
+    private NotFoundRequestHandler() {
+    }
+
+    public static final NotFoundRequestHandler getInstance() {
+        return instance;
+    }
+
+    @Override
+    public void get(HttpRequest request, HttpResponse response) {
+        response.setStatus(HttpStatus.CLIENT_ERROR_NOT_FOUND);
+        response.setHeader("Connection", "close");
+        response.write("Requested URL: " + request.getRequestedPath() + " was not found");
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandler.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandler.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandler.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandler.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,105 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *  
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *  
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License. 
+ *  
+ */
+package org.apache.awf.web.handler;
+
+import java.lang.annotation.Annotation;
+import java.util.Map;
+
+import org.apache.awf.annotation.Asynchronous;
+import org.apache.awf.annotation.Authenticated;
+import org.apache.awf.web.http.HttpRequest;
+import org.apache.awf.web.http.HttpResponse;
+import org.apache.awf.web.http.protocol.HttpStatus;
+import org.apache.awf.web.http.protocol.HttpVerb;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+
+public abstract class RequestHandler implements Cloneable {
+
+    private final ImmutableMap<HttpVerb, Boolean> asynchVerbs;
+    private final ImmutableMap<HttpVerb, Boolean> authVerbs;
+
+    public RequestHandler() {
+
+        Map<HttpVerb, Boolean> asyncV = Maps.newHashMap();
+        Map<HttpVerb, Boolean> authV = Maps.newHashMap();
+        for (HttpVerb verb : HttpVerb.values()) {
+            asyncV.put(verb, isMethodAnnotated(verb, Asynchronous.class));
+            authV.put(verb, isMethodAnnotated(verb, Authenticated.class));
+        }
+
+        asynchVerbs = ImmutableMap.copyOf(asyncV);
+        authVerbs = ImmutableMap.copyOf(authV);
+    }
+
+    private boolean isMethodAnnotated(HttpVerb verb, Class<? extends Annotation> annotation) {
+        try {
+            Class<?>[] parameterTypes = { HttpRequest.class, HttpResponse.class };
+            return getClass().getMethod(verb.toString().toLowerCase(), parameterTypes).getAnnotation(annotation) != null;
+        } catch (NoSuchMethodException nsme) {
+            return false;
+        }
+    }
+
+    public boolean isMethodAsynchronous(HttpVerb verb) {
+        return asynchVerbs.get(verb);
+    }
+
+    public boolean isMethodAuthenticated(HttpVerb verb) {
+        return authVerbs.get(verb);
+    }
+
+    // Default implementation of HttpMethods return a 501 page
+    public void get(HttpRequest request, HttpResponse response) {
+        response.setStatus(HttpStatus.SERVER_ERROR_NOT_IMPLEMENTED);
+        response.write("");
+    }
+
+    public void post(HttpRequest request, HttpResponse response) {
+        response.setStatus(HttpStatus.SERVER_ERROR_NOT_IMPLEMENTED);
+        response.write("");
+    }
+
+    public void put(HttpRequest request, HttpResponse response) {
+        response.setStatus(HttpStatus.SERVER_ERROR_NOT_IMPLEMENTED);
+        response.write("");
+    }
+
+    public void delete(HttpRequest request, HttpResponse response) {
+        response.setStatus(HttpStatus.SERVER_ERROR_NOT_IMPLEMENTED);
+        response.write("");
+    }
+
+    public void head(HttpRequest request, HttpResponse response) {
+        response.setStatus(HttpStatus.SERVER_ERROR_NOT_IMPLEMENTED);
+        response.write("");
+    }
+
+    public String getCurrentUser(HttpRequest request) {
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+        return super.clone();
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandlerFactory.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandlerFactory.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandlerFactory.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandlerFactory.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,54 @@
+/*
+ *  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.awf.web.handler;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory for the creation and retrieval of <code>RequestHandler</code> types.
+ */
+public class RequestHandlerFactory {
+
+    /**
+     * The <code>Logger</code>.
+     */
+    private final static Logger logger = LoggerFactory.getLogger(RequestHandlerFactory.class);
+
+    /**
+     * Clone the given instance of <code>RequestHandler</code>.
+     * 
+     * @param handler the <code>RequestHandler</code> to clone.
+     * @return a new instance, or <code>null</code> on any problem.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T extends RequestHandler> T cloneHandler(T handler) {
+
+        if (handler != null) {
+            try {
+                return (T) handler.clone();
+            } catch (CloneNotSupportedException e) {
+                logger.error("Could not clone RequestHandler", e);
+            }
+        }
+
+        return null;
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/StaticContentHandler.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/StaticContentHandler.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/StaticContentHandler.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/handler/StaticContentHandler.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,104 @@
+/*
+ *  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.awf.web.handler;
+
+import java.io.File;
+
+import javax.activation.FileTypeMap;
+
+import org.apache.awf.util.DateUtil;
+import org.apache.awf.web.http.HttpException;
+import org.apache.awf.web.http.HttpRequest;
+import org.apache.awf.web.http.HttpResponse;
+import org.apache.awf.web.http.protocol.HttpStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A RequestHandler that serves static content (files) from a predefined
+ * directory.
+ * 
+ * "Cache-Control: public" indicates that the response MAY be cached by any
+ * cache, even if it would normally be non-cacheable or cacheable only within a
+ * non- shared cache.
+ */
+public class StaticContentHandler extends RequestHandler {
+
+    private final static Logger logger = LoggerFactory.getLogger(StaticContentHandler.class);
+
+    private final static StaticContentHandler instance = new StaticContentHandler();
+
+    private final FileTypeMap mimeTypeMap = FileTypeMap.getDefaultFileTypeMap();
+
+    public static StaticContentHandler getInstance() {
+        return instance;
+    }
+
+    /** {inheritDoc} */
+    @Override
+    public void get(HttpRequest request, HttpResponse response) {
+        perform(request, response, true);
+    }
+
+    /** {inheritDoc} */
+    @Override
+    public void head(final HttpRequest request, final HttpResponse response) {
+        perform(request, response, false);
+    }
+
+    /**
+     * @param request the <code>HttpRequest</code>
+     * @param response the <code>HttpResponse</code>
+     * @param hasBody <code>true</code> to write the message body;
+     *            <code>false</code> otherwise.
+     */
+    private void perform(final HttpRequest request, final HttpResponse response, boolean hasBody) {
+
+        final String path = request.getRequestedPath();
+        final File file = new File(path.substring(1)); // remove the leading '/'
+        if (!file.exists()) {
+            throw new HttpException(HttpStatus.CLIENT_ERROR_NOT_FOUND);
+        } else if (!file.isFile()) {
+            throw new HttpException(HttpStatus.CLIENT_ERROR_FORBIDDEN, path + "is not a file");
+        }
+
+        final long lastModified = file.lastModified();
+        response.setHeader("Last-Modified", DateUtil.parseToRFC1123(lastModified));
+        response.setHeader("Cache-Control", "public");
+        String mimeType = mimeTypeMap.getContentType(file);
+        if ("text/plain".equals(mimeType)) {
+            mimeType += "; charset=utf-8";
+        }
+        response.setHeader("Content-Type", mimeType);
+        final String ifModifiedSince = request.getHeader("If-Modified-Since");
+        if (ifModifiedSince != null) {
+            final long ims = DateUtil.parseToMilliseconds(ifModifiedSince);
+            if (lastModified <= ims) {
+                response.setStatus(HttpStatus.REDIRECTION_NOT_MODIFIED);
+                logger.debug("not modified");
+                return;
+            }
+        }
+
+        if (hasBody) {
+            response.write(file);
+        }
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpBufferedLexer.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpBufferedLexer.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpBufferedLexer.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpBufferedLexer.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,242 @@
+/*
+ *  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.awf.web.http;
+
+/**
+ * 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;
+
+
+    /**
+     * Use ' ' as separator and forbids CR / LF
+     */
+    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;
+        }
+    };
+
+    /**
+     * Use CR or LF as separator and forbids nothing
+     */
+    static final StopChars CRLF_SEPARATOR = new StopChars() {
+
+        public boolean isSeparator(int ptr) {
+            return ptr == CR || ptr == LF;
+        }
+
+        public boolean isForbidden(int ptr) {
+            return false;
+        }
+    };
+
+    /**
+     * Use ':' as separator and forbids CR or LF
+     */
+    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;
+        }
+    };
+
+    static final int METHOD_LENGTH = 7;
+    static final int URI_LENGTH = 255;
+    static final int VERSION_LENGTH = 10;
+    static final int HEADER_NAME_LENGTH = 30;
+    static final int HEADER_VALUE_LENGTH = 300;
+
+    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;
+        context.clearTokenBuffer(); // Clean the token buffer if we start a new token akka last token was complete
+
+        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, METHOD_LENGTH);
+                    
+                }else{ // EOS reached with no data 
+                    return 0;
+                }
+                break;
+            }
+            case REQUEST_METHOD:{
+                // Get URI token
+                res =  nextWord(context, HttpParsingContext.TokenType.REQUEST_URI, SP_SEPARATOR, URI_LENGTH);
+                break;
+            }
+            case REQUEST_URI:{ // request version
+                res = nextWord(context, HttpParsingContext.TokenType.HTTP_VERSION, CRLF_SEPARATOR, VERSION_LENGTH);
+                break;
+            }
+            case HTTP_VERSION:{ // First header line
+                context.skips = 0;
+                if (!skipEndOfLine(context)){
+                    res = nextWord(context, HttpParsingContext.TokenType.HEADER_NAME, HEADER_NAME_SEPARATOR, HEADER_NAME_LENGTH);
+                }else {
+                    context.setBodyFound();
+                    res = 1;
+                }
+                break;
+            }
+            case HEADER_NAME:{ // header value
+               res = nextWord(context, HttpParsingContext.TokenType.HEADER_VALUE, CRLF_SEPARATOR, HEADER_VALUE_LENGTH);
+               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){
+                        context.deleteFirstCharFromTokenBuffer(); // Don't keep the first whitespace character
+                        res = nextWord(context, HttpParsingContext.TokenType.HEADER_VALUE,CRLF_SEPARATOR, HEADER_VALUE_LENGTH);
+                    }else {
+                        res = nextWord(context, HttpParsingContext.TokenType.HEADER_NAME, HEADER_NAME_SEPARATOR, HEADER_NAME_LENGTH);
+                    }
+                }else {
+                    context.setBodyFound();
+                    res = 1;
+                }
+                break;
+            }
+            default:{ // If BODY or other nothing to do
+                res = 0;
+            }
+        }
+        return res;
+
+    }
+
+	
+	public boolean skipWhiteSpaceAndLine(HttpParsingContext context){
+
+		while(context.hasRemaining()){
+			if (context.incrementAndGetPointer() != CR && context.currentPointer != LF && context.currentPointer != SP){
+                context.appendChar();
+				return true;
+			}
+		}
+		return false;
+	}
+
+    /**
+     *
+     */
+    public int nextWord(HttpParsingContext context, HttpParsingContext.TokenType type, StopChars stopChars, int maxLen){
+        int currentChar = 0;
+
+		while(context.hasRemaining()){
+			currentChar = context.incrementAndGetPointer();
+			if (stopChars.isForbidden(currentChar)){
+				return -1; // Bad format Request should not contain this char at this point
+			} else if (stopChars.isSeparator(currentChar)){
+                if (context.tokenGreaterThan(maxLen)){
+                    return -1; // Too long
+                }
+                context.storeCompleteToken(type);
+                return 1;
+            }
+            context.appendChar();
+		}
+        // No errors but the token is not complete
+        if (context.tokenGreaterThan(maxLen)){
+            return -1; // Too long
+        }
+        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.appendChar();
+                return false;
+            }else if (context.skips >= 2){ // Here we got CRLFCRLF combination so rest is the body
+                return true;
+            }
+
+            context.skips++;
+        }
+
+        return false;
+    }
+
+
+    /**
+     * Defines the Stop characters to use (Separator and Forbidden)
+     */
+    private interface StopChars {
+
+        /**
+         * Tells wether this char is a separator endind the current Token under parsing
+         */
+        boolean isSeparator(int ptr);
+
+        /**
+         * Tells wether this char is forbidden or not. If forbidden then parsing will raise an error
+         */
+        boolean isForbidden(int ptr);
+
+    }
+	
+}
\ No newline at end of file

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpException.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpException.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpException.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpException.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,65 @@
+/*
+ *  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.awf.web.http;
+
+import org.apache.awf.web.http.protocol.HttpStatus;
+
+/**
+ * Representation of an exception thrown by the server through the HTTP
+ * protocol.
+ */
+public class HttpException extends RuntimeException {
+
+    /** Serial Version UID */
+    private static final long serialVersionUID = 8066634515515557043L;
+
+    /** The HTTP status for this exception. */
+    private final HttpStatus status;
+
+    /**
+     * Create an instance of this type, with the given <code>HttpStatus</code>
+     * and an empty message.
+     * 
+     * @param status the <code>HttpStatus</code> to apply.
+     */
+    public HttpException(HttpStatus status) {
+        this(status, "");
+    }
+
+    /**
+     * Create an instance of this type, with the given <code>HttpStatus</code>
+     * and message.
+     * 
+     * @param status the <code>HttpStatus</code> to apply.
+     */
+    public HttpException(HttpStatus status, String message) {
+        super(message);
+        this.status = status;
+    }
+
+    /**
+     * Retrieve the <code>HttpStatus</code> represented by this exception.
+     * 
+     * @return the represented <code>HttpStatus</code>.
+     */
+    public HttpStatus getStatus() {
+        return status;
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpParsingContext.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpParsingContext.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpParsingContext.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpParsingContext.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,125 @@
+/*
+ *  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.awf.web.http;
+
+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;
+
+    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;
+    }
+
+    public boolean tokenGreaterThan(int maxLen) {
+        return tokenValue.length() > maxLen;
+    }
+
+    void setBuffer(ByteBuffer buffer){
+        this.buffer = buffer;
+    }
+
+    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 clearTokenBuffer(){
+        if (complete){ // Free buffer when last was complete
+            tokenValue.delete(0, Integer.MAX_VALUE);
+        }
+    }
+
+
+    void deleteFirstCharFromTokenBuffer(){
+        tokenValue.deleteCharAt(0);
+    }
+
+    void appendChar(){
+        tokenValue.append((char)currentPointer);
+    }
+
+    /**
+     * Stores the token value and define the completeness
+     */
+    void storeIncompleteToken(){
+        storeTokenValue(currentType, false);
+    }
+
+    void storeCompleteToken(TokenType type){
+        storeTokenValue(type, true);
+    }
+
+    private void storeTokenValue(TokenType type, boolean _complete){
+
+        currentType = type;
+        complete = _complete;
+    }
+
+    String getTokenValue(){
+        return tokenValue.toString();
+    }
+
+    public void persistHeaderName() {
+        lastHeaderName = tokenValue.toString();
+    }
+
+    public String getLastHeaderName() {
+        return lastHeaderName;
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpProtocol.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpProtocol.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpProtocol.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpProtocol.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,280 @@
+/*
+ *  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.awf.web.http;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.Map;
+
+import com.google.common.collect.Maps;
+
+import org.apache.awf.io.IOHandler;
+import org.apache.awf.io.IOLoop;
+import org.apache.awf.io.buffer.DynamicByteBuffer;
+import org.apache.awf.io.timeout.Timeout;
+import org.apache.awf.util.Closeables;
+import org.apache.awf.web.Application;
+import org.apache.awf.web.handler.RequestHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.apache.awf.web.http.HttpServerDescriptor.KEEP_ALIVE_TIMEOUT;
+import static org.apache.awf.web.http.HttpServerDescriptor.READ_BUFFER_SIZE;
+
+public class HttpProtocol implements IOHandler {
+
+    private final static Logger logger = LoggerFactory.getLogger(HttpProtocol.class);
+
+    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, HttpRequestImpl> partials = Maps.newHashMap();
+
+    public HttpProtocol(Application app) {
+        this(IOLoop.INSTANCE, app);
+    }
+
+    public HttpProtocol(IOLoop ioLoop, Application app) {
+        this.ioLoop = ioLoop;
+        application = app;
+        parser = new HttpRequestParser();
+    }
+
+    @Override
+    public void handleAccept(SelectionKey key) throws IOException {
+        logger.debug("handle accept...");
+        SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
+        if (clientChannel != null) {
+            // could be null in a multithreaded environment because another
+            // ioloop was "faster" to accept()
+            clientChannel.configureBlocking(false);
+            ioLoop.addHandler(clientChannel, this, SelectionKey.OP_READ, ByteBuffer.allocate(READ_BUFFER_SIZE));
+        }
+    }
+
+    @Override
+    public void handleConnect(SelectionKey key) throws IOException {
+        logger.error("handle connect in HttpProcotol...");
+    }
+
+    @Override
+    public void handleRead(SelectionKey key) throws IOException {
+        logger.debug("handle read...");
+        SocketChannel clientChannel = (SocketChannel) key.channel();
+        HttpRequest request = getHttpRequest(key, clientChannel);
+
+        // Request is null when End-of-Stream have been reached
+        // No need to do more things right now
+        if(request != null){        	
+        	logger.debug("received request: \n"+request.toString()); 
+            if (request.isKeepAlive()) {
+                ioLoop.addKeepAliveTimeout(clientChannel, Timeout.newKeepAliveTimeout(ioLoop, clientChannel,
+                        KEEP_ALIVE_TIMEOUT));
+            }
+
+            HttpResponse response = new HttpResponseImpl(this, key, request.isKeepAlive());
+            response.setCreateETag(application.getConfiguration().shouldCreateETags());
+
+            RequestHandler rh = application.getHandler(request);
+            HttpRequestDispatcher.dispatch(rh, request, response);
+            
+            // Only close if not async. In that case its up to RH to close it
+            if (!rh.isMethodAsynchronous(request.getMethod()) ) {
+                response.finish();
+            }
+        }
+    }
+
+    @Override
+    public void handleWrite(SelectionKey key) {
+        logger.debug("handle write...");
+        SocketChannel channel = (SocketChannel) key.channel();
+
+        if (key.attachment() instanceof FileInputStream) {
+            writeMappedByteBuffer(key, channel);
+        } else if (key.attachment() instanceof DynamicByteBuffer) {
+            writeDynamicByteBuffer(key, channel);
+        }
+        if (ioLoop.hasKeepAliveTimeout(channel)) {
+            prolongKeepAliveTimeout(channel);
+        }
+
+    }
+
+    private void writeMappedByteBuffer(SelectionKey key, SocketChannel channel) {
+        FileInputStream fileInputStream = (FileInputStream) key.attachment();
+
+        try {
+            long bytesWritten = 0;
+            FileChannel fileChannel = fileInputStream.getChannel();
+            long sizeNeeded = fileChannel.size();
+            bytesWritten = fileChannel.position();
+            bytesWritten += fileChannel.transferTo(bytesWritten, sizeNeeded - bytesWritten, channel);
+
+            if (bytesWritten < sizeNeeded){
+                // Set channel Position to write rest of data from good starting offset
+                fileChannel.position(bytesWritten);
+            }else{
+                // Only close channel when file is totally transferred to SocketChannel
+                com.google.common.io.Closeables.closeQuietly(fileInputStream);
+                closeOrRegisterForRead(key);
+            }
+        } catch (IOException e) {
+            logger.error("Failed to send data to client: {}", e.getMessage());
+            com.google.common.io.Closeables.closeQuietly(fileInputStream);
+            Closeables.closeQuietly(channel);
+        }
+    }
+
+    private void writeDynamicByteBuffer(SelectionKey key, SocketChannel channel) {
+        DynamicByteBuffer dbb = (DynamicByteBuffer) key.attachment();
+        logger.debug("pending data about to be written");
+        ByteBuffer toSend = dbb.getByteBuffer();
+        toSend.flip(); // prepare for write
+        long bytesWritten = 0;
+        try {
+            bytesWritten = channel.write(toSend);
+        } catch (IOException e) {
+            logger.error("Failed to send data to client: {}", e.getMessage());
+            Closeables.closeQuietly(channel);
+        }
+        logger.debug("sent {} bytes to wire", bytesWritten);
+        if (!toSend.hasRemaining()) {
+            logger.debug("sent all data in toSend buffer");
+            closeOrRegisterForRead(key); // should probably only be done if the
+            // HttpResponse is finished
+        } else {
+            toSend.compact(); // make room for more data be "read" in
+        }
+    }
+
+    public void closeOrRegisterForRead(SelectionKey key) {
+        if (key.isValid() && ioLoop.hasKeepAliveTimeout(key.channel())) {
+            try {
+                key.channel().register(key.selector(), SelectionKey.OP_READ, reuseAttachment(key));
+                prolongKeepAliveTimeout(key.channel());
+                logger.debug("keep-alive connection. registrating for read.");
+            } catch (ClosedChannelException e) {
+                logger.debug("ClosedChannelException while registrating key for read: {}", e.getMessage());
+                Closeables.closeQuietly(ioLoop, key.channel());
+            }
+        } else {
+            // http request should be finished and no 'keep-alive' => close
+            // connection
+            logger.debug("Closing finished (non keep-alive) http connection");
+            Closeables.closeQuietly(ioLoop, key.channel());
+        }
+    }
+
+    public void prolongKeepAliveTimeout(SelectableChannel channel) {
+        ioLoop.addKeepAliveTimeout(channel, Timeout.newKeepAliveTimeout(ioLoop, channel, KEEP_ALIVE_TIMEOUT));
+    }
+
+    public IOLoop getIOLoop() {
+        return ioLoop;
+    }
+
+    /**
+     * Clears the buffer (prepares for reuse) attached to the given
+     * SelectionKey.
+     * 
+     * @return A cleared (position=0, limit=capacity) ByteBuffer which is ready
+     *         for new reads
+     */
+    private ByteBuffer reuseAttachment(SelectionKey key) {
+        Object o = key.attachment();
+        ByteBuffer attachment = null;
+        if (o instanceof FileInputStream) {
+            com.google.common.io.Closeables.closeQuietly(((FileInputStream)o));
+            attachment = ByteBuffer.allocate(READ_BUFFER_SIZE);
+        } else if (o instanceof DynamicByteBuffer) {
+            attachment = ((DynamicByteBuffer) o).getByteBuffer();
+        } else {
+            attachment = (ByteBuffer) o;
+        }
+
+        if (attachment.capacity() < READ_BUFFER_SIZE) {
+            attachment = ByteBuffer.allocate(READ_BUFFER_SIZE);
+        }
+        attachment.clear(); // prepare for reuse
+        return attachment;
+    }
+
+    private HttpRequest getHttpRequest(SelectionKey key, SocketChannel clientChannel) {
+        ByteBuffer buffer = (ByteBuffer) key.attachment();
+        int bytesRead = -1;
+        try {
+            bytesRead = clientChannel.read(buffer);
+        } catch (IOException e) {
+            logger.warn("Could not read buffer: {}", e.getMessage());
+            Closeables.closeQuietly(ioLoop, clientChannel);
+        }
+        buffer.flip();
+
+        if (bytesRead < 0){
+            logger.warn("Reaches end-of-stream on clientChannel");
+            Closeables.closeQuietly(ioLoop, clientChannel);
+            return null;
+        }
+
+        return doGetHttpRequest(key, clientChannel, buffer);
+    }
+
+    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 = 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.expectContinue() ? request : null);
+    }
+
+    @Override
+    public String toString() {
+        return "HttpProtocol";
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequest.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequest.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequest.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequest.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,192 @@
+/*
+ *  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.awf.web.http;
+
+import java.net.InetAddress;
+import java.util.Collection;
+import java.util.Map;
+
+import org.apache.awf.web.http.protocol.HttpVerb;
+
+/**
+ * An HTTP request received from a client
+ */
+public interface HttpRequest {
+
+    /**
+     * Get the HTTP request line for the request.
+     * <p>
+     * Ex :
+     * 
+     * <pre>
+     * GET http://www.w3.org/pub/WWW/TheProject.html HTTP/1.1
+     * </pre>
+     * 
+     * @return the current request line.
+     */
+    public String getRequestLine();
+
+    /**
+     * The path of this request
+     * <p>
+     * Ex :
+     * 
+     * <pre>
+     * http://www.w3.org/pub/WWW/TheProject.html
+     * </pre>
+     * 
+     * @return the path requested
+     */
+    public String getRequestedPath();
+
+    /**
+     * The version of the HTTP protocol.
+     * <p>
+     * Can be <tt>HTTP/1.0</tt> or <tt>HTTP/1.1</tt>.
+     * 
+     * @return the HTTP version
+     */
+    public String getVersion();
+
+    /**
+     * Get the read-only header of this request.
+     * 
+     * @see HttpRequest#getHeader(String)
+     * 
+     * @return the header.
+     */
+    public Map<String, String> getHeaders();
+
+    /**
+     * Get the value of a given HTTP header.
+     * 
+     * @see HttpRequest#getHeaders()
+     * @param name the name of the requested header
+     * @return the value or <code>null</code> if the header is not found.
+     */
+    public String getHeader(String name);
+
+    /**
+     * The method (POST,GET ..) used for this request.
+     * 
+     * @see HttpVerb
+     * 
+     * @return the method
+     */
+    public HttpVerb getMethod();
+
+    /**
+     * Returns the value of a request parameter as a String, or null if the
+     * parameter does not exist.
+     * 
+     * You should only use this method when you are sure the parameter has only
+     * one value. If the parameter might have more than one value, use
+     * getParameterValues(java.lang.String). If you use this method with a
+     * multi-valued parameter, the value returned is equal to the first value in
+     * the array returned by getParameterValues.
+     */
+    public String getParameter(String name);
+
+    /**
+     * Returns a map of all parameters where each key is a parameter name,
+     * linked value is a Collection of String containing all known values for the parameter.
+     * When the parameter has no value, the collection will be empty.
+     * 
+     * @return all the request parameters
+     */
+    public Map<String, Collection<String>> getParameters();
+
+    /**
+     * Returns a collection of all values associated with the provided
+     * parameter. If no values are found and empty collection is returned.
+     */
+    public Collection<String> getParameterValues(String name);
+
+    /**
+     * The body of this request
+     * 
+     * @return the body as a {@link String}
+     */
+    public String getBody();
+
+    /**
+     * The address of the client which issued this request.
+     * 
+     * @return the address
+     */
+    public InetAddress getRemoteHost();
+
+    /**
+     * The TCP port of the client which issued this request.
+     * 
+     * @return the remote port number.
+     */
+    public int getRemotePort();
+
+    /**
+     * The address of the server which received this request.
+     * 
+     * @return an <code>InetAddress</code> representing the server address.
+     */
+    public InetAddress getServerHost();
+
+    /**
+     * The TCP port of the server which received this request.
+     * 
+     * @return the server port number.
+     */
+    public int getServerPort();
+
+    /**
+     * Returns a map with all cookies contained in the request. Cookies are
+     * represented as strings, and are parsed at the first invocation of this
+     * method
+     * 
+     * @return a map containing all cookies of request
+     */
+    public Map<String, String> getCookies();
+
+    /**
+     * Returns a given cookie. Cookies are represented as strings, and are
+     * parsed at the first invocation of this method
+     * 
+     * @param name the name of cookie
+     * @return the corresponding cookie, or null if the cookie does not exist
+     */
+    public String getCookie(String name);
+
+    /**
+     * Does keep-alive was requested.
+     * 
+     * @see <a
+     *      href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html">HTTP/1.1
+     *      persistent connections</a>
+     * @return <code>true</code> if keep-alive requested
+     */
+    public boolean isKeepAlive();
+    
+    /**
+     * Check if the request expect a response with 100 Continue header
+     * 
+     * @return
+     */
+    public boolean expectContinue();
+
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestDispatcher.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestDispatcher.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestDispatcher.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestDispatcher.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,72 @@
+/*
+ *  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.awf.web.http;
+
+import org.apache.awf.web.handler.RequestHandler;
+import org.apache.awf.web.http.protocol.HttpVerb;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The <code>RequestDispatcher</code> is responsible for invoking the
+ * appropriate <code>RequestHandler</code> method for the current
+ * <code>HttpRequest</code>.
+ */
+public class HttpRequestDispatcher {
+
+    private static final Logger logger = LoggerFactory.getLogger(HttpRequestDispatcher.class);
+
+    public static void dispatch(RequestHandler rh, HttpRequest request, HttpResponse response) {
+        if (rh != null) {
+            HttpVerb method = request.getMethod();
+            try {
+                switch (method) {
+                case GET:
+                    rh.get(request, response);
+                    break;
+                case POST:
+                    rh.post(request, response);
+                    break;
+                case HEAD:
+                    rh.head(request, response);
+                    break;
+                case PUT:
+                    rh.put(request, response);
+                    break;
+                case DELETE:
+                    rh.delete(request, response);
+                    break;
+                case OPTIONS: // Fall through
+                case TRACE:
+                case CONNECT:
+                default:
+                    logger.warn("Unimplemented Http metod received: {}", method);
+                    // TODO send "not supported page (501) back to client"
+                }
+            } catch (HttpException he) {
+                response.setStatus(he.getStatus());
+                response.write(he.getMessage());
+                if (rh.isMethodAsynchronous(request.getMethod())) {
+                    response.finish();
+                }
+            }
+        }
+    }
+}

Added: incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestImpl.java
URL: http://svn.apache.org/viewvc/incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestImpl.java?rev=1243729&view=auto
==============================================================================
--- incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestImpl.java (added)
+++ incubator/deft/trunk/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestImpl.java Mon Feb 13 23:07:46 2012
@@ -0,0 +1,389 @@
+/*
+ *  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.awf.web.http;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.URLDecoder;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Maps;
+import org.apache.awf.io.buffer.DynamicByteBuffer;
+import org.apache.awf.web.http.protocol.HttpVerb;
+
+/**
+ *
+ */
+public class HttpRequestImpl implements HttpRequest {
+
+    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;
+    private boolean keepAlive;
+    private InetAddress remoteHost;
+    private InetAddress serverHost;
+    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(" ");
+    /** Regex to parse out QueryString from HttpRequest */
+    public static final Pattern QUERY_STRING_PATTERN = Pattern.compile("\\?");
+    /** Regex to parse out parameters from query string */
+    public static final Pattern PARAM_STRING_PATTERN = Pattern.compile("\\&|;");
+    /** Regex to parse out key/value pairs */
+    public static final Pattern KEY_VALUE_PATTERN = Pattern.compile("=");
+    /** 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
+     * 
+     * @param requestLine The Http request text line
+     * @param headers The Http request headers
+     */
+    public HttpRequestImpl(String requestLine, Map<String, String> headers) {
+        this.requestLine = requestLine;
+        String[] elements = REQUEST_LINE_PATTERN.split(requestLine);
+        method = HttpVerb.valueOf(elements[0]);
+        String[] pathFrags = QUERY_STRING_PATTERN.split(elements[1]);
+        requestedPath = pathFrags[0];
+        version = elements[2];
+        this.headers = headers;
+        body = null;
+        initKeepAlive();
+        parameters = parseParameters((pathFrags.length>1 ? pathFrags[1]: ""));
+    }
+
+    @Override
+    public String getRequestLine() {
+        return requestLine;
+    }
+
+    @Override
+    public String getRequestedPath() {
+        return requestedPath;
+    }
+
+    @Override
+    public String getVersion() {
+        return version;
+    }
+
+    @Override
+    public Map<String, String> getHeaders() {
+        return Collections.unmodifiableMap(headers);
+    }
+
+    @Override
+    public String getHeader(String name) {
+        return headers.get(name.toLowerCase());
+    }
+
+    @Override
+    public HttpVerb getMethod() {
+        return method;
+    }
+
+    /**
+     * Returns the value of a request parameter as a String, or null if the
+     * parameter does not exist.
+     * 
+     * You should only use this method when you are sure the parameter has only
+     * one value. If the parameter might have more than one value, use
+     * getParameterValues(java.lang.String). If you use this method with a
+     * multi-valued parameter, the value returned is equal to the first value in
+     * the array returned by getParameterValues.
+     */
+    @Override
+    public String getParameter(String name) {
+        Collection<String> values = parameters.get(name);
+        return values.isEmpty() ? null : values.iterator().next();
+    }
+
+    @Override
+    public Map<String, Collection<String>> getParameters() {
+        return parameters.asMap();
+    }
+
+    @Override
+    public String getBody() {
+
+        if(bodyBuffer != null){
+            return new String (bodyBuffer.array(), Charsets.ISO_8859_1);
+        }else {
+            return body;
+        }
+    }
+
+
+
+    @Override
+    public InetAddress getRemoteHost() {
+        return remoteHost;
+    }
+
+    @Override
+    public InetAddress getServerHost() {
+        return serverHost;
+    }
+
+    @Override
+    public int getRemotePort() {
+        return remotePort;
+    }
+
+    @Override
+    public int getServerPort() {
+        return serverPort;
+    }
+
+    protected void setRemoteHost(InetAddress host) {
+        remoteHost = host;
+    }
+
+    protected void setServerHost(InetAddress host) {
+        serverHost = host;
+    }
+
+    protected void setRemotePort(int port) {
+        remotePort = port;
+    }
+
+    protected void setServerPort(int port) {
+        serverPort = port;
+    }
+
+    /**
+     * Returns a map with all cookies contained in the request. Cookies are
+     * represented as strings, and are parsed at the first invocation of this
+     * method
+     * 
+     * @return a map containing all cookies of request
+     */
+    @Override
+    public Map<String, String> getCookies() {
+        if (cookies == null) {
+            parseCookies();
+        }
+        return Collections.unmodifiableMap(cookies);
+    }
+
+    /**
+     * Returns a given cookie. Cookies are represented as strings, and are
+     * parsed at the first invocation of this method
+     * 
+     * @param name the name of cookie
+     * @return the corresponding cookie, or null if the cookie does not exist
+     */
+    @Override
+    public String getCookie(String name) {
+        if (cookies == null) {
+            parseCookies();
+        }
+        return cookies.get(name);
+    }
+
+    /**
+     * Returns a collection of all values associated with the provided
+     * parameter. If no values are found an empty collection is returned.
+     */
+    @Override
+    public Collection<String> getParameterValues(String name) {
+        return parameters.get(name);
+    }
+
+    @Override
+    public boolean isKeepAlive() {
+        return keepAlive;
+    }
+
+    /**
+     * TODO SLM This should output the real request and use a StringBuilder
+     * @return
+     */
+    @Override
+    public String toString() {
+        String result = "METHOD: " + method + "\n";
+        result += "VERSION: " + version + "\n";
+        result += "PATH: " + requestedPath + "\n";
+
+        result += "--- HEADER --- \n";
+        for (String key : headers.keySet()) {
+            String value = headers.get(key);
+            result += key + ":" + value + "\n";
+        }
+
+        result += "--- PARAMETERS --- \n";
+        for (String key : parameters.keySet()) {
+            Collection<String> values = parameters.get(key);
+            for (String value : values) {
+                result += key + ":" + value + "\n";
+            }
+        }
+        if (getBody() != null) {
+        	result += "--- BODY --- \n";
+            result += getBody();
+        }
+        
+        return result;
+    }
+
+    private ImmutableMultimap<String, String> parseParameters(String params) {
+        ImmutableMultimap.Builder<String, String> builder = ImmutableMultimap.builder();
+
+            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
+                // it.
+                if (keyValueArray.length > 1) {
+                	String value = keyValueArray[1];
+                	try {
+                		value = URLDecoder.decode(value, "UTF-8");
+                	} catch (UnsupportedEncodingException e) {
+                		// Should not happen
+                	}
+                	builder.put(keyValueArray[0], value);
+                }
+            }
+
+        return builder.build();
+    }
+
+    /**
+     * Parse the cookie's http header (RFC6265 Section 5.4)
+     */
+    private void parseCookies() {
+        String cookiesHeader = Strings.nullToEmpty(getHeader("Cookie")).trim();
+        cookies = Maps.newHashMap();
+        if (!cookiesHeader.equals("")) {
+            String[] cookiesStrings = COOKIE_SEPARATOR_PATTERN.split(cookiesHeader);
+            for (String cookieString : cookiesStrings) {
+                String[] cookie = KEY_VALUE_PATTERN.split(cookieString, 2);
+                cookies.put(cookie[0].trim(), cookie[1].trim());
+            }
+        }
+    }
+
+    protected void initKeepAlive() {
+        keepAlive = true;
+        String connection = getHeader("Connection");
+        if ("close".equalsIgnoreCase(connection) || requestLine.contains("1.0")) {
+            keepAlive = false;
+        }
+    }
+
+
+    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;
+    }
+    
+    public boolean expectContinue() {    	
+    	return (bodyBuffer == null || bodyBuffer.position() == 0) && headers.containsKey("expect");
+    }
+
+}