Invoker Servlet
Overview
Introduction
The purpose of the Invoker Servlet is to allow a web application to dynamically register new servlet definitions that correspond with a
<servlet>
element in the/WEB-INF/web.xml
deployment descriptor, and execute requests utilizing the new servlet definitions. From the perspective of the newly registered servlets, all servlet lifecycle requirements of the Servlet Specification (such as callinginit()
anddestroy()
at the correct times) will be respected.
External Specifications
I do not know of any formal specification of the behavior of an invoker servlet that is publicly available. Anyone know of one?
Implementation Requirements
The implementation of this functionality shall conform to the following requirements:
- Implemented as a servlet.
- Exist in the
org.apache.catalina.servlets
package so that it can be loaded by the Catalina class loader.- Implement the
org.apache.catalina.ContainerServlet
interface, so that it gains knowledge of theWrapper
that is responsible for itself and, therefore, access to other internal Catalina components.- Support a configurable debugging detail level.
- Log debugging and operational messages (suitably internationalized) via the
getServletContext().log()
method.
Dependencies
Environmental Dependencies
The following environmental dependencies must be met in order for the Invoker servlet to operate correctly:
- The invoker servlet must be registered in the application deployment descriptor (or the default deployment descriptor in file
$CATALINA_HOME/conf/web.xml
) using a "path mapped" servlet mapping. The historical default mapping is to URL pattern "/servlet/*
", although the invoker servlet must operate correctly with an arbitrary mapping.
Container Dependencies
Correct operation of the invoker servlet depends on the following specific features of the surrounding container:
- Correct support for the
ContainerServlet
interface, including callingsetWrapper()
before theinit()
method of the invoker servlet is called.- The web application class loader must be stored as the context class loader of the request processing thread.
Functionality
Initialization Functionality
The following processing must be performed when the
init()
method of the invoker servlet is called:
- Ensure that the container has called
setWrapper()
. If not, throw a permanentUnavailableException
.- Look up and cache the
Context
that corresponds to ourWrapper
. This is the component with which new servlet definitions and mappings will be registered.
Per-Request Functionality
On each request, the following processing shall be performed:
- Calculate the
{ServletPath}
for this request, either from request attributejavax.servlet.include.servlet_path
or by callingrequest.getServletPath()
.- Calculate the
{PathInfo}
for this request, either from request attributejavax.servlet.include.path_info
or by callingrequest.getPathInfo()
. If the calculated{PathInfo}
is null, return HTTP status 400 (bad request).- Parse the calculated
{PathInfo}
value as follows:
- Ignore the leading slash character.
- Accumulate characters up to the next '/' (if any) as the
{ServletSelector}
.- If a '/' was encountered, accumulate all characters from that slash (inclusive) to the end of the string as
{PathRemainder}
. If no slash was encountered, set{PathRemainder}
to a zero-length string.- Determine whether
{ServletSelector}
is the name of an existing servlet definition, and process it as follows:
- Ask our associated
Context
to find and return a childWrapper
named{ServletSelector}
.- If there is no such child, skip to the next major step.
- Register a new servlet mapping for this
Wrapper
, using a URL pattern calculated as follows:{ServletPath}
+ "/" +{ServletSelector}
+ "/*"- Create a request dispatcher using a path calculated as follows:
{ServletPath}
+ "/" +{ServletSelector}
+{PathRemainder}
- Forward this request to the created request dispatcher, and exit from this request.
- Assume that
{ServletSelector}
is the fully qualified name of a Java class that implementsjavax.servlet.Servlet
and process it as follows:
- Synthesize a new
{ServletName}
for the servlet definition that will be created.- If there is already a child
Wrapper
associated with this name, return HTTP status 500 (internal server error), because a mapping should have already been created for this servlet.- Attempt to load a class named
{ServletSelector}
from the web application class loader (i.e. the context class loader for our current thread). If this fails, return HTTP status 404 (not found).- Instantiate an instance of this class. If an error occurs, return HTTP status 404 (not found).
- If this class does not implement the
javax.servlet.Servlet
interface, return HTTP status 404 (not found).- Create and register a new
Wrapper
child with ourContext
, under name{ServletName}
.- Register a new servlet mapping for this
Wrapper
, using a URL pattern calculated as follows:{ServletPath}
+ "/" +{ServletSelector}
+ "/*"- Create a request dispatcher using a path calculated as follows:
{ServletPath}
+ "/" +{ServletSelector}
+{PathRemainder}
- Forward this request to the created request dispatcher, and exit from this request.
Finalization Functionality
No specific processing is required when the
destroy()
method is called:
Testable Assertions
In addition the the assertions implied by the functionality requirements listed above, the following additional assertions shall be tested to validate the behavior of the invoker servlet:
- It is possible to access an existing servlet definition by name
through the invoker. The existing servlet definition can include
either a
<servlet-class>
or<jsp-file>
subelement. - When an existing servlet definition is accessed by name, the request will be ultimately processed by the same servlet instance that would have processed it had a mapping to that servlet definition been used on the request directly.
- It is possible to access an anonymous servlet by class name through the invoker.
- When an anonymous servlet is accessed, the servlet instance is processed according to the lifecycle requirements of the Servlet Specification.
- When an anonymous servlet is accessed, the servlet instance receives
a
ServletConfig
instance with no servlet initialization parameters. - It is possible to utilize the invoker servlet via a direct request.
- It is possible to utilize the invoker servlet via a call to
RequestDispatcher.forward()
, or the corresponding<jsp:forward>
tag in a JSP page. - It is possible to utilize the invoker servlet via a call to
RequestDispatcher.include()
, or the corresponding<jsp:include>
tag in a JSP page. - It is possible to use any HTTP method (including GET and POST) that is supported by the Servlet class that is ultimately executed.
- The invoker servlet should never be asked to process a second or
subsequent request for the same
{ServletSelector}
(because it will have registered an appropriate servlet mapping.