Domino HttpService and the NSF Router Project
Thu Mar 18 15:27:04 EDT 2021
In my last post and its predecessor, I talked about my tinkering at the XspCmdManager
level of Domino's HTTP stack and then more specifically about the com.ibm.designer.runtime.domino.adapter.HttpService
class.
The Stack
Now, HttpService
is about as generic a name as you can get for this sort of thing, and it doesn't really tell you what it represents. You can think of Domino's HTTP stack since at least the 8.5 era as having two cooperating parts: the core native portion that handles HTTP requests in basically the same way as Domino always did, plus the Java layer as organized by XspCmdManager
. The Java layer gets "right of first refusal" for any incoming request that wasn't handled by a DSAPI plugin: before routing the request to the legacy HTTP code, Domino asks XspCmdManager
if it'd like to handle it, and only takes care of it at the native layer if Java says no.
XspCmdManager
on its own doesn't do much. It accepts the JNI calls from the native side, but otherwise quickly passes the buck to LCDEnvironment
(I assume the "LCD" here stands for "Lotus Component Designer"). LCDEnvironment
, in turn, really just aggregates registered handlers and dispatches requests. It does a little work to handle exception cases more cleanly than XspCmdManager
would, but it's mostly just a dispatcher.
The things that it dispatches to, though, are the HttpService
s. These are registered by using the com.ibm.xsp.adapter.serviceFactory
IBM Commons extension point, such as here in the plugin.xml form:
1 2 3 | <extension point="com.ibm.commons.Extension"> <service type="com.ibm.xsp.adapter.serviceFactory" class="org.openntf.nsfrouter.NSFRouterServiceFactory" /> </extension> |
The class you register there is an implementation of IServiceFactory
, which supplies zero or more HttpService
implementations on request.
As a side note, I've been using this extension point for years and years, but never before to actually handle HTTP requests. It's extremely convenient in that it's something you can register that is loaded up immediately when the HTTP task starts and is notified as it's terminating, giving you a useful lifecycle without having to wait for a request to come in. I learned about it from the OpenNTF Domino API team and it's been a regular part of my toolkit since.
The HttpService
So that brings us to the HttpService
implementation classes themselves. Once LCDEnvironment
has gathered them all together, it asks each one in turn (via #isXspUrl
) if it can handle a given URL. If any of them say that they can, then it calls the #doService
method on each in turn (based on the #getPriority
method's return value) until one says that it handled it.
There are a few main HttpService
implementations in action on Domino:
com.ibm.domino.xsp.module.nsf.NSFService
, which handles in-NSF XPages and resourcescom.ibm.domino.xsp.adapter.osgi.OSGIService
, which handles OSGi-registered servlets and webappscom.ibm.domino.xsp.module.nsf.StaticResourcesService
, which helps serve static resources
These services also tend to go another layer deeper, passing actual requests off to ComponentModule
implementations like NSFComponentModule
. That's beyond the scope of what I'm talking about today, but it's interesting to see just how much the Domino stack is basically one giant webapp that contains progressively smaller bounded webapps, like a Matryoshka doll.
For those keeping track, we're about here on a typical XPages call stack:
at com.ibm.domino.xsp.module.nsf.NSFComponentModule.doService(NSFComponentModule.java:1336)
at com.ibm.domino.xsp.module.nsf.NSFService.doServiceInternal(NSFService.java:662)
at com.ibm.domino.xsp.module.nsf.NSFService.doService(NSFService.java:482)
at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.doService(LCDEnvironment.java:357)
at com.ibm.designer.runtime.domino.adapter.LCDEnvironment.service(LCDEnvironment.java:313)
at com.ibm.domino.xsp.bridge.http.engine.XspCmdManager.service(XspCmdManager.java:272)
For our purposes this week, the #isXspUrl
and #doService
methods on HttpService
are our stopping points.
NSF Router Service
In a Twitter conversation yesterday, Per Lausten gave me the idea of using this low level of access to implement improved in-NSF routing. That is to say, if you want "foo.nsf/some/nice/url/here" to actually load up "index.xsp?path=nice/url/here" or the like. Generally, if you want to do this, you either have to set up Web Site rules in names.nsf or settle for next-best options like "index.xsp/nice/url/here".
Since an HttpService
comes in at a low-enough level to tackle this, though, it's entirely doable to improve this situation there. So, this morning, I did just that. This new project is a pretty simple one, with all of the action going on in one class.
The way it works is that it looks for a ".nsf" URL and, when it finds one, attempts to load a file or classpath resource named "nsfrouter.properties". The contents of this is a Java Properties file enumerating regex-based routing you'd like. For example:
1 2 | foo/(\\w+)=somepage.xsp?bit=bar baz=somepage.xsp |
When found, the class loads up the rules and then uses them to check incoming URLs.
The #doService
method then picks up that URL, does a String#replaceAll
call to map it to the target, and then redirects the browser over:
The user still ends up at the "uglier" URL, but that's the safest way to do it without breaking on-page references.
I felt like that was a neat little exercise, and one that's not only potentially useful on its own but also serves as a good way to play around with these somewhat-lower-level Domino components.