Structure of the Domino Web App Container
Fri Feb 25 15:01:33 EST 2022
A while back, I talked about the uses of HttpService
in Domino. In that post, I talked about how the various HttpService
implementations take a look at incoming URLs, see if they're something that should be handled on the Java layer, and then either handle them or pass them back to the legacy NHTTP code to do its thing. My fiddling with the XPages Jakarta EE project in recent days has gotten me thinking about this layer again, and I think it'll be interesting to expand how how this whole layer works (at least as I understand it).
Along with this post, it might be useful to peruse the slide deck for AD105 from LotusSphere 2011. There's a lot of good stuff in there, and basically nothing has changed in the intervening 11 years.
The Stack (Conceptually)
The Domino HTTP stack looks conceptually something like this:
This is, setting aside the specifics, pretty similar to how other app servers of various kinds are laid out. There's some bottom layer that handles the actual network connection, some part just above that that handles interpreting the requests as HTTP (optionally bypassed in some cases), and then an orchestrator that manages the actual apps sitting on the top layer and routes requests as appropriate.
The Stacks (Java-wise)
Before I continue, I think it will be useful to have some stack traces to reference back to, to see what they share in common and where they diverge. These three examples - from an XPage request, an Equinox-registered Servlet, and an OSGi-packaged webapp - all cover the part of the stack from the bottom up until where user code comes into play.
XPages (DesignerFacesServlet
is what handles serving an XPage):
1 2 3 4 5 6 7 8 9 10 11 12 | at com.ibm.xsp.webapp.DesignerFacesServlet.service(DesignerFacesServlet.java:103) at com.ibm.designer.runtime.domino.adapter.ComponentModule.invokeServlet(ComponentModule.java:600) at com.ibm.domino.xsp.module.nsf.NSFComponentModule.invokeServlet(NSFComponentModule.java:1352) at com.ibm.designer.runtime.domino.adapter.ComponentModule$AdapterInvoker.invokeServlet(ComponentModule.java:877) at com.ibm.designer.runtime.domino.adapter.ComponentModule$ServletInvoker.doService(ComponentModule.java:820) at com.ibm.designer.runtime.domino.adapter.ComponentModule.doService(ComponentModule.java:589) at com.ibm.domino.xsp.module.nsf.NSFComponentModule.doService(NSFComponentModule.java:1336) at com.ibm.domino.xsp.module.nsf.NSFService.doServiceInternal(NSFService.java:725) at com.ibm.domino.xsp.module.nsf.NSFService.doService(NSFService.java:515) at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.doService(LCDEnvironment.java:363) at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.service(LCDEnvironment.java:319) at com.ibm.domino.xsp.bridge.http.engine.XspCmdManager.service(XspCmdManager.java:272) |
Equinox Servlet (the org.eclipse.equinox.http.registry.servlets
extension point):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | at com.example.SomeServlet.service(SomeServlet.java:104) at javax.servlet.http.HttpServlet.service(HttpServlet.java:806) at org.eclipse.equinox.http.registry.internal.ServletManager$ServletWrapper.service(ServletManager.java:180) at org.eclipse.equinox.http.servlet.internal.ServletRegistration.handleRequest(ServletRegistration.java:90) at org.eclipse.equinox.http.servlet.internal.ProxyServlet.processAlias(ProxyServlet.java:111) at org.eclipse.equinox.http.servlet.internal.ProxyServlet.service(ProxyServlet.java:67) at javax.servlet.http.HttpServlet.service(HttpServlet.java:806) at com.ibm.domino.xsp.adapter.osgi.OSGIModule.invokeServlet(OSGIModule.java:167) at com.ibm.domino.xsp.adapter.osgi.OSGIModule.access$0(OSGIModule.java:153) at com.ibm.domino.xsp.adapter.osgi.OSGIModule$1.invokeServlet(OSGIModule.java:134) at com.ibm.domino.xsp.adapter.osgi.AbstractOSGIModule.invokeServletWithNotesContext(AbstractOSGIModule.java:181) at com.ibm.domino.xsp.adapter.osgi.OSGIModule.doService(OSGIModule.java:128) at com.ibm.domino.xsp.adapter.osgi.OSGIService.doService(OSGIService.java:418) at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.doService(LCDEnvironment.java:363) at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.service(LCDEnvironment.java:319) at com.ibm.domino.xsp.bridge.http.engine.XspCmdManager.service(XspCmdManager.java:272) |
Web Container (the com.ibm.pvc.webcontainer.application
extension point):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | at ccom.example.ExampleServlet.doGet(ExampleServlet.java:18) at javax.servlet.http.HttpServlet.service(HttpServlet.java:693) at javax.servlet.http.HttpServlet.service(HttpServlet.java:806) at com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1661) at com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:937) at com.ibm.pvc.internal.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:85) at com.ibm.ws.webcontainer.servlet.ServletWrapper.handleRequest(ServletWrapper.java:500) at com.ibm.ws.webcontainer.webapp.WebApp.handleRequest(WebApp.java:3810) at com.ibm.ws.webcontainer.webapp.WebGroup.handleRequest(WebGroup.java:276) at com.ibm.pvc.internal.webcontainer.VirtualHost.handleRequest(VirtualHost.java:143) at com.ibm.ws.webcontainer.WebContainer.handleRequest(WebContainer.java:931) at com.ibm.pvc.internal.webcontainer.WebContainerBridge.handleRequest(WebContainerBridge.java:25) at com.ibm.domino.osgi.core.webContainer.WebApplicationsTracker.doService(WebApplicationsTracker.java:141) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.ibm.domino.xsp.adapter.osgi.webContainer.OSGIWebContainerModule.invokeWebAppContainerService(OSGIWebContainerModule.java:207) at com.ibm.domino.xsp.adapter.osgi.webContainer.OSGIWebContainerModule.doService(OSGIWebContainerModule.java:178) at com.ibm.domino.xsp.adapter.osgi.OSGIService.doService(OSGIService.java:418) at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.doService(LCDEnvironment.java:363) at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.service(LCDEnvironment.java:319) at com.ibm.domino.xsp.bridge.http.engine.XspCmdManager.service(XspCmdManager.java:272) |
Each one of these has some intriguing lines, but we'll end up focusing on the bottom 5-6 in each.
Anyway, back to the details.
Orchestrator
The bottom three lines of all three stacks are identical, and they show the entrypoint from the C side to Java.
The job of XspCmdManager
is to take a bunch of handles and flags given to it by the C side, wrap them into something a little more suitable for polite company, and pass that off immediately to LCDEnvironment
. At this level, while the code is clearly focused around getting to the point of handling Servlets, the actual classes don't actually implement javax.servlet
parts - they're all a little more abstract than that. XspCmdManager
has a few other responsibilities, but it's best thought of as just the glue layer between the native side and the Java stack.
From there, it passes the request along to LCDEnvironment
, as the sole implementation of LCDRequestHandler
. Things get a little meatier here. This is the part that loads up all of the HttpService
implementations we've discussed before. It uses these services to answer calls to isXspUrl
(which basically means "should Java handle this?") and then to handle the incoming requests. The first HttpService
(sorted by its getPriority()
value) that will handle the incoming request gets it, and here's our first point of divergence above. NSFService
is the in-NSF XPages-and-stuff handler, taking care of requests with ".nsf" and either "xsp" or a registered extension in the path. OSGIService
, for its part, handles both Equinox-registered servlets and Expeditor webapps, albeit in different ways.
ComponentModules
The next layer is interesting, and it's a part I didn't really talk about in the previous post. While an HttpService
can just handle an incoming request directly (as the proxy service in the Domino Open Liberty Runtime project does), the idiom in the Domino stack is to use ComponentModule
implementations to do it. These correspond conceptually to web apps deployed from WAR files in a standard app server: they're a cordoned-off blob of user code, with its own ClassLoader
and notions for how to access resources.
For an NSF, this is NSFComponentModule
. These objects are spawned by NSFService
as needed when a matching request comes in for an NSF the first time and create a weird sort of webapp out of the NSF contents (with special handling for Single-Copy XPage Design). It doesn't go as far in that direction as the full web container, but it's enough to run and retain the XPages application. This type of module also opts in to the IServletFactory
extension system, where contributors can add Servlets to the module either internally or via an OSGi extension. All ComponentModule
types have the ability to opt in to this, but NSFComponentModule
is the only one that does in practice.
Though both the Equinox Servlet and the PVC web container app go through OSGIService
, here is where they split apart into OSGIModule
(for standalone Servlets) and OSGIWebContainerModule
.
OSGIModule
uses some of the parts of the Equinox Servlet Container support to run an individual Servlet. It's the smallest of the three and mostly just creates the ServletRequest
et al implementations, does a little wrapping in Equinox garb, and passes them on to your HttpServlet
class.
OSGIWebContainerModule
is fancier, since its job is to run (most of) a JEE-style web.xml-based app contained in an OSGi bundle. To do this, it uses a chopped-down fork of WebSphere - indeed, many of the classes in the stack still exist in much the same form in Liberty, but here are intermingled with Domino-specific stuff and some Expeditor PvC detritus. This isn't quite as capable as a full Servlet 2.5 container, lacking things like Filter
s and various listeners, but it gets the job done.
Module Adaptability
Though this system hasn't in practice grown beyond the built-in implementations to my knowledge, it's a neat little structure. There's some unfinished stuff in there in a mashupmaker
package that I guess must have been to do with Lotus Mashups (which is apparently a product that existed at some point), but that's about it as far as extending it.
This is an intriguing layer, though, since it's also the one where the "adapter" Servlet objects from above are actually turned into instances of javax.servlet
classes, specifically using classes like LCDAdapterHttpServletResponse
. At this layer, you have a good amount of Java scaffolding, but being before the full conversion to javax.servlet
classes means that a lot of the limitations in Domino's Servlet support only really come in in the upper echelons. Other than network- and HTTP-layer technologies like WebSocket and HTTP/2 (which are handled at the native layer), it'd be entirely possible to plug into this system with more-modern technologies while still participating cleanly in the environment. For example, you could write an HttpService
that declares a higher priority than NSFService
and use it to treat an NSF as an entirely-different sort of app, intercepting all URLs prefixed with it. I don't know that it would be a good idea to do so, but it's possible, and it's fun to think about.