FELIX-4421 - Upgrade to Jetty 8:
- bumped the embedded Jetty version to latest Jetty 8 version;
- bumped minor versions of all bundles to indicate that this
version no longer is compatible with Java 5;
- added simple integration test to verify the basic functionality
provided by the HTTP service.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1565705 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java
new file mode 100644
index 0000000..8fa35ac
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/BaseIntegrationTest.java
@@ -0,0 +1,440 @@
+/*
+ * 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.felix.http.itest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.ops4j.pax.exam.Constants.START_LEVEL_SYSTEM_BUNDLES;
+import static org.ops4j.pax.exam.Constants.START_LEVEL_TEST_BUNDLE;
+import static org.ops4j.pax.exam.CoreOptions.bootDelegationPackage;
+import static org.ops4j.pax.exam.CoreOptions.cleanCaches;
+import static org.ops4j.pax.exam.CoreOptions.felix;
+import static org.ops4j.pax.exam.CoreOptions.frameworkStartLevel;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.url;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+
+import javax.inject.Inject;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.http.api.ExtHttpService;
+import org.junit.After;
+import org.junit.Before;
+import org.ops4j.pax.exam.CoreOptions;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.Configuration;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.http.NamespaceException;
+import org.osgi.util.tracker.ServiceTracker;
+
+/**
+ * Base class for integration tests.
+ *
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+public abstract class BaseIntegrationTest
+{
+ protected static class TestFilter implements Filter
+ {
+ private final CountDownLatch m_initLatch;
+ private final CountDownLatch m_destroyLatch;
+
+ public TestFilter(CountDownLatch initLatch, CountDownLatch destroyLatch)
+ {
+ m_initLatch = initLatch;
+ m_destroyLatch = destroyLatch;
+ }
+
+ public void destroy()
+ {
+ if (m_destroyLatch != null)
+ {
+ m_destroyLatch.countDown();
+ }
+ }
+
+ public final void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException
+ {
+ filter((HttpServletRequest) req, (HttpServletResponse) resp, chain);
+ }
+
+ public void init(FilterConfig config) throws ServletException
+ {
+ if (m_initLatch != null)
+ {
+ m_initLatch.countDown();
+ }
+ }
+
+ protected void filter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException
+ {
+ ((HttpServletResponse) resp).setStatus(HttpServletResponse.SC_OK);
+ }
+ }
+
+ protected static class TestServlet extends HttpServlet
+ {
+ private static final long serialVersionUID = 1L;
+
+ private final CountDownLatch m_initLatch;
+ private final CountDownLatch m_destroyLatch;
+
+ public TestServlet(CountDownLatch initLatch, CountDownLatch destroyLatch)
+ {
+ m_initLatch = initLatch;
+ m_destroyLatch = destroyLatch;
+ }
+
+ @Override
+ public void destroy()
+ {
+ super.destroy();
+ if (m_destroyLatch != null)
+ {
+ m_destroyLatch.countDown();
+ }
+ }
+
+ @Override
+ public void init() throws ServletException
+ {
+ super.init();
+ if (m_initLatch != null)
+ {
+ m_initLatch.countDown();
+ }
+ }
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ resp.setStatus(HttpServletResponse.SC_OK);
+ }
+ }
+
+ private static final int DEFAULT_TIMEOUT = 10000;
+
+ protected static final String ORG_APACHE_FELIX_HTTP_JETTY = "org.apache.felix.http.jetty";
+
+ protected static void assertContent(int expectedRC, String expected, URL url) throws IOException
+ {
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+
+ int rc = conn.getResponseCode();
+ assertEquals("Unexpected response code,", expectedRC, rc);
+
+ if (rc >= 200 && rc < 500)
+ {
+ InputStream is = null;
+ try
+ {
+ is = conn.getInputStream();
+ assertEquals(expected, slurpAsString(is));
+ }
+ finally
+ {
+ close(is);
+ conn.disconnect();
+ }
+ }
+ else
+ {
+ InputStream is = null;
+ try
+ {
+ is = conn.getErrorStream();
+ assertEquals(expected, slurpAsString(is));
+ }
+ finally
+ {
+ close(is);
+ conn.disconnect();
+ }
+ }
+ }
+
+ protected static void assertContent(String expected, URL url) throws IOException
+ {
+ assertContent(200, expected, url);
+ }
+
+ protected static void assertResponseCode(int expected, URL url) throws IOException
+ {
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ try
+ {
+ assertEquals(expected, conn.getResponseCode());
+ }
+ finally
+ {
+ conn.disconnect();
+ }
+ }
+
+ protected static void close(Closeable resource)
+ {
+ if (resource != null)
+ {
+ try
+ {
+ resource.close();
+ }
+ catch (IOException e)
+ {
+ // Ignore...
+ }
+ }
+ }
+
+ protected static URL createURL(String path)
+ {
+ if (path == null)
+ {
+ path = "";
+ }
+ while (path.startsWith("/"))
+ {
+ path = path.substring(1);
+ }
+ int port = Integer.getInteger("org.osgi.service.http.port", 8080);
+ try
+ {
+ return new URL(String.format("http://localhost:%d/%s", port, path));
+ }
+ catch (MalformedURLException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected static String slurpAsString(InputStream is) throws IOException
+ {
+ // See <weblogs.java.net/blog/pat/archive/2004/10/stupid_scanner_1.html>
+ Scanner scanner = new Scanner(is, "UTF-8");
+ try
+ {
+ scanner.useDelimiter("\\A");
+
+ return scanner.hasNext() ? scanner.next() : null;
+ }
+ finally
+ {
+ try
+ {
+ scanner.close();
+ }
+ catch (Exception e)
+ {
+ // Ignore...
+ }
+ }
+ }
+
+ @Inject
+ protected volatile BundleContext m_context;
+
+ @Configuration
+ public Option[] config()
+ {
+ return options(
+ bootDelegationPackage("sun.*"),
+ cleanCaches(),
+ CoreOptions.systemProperty("logback.configurationFile").value("file:src/test/resources/logback.xml"), //
+// CoreOptions.vmOption("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8787"),
+
+ mavenBundle("org.slf4j", "slf4j-api").version("1.6.5").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+ mavenBundle("ch.qos.logback", "logback-core").version("1.0.6").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+ mavenBundle("ch.qos.logback", "logback-classic").version("1.0.6").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+
+ url("link:classpath:META-INF/links/org.ops4j.pax.exam.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+ url("link:classpath:META-INF/links/org.ops4j.pax.exam.inject.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+ url("link:classpath:META-INF/links/org.ops4j.pax.extender.service.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+ url("link:classpath:META-INF/links/org.ops4j.base.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+ url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.core.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+ url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.extender.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+ url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.lifecycle.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+ url("link:classpath:META-INF/links/org.ops4j.pax.swissbox.framework.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+ url("link:classpath:META-INF/links/org.apache.geronimo.specs.atinject.link").startLevel(START_LEVEL_SYSTEM_BUNDLES),
+
+ mavenBundle("org.apache.felix", ORG_APACHE_FELIX_HTTP_JETTY).versionAsInProject().startLevel(START_LEVEL_SYSTEM_BUNDLES),
+
+ junitBundles(), frameworkStartLevel(START_LEVEL_TEST_BUNDLE), felix());
+ }
+
+ @Before
+ public void setUp() throws Exception
+ {
+ assertNotNull("No bundle context?!", m_context);
+ }
+
+ @After
+ public void tearDown() throws Exception
+ {
+ Bundle bundle = getHttpJettyBundle();
+ // Restart the HTTP-service to clean all registrations...
+ if (bundle.getState() == Bundle.ACTIVE)
+ {
+ bundle.stop();
+ bundle.start();
+ }
+ }
+
+ /**
+ * Waits for a service to become available in certain time interval.
+ * @param serviceName
+ * @return
+ * @throws Exception
+ */
+ protected <T> T awaitService(String serviceName) throws Exception
+ {
+ ServiceTracker tracker = new ServiceTracker(m_context, serviceName, null);
+ tracker.open();
+ T result;
+ try
+ {
+ result = (T) tracker.waitForService(DEFAULT_TIMEOUT);
+ }
+ finally
+ {
+ tracker.close();
+ }
+ return result;
+ }
+
+ /**
+ * @param bsn
+ * @return
+ */
+ protected Bundle findBundle(String bsn)
+ {
+ for (Bundle bundle : m_context.getBundles())
+ {
+ if (bsn.equals(bundle.getSymbolicName()))
+ {
+ return bundle;
+ }
+ }
+ return null;
+ }
+
+ protected ExtHttpService getExtHttpService()
+ {
+ return getService(ExtHttpService.class.getName());
+ }
+
+ protected Bundle getHttpJettyBundle()
+ {
+ Bundle b = findBundle(ORG_APACHE_FELIX_HTTP_JETTY);
+ assertNotNull("Filestore bundle not found?!", b);
+ return b;
+ }
+
+ protected HttpService getHttpService()
+ {
+ return getService(HttpService.class.getName());
+ }
+
+ /**
+ * Obtains a service without waiting for it to become available.
+ * @param serviceName
+ * @return
+ */
+ protected <T> T getService(String serviceName)
+ {
+ ServiceTracker tracker = new ServiceTracker(m_context, serviceName, null);
+ tracker.open();
+ T result;
+ try
+ {
+ result = (T) tracker.getService();
+ }
+ finally
+ {
+ tracker.close();
+ }
+ return result;
+ }
+
+ protected void register(String pattern, Filter filter) throws ServletException, NamespaceException
+ {
+ register(pattern, filter, null);
+ }
+
+ protected void register(String pattern, Filter servlet, HttpContext context) throws ServletException, NamespaceException
+ {
+ getExtHttpService().registerFilter(servlet, pattern, null, 0, context);
+ }
+
+ protected void register(String alias, Servlet servlet) throws ServletException, NamespaceException
+ {
+ register(alias, servlet, null);
+ }
+
+ protected void register(String alias, Servlet servlet, HttpContext context) throws ServletException, NamespaceException
+ {
+ getHttpService().registerServlet(alias, servlet, null, context);
+ }
+
+ protected void register(String alias, String name) throws ServletException, NamespaceException
+ {
+ register(alias, name, null);
+ }
+
+ protected void register(String alias, String name, HttpContext context) throws ServletException, NamespaceException
+ {
+ getExtHttpService().registerResources(alias, name, context);
+ }
+
+ protected void unregister(Filter filter) throws ServletException, NamespaceException
+ {
+ getExtHttpService().unregisterFilter(filter);
+ }
+
+ protected void unregister(Servlet servlet) throws ServletException, NamespaceException
+ {
+ getExtHttpService().unregisterServlet(servlet);
+ }
+
+ protected void unregister(String alias) throws ServletException, NamespaceException
+ {
+ getExtHttpService().unregister(alias);
+ }
+}
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyTest.java
new file mode 100644
index 0000000..76e354f
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyTest.java
@@ -0,0 +1,309 @@
+/*
+ * 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.felix.http.itest;
+
+import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.Bundle;
+import org.osgi.service.http.HttpContext;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@RunWith(JUnit4TestRunner.class)
+public class HttpJettyTest extends BaseIntegrationTest
+{
+
+ /**
+ * Tests the starting of Jetty.
+ */
+ @Test
+ public void test00_StartJettyOk() throws Exception
+ {
+ assertTrue(getHttpJettyBundle().getState() == Bundle.ACTIVE);
+
+ assertResponseCode(SC_NOT_FOUND, createURL("/"));
+ }
+
+ /**
+ * Tests the starting of Jetty.
+ */
+ @Test
+ public void test00_StopJettyOk() throws Exception
+ {
+ Bundle bundle = getHttpJettyBundle();
+
+ assertTrue(bundle.getState() == Bundle.ACTIVE);
+
+ CountDownLatch initLatch = new CountDownLatch(1);
+ CountDownLatch destroyLatch = new CountDownLatch(1);
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch);
+
+ register("/test", servlet);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertResponseCode(SC_OK, createURL("/test"));
+
+ bundle.stop();
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+ try
+ {
+ createURL("/test").openStream();
+ fail("Could connect to stopped Jetty instance?!");
+ }
+ catch (ConnectException e)
+ {
+ // Ok; expected...
+ }
+
+ bundle.start();
+
+ Thread.sleep(500); // Allow Jetty to start (still done asynchronously)...
+
+ assertResponseCode(SC_NOT_FOUND, createURL("/test"));
+ }
+
+ /**
+ * Tests that we can register a filter with Jetty and that its lifecycle is correctly controlled.
+ */
+ @Test
+ public void testRegisterFilterLifecycleOk() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(1);
+ CountDownLatch destroyLatch = new CountDownLatch(1);
+
+ TestFilter filter = new TestFilter(initLatch, destroyLatch);
+
+ register("/test", filter);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ unregister(filter);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ /**
+ * Tests that we can register a servlet with Jetty and that its lifecycle is correctly controlled.
+ */
+ @Test
+ public void testRegisterServletLifecycleOk() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(1);
+ CountDownLatch destroyLatch = new CountDownLatch(1);
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch);
+
+ register("/test", servlet);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ unregister(servlet);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testUseServletContextOk() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(1);
+ CountDownLatch destroyLatch = new CountDownLatch(1);
+
+ HttpContext context = new HttpContext()
+ {
+ public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ return true;
+ }
+
+ public URL getResource(String name)
+ {
+ try
+ {
+ File f = new File("src/test/resources/resource/" + name);
+ if (f.exists())
+ {
+ return f.toURI().toURL();
+ }
+ }
+ catch (MalformedURLException e)
+ {
+ fail();
+ }
+ return null;
+ }
+
+ public String getMimeType(String name)
+ {
+ return null;
+ }
+ };
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void init(ServletConfig config) throws ServletException
+ {
+ ServletContext context = config.getServletContext();
+ try
+ {
+ assertEquals("", context.getContextPath());
+ assertNotNull(context.getResource("test.html"));
+ assertNotNull(context.getRealPath("test.html"));
+ }
+ catch (MalformedURLException e)
+ {
+ fail();
+ }
+
+ super.init(config);
+ }
+ };
+
+ register("/foo", servlet, context);
+
+ URL testURL = createURL("/foo");
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertResponseCode(SC_OK, testURL);
+
+ unregister(servlet);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+ assertResponseCode(SC_NOT_FOUND, testURL);
+ }
+
+ /**
+ * Tests that we can register servlets and filters together.
+ */
+ @Test
+ public void testHandleMultipleRegistrationsOk() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(3);
+ CountDownLatch destroyLatch = new CountDownLatch(3);
+
+ TestServlet servlet1 = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ final AtomicLong m_count = new AtomicLong();
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ resp.setStatus(SC_OK);
+ resp.getWriter().printf("1.%d", m_count.incrementAndGet());
+ resp.flushBuffer();
+ }
+ };
+
+ TestServlet servlet2 = new TestServlet(initLatch, destroyLatch)
+ {
+ private static final long serialVersionUID = 1L;
+
+ final AtomicLong m_count = new AtomicLong();
+
+ @Override
+ protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
+ {
+ resp.setStatus(SC_OK);
+ resp.getWriter().printf("2.%d", m_count.incrementAndGet());
+ resp.flushBuffer();
+ }
+ };
+
+ TestFilter filter = new TestFilter(initLatch, destroyLatch)
+ {
+ @Override
+ protected void filter(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException
+ {
+ String param = req.getParameter("param");
+ if ("forbidden".equals(param))
+ {
+ resp.reset();
+ resp.sendError(SC_FORBIDDEN);
+ resp.flushBuffer();
+ }
+ else
+ {
+ chain.doFilter(req, resp);
+ }
+ }
+ };
+
+ register("/test1", servlet1);
+ register("/test2", servlet2);
+ register("/test.*", filter);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertContent("1.1", createURL("/test1"));
+ assertContent("2.1", createURL("/test2"));
+ assertContent("2.2", createURL("/test2"));
+ assertContent("1.2", createURL("/test1"));
+ assertContent("2.3", createURL("/test2"));
+
+ assertResponseCode(SC_FORBIDDEN, createURL("/test2?param=forbidden"));
+ assertResponseCode(SC_NOT_FOUND, createURL("/?test=forbidden"));
+
+ assertContent("2.4", createURL("/test2"));
+ assertContent("1.3", createURL("/test1"));
+
+ assertResponseCode(SC_FORBIDDEN, createURL("/test?param=forbidden"));
+
+ unregister(servlet1);
+ unregister(servlet2);
+ unregister(filter);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+ }
+}
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/ResourceTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/ResourceTest.java
new file mode 100644
index 0000000..02cf276
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/ResourceTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.felix.http.itest;
+
+import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.service.http.HttpContext;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@RunWith(JUnit4TestRunner.class)
+public class ResourceTest extends BaseIntegrationTest
+{
+
+ @Test
+ public void testHandleResourceRegistrationOk() throws Exception
+ {
+ CountDownLatch initLatch = new CountDownLatch(1);
+ CountDownLatch destroyLatch = new CountDownLatch(1);
+
+ HttpContext context = new HttpContext()
+ {
+ public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException
+ {
+ return true;
+ }
+
+ public URL getResource(String name)
+ {
+ try
+ {
+ File f = new File("src/test/resources/" + name);
+ if (f.exists())
+ {
+ return f.toURI().toURL();
+ }
+ }
+ catch (MalformedURLException e)
+ {
+ fail();
+ }
+ return null;
+ }
+
+ public String getMimeType(String name)
+ {
+ return null;
+ }
+ };
+
+ TestServlet servlet = new TestServlet(initLatch, destroyLatch);
+
+ register("/", "/resource", context);
+ register("/test", servlet, context);
+
+ URL testHtmlURL = createURL("/test.html");
+ URL testURL = createURL("/test");
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ assertResponseCode(SC_OK, testHtmlURL);
+ assertResponseCode(SC_OK, testURL);
+
+ unregister(servlet);
+
+ assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
+
+ assertResponseCode(SC_OK, testHtmlURL);
+ assertResponseCode(SC_NOT_FOUND, testURL);
+
+ unregister("/");
+
+ assertResponseCode(SC_NOT_FOUND, testHtmlURL);
+ }
+}
diff --git a/http/itest/src/test/resources/logback.xml b/http/itest/src/test/resources/logback.xml
new file mode 100644
index 0000000..03dedea
--- /dev/null
+++ b/http/itest/src/test/resources/logback.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<configuration>
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+ </pattern>
+ </encoder>
+ </appender>
+
+ <root level="WARN">
+ <appender-ref ref="STDOUT" />
+ </root>
+
+ <logger name="org.ops4j" level="WARN" />
+</configuration>
diff --git a/http/itest/src/test/resources/resource/test.html b/http/itest/src/test/resources/resource/test.html
new file mode 100644
index 0000000..5573d33
--- /dev/null
+++ b/http/itest/src/test/resources/resource/test.html
@@ -0,0 +1,19 @@
+<!--
+ 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.
+-->
+<html><body><h1>TEST</h1></body></html>
\ No newline at end of file