The graphs above show the results of running 5,000 requests using Apache Bench against OSGi, resource-based and path-based servlets*. While the resource and path-based servlets were able to process an average of 1313.33 requests per second, the OSGi HTTP servlet was able to process 12,379.27 requests per second or approximately 10x as many requests per second!
Based on this performance difference, OSGi HTTP servlets will be significantly faster than Sling Servlets, as long as you don’t need to access the Sling / AEM repository. A few examples would include servlets to post form data back to a backend system or to perform a search in a 3rd party system and return the results.
What if you do need to access the repository though? Does this performance improvement still hold?
I created a similar test case, but creating a service user based resource resolver and reading a resource property to see:
Even with having to open a Resource Resolver and read a resource, the OSGi HTTP Servlet can still handle approximately 4.5x more requests per second than the Sling servlets.
This still skips the Sling Authentication process, which is an expensive part of standard Sling request processing. Here’s a third run using the SlingAuthenticator for authenticating the OSGi HTTP Servlet request.
It may not look like much of a difference, but as Mark Twain said, there’s lies, damn lies and statistics. I fixed the graph’s maximum to ensure consistency, if I remove the maximum, it tells a much different story:
Even using Sling Authentication, an OSGi HTTP servlet is still able to process 1.9x more requests per second.
Are OSGi HTTP servlets appropriate for every use case? Unsurprisingly, no. For most common resource based use cases or non-public facing servlets, resource or path based servlets are easier and simpler.
OSGi servlets are most useful for performance-critical, non-content oriented use cases such as serving search results, handing form posts or acting as a proxy for a backend API.
OSGi servlets start with a context. In fact, every servlet in Apache Sling / AEM is in the org.apache.sling context by default. To create your servlet’s context, register a service for the ServletContextHelper abstract class, something like:
@Component(service = ServletContextHelper.class, property = {
"osgi.http.whiteboard.context.name=" + TestHttpContext.CONTEXT_NAME,
"osgi.http.whiteboard.context.path=/test"
})
public class TestHttpContext extends ServletContextHelper {
public static final String CONTEXT_NAME = "com.danklco.blog.test";
private final MimeTypeService mimeTypeService;
private final AuthenticationSupport authenticationSupport;
/**
* Constructs a new context using contructor injection
*
* @param mimeTypeService Used when providing mime type of requests
* @param authenticationSupport Used to authenticate requests with sling
*/
@Activate
public TestHttpContext(@Reference final MimeTypeService mimeTypeService,
@Reference final AuthenticationSupport authenticationSupport) {
this.mimeTypeService = mimeTypeService;
this.authenticationSupport = authenticationSupport;
}
/**
* Returns the MIME type as resolved by the MimeTypeService
*/
@Override
public String getMimeType(String name) {
return mimeTypeService.getMimeType(name);
}
/**
* Always returns <code>null</code> because resources are all provided
* through individual endpoint implementations.
*/
@Override
public URL getResource(String name) {
return null;
}
/**
* Only attempts to authenticate requests to /test/auth
*/
@Override
public boolean handleSecurity(HttpServletRequest request,
HttpServletResponse response) throws IOException {
if (request.getRequestURI().equals("/test/auth")) {
return this.authenticationSupport.handleSecurity(request, response);
} else {
return true;
}
}
}
See TestHttpContext for the full code. A few things to note:
Here’s a simple example servlet:
@Component(service = { Servlet.class }, property = {
HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN + "=/demo/*",
HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT + "=("
+ HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_NAME + "=" + TestHttpContext.CONTEXT_NAME + ")" })
public class AuthOsgiServlet extends HttpServlet {
@Override
protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().write("Hello " + getResourceResolver().getUserID());
}
public ResourceResolver getResourceResolver(final HttpServletRequest request) {
Object resolverAttribute = request.getAttribute(AuthenticationSupport.REQUEST_ATTRIBUTE_RESOLVER);
if (resolverAttribute instanceof ResourceResolver) {
return (ResourceResolver) resolverAttribute;
}
throw new SlingException("Could not get ResourceResolver from request", null);
}
}
In the @Component annotation properties, the servlet is using the TextHttpContext and is registering for the pattern /demo/*. This means that the servlet will handle all requests under /test/demo.
As noted above the getResourceResolver method retrieves the request’s ResourceResolver from the request attribute set by the Sling Authenticator. The attribute will not be set if you do not authenticate the request in the context. You can use this ResourceResolver to retrieve resources from the repository on behalf of the requesting user.
Hopefully this gives you a good overview of OSGi HTTP servlets and explains why they can be a valuable tool in your toolbelt. You can find more examples in the com.danklco.blog.servletdemo repository. Please leave a comment if you have any questions!
* Performance Methodology:
Environment:
- AEM Version: 2022.10.9398.20221020T071514Z-220900-000001
- System: macOS 12.6 (21G115), 8-Core Intel Core i9, 16 GB Memory
- Java: OpenJDK 64-Bit Server VM(build 11.0.11+9, mixed mode)
Collected performance metrics with Apache Bench v2.3 using 5,000 requests and 5 concurrent requests. Converted to CSV via AB Parser v0.1.0. Graphs created in Excel.
Sling Authentication was disabled for the first two resource and path-based tests.
More information can be found in the com.danklco.blog.servletdemo repository.