Warning: As usual, the following is just my own opion based on my current work. Some of it is “off the cuff” ideas I haven’t fully thought through yet. So if I’ve entirely misunderstood anything, or missed some vital fact, please let me know!
This week I’ve been finishing off some work involving Servlet request dispatching, and have once again been rather frustrated by the way the Servlet API handles HTTP vs non-HTTP requests.
The particular issue I’ve encountered is that whilst HttpServletRequest exposes path information (via getRequestURI, getRequestURL, getContextPath, getServletPath, getPathInfo, getPathTranslated), this is all introduced in HttpServletRequest itself, and none of it is present in the underlying ServletRequest interface. This implies that the Servlet API regards such URI and path information as being protocol-specific, and relevant for HTTP requests but not necessarily for other protocols.
That might be fair enough, depending on how broad a set of protocols you regard the Servlet API as being potentially applicable to. But the problem is that much of the rest of the API seems to take it for granted that requests are based on URIs and server-relative paths, and it’s sometime difficult to know what this means when handling a general ServletRequest that isn’t necessarily an HttpServletRequest and thus doesn’t expose any URI or path details.
For example, the Javadoc for RequestDispatcher.forward(ServletRequest, ServletRequest) states that “the ServletRequest object has its path elements and parameters adjusted to match the path of the target resource”… which doesn’t especially make sense for ServletRequests in general if these don’t have such things as “path elements”. Similarly, it seems rather odd that a non-HTTP request can be “forwarded” to a specific path but still can’t expose path information because it is a non-HTTP request and thus doesn’t have a path. More generally, and more fundamentally, deployment descriptor servlet-mappings, filter-mappings, web-resource-collections etc are all based on “URL patterns” for matching against the request’s path – which assumes that requests have a path.
In theory I guess the underlying design is that the Servlet API itself only supports the HTTP protocol, but also provides base classes on which extensions to handle other protocols can be built. In practice, the implementation of this split seems far from perfect. For example:
- The Javadoc of many of the javax.servlet protocol-independent interfaces make explicit provision for HTTP (such as ServletResponse specifying how its properties affect HTTP headers). It doesn’t smell right for a base class to be telling a specific subclass how it should handle its own particular special case.
- As per RequestDispatcher.forward, the Javadoc definitions of methods that handle ServletRequests and ServletResponses sometimes refer to, and implicitly assume, features that are only present in the relevant HTTP-specific subclasses.
- The use of inheritance for this relationship between protocol-independent base classes and protocol-specific subclasses is problematic once the need for “wrapper” classes is introduced. A wrapper for a ServletRequest isn’t an HttpServletRequest even when its underlying request is; so typically you need one wrapper class for requests that are HttpServletRequests, another wrapper class for any other protocol you’re supporting, and another for general ServletRequests. Then you typically need a corresponding set of wrappers for the responses. Then if you need subclasses of these wrappers for any reason, you need a separate subclass for each of those wrappers. This can quickly expand into a large hierarchy of different wrapper classes, with “switch”-like code to decide which type of wrapper to use based on the request’s actual type.
The lack of path information within ServletRequest thus appears to be part of a broader ambiguity as to whether the Servlet API is only concerned with HTTP or makes provision for non-HTTP protocols, and if so what types of protocols it can and can’t cater for. The current Servlet API appears to me to fall somewhere in the middle, with an architecture that theoretically allows a broad range of other protocols to be supported, but with method definitions that sometimes entirely ignore this and sometimes just silently assume HTTP.
Of course, in practice this only really affects the internal implementation of servlet containers, or other such implementations of the Servlet API interface classes. Most servlets and other web-application code can simply be HTTP-specific, and can cast all requests and responses to the HTTP-specific subclass and ignore these issues. Similarly, I’d guess that most servlet containers only support HTTP anyway, and can thus always cast each ServletRequest to its own internal HttpServletRequest implementation whenever necessary. Any servlet containers that do want to support other protocols can use whatever code and non-standard extensions to the Servlet API they find necessary, and even if this is rather harder than it ought to be it probably isn’t that big a deal.
So none of this really poses any serious problems, it just seems less than ideal. We seem to have a Servlet API that goes half-way towards catering for other protocols, and is more complex than would be necessary for HTTP on its own, without actually making it particularly easy to support other protocols. My own particular gripe is mainly with the inconsistency of the Javadoc, though getting back to my original problem, I still think it’d be useful and reasonable for javax.servlet.ServletRequest to make URI and/or path information available when appropriate.
It’s probably way too late for any of this to ever be changed, or even improved in any meaningful way. But if I put on my “blue sky” hat, a number of ideas come to mind (some fairly straightforward, some probably rather wild and crazy, or at best impractical at this stage):
- Move getRequestURL, getRequestURI (and possibly some or all of the other path-related methods) from HttpServletRequest up into ServletRequest (if necessary, with provision for these to return null where not applicable for the request’s protocol).
- Remove all references to HTTP from the javax.servlet classes, and instead move any such details into the javax.servlet.http classes (e.g. by defining overrides for the underling javax.servlet methods, even where the only change is in the method’s Javadoc in order to specify the HTTP-specific behaviour).
- Explicitly limit the scope of the Servlet API to URI-based protocols, or possibly even protocols that use URIs that include “path”s.
- Introduce one or more additional layers into the Servlet API to provide base classes for categories of protocols (e.g. for all URI-based protocols). For example, HttpServletRequest extends URIPathServletRequest extends URIServletRequest extends ServletRequest or something like that). However, I don’t like this idea as it would introduce far more complexity than it would be worth (e.g. see the “inheritance” problems noted above).
- Provide protocol-specific facilities by composition rather than inheritance (e.g have ServletRequest contain a Set of protocol-specific “extension” objects that provide the protocol-specific properties and methods). This might even allow various features that are common to multiple protocols to be separated out and then used in “mix and match” fashion, e.g. cookie support, SSL etc (rather than each individual protocol having to separately provide all such facilities).
- Limit the Servlet API to being entirely HTTP-specific, and simplify it accordingly, leaving any support for other protocols as being entirely separate and container-specific/proprietary.
Out of interest, does anyone know of an actual use of the Servlet API for a protocol that doesn’t use URIs with paths? Or for a protocol that don’t use URIs at all? Is anyone implementing or using the Servlet API for protocols other than HTTP/HTTPS? I seem to remember there being JSRs for supporting the SIP protocol on top of the Servlet API – can anyone comment on how easy or hard it was to implement this on top of the javax.servlet classes? I haven’t managed to find anybody else commenting on this, but I’d like to think I’m not just imagining these issues…