Structure of the Domino Web App Container
Feb 25, 2022, 3:01 PM
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.
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.
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.
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
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
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
Filters and various listeners, but it gets the job done.
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.