The Sling Servlet Helpers bundle provides mock implementations of the SlingHttpServletRequest
, SlingHttpServletResponse
and related classes, along with fluent SlingInternalRequest
and ServletInternalRequest
helpers for internal requests.
The mock request/response implementations are meant to be used in tests and also with services like the SlingRequestProcessor
when making requests to that service outside of an HTTP request processing context.
They are used under the hood by the SlingInternalRequest
and ServletInternalRequest
helpers to provide a simple and foolproof way of executing internal Sling requests.
The GraphQL Core module, for example, uses them for internal requests that retrieve a GraphQL schema dynamically, taking into account the current Resource and request selectors.
See the automated tests of the servlet-helpers
module for more info, besides the general descriptions found below.
The internal request helpers use 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. The necessary "mocking" of requests are responses happens under the hood which leads to much simpler code than using the mock request/response classes directly.
The latter direct-to-servlet (or script) mode is more efficient but less faithful to the way HTTP requests are processed, as it bypasses all Servlet Filters, in particular.
Here's an example using the SlingInternalRequest
helper - see the test code for more. The ServletInternalRequest
API is very similar but takes a ServletResolver
and an actual Resource
as its starting points.
OutputStream os = new SlingInternalRequest(resourceResolver, slingRequestProcessor, path)
.withResourceType("website/article/news")
.withResourceSuperType("website/article")
.withSelectors("print", "a4")
.withExtension("pdf")
.execute()
.checkStatus(200)
.checkResponseContentType("application/pdf")
.getResponse()
.getOutputStream()
Not all servlets and scripts are suitable to be called by the ServletInternalRequest
, depending on their "environmental" requirements like Request
attributes for example.
In case of doubt you can start with the SlingInternalRequest
helper which uses the SlingRequestProcessor
so that servlets or scripts should see no difference compared to HTTP requests. And once that works you can try the more efficient ServletInternalRequest
helper to check if your scripts and servlets support that mode.
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.
To help map log messages to internal requests, as several of those might be used to handle a single HTTP request, the InternalRequest
parent class of the helpers discussed above sets a log4j Mapped Diagnostic Context (MDC) value with the sling.InternalRequest
key.
The value of that key provides the essential attributes of the current request, so that using a log formatting pattern that displays it, like:
%-5level [%-50logger{50}] %message ## %mdc{sling.InternalRequest} %n
Causes the internal request information to be logged, like in this example (lines folded for readability):
DEBUG [o.a.s.s.internalrequests.SlingInternalRequest ]
Executing request using the SlingRequestProcessor
## GET P=/content/tags/monitor+array S=null EXT=json RT=samples/tag(null)
WARN [org.apache.sling.engine.impl.request.RequestData ]
SlingRequestProgressTracker not found in request attributes
## GET P=/content/tags/monitor+array S=null EXT=json RT=samples/tag(null)
DEBUG [o.a.s.s.resolver.internal.SlingServletResolver ]
Using cached servlet /apps/samples/tag/json.gql
## GET P=/content/tags/monitor+array S=null EXT=json RT=samples/tag(null)
In these log messages, GET P=/content/tags/monitor+array S=null EXT=json RT=samples/tag(null)
points to the current internal request, showing its method, path, selectors, extension, resource type and resource supertype.
These are useful for testing or if you need to do something that the internal request helpers do not support.
Example for preparing a sling request with custom request data:
// prepare sling request
MockSlingHttpServletRequest request = new MockSlingHttpServletRequest(resourceResolver);
// simulate query string
request.setQueryString("param1=aaa¶m2=bbb");
// alternative - set query parameters as map
request.setParameterMap(ImmutableMap.<String,Object>builder()
.put("param1", "aaa")
.put("param2", "bbb")
.build());
// set current resource
request.setResource(resourceResolver.getResource("/content/sample"));
// set sling request path info properties
MockRequestPathInfo requestPathInfo = (MockRequestPathInfo)request.getRequestPathInfo();
requestPathInfo.setSelectorString("selector1.selector2");
requestPathInfo.setExtension("html");
// set method
request.setMethod(HttpConstants.METHOD_POST);
// set attributes
request.setAttribute("attr1", "value1");
// set headers
request.addHeader("header1", "value1");
// set cookies
request.addCookie(new Cookie("cookie1", "value1"));
Example for preparing a sling response which can collect the data that was written to it:
// prepare sling response
MockSlingHttpServletResponse response = new MockSlingHttpServletResponse();
// execute the code that writes to the response...
// validate status code
assertEquals(HttpServletResponse.SC_OK, response.getStatus());
// validate content type and content length
assertEquals("text/plain;charset=UTF-8", response.getContentType());
assertEquals(CharEncoding.UTF_8, response.getCharacterEncoding());
assertEquals(55, response.getContentLength());
// validate headers
assertTrue(response.containsHeader("header1"));
assertEquals("5", response.getHeader("header2"));
// validate response body as string
assertEquals(TEST_CONTENT, response.getOutputAsString());
// validate response body as binary data
assertArrayEquals(TEST_DATA, response.getOutput());