You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by bd...@apache.org on 2020/06/10 13:55:18 UTC

[sling-org-apache-sling-servlet-helpers] branch master updated (300bd66 -> 3aa681e)

This is an automated email from the ASF dual-hosted git repository.

bdelacretaz pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-servlet-helpers.git.


    from 300bd66  Merge pull request #7 from Buuhuu/SLING-9500
     new 15feafb  SLING-9513 - add MockRequestProgressTracker
     new 3aa681e  SLING-9512 - first implementation of an InternalRequest helper

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 README.md                                          |  39 ++--
 pom.xml                                            |  14 +-
 .../servlethelpers/MockRequestProgressTracker.java |  73 +++++++
 .../MockSlingHttpServletRequest.java               |   7 +-
 .../internalrequests/InternalRequest.java          | 220 +++++++++++++++++++++
 .../internalrequests/MockResource.java             | 109 ++++++++++
 .../internalrequests/ServletInternalRequest.java   |  50 +++++
 .../internalrequests/SlingInternalRequest.java     |  46 +++++
 .../{ => internalrequests}/package-info.java       |   6 +-
 .../apache/sling/servlethelpers/package-info.java  |   2 +-
 .../internalrequests/MockServletResolver.java}     |  41 ++--
 .../MockSlingRequestProcessor.java}                |  30 ++-
 .../internalrequests/RequestInfoServlet.java       |  66 +++++++
 .../ServletInternalRequestTest.java                | 140 +++++++++++++
 .../SlingInternalRequestTest.java}                 |  13 +-
 15 files changed, 784 insertions(+), 72 deletions(-)
 create mode 100644 src/main/java/org/apache/sling/servlethelpers/MockRequestProgressTracker.java
 create mode 100644 src/main/java/org/apache/sling/servlethelpers/internalrequests/InternalRequest.java
 create mode 100644 src/main/java/org/apache/sling/servlethelpers/internalrequests/MockResource.java
 create mode 100644 src/main/java/org/apache/sling/servlethelpers/internalrequests/ServletInternalRequest.java
 create mode 100644 src/main/java/org/apache/sling/servlethelpers/internalrequests/SlingInternalRequest.java
 copy src/main/java/org/apache/sling/servlethelpers/{ => internalrequests}/package-info.java (84%)
 copy src/{main/java/org/apache/sling/servlethelpers/CookieSupport.java => test/java/org/apache/sling/servlethelpers/internalrequests/MockServletResolver.java} (52%)
 copy src/test/java/org/apache/sling/servlethelpers/{MockServletContextTest.java => internalrequests/MockSlingRequestProcessor.java} (53%)
 create mode 100644 src/test/java/org/apache/sling/servlethelpers/internalrequests/RequestInfoServlet.java
 create mode 100644 src/test/java/org/apache/sling/servlethelpers/internalrequests/ServletInternalRequestTest.java
 copy src/{main/java/org/apache/sling/servlethelpers/package-info.java => test/java/org/apache/sling/servlethelpers/internalrequests/SlingInternalRequestTest.java} (68%)


[sling-org-apache-sling-servlet-helpers] 01/02: SLING-9513 - add MockRequestProgressTracker

Posted by bd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

bdelacretaz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-servlet-helpers.git

commit 15feafbaa14f63e741d96734206fa583e141d46e
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Wed Jun 10 15:50:09 2020 +0200

    SLING-9513 - add MockRequestProgressTracker
---
 .../servlethelpers/MockRequestProgressTracker.java | 73 ++++++++++++++++++++++
 .../MockSlingHttpServletRequest.java               |  7 +--
 .../apache/sling/servlethelpers/package-info.java  |  2 +-
 3 files changed, 77 insertions(+), 5 deletions(-)

diff --git a/src/main/java/org/apache/sling/servlethelpers/MockRequestProgressTracker.java b/src/main/java/org/apache/sling/servlethelpers/MockRequestProgressTracker.java
new file mode 100644
index 0000000..3453341
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/MockRequestProgressTracker.java
@@ -0,0 +1,73 @@
+/*
+ * 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.sling.servlethelpers;
+
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.Iterator;
+
+import org.apache.sling.api.request.RequestProgressTracker;
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * Mock {@link RequestProgressTracker} implementation.
+ */
+@ConsumerType
+public class MockRequestProgressTracker implements RequestProgressTracker {
+
+    @Override
+    public void log(String message) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public void log(String format, Object... args) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public void startTimer(String timerName) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public void logTimer(String timerName) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public void logTimer(String timerName, String format, Object... args) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public Iterator<String> getMessages() {
+        return Collections.emptyIterator();
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        // does nothing in this mock class
+    }
+
+    @Override
+    public void done() {
+        // does nothing in this mock class
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/MockSlingHttpServletRequest.java b/src/main/java/org/apache/sling/servlethelpers/MockSlingHttpServletRequest.java
index 36a163b..45e0a8e 100644
--- a/src/main/java/org/apache/sling/servlethelpers/MockSlingHttpServletRequest.java
+++ b/src/main/java/org/apache/sling/servlethelpers/MockSlingHttpServletRequest.java
@@ -856,14 +856,13 @@ public class MockSlingHttpServletRequest extends SlingAdaptable implements Sling
 
     }
 
-
-    // --- unsupported operations ---
-
     @Override
     public RequestProgressTracker getRequestProgressTracker() {
-        throw new UnsupportedOperationException();
+        return new MockRequestProgressTracker();
     }
 
+    // --- unsupported operations ---
+
     @Override
     public String getPathTranslated() {
         throw new UnsupportedOperationException();
diff --git a/src/main/java/org/apache/sling/servlethelpers/package-info.java b/src/main/java/org/apache/sling/servlethelpers/package-info.java
index 2bbf9ae..1a22083 100644
--- a/src/main/java/org/apache/sling/servlethelpers/package-info.java
+++ b/src/main/java/org/apache/sling/servlethelpers/package-info.java
@@ -19,5 +19,5 @@
 /**
  * Mock implementation of selected Servlet-related Sling APIs.
  */
-@org.osgi.annotation.versioning.Version("1.7")
+@org.osgi.annotation.versioning.Version("1.8")
 package org.apache.sling.servlethelpers;


[sling-org-apache-sling-servlet-helpers] 02/02: SLING-9512 - first implementation of an InternalRequest helper

Posted by bd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

bdelacretaz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-servlet-helpers.git

commit 3aa681e3f24165474624210b799eb3585b2381d4
Author: Bertrand Delacretaz <bd...@apache.org>
AuthorDate: Wed Jun 10 15:51:20 2020 +0200

    SLING-9512 - first implementation of an InternalRequest helper
---
 README.md                                          |  39 ++--
 pom.xml                                            |  14 +-
 .../internalrequests/InternalRequest.java          | 220 +++++++++++++++++++++
 .../internalrequests/MockResource.java             | 109 ++++++++++
 .../internalrequests/ServletInternalRequest.java   |  50 +++++
 .../internalrequests/SlingInternalRequest.java     |  46 +++++
 .../internalrequests/package-info.java             |  23 +++
 .../internalrequests/MockServletResolver.java      |  44 +++++
 .../MockSlingRequestProcessor.java                 |  38 ++++
 .../internalrequests/RequestInfoServlet.java       |  66 +++++++
 .../ServletInternalRequestTest.java                | 140 +++++++++++++
 .../internalrequests/SlingInternalRequestTest.java |  26 +++
 12 files changed, 798 insertions(+), 17 deletions(-)

diff --git a/README.md b/README.md
index 4694e4e..f5f3381 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,8 @@
 
 This module is part of the [Apache Sling](https://sling.apache.org) project.
 
-It provides mock implementations of the `SlingHttpServletRequest`, `SlingHttpServletRepsonse` and related classes.
+It provides helper mock implementations of the `SlingHttpServletRequest`, `SlingHttpServletRepsonse` and related classes, along
+with helpers for internal Sling requests described below.
 
 These helpers can be used for **testing**, like the [Sling Mocks](https://sling.apache.org/documentation/development/sling-mock.html) do.
 
@@ -14,23 +15,29 @@ They are also useful for **internal requests using the `SlingRequestProcessor` s
 [GraphQL Core](https://github.com/apache/sling-org-apache-sling-graphql-core/) module which uses
 that technique to retrieve GraphQL schemas using the powerful Sling request processing mechanisms.
 
-## How about a fluent API?
+## InternalRequest helpers
 
-Internal requests using the `SlingRequestProcessor` service currently require a lot of boilerplate
-code, which we could remove by creating a fluent interface that manages the required request and response
-objects under the hood.
+The `InternalRequest` class uses either a `SlingRequestProcessor` to execute internal requests using
+the full Sling request processing pipeline, or a `ServletResolver` to resolve and call a Servlet or Script
+directly. 
 
-Maybe something like:
+The direct mode is more efficient but less faithful to the way HTTP requests are processed, as it bypasses
+all Servlet Filters, in particular.
 
-    InternalRequest
-      .get(SlingRequestProcessor, ResourceResolver)
-      .forPath("/yocats")
-      .withSelectors("farenheit", "451")
-      .withExtension("json")
+In both cases, the standard Sling Servlet/Script resolution mechanism is used, which can be useful to execute
+scripts that are resolved based on the current resource type, for non-HTTP operations. Inventing HTTP method
+names for this is fine and allows for reusing this powerful resolution mechanism in other contexts.
+
+Here's an example using this `InternalRequest` helper - see the test code for more.
+
+    OutputStream os = InternalRequest
+      .servletRequest(resourceResolver, servletResolver, "/some/path")
+      .withResourceType("website/article/news")
+      .withResourceSuperType("website/article")
+      .withSelectors("print", "a4")
+      .withExtension("pdf")
       .execute()
       .checkStatus(200)
-      .checkContentType("application/json")
-      .getContentAsStream();
-      
-This is just an idea so far...patches welcome, and maybe such an API doesn't belong in this module, but 
-would just use its helpers.
+      .checkResponseContentType("application/pdf")
+      .getResponse()
+      .getOutputStream()
diff --git a/pom.xml b/pom.xml
index 589ac0b..e411eb7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
 
     <artifactId>org.apache.sling.servlet-helpers</artifactId>
     <packaging>jar</packaging>
-    <version>1.3.1-SNAPSHOT</version>
+    <version>1.4.0-SNAPSHOT</version>
 
     <name>Apache Sling Servlet Helpers</name>
     <description>Mock implementations of SlingHttpServletRequest, SlingHttpServletResponse and related classes.</description>
@@ -73,6 +73,18 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.engine</artifactId>
+            <version>2.6.22</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+            <version>16.0.3</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
             <version>3.0.1</version>
diff --git a/src/main/java/org/apache/sling/servlethelpers/internalrequests/InternalRequest.java b/src/main/java/org/apache/sling/servlethelpers/internalrequests/InternalRequest.java
new file mode 100644
index 0000000..4c45cba
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/internalrequests/InternalRequest.java
@@ -0,0 +1,220 @@
+/*
+ * 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.sling.servlethelpers.internalrequests;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.servlets.ServletResolver;
+import org.apache.sling.engine.SlingRequestProcessor;
+import org.apache.sling.servlethelpers.MockRequestPathInfo;
+import org.apache.sling.servlethelpers.MockSlingHttpServletRequest;
+import org.apache.sling.servlethelpers.MockSlingHttpServletResponse;
+import org.jetbrains.annotations.NotNull;
+
+/** Fluent helper for Sling internal requests. 
+ * 
+ *  Two modes are supported: "full Sling request processing chain" which
+ *  uses a SlingRequestProcessor, and a "direct request to the resolved 
+ *  Servlet or Script" mode which is faster but less faithful to the way
+ *  Sling processes HTTP requests.
+ * 
+ *  Both modes use the standard Sling request attributes (resource type
+ *  and supertype, HTTP method, selectors, extension) to resolve the
+ *  Servlet or Script to use. This allows that powerful resolution mechanism
+ *  to be used to other purposes than processing incoming HTTP requests,
+ *  like content aggregation, generating query schemas dynamically, etc.
+ */
+public abstract class InternalRequest {
+    protected final String path;
+    private String selectorString;
+    private String extension;
+    private String requestMethod;
+    private String resourceType;
+    private String resourceSuperType;
+    private final ResourceResolver resourceResolver;
+    private MockSlingHttpServletRequest request;
+    private MockSlingHttpServletResponse response;
+
+    public static final String DEFAULT_METHOD = "GET";
+
+    protected InternalRequest(ResourceResolver resourceResolver, String path) {
+        this.resourceResolver = resourceResolver;
+        this.path = path;
+        this.requestMethod = DEFAULT_METHOD;
+    }
+
+    /** Start preparing an internal request that uses the "SlingRequest"Processor mode.
+     * 
+     * @param resourceResolver Used for access control
+     * @param processor The SlingRequestProcessor to use for processing
+     * @param path The path of the request
+     * @return a fluent InternalRequest 
+     */
+    @NotNull
+    public static InternalRequest slingRequest(
+        @NotNull ResourceResolver resourceResolver, 
+        @NotNull SlingRequestProcessor processor,
+        @NotNull String path) {
+        return new SlingInternalRequest(resourceResolver, processor, path);
+    }
+
+    /** Start preparing an internal request that calls the resolved Servlet
+     *  directly. This bypasses the Servlet Filters used by the default
+     *  Sling request processing pipeline, which are often not needed
+     *  for internal requests.
+     * 
+     * @param resourceResolver Used for access control
+     * @param servletResolver Resolves the Servlet or Script used to process the internal request
+     * @param path The path of the request
+     * @return a fluent InternalRequest 
+     */
+    public static InternalRequest servletRequest(ResourceResolver resourceResolver, ServletResolver servletResolver, String path) {
+        return new ServletInternalRequest(resourceResolver, servletResolver, path);
+    }
+
+    /** Set the HTTP request method to use - defaults to GET */
+    public InternalRequest withRequestMethod(String method) {
+        this.requestMethod = method.toUpperCase();
+        return this;
+    }
+
+    /** Sets the sling:resourceSuperType of the fake Resource used to resolve
+     *  the Script or Servlet to use for the internal request */
+    public InternalRequest withResourceSuperType(String resourceSuperType) {
+        this.resourceSuperType = resourceSuperType;
+        return this;
+    }
+
+    /** Sets the sling:resourceType of the fake Resource used to resolve
+     *  the Script or Servlet to use for the internal request */
+    public InternalRequest withResourceType(String resourceType) {
+        this.resourceType = resourceType;
+        return this;
+    }
+
+    /** Sets the optional selectors of the internal request, which influence
+     *  the Servlet/Script resolution.
+     */
+    public InternalRequest withSelectors(String ... selectors) {
+        if(selectors == null) {
+            return this;
+        }
+        StringBuilder sb = new StringBuilder();
+        Arrays.stream(selectors).forEach(sel -> sb.append(sb.length() == 0 ? "" : ".").append(sel));
+        selectorString = sb.toString();
+        return this;
+    }
+
+    /** Sets the optional extension of the internal request, which influence
+     *  the Servlet/Script resolution.
+     */
+    public InternalRequest withExtension(String extension) {
+        this.extension = extension;
+        return this;
+    }
+
+    /** Call the other execute() method, expecting a response with status 200 OK */
+    public InternalRequest execute() throws IOException {
+        return execute(HttpServletResponse.SC_OK);
+    }
+
+    /** Execute the internal request. Can be called right after
+     *  creating it, if not options need to be set.
+     * 
+     *  @throws IOException if the request was already executed,
+     *      if an error occurs during execution or if the
+     *      response status doesn't match the provided value
+     * 
+     *  @param expectedResponseStatus a negative value means "do not check the status"
+     */
+    public final InternalRequest execute(int expectedResponseStatus) throws IOException {
+        if(request != null) {
+            throw new IOException("Request was already executed");
+        }
+        request = new MockSlingHttpServletRequest(resourceResolver) {
+            @Override
+            protected MockRequestPathInfo newMockRequestPathInfo() {
+                MockRequestPathInfo rpi = super.newMockRequestPathInfo();
+                rpi.setResourcePath(path);
+                rpi.setExtension(extension);
+                rpi.setSelectorString(selectorString);
+                return rpi;
+            }
+        };
+        request.setMethod(requestMethod);
+        request.setResource(new MockResource(resourceResolver, path, resourceType, resourceSuperType));
+        response = new MockSlingHttpServletResponse();
+        try {
+            delegateExecute(request, response, resourceResolver);
+        } catch(ServletException sx) {
+            throw new IOException("ServletException in execute()", sx);
+        }
+
+        if(expectedResponseStatus > 0 && (expectedResponseStatus != response.getStatus())) {
+            throw new IOException("Expected response status " + expectedResponseStatus + " but got " + response.getStatus());
+        }
+
+        return this;
+    }
+
+    protected abstract void delegateExecute(SlingHttpServletRequest request, SlingHttpServletResponse response, ResourceResolver resourceResolver)
+    throws ServletException, IOException;
+
+    protected void assertRequestExecuted() throws IOException {
+        if(request == null) {
+            throw new IOException("Request hasn't been executed");
+        }
+    }
+
+    /** After executing the request, checks that the response content-type
+     *  is as expected.
+     * 
+     *  @throws IOException if the actual content-type doesn't match the expected one
+     */
+    public InternalRequest checkResponseContentType(String contentType) throws IOException {
+        assertRequestExecuted();
+        if(!contentType.equals(response.getContentType())) {
+            throw new IOException("Expected content type " + contentType + " but got " + response.getContentType());
+        }
+        return this;
+    }
+
+    /** Return the response object. The execute method must be called before this one.
+     *  @throws IOException if the request hasn't been executed yet
+     */
+    public SlingHttpServletResponse getResponse() throws IOException {
+        assertRequestExecuted();
+        return response;
+    }
+
+    /** Return the response as a String. The execute method must be called before this one.
+     *  @throws IOException if the request hasn't been executed yet
+     */
+    public String getResponseAsString() throws IOException {
+        assertRequestExecuted();
+        return response.getOutputAsString();
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/servlethelpers/internalrequests/MockResource.java b/src/main/java/org/apache/sling/servlethelpers/internalrequests/MockResource.java
new file mode 100644
index 0000000..8d98529
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/internalrequests/MockResource.java
@@ -0,0 +1,109 @@
+/*
+ * 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.sling.servlethelpers.internalrequests;
+
+import java.util.Iterator;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceMetadata;
+import org.apache.sling.api.resource.ResourceResolver;
+
+/** Minimal Resource implementation for our internal requests */
+class MockResource implements Resource {
+
+    private final String path;
+    private final String resourceType;
+    private final String resourceSuperType;
+    private final ResourceResolver resourceResolver;
+
+    static class NotNeededException extends UnsupportedOperationException {
+        private static final long serialVersionUID = 1L;
+
+        NotNeededException() {
+            super("Not implemented - this method should not be needed?");
+        }
+    }
+
+    MockResource(ResourceResolver resourceResolver, String path, String resourceType, String resourceSuperType) {
+        this.path = path;
+        this.resourceType = resourceType;
+        this.resourceSuperType = resourceSuperType;
+        this.resourceResolver = resourceResolver;
+    }
+
+    @Override
+    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        throw new NotNeededException();
+    }
+
+    @Override
+    public String getPath() {
+        return path;
+    }
+
+    @Override
+    public String getName() {
+        throw new NotNeededException();
+    }
+
+    @Override
+    public Resource getParent() {
+        throw new NotNeededException();
+    }
+
+    @Override
+    public Iterator<Resource> listChildren() {
+        throw new NotNeededException();
+    }
+
+    @Override
+    public Iterable<Resource> getChildren() {
+        throw new NotNeededException();
+    }
+
+    @Override
+    public Resource getChild(String relPath) {
+        throw new NotNeededException();
+    }
+
+    @Override
+    public String getResourceType() {
+        return resourceType;
+    }
+
+    @Override
+    public String getResourceSuperType() {
+        return resourceSuperType;
+    }
+
+    @Override
+    public boolean isResourceType(String resourceType) {
+        throw new NotNeededException();
+    }
+
+    @Override
+    public ResourceMetadata getResourceMetadata() {
+        throw new NotNeededException();
+    }
+
+    @Override
+    public ResourceResolver getResourceResolver() {
+        return resourceResolver;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/servlethelpers/internalrequests/ServletInternalRequest.java b/src/main/java/org/apache/sling/servlethelpers/internalrequests/ServletInternalRequest.java
new file mode 100644
index 0000000..1a3c7b2
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/internalrequests/ServletInternalRequest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.sling.servlethelpers.internalrequests;
+
+import java.io.IOException;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.servlets.ServletResolver;
+
+class ServletInternalRequest extends InternalRequest {
+    protected final ServletResolver servletResolver;
+
+    ServletInternalRequest(ResourceResolver resourceResolver, ServletResolver servletResolver, String path) {
+        super(resourceResolver, path);
+        this.servletResolver = servletResolver;
+    }
+
+    @Override
+    protected void delegateExecute(SlingHttpServletRequest request, SlingHttpServletResponse response, ResourceResolver resourceResolver)
+    throws ServletException, IOException {
+        final Servlet s = servletResolver.resolveServlet(request);
+        if(s == null) {
+            response.sendError(HttpServletResponse.SC_NOT_FOUND, "Servlet not found by " + getClass().getName());
+        } else {
+            s.service(request, response);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/servlethelpers/internalrequests/SlingInternalRequest.java b/src/main/java/org/apache/sling/servlethelpers/internalrequests/SlingInternalRequest.java
new file mode 100644
index 0000000..393687c
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/internalrequests/SlingInternalRequest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.sling.servlethelpers.internalrequests;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.engine.SlingRequestProcessor;
+
+/** Common interface between the various types of 
+ *  internal requests
+ */
+class SlingInternalRequest extends InternalRequest {
+    protected final SlingRequestProcessor processor;
+
+    SlingInternalRequest(ResourceResolver resolver, SlingRequestProcessor p, String path) {
+        super(resolver, path);
+        processor = p;
+    }
+
+    @Override
+    protected void delegateExecute(SlingHttpServletRequest request, SlingHttpServletResponse response, ResourceResolver resourceResolver)
+    throws ServletException, IOException {
+        processor.processRequest(request, response, resourceResolver);
+    }
+}
diff --git a/src/main/java/org/apache/sling/servlethelpers/internalrequests/package-info.java b/src/main/java/org/apache/sling/servlethelpers/internalrequests/package-info.java
new file mode 100644
index 0000000..23416e5
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlethelpers/internalrequests/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+/**
+ * Helpers for internal Sling requests
+ */
+@org.osgi.annotation.versioning.Version("1.0")
+package org.apache.sling.servlethelpers.internalrequests;
diff --git a/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockServletResolver.java b/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockServletResolver.java
new file mode 100644
index 0000000..17c7f55
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockServletResolver.java
@@ -0,0 +1,44 @@
+/*
+ * 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.sling.servlethelpers.internalrequests;
+
+import javax.servlet.Servlet;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.servlets.ServletResolver;
+
+class MockServletResolver implements ServletResolver {
+
+    @Override
+    public Servlet resolveServlet(SlingHttpServletRequest request) {
+        return new RequestInfoServlet(request);
+    }
+
+    @Override
+    public Servlet resolveServlet(Resource resource, String scriptName) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Servlet resolveServlet(ResourceResolver resolver, String scriptName) {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockSlingRequestProcessor.java b/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockSlingRequestProcessor.java
new file mode 100644
index 0000000..20c2b26
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlethelpers/internalrequests/MockSlingRequestProcessor.java
@@ -0,0 +1,38 @@
+/*
+ * 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.sling.servlethelpers.internalrequests;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.engine.SlingRequestProcessor;
+
+class MockSlingRequestProcessor implements SlingRequestProcessor {
+
+    @Override
+    public void processRequest(HttpServletRequest request, HttpServletResponse response, ResourceResolver resourceResolver) 
+        throws ServletException, IOException {
+        new RequestInfoServlet((SlingHttpServletRequest)request).service(request, response);
+    }
+}
diff --git a/src/test/java/org/apache/sling/servlethelpers/internalrequests/RequestInfoServlet.java b/src/test/java/org/apache/sling/servlethelpers/internalrequests/RequestInfoServlet.java
new file mode 100644
index 0000000..4c8b135
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlethelpers/internalrequests/RequestInfoServlet.java
@@ -0,0 +1,66 @@
+/*
+ * 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.sling.servlethelpers.internalrequests;
+
+import java.io.IOException;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.request.RequestPathInfo;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+
+class RequestInfoServlet extends SlingAllMethodsServlet {
+    private static final long serialVersionUID = 1L;
+    private final static String PREFIX = "TEST_";
+    private final String resolutionInfo;
+
+    RequestInfoServlet(SlingHttpServletRequest resolutionRequest) {
+        final StringBuilder sb = new StringBuilder();
+
+        // Verify that we get the usual Sling request
+        // attributes in the resolution call
+        final RequestPathInfo rpi = resolutionRequest.getRequestPathInfo();
+        sb.append("M_").append(resolutionRequest.getMethod());
+        sb.append(" PI_").append(resolutionRequest.getPathInfo());
+        sb.append(" RPI_EXT_").append(rpi.getExtension());
+        sb.append(" RPI_SEL_").append(rpi.getSelectorString());
+        sb.append(" RPI_P_").append(rpi.getResourcePath());
+        if(resolutionRequest.getResource() != null) {
+            sb.append(" RT_").append(resolutionRequest.getResource().getResourceType());
+            sb.append(" RST_").append(resolutionRequest.getResource().getResourceSuperType());
+        }
+        sb.append(" RRA_").append(resolutionRequest.getResource().getResourceResolver().getAttribute("testAttribute"));
+
+        resolutionInfo = sb.toString();
+    }
+
+    @Override
+    public void service(SlingHttpServletRequest request, SlingHttpServletResponse response) throws IOException {
+        if(request.getMethod().equals("EXCEPTION")) {
+            throw new IOException("Failing as designed");
+        }
+        if(request.getMethod().equals("STATUS")) {
+            response.sendError(451);
+            return;
+        }
+        response.setContentType(PREFIX + request.getContentType());
+        response.getWriter().write(resolutionInfo);
+        response.setContentType("CT_" + request.getContentType());
+    }
+}
diff --git a/src/test/java/org/apache/sling/servlethelpers/internalrequests/ServletInternalRequestTest.java b/src/test/java/org/apache/sling/servlethelpers/internalrequests/ServletInternalRequestTest.java
new file mode 100644
index 0000000..f280ee9
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlethelpers/internalrequests/ServletInternalRequestTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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.sling.servlethelpers.internalrequests;
+
+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.IOException;
+
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class ServletInternalRequestTest {
+    protected ResourceResolver resourceResolver;
+
+    protected InternalRequest request(String path) {
+        return InternalRequest.servletRequest(resourceResolver, new MockServletResolver(), path);
+    }
+
+    @Before
+    public void setup() {
+        resourceResolver = Mockito.mock(ResourceResolver.class);
+        Mockito.when(resourceResolver.getAttribute(Mockito.any(String.class))).thenReturn("RR_attribute");
+    }
+
+    @Test
+    public void minimalParameters() throws IOException {
+        assertEquals(
+            "M_GET PI_/monday RPI_EXT_null RPI_SEL_null RPI_P_/monday RT_null RST_null RRA_RR_attribute", 
+            request("/monday").execute().getResponseAsString());
+    }
+
+    @Test
+    public void allOptions() throws IOException {
+        final String content = request("/451")
+            .withResourceType("quincy")
+            .withResourceSuperType("jones")
+            .withSelectors("leo", "nardo")
+            .withExtension("davinci")
+            .execute()
+            .checkResponseContentType("CT_null")
+            .getResponseAsString();
+
+        // Verify that the servlet that got called (our RequestInfoServlet)
+        // got all the objects and values that influence servlet/script resolution
+        assertEquals(
+            "M_GET PI_/451.leo.nardo.davinci RPI_EXT_davinci RPI_SEL_leo.nardo RPI_P_/451 RT_quincy RST_jones RRA_RR_attribute",
+            content);
+    }
+
+    @Test
+    public void postMethod() throws IOException {
+        assertEquals(
+            "M_POST PI_/tuesday RPI_EXT_null RPI_SEL_null RPI_P_/tuesday RT_null RST_null RRA_RR_attribute", 
+            request("/tuesday").withRequestMethod("post").execute().getResponseAsString());
+    }
+
+    @Test(expected = IOException.class)
+    public void doubleExecute() throws IOException {
+        request("/never").execute().execute();
+    }
+
+    @Test(expected = IOException.class)
+    public void servletException() throws IOException {
+        request("/never").withRequestMethod("EXCEPTION").execute();
+    }
+
+    @Test
+    public void non200Status() throws IOException {
+        final InternalRequest req = request("/never").withRequestMethod("STATUS");
+        try {
+            req.execute();
+            fail("Expecting status check to fail");
+        } catch(IOException asExpected) {
+        }
+        assertTrue("Expecting non-200 status to return no content", req.getResponseAsString().isEmpty());
+    }
+
+    @Test
+    public void specificStatus() throws IOException {
+        request("/never").withRequestMethod("STATUS").execute(451);
+    }
+
+    @Test
+    public void ignoreNon200Status() throws IOException {
+        final HttpServletResponse r = request("/ignore").withRequestMethod("STATUS").execute(-1).getResponse();
+        assertEquals(451, r.getStatus());
+    }
+
+    @Test
+    public void nullSelectors() throws IOException {
+        final String [] theyAreNull = null;
+        assertEquals(
+            "M_GET PI_/nothing RPI_EXT_null RPI_SEL_null RPI_P_/nothing RT_null RST_null RRA_RR_attribute", 
+            request("/nothing").withSelectors(theyAreNull).execute().getResponseAsString()
+        );
+    }
+
+    @Test
+    public void contentTypeMismatch() throws IOException {
+        final InternalRequest req = request("/contentType").execute().checkResponseContentType("CT_null");
+        try {
+            req.checkResponseContentType("not/this");
+            fail("Expecting content type check to fail");
+        } catch(IOException asExpected) {
+        }
+    }
+
+    @Test
+    public void responseProvided() throws IOException {
+        assertNotNull(request("/response").execute().getResponse());
+    }
+
+    @Test(expected=IOException.class)
+    public void forgotToExecute() throws IOException {
+        request("/response").getResponseAsString();
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/servlethelpers/internalrequests/SlingInternalRequestTest.java b/src/test/java/org/apache/sling/servlethelpers/internalrequests/SlingInternalRequestTest.java
new file mode 100644
index 0000000..2de36a7
--- /dev/null
+++ b/src/test/java/org/apache/sling/servlethelpers/internalrequests/SlingInternalRequestTest.java
@@ -0,0 +1,26 @@
+/*
+ * 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.sling.servlethelpers.internalrequests;
+
+/** Run the same tests as the ServletInternalRequestTest but in "sling" mode */
+public class SlingInternalRequestTest extends ServletInternalRequestTest {
+    protected InternalRequest request(String path) {
+        return InternalRequest.slingRequest(resourceResolver, new MockSlingRequestProcessor(), path);
+    }
+}