You are viewing a plain text version of this content. The canonical link for it is here.
Posted to droids-commits@incubator.apache.org by ol...@apache.org on 2008/11/03 16:23:02 UTC

svn commit: r710092 - in /incubator/droids/trunk: ./ src/java/org/apache/droids/api/ src/java/org/apache/droids/impl/ src/java/org/apache/droids/protocol/ src/test/java/org/apache/droids/impl/ src/test/java/org/apache/droids/localserver/ src/test/java/...

Author: olegk
Date: Mon Nov  3 08:23:01 2008
New Revision: 710092

URL: http://svn.apache.org/viewvc?rev=710092&view=rev
Log:
Added an end to end test case based on an embedded HTTP server

Added:
    incubator/droids/trunk/src/test/java/org/apache/droids/impl/TestSimpleDroid.java
    incubator/droids/trunk/src/test/java/org/apache/droids/localserver/
    incubator/droids/trunk/src/test/java/org/apache/droids/localserver/LocalHttpServer.java
    incubator/droids/trunk/src/test/java/org/apache/droids/localserver/ResourceHandler.java
    incubator/droids/trunk/src/test/java/resources/
    incubator/droids/trunk/src/test/java/resources/page1_html
    incubator/droids/trunk/src/test/java/resources/page2_html
    incubator/droids/trunk/src/test/java/resources/page3_html
    incubator/droids/trunk/src/test/java/resources/page4_html
    incubator/droids/trunk/src/test/java/resources/start_html
Modified:
    incubator/droids/trunk/pom.xml
    incubator/droids/trunk/src/java/org/apache/droids/api/TaskMaster.java
    incubator/droids/trunk/src/java/org/apache/droids/impl/MultiThreadedTaskMaster.java
    incubator/droids/trunk/src/java/org/apache/droids/protocol/HttpBase.java

Modified: incubator/droids/trunk/pom.xml
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/pom.xml?rev=710092&r1=710091&r2=710092&view=diff
==============================================================================
--- incubator/droids/trunk/pom.xml (original)
+++ incubator/droids/trunk/pom.xml Mon Nov  3 08:23:01 2008
@@ -137,6 +137,15 @@
         </includes>
       </resource>
     </resources>
+    <testResources>
+      <testResource>
+        <directory>src/test/java</directory>
+        <filtering>false</filtering>
+        <includes>
+            <include>**/resources/**</include>
+        </includes>
+      </testResource>
+    </testResources>
     <plugins>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>

Modified: incubator/droids/trunk/src/java/org/apache/droids/api/TaskMaster.java
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/src/java/org/apache/droids/api/TaskMaster.java?rev=710092&r1=710091&r2=710092&view=diff
==============================================================================
--- incubator/droids/trunk/src/java/org/apache/droids/api/TaskMaster.java (original)
+++ incubator/droids/trunk/src/java/org/apache/droids/api/TaskMaster.java Mon Nov  3 08:23:01 2008
@@ -17,6 +17,7 @@
 package org.apache.droids.api;
 
 import java.util.Date;
+import java.util.concurrent.TimeUnit;
 
 
 
@@ -24,6 +25,7 @@
  * Responsible for running all the tasks
  */
 public interface TaskMaster<T extends Task> {
+ 
   void processAllTasks( final TaskQueue<T> queue, final Droid<T> droid );
 
   Date getStartTime();
@@ -33,4 +35,7 @@
   int getCompletedTasks();
 
   T getLastCompletedTask();
+  
+  void awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
+  
 }

Modified: incubator/droids/trunk/src/java/org/apache/droids/impl/MultiThreadedTaskMaster.java
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/src/java/org/apache/droids/impl/MultiThreadedTaskMaster.java?rev=710092&r1=710091&r2=710092&view=diff
==============================================================================
--- incubator/droids/trunk/src/java/org/apache/droids/impl/MultiThreadedTaskMaster.java (original)
+++ incubator/droids/trunk/src/java/org/apache/droids/impl/MultiThreadedTaskMaster.java Mon Nov  3 08:23:01 2008
@@ -46,7 +46,6 @@
    */
   public void processAllTasks(final TaskQueue<T> queue, final Droid<T> droid) 
   {
-    // TODO Auto-generated method stub
     this.queue = queue;
     this.droid = droid;
     this.startedWorking = new Date();
@@ -219,4 +218,9 @@
   public Date getStartTime() {
     return startedWorking;
   }
+  
+  public void awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+    pool.awaitTermination(timeout, unit);
+  }
+  
 }

Modified: incubator/droids/trunk/src/java/org/apache/droids/protocol/HttpBase.java
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/src/java/org/apache/droids/protocol/HttpBase.java?rev=710092&r1=710091&r2=710092&view=diff
==============================================================================
--- incubator/droids/trunk/src/java/org/apache/droids/protocol/HttpBase.java (original)
+++ incubator/droids/trunk/src/java/org/apache/droids/protocol/HttpBase.java Mon Nov  3 08:23:01 2008
@@ -148,9 +148,15 @@
   protected HttpURLConnection prepareConnection(URL source) throws IOException {
     HttpURLConnection urlc = null;
     urlc = (HttpURLConnection) source.openConnection();
-    urlc.setRequestProperty("User-Agent", userAgent);
-    urlc.addRequestProperty("From", from);
-    urlc.setRequestProperty("Referer", refer);
+    if (userAgent != null) {
+      urlc.setRequestProperty("User-Agent", userAgent);
+    }
+    if (from != null) {
+      urlc.addRequestProperty("From", from);
+    }
+    if (refer != null) {
+      urlc.setRequestProperty("Referer", refer);
+    }
     urlc.setConnectTimeout(timeout);
     return urlc;
   }

Added: incubator/droids/trunk/src/test/java/org/apache/droids/impl/TestSimpleDroid.java
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/src/test/java/org/apache/droids/impl/TestSimpleDroid.java?rev=710092&view=auto
==============================================================================
--- incubator/droids/trunk/src/test/java/org/apache/droids/impl/TestSimpleDroid.java (added)
+++ incubator/droids/trunk/src/test/java/org/apache/droids/impl/TestSimpleDroid.java Mon Nov  3 08:23:01 2008
@@ -0,0 +1,147 @@
+/*
+ * 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.droids.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+import junit.framework.Assert;
+
+import org.apache.droids.api.Link;
+import org.apache.droids.api.Parse;
+import org.apache.droids.api.Handler;
+import org.apache.droids.delay.SimpleDelayTimer;
+import org.apache.droids.helper.factories.DroidFactory;
+import org.apache.droids.helper.factories.HandlerFactory;
+import org.apache.droids.helper.factories.ParserFactory;
+import org.apache.droids.helper.factories.ProtocolFactory;
+import org.apache.droids.helper.factories.URLFiltersFactory;
+import org.apache.droids.localserver.LocalHttpServer;
+import org.apache.droids.localserver.ResourceHandler;
+import org.apache.droids.net.RegexURLFilter;
+import org.apache.droids.parse.html.HtmlParser;
+import org.apache.droids.protocol.http.Http;
+import org.apache.droids.robot.crawler.CrawlingDroid;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestSimpleDroid
+{
+
+  protected LocalHttpServer testserver;
+  
+  @Before
+  public void initializeLocalTestServer() {
+    this.testserver = new LocalHttpServer();
+  }
+  
+  @After
+  public void shutdownLocalTestServer() throws IOException {
+    this.testserver.stop();
+  }
+
+  @Test
+  public void testBasicCrawling() throws Exception
+  {
+    this.testserver.register("*", new ResourceHandler());
+    this.testserver.start();
+    
+    String baseURI = "http:/" + this.testserver.getServiceAddress();     
+    String targetURI = baseURI + "/start_html";     
+    
+    final Set<URL> visitedLinks = new HashSet<URL>();
+    
+    ParserFactory parserFactory = new ParserFactory();
+    HtmlParser htmlParser = new HtmlParser();
+    htmlParser.setElements(new HashMap<String, String>());
+    htmlParser.getElements().put("a", "href");
+    htmlParser.getElements().put("link", "href");
+    htmlParser.getElements().put("img", "src");
+    htmlParser.getElements().put("script", "src");
+    parserFactory.setMap(new HashMap<String, Object>());
+    parserFactory.getMap().put("text/html", htmlParser);
+
+    ProtocolFactory protocolFactory = new ProtocolFactory();
+    Http httpProtocol = new Http();
+    httpProtocol.setForceAllow(true);
+    httpProtocol.setUserAgent("Droids/1.1");
+    
+    protocolFactory.setMap(new HashMap<String, Object>());
+    protocolFactory.getMap().put("http", httpProtocol);
+    
+    URLFiltersFactory filtersFactory = new URLFiltersFactory();
+    RegexURLFilter defaultURLFilter = new RegexURLFilter();
+    defaultURLFilter.setFile("classpath:/regex-urlfilter.txt");
+    filtersFactory.setMap(new HashMap<String, Object>());
+    filtersFactory.getMap().put("default", defaultURLFilter);
+    
+    HandlerFactory handlerFactory = new HandlerFactory();
+    Handler defaultHandler = new Handler() {
+
+      public void handle(InputStream openStream, URL url, Parse parse) throws Exception {
+        visitedLinks.add(url); 
+      }
+      
+    };
+    handlerFactory.setMap(new HashMap<String, Object>());
+    handlerFactory.getMap().put("default", defaultHandler);
+    
+    DroidFactory<Link> droidFactory = new DroidFactory<Link>();
+    droidFactory.setMap(new HashMap<String, Object>());
+    
+    SimpleDelayTimer simpleDelayTimer = new SimpleDelayTimer();
+    simpleDelayTimer.setDelayMillis(100);
+    
+    SimpleTaskQueue<Link> simpleQueue = new SimpleTaskQueue<Link>();
+
+    MultiThreadedTaskMaster<Link> taskMaster = new MultiThreadedTaskMaster<Link>();
+    taskMaster.setMaxThreads( 1 );
+    taskMaster.setDelayTimer( simpleDelayTimer );
+    
+    CrawlingDroid helloCrawler = new CrawlingDroid( simpleQueue, taskMaster );
+    helloCrawler.setFiltersFactory(filtersFactory);
+    helloCrawler.setParserFactory(parserFactory);
+    helloCrawler.setProtocolFactory(protocolFactory);
+    helloCrawler.setHandlerFactory(handlerFactory);
+    
+    Collection<String> initialLocations = new ArrayList<String>();
+    initialLocations.add( targetURI );
+    helloCrawler.setInitialLocations(initialLocations);
+    
+    helloCrawler.init();
+    helloCrawler.start();
+    
+    helloCrawler.getTaskMaster().awaitTermination(30, TimeUnit.SECONDS);
+    
+    Assert.assertFalse(visitedLinks.isEmpty());
+    Assert.assertEquals(5, visitedLinks.size());
+    Assert.assertTrue(visitedLinks.contains(new URL(baseURI + "/start_html")));
+    Assert.assertTrue(visitedLinks.contains(new URL(baseURI + "/page1_html")));
+    Assert.assertTrue(visitedLinks.contains(new URL(baseURI + "/page2_html")));
+    Assert.assertTrue(visitedLinks.contains(new URL(baseURI + "/page3_html")));
+    Assert.assertTrue(visitedLinks.contains(new URL(baseURI + "/page4_html")));
+  }
+  
+}

Added: incubator/droids/trunk/src/test/java/org/apache/droids/localserver/LocalHttpServer.java
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/src/test/java/org/apache/droids/localserver/LocalHttpServer.java?rev=710092&view=auto
==============================================================================
--- incubator/droids/trunk/src/test/java/org/apache/droids/localserver/LocalHttpServer.java (added)
+++ incubator/droids/trunk/src/test/java/org/apache/droids/localserver/LocalHttpServer.java Mon Nov  3 08:23:01 2008
@@ -0,0 +1,328 @@
+/*
+ * 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.droids.localserver;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.http.ConnectionReuseStrategy;
+import org.apache.http.HttpException;
+import org.apache.http.HttpServerConnection;
+import org.apache.http.impl.DefaultConnectionReuseStrategy;
+import org.apache.http.impl.DefaultHttpResponseFactory;
+import org.apache.http.impl.DefaultHttpServerConnection;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.CoreProtocolPNames;
+import org.apache.http.protocol.BasicHttpProcessor;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.BasicHttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+import org.apache.http.protocol.HttpRequestHandlerRegistry;
+import org.apache.http.protocol.HttpService;
+import org.apache.http.protocol.ResponseConnControl;
+import org.apache.http.protocol.ResponseContent;
+import org.apache.http.protocol.ResponseDate;
+import org.apache.http.protocol.ResponseServer;
+
+/**
+ * Local HTTP server for tests that require one.
+ */
+public class LocalHttpServer
+{
+
+  /**
+   * The local address to bind to. The host is an IP number rather than
+   * "localhost" to avoid surprises on hosts that map "localhost" to an IPv6
+   * address or something else. The port is 0 to let the system pick one.
+   */
+  public final static InetSocketAddress TEST_SERVER_ADDR = new InetSocketAddress("127.0.0.1", 0);
+
+  /** The request handler registry. */
+  private final HttpRequestHandlerRegistry handlerRegistry;
+
+  /** The server-side connection re-use strategy. */
+  private final ConnectionReuseStrategy reuseStrategy;
+
+  /**
+   * The HTTP processor. If the interceptors are thread safe and the list is not
+   * modified during operation, the processor is thread safe.
+   */
+  private final BasicHttpProcessor httpProcessor;
+
+  /** The server parameters. */
+  private final HttpParams params;
+
+  /** The server socket, while being served. */
+  private volatile ServerSocket servicedSocket;
+
+  /** The request listening thread, while listening. */
+  private volatile Thread listenerThread;
+
+  /** The number of connections this accepted. */
+  private final AtomicInteger acceptedConnections = new AtomicInteger(0);
+
+  /**
+   * Creates a new test server.
+   */
+  public LocalHttpServer() {
+    this.handlerRegistry = new HttpRequestHandlerRegistry();
+    this.reuseStrategy = new DefaultConnectionReuseStrategy();
+    this.httpProcessor = new BasicHttpProcessor();
+    this.httpProcessor.addInterceptor(new ResponseDate());
+    this.httpProcessor.addInterceptor(new ResponseServer());
+    this.httpProcessor.addInterceptor(new ResponseContent());
+    this.httpProcessor.addInterceptor(new ResponseConnControl());
+    this.params = new BasicHttpParams();
+    this.params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
+      .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
+      .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
+      .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
+      .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "LocalTestServer/1.1");
+  }
+
+  /**
+   * Returns the number of connections this test server has accepted.
+   */
+  public int getAcceptedConnectionCount() {
+    return this.acceptedConnections.get();
+  }
+
+  /**
+   * Registers a handler with the local registry.
+   * 
+   * @param pattern
+   *          the URL pattern to match
+   * @param handler
+   *          the handler to apply
+   */
+  public void register(String pattern, HttpRequestHandler handler) {
+    this.handlerRegistry.register(pattern, handler);
+  }
+
+  /**
+   * Unregisters a handler from the local registry.
+   * 
+   * @param pattern
+   *          the URL pattern
+   */
+  public void unregister(String pattern) {
+    this.handlerRegistry.unregister(pattern);
+  }
+
+  /**
+   * Starts this test server. Use {@link #getServicePort getServicePort} to
+   * obtain the port number afterwards.
+   */
+  public void start() throws IOException {
+    if (servicedSocket != null) {
+      return; // Already running
+    }
+
+    ServerSocket ssock = new ServerSocket();
+    ssock.setReuseAddress(true); // probably pointless for port '0'
+    ssock.bind(TEST_SERVER_ADDR);
+    this.servicedSocket = ssock;
+
+    this.listenerThread = new Thread(new RequestListener());
+    this.listenerThread.setDaemon(false);
+    this.listenerThread.start();
+  }
+
+  /**
+   * Stops this test server.
+   */
+  public void stop() throws IOException {
+    if (this.servicedSocket == null) {
+      return; // not running
+    }
+    
+    try {
+      this.servicedSocket.close();
+    }
+    catch (IOException iox) {
+      System.out.println("error stopping " + this);
+      iox.printStackTrace(System.out);
+    }
+    finally {
+      this.servicedSocket = null;
+    }
+
+    if (this.listenerThread != null) {
+      this.listenerThread.interrupt();
+      this.listenerThread = null;
+    }
+  }
+
+  @Override
+  public String toString() {
+    ServerSocket ssock = servicedSocket; // avoid synchronization
+    StringBuffer sb = new StringBuffer(80);
+    sb.append("LocalTestServer/");
+    if (ssock == null) {
+      sb.append("stopped");
+    } else {
+      sb.append(ssock.getLocalSocketAddress());
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Obtains the port this server is servicing.
+   * 
+   * @return the service port
+   */
+  public int getServicePort() {
+    ServerSocket ssock = this.servicedSocket; // avoid synchronization
+    if (ssock == null) {
+      throw new IllegalStateException("not running");
+    }
+    return ssock.getLocalPort();
+  }
+
+  /**
+   * Obtains the local address the server is listening on
+   * 
+   * @return the service address
+   */
+  public SocketAddress getServiceAddress() {
+    ServerSocket ssock = this.servicedSocket; // avoid synchronization
+    if (ssock == null) {
+      throw new IllegalStateException("not running");
+    }
+    return ssock.getLocalSocketAddress();
+  }
+
+  /**
+   * The request listener. Accepts incoming connections and launches a service
+   * thread.
+   */
+  public class RequestListener implements Runnable
+  {
+
+    /** The workers launched from here. */
+    private final Set<Thread> workerThreads;
+
+    public RequestListener() {
+      super();
+      this.workerThreads = Collections.synchronizedSet(new HashSet<Thread>());
+    }
+    
+    public void run() {
+      try {
+        while ((servicedSocket != null) && (listenerThread == Thread.currentThread())
+            && !Thread.interrupted()) {
+          try {
+            accept();
+          }
+          catch (Exception e) {
+            ServerSocket ssock = servicedSocket;
+            if ((ssock != null) && !ssock.isClosed()) {
+              System.out.println(LocalHttpServer.this.toString() + " could not accept");
+              e.printStackTrace(System.out);
+            }
+            // otherwise ignore the exception silently
+            break;
+          }
+        }
+      }
+      finally {
+        cleanup();
+      }
+    }
+
+    protected void accept() throws IOException {
+      // Set up HTTP connection
+      Socket socket = servicedSocket.accept();
+      acceptedConnections.incrementAndGet();
+      DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
+      conn.bind(socket, params);
+
+      // Set up the HTTP service
+      HttpService httpService = new HttpService(httpProcessor,
+          new DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory());
+      httpService.setParams(params);
+      httpService.setHandlerResolver(handlerRegistry);
+
+      // Start worker thread
+      Thread t = new Thread(new Worker(httpService, conn));
+      workerThreads.add(t);
+      t.setDaemon(true);
+      t.start();
+
+    }
+
+    protected void cleanup() {
+      Thread[] threads = workerThreads.toArray(new Thread[0]);
+      for (int i = 0; i < threads.length; i++) {
+        if (threads[i] != null)
+          threads[i].interrupt();
+      }
+    }
+
+    /**
+     * A worker for serving incoming requests.
+     */
+    public class Worker implements Runnable
+    {
+
+      private final HttpService httpservice;
+
+      private final HttpServerConnection conn;
+
+      public Worker(final HttpService httpservice, final HttpServerConnection conn) {
+
+        this.httpservice = httpservice;
+        this.conn = conn;
+      }
+
+      public void run() {
+        HttpContext context = new BasicHttpContext(null);
+        try {
+          while ((servicedSocket != null) && this.conn.isOpen() && !Thread.interrupted()) {
+            this.httpservice.handleRequest(this.conn, context);
+          }
+        }
+        catch (IOException ex) {
+          // ignore silently
+        }
+        catch (HttpException ex) {
+          // ignore silently
+        }
+        finally {
+          workerThreads.remove(Thread.currentThread());
+          try {
+            this.conn.shutdown();
+          }
+          catch (IOException ignore) {
+          }
+        }
+      }
+
+    }
+
+  }
+
+}

Added: incubator/droids/trunk/src/test/java/org/apache/droids/localserver/ResourceHandler.java
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/src/test/java/org/apache/droids/localserver/ResourceHandler.java?rev=710092&view=auto
==============================================================================
--- incubator/droids/trunk/src/test/java/org/apache/droids/localserver/ResourceHandler.java (added)
+++ incubator/droids/trunk/src/test/java/org/apache/droids/localserver/ResourceHandler.java Mon Nov  3 08:23:01 2008
@@ -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.droids.localserver;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Locale;
+
+import org.apache.http.HttpException;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.MethodNotSupportedException;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.protocol.HttpRequestHandler;
+
+/**
+ * A handler that serves out a resource
+ */
+public class ResourceHandler implements HttpRequestHandler
+{
+
+  public void handle(final HttpRequest request, final HttpResponse response,
+      final HttpContext context) throws HttpException, IOException {
+
+    String method = request.getRequestLine().getMethod().toUpperCase(Locale.ENGLISH);
+    if (!"GET".equals(method) && !"HEAD".equals(method)) {
+      throw new MethodNotSupportedException(method + " not supported by " + getClass().getName());
+    }
+    String requestURI = request.getRequestLine().getUri();
+    String s = requestURI; 
+    if (!s.startsWith("/")) {
+      s = "/" + s;
+    }
+    s = "resources" + s;
+    
+    ClassLoader cl = ResourceHandler.class.getClassLoader();
+    URL resource = cl.getResource(s);
+
+    if (resource != null) { 
+      InputStream instream = resource.openStream();
+      InputStreamEntity entity = new InputStreamEntity(instream, -1);
+      if (requestURI.endsWith("_html")) {
+        entity.setContentType("text/html");
+      }
+      response.setEntity(entity);
+    } else {
+      response.setStatusCode(HttpStatus.SC_NOT_FOUND);
+      StringEntity entity = new StringEntity(requestURI + " not found", "US-ASCII");
+      entity.setContentType("text/html");
+      response.setEntity(entity);
+    }
+  }
+
+}

Added: incubator/droids/trunk/src/test/java/resources/page1_html
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/src/test/java/resources/page1_html?rev=710092&view=auto
==============================================================================
--- incubator/droids/trunk/src/test/java/resources/page1_html (added)
+++ incubator/droids/trunk/src/test/java/resources/page1_html Mon Nov  3 08:23:01 2008
@@ -0,0 +1,6 @@
+<html>
+  <head>Page 1</head>
+  <body>
+    <a href="/page3_html">Page1</a>
+  </body>
+</html>
\ No newline at end of file

Added: incubator/droids/trunk/src/test/java/resources/page2_html
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/src/test/java/resources/page2_html?rev=710092&view=auto
==============================================================================
--- incubator/droids/trunk/src/test/java/resources/page2_html (added)
+++ incubator/droids/trunk/src/test/java/resources/page2_html Mon Nov  3 08:23:01 2008
@@ -0,0 +1,6 @@
+<html>
+  <head>Page 2</head>
+  <body>
+    <a href="/page4_html">Page1</a>
+  </body>
+</html>
\ No newline at end of file

Added: incubator/droids/trunk/src/test/java/resources/page3_html
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/src/test/java/resources/page3_html?rev=710092&view=auto
==============================================================================
--- incubator/droids/trunk/src/test/java/resources/page3_html (added)
+++ incubator/droids/trunk/src/test/java/resources/page3_html Mon Nov  3 08:23:01 2008
@@ -0,0 +1,6 @@
+<html>
+  <head>Page 4</head>
+  <body>
+    <p>Yada yada</p>
+  </body>
+</html>
\ No newline at end of file

Added: incubator/droids/trunk/src/test/java/resources/page4_html
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/src/test/java/resources/page4_html?rev=710092&view=auto
==============================================================================
--- incubator/droids/trunk/src/test/java/resources/page4_html (added)
+++ incubator/droids/trunk/src/test/java/resources/page4_html Mon Nov  3 08:23:01 2008
@@ -0,0 +1,6 @@
+<html>
+  <head>Page 4</head>
+  <body>
+    <p>Blah blah blah</p>
+  </body>
+</html>
\ No newline at end of file

Added: incubator/droids/trunk/src/test/java/resources/start_html
URL: http://svn.apache.org/viewvc/incubator/droids/trunk/src/test/java/resources/start_html?rev=710092&view=auto
==============================================================================
--- incubator/droids/trunk/src/test/java/resources/start_html (added)
+++ incubator/droids/trunk/src/test/java/resources/start_html Mon Nov  3 08:23:01 2008
@@ -0,0 +1,7 @@
+<html>
+  <head>Start page</head>
+  <body>
+    <a href="/page1_html">Page1</a>
+    <a href="/page2_html">Page1</a>
+  </body>
+</html>
\ No newline at end of file