Domino HttpService and the NSF Router Project
Mar 18, 2021, 3:27 PM
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
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
HttpServices. 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.
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 resources
com.ibm.domino.xsp.adapter.osgi.OSGIService, which handles OSGi-registered servlets and webapps
com.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
#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".
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:
When found, the class loads up the rules and then uses them to check incoming URLs.
#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.