From deft-commits-return-330-apmail-incubator-deft-commits-archive=incubator.apache.org@incubator.apache.org Sun Feb 12 20:25:28 2012 Return-Path: X-Original-To: apmail-incubator-deft-commits-archive@minotaur.apache.org Delivered-To: apmail-incubator-deft-commits-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 8100C99CE for ; Sun, 12 Feb 2012 20:25:28 +0000 (UTC) Received: (qmail 57945 invoked by uid 500); 12 Feb 2012 20:25:28 -0000 Delivered-To: apmail-incubator-deft-commits-archive@incubator.apache.org Received: (qmail 57901 invoked by uid 500); 12 Feb 2012 20:25:28 -0000 Mailing-List: contact deft-commits-help@incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: deft-dev@incubator.apache.org Delivered-To: mailing list deft-commits@incubator.apache.org Received: (qmail 57862 invoked by uid 99); 12 Feb 2012 20:25:27 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 12 Feb 2012 20:25:27 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 12 Feb 2012 20:25:06 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 9A3162388A3D; Sun, 12 Feb 2012 20:24:43 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: svn commit: r1243316 [3/8] - in /incubator/deft/sandbox/jmeehan: ./ awf-core/ awf-core/.settings/ 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-co... Date: Sun, 12 Feb 2012 20:24:36 -0000 To: deft-commits@incubator.apache.org From: jmeehan@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20120212202443.9A3162388A3D@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Added: incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/Application.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/Application.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/Application.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/Application.java Sun Feb 12 20:24:30 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 absoluteHandlers; + + /** + * Group capturing RequestHandlers e.g. "/persons/([0-9]+)", + * "/persons/(\\d{1,3})" + */ + private final ImmutableMap capturingHandlers; + + /** + * A mapping between group capturing RequestHandlers and their corresponding + * pattern ( e.g. "([0-9]+)" ) + */ + private final ImmutableMap patterns; + + /** + * The directory where static content (files) will be served from. + */ + private String staticContentDir; + + /** + * A copy of the Configuration used to create this type. + */ + private Configuration configuration; + + public Application(Map handlers) { + ImmutableMap.Builder builder = new ImmutableMap.Builder(); + ImmutableMap.Builder capturingBuilder = new ImmutableMap.Builder(); + ImmutableMap.Builder patternsBuilder = new ImmutableMap.Builder(); + + 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 Configuration for use with this type. + * + * @param configuration the Configuration to apply. + */ + public void setConfiguration(Configuration configuration) { + + this.configuration = configuration; + } + + /** + * Retrieve the Configuration used by this type. + * + * @return the current Configuration. + */ + public Configuration getConfiguration() { + return configuration; + } +} Added: incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/AsyncCallback.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/AsyncCallback.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/AsyncCallback.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/AsyncCallback.java Sun Feb 12 20:24:30 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/AsyncResult.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/AsyncResult.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/AsyncResult.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/AsyncResult.java Sun Feb 12 20:24:30 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 { + + /** + * 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/HttpServer.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/HttpServer.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/HttpServer.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/HttpServer.java Sun Feb 12 20:24:30 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 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 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 List of current IOLoops. + */ + protected List getIoLoops() { + return ioLoops; + } +} Added: incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/BadRequestRequestHandler.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/BadRequestRequestHandler.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/BadRequestRequestHandler.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/BadRequestRequestHandler.java Sun Feb 12 20:24:30 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/ForbiddenRequestHandler.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/ForbiddenRequestHandler.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/ForbiddenRequestHandler.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/ForbiddenRequestHandler.java Sun Feb 12 20:24:30 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/HttpContinueRequestHandler.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/HttpContinueRequestHandler.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/HttpContinueRequestHandler.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/HttpContinueRequestHandler.java Sun Feb 12 20:24:30 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/NotFoundRequestHandler.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/NotFoundRequestHandler.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/NotFoundRequestHandler.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/NotFoundRequestHandler.java Sun Feb 12 20:24:30 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandler.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandler.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandler.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandler.java Sun Feb 12 20:24:30 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 asynchVerbs; + private final ImmutableMap authVerbs; + + public RequestHandler() { + + Map asyncV = Maps.newHashMap(); + Map 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 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandlerFactory.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandlerFactory.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandlerFactory.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/RequestHandlerFactory.java Sun Feb 12 20:24:30 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 RequestHandler types. + */ +public class RequestHandlerFactory { + + /** + * The Logger. + */ + private final static Logger logger = LoggerFactory.getLogger(RequestHandlerFactory.class); + + /** + * Clone the given instance of RequestHandler. + * + * @param handler the RequestHandler to clone. + * @return a new instance, or null on any problem. + */ + @SuppressWarnings("unchecked") + public static 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/StaticContentHandler.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/StaticContentHandler.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/StaticContentHandler.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/handler/StaticContentHandler.java Sun Feb 12 20:24:30 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 HttpRequest + * @param response the HttpResponse + * @param hasBody true to write the message body; + * false 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpBufferedLexer.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpBufferedLexer.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpBufferedLexer.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpBufferedLexer.java Sun Feb 12 20:24:30 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpException.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpException.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpException.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpException.java Sun Feb 12 20:24:30 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 HttpStatus + * and an empty message. + * + * @param status the HttpStatus to apply. + */ + public HttpException(HttpStatus status) { + this(status, ""); + } + + /** + * Create an instance of this type, with the given HttpStatus + * and message. + * + * @param status the HttpStatus to apply. + */ + public HttpException(HttpStatus status, String message) { + super(message); + this.status = status; + } + + /** + * Retrieve the HttpStatus represented by this exception. + * + * @return the represented HttpStatus. + */ + public HttpStatus getStatus() { + return status; + } +} Added: incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpParsingContext.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpParsingContext.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpParsingContext.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpParsingContext.java Sun Feb 12 20:24:30 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpProtocol.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpProtocol.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpProtocol.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpProtocol.java Sun Feb 12 20:24:30 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 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequest.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequest.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequest.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequest.java Sun Feb 12 20:24:30 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. + *

+ * Ex : + * + *

+     * GET http://www.w3.org/pub/WWW/TheProject.html HTTP/1.1
+     * 
+ * + * @return the current request line. + */ + public String getRequestLine(); + + /** + * The path of this request + *

+ * Ex : + * + *

+     * http://www.w3.org/pub/WWW/TheProject.html
+     * 
+ * + * @return the path requested + */ + public String getRequestedPath(); + + /** + * The version of the HTTP protocol. + *

+ * Can be HTTP/1.0 or HTTP/1.1. + * + * @return the HTTP version + */ + public String getVersion(); + + /** + * Get the read-only header of this request. + * + * @see HttpRequest#getHeader(String) + * + * @return the header. + */ + public Map getHeaders(); + + /** + * Get the value of a given HTTP header. + * + * @see HttpRequest#getHeaders() + * @param name the name of the requested header + * @return the value or null 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> getParameters(); + + /** + * Returns a collection of all values associated with the provided + * parameter. If no values are found and empty collection is returned. + */ + public Collection 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 InetAddress 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 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 HTTP/1.1 + * persistent connections + * @return true 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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestDispatcher.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestDispatcher.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestDispatcher.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestDispatcher.java Sun Feb 12 20:24:30 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 RequestDispatcher is responsible for invoking the + * appropriate RequestHandler method for the current + * HttpRequest. + */ +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/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestImpl.java URL: http://svn.apache.org/viewvc/incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestImpl.java?rev=1243316&view=auto ============================================================================== --- incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestImpl.java (added) +++ incubator/deft/sandbox/jmeehan/awf-core/src/main/java/org/apache/awf/web/http/HttpRequestImpl.java Sun Feb 12 20:24:30 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 headers; + private ImmutableMultimap parameters; + private String body; + private boolean keepAlive; + private InetAddress remoteHost; + private InetAddress serverHost; + private int remotePort; + private int serverPort; + private Map 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 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 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 values = parameters.get(name); + return values.isEmpty() ? null : values.iterator().next(); + } + + @Override + public Map> 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 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 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 values = parameters.get(key); + for (String value : values) { + result += key + ":" + value + "\n"; + } + } + if (getBody() != null) { + result += "--- BODY --- \n"; + result += getBody(); + } + + return result; + } + + private ImmutableMultimap parseParameters(String params) { + ImmutableMultimap.Builder 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"); + } + +}