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