5. New and Unresolved Problems

Despite the numerous clarifications (and in some cases because of them), there are still some significant problems and ambiguities in the 2.4 specification.

5.1. Welcome Files

In addition to the non-clarification of welcome file dispatch mechanisms, the 2.4 servlet specification contains a new feature that allows servlets to be welcome "files" even if no real file exists:

SRV.9.10 ?The Web server must append each welcome file in the order specified in the deployment descriptor to the partial request and check whether a static resource or servlet in the WAR is mapped to that request URI.?

The intent of this change is to allow the welcome file mechanism to invoke template mechanisms such as TEA and Velocity that often store their templates outside of the normal web application resource hierarchy. Thus if a request is received for /foo/, the welcome mechanism can now dispatch this to /foo/index.tea even if the file index.tea does not exist in the foo directory.

Other than stretching the meaning of the word "file", this change initially looks appealing. Unfortunately it represents a significant change in the semantics of the welcome file mechanism and could break many web applications if implemented as specified.

Consider the following section of a deployment descriptor:


    index.jsp
    index.html

which is applied to a web application with the following file hierarchy:

webapp
|-- WEB-INF
|   `-- web.xml
|-- dirA
|   `-- index.jsp
`-- dirB
    `-- index.html

A request to /dirA/ will be handled as expected, as a file index.jsp exists and the request will be dispatched or redirected to /dirA/index.jsp. On many containers, this will match a JSPServlet mapped to *.jsp and execute normally.

Unfortunately, a request to /dirB/ will not serve /dirB/index.html as would be the case with the current 2.3 specification. Instead the first welcome file mapping of /dirB/index.jsp will be tried and will result in a match of the JSPServlet mapped to *.jsp. Thus a 2.4 compliant container will dispatch or redirect this request to the non-existent dirB/index.jsp resource. At best this will result in a 404 error, or possibly a 500 error complete with a nasty exception.

The situation is further muddied by the specification, as it's own examples have not been updated to reflect the inclusion of servlet mappings to the welcome file mechanism. It shows requests being passed to the default servlet, when a mapping to a JSPServlet is the more likely result.

The great pity of this flawed solution is that the problem really just reflects the deficiency of the servlet URL patterns supported by the specification. If the URL patterns were able to support even simple wildcard pattern matching, it would not be difficult to map a servlet to every URL ending in /. Even without such a change to the specification, the desired functionality of a servlet welcome resource can easily be provided with the following simple Filter:

public  class WelcomeFilter implements Filter
{
  private String welcome;
    
  public void init(FilterConfig filterConfig)
  {
    welcome=filterConfig.getInitParameter("welcome");
  }

  public void doFilter(ServletRequest request,ServletResponse response,FilterChain chain)
    throws IOException, ServletException
  {
    String path=((HttpServletRequest)request).getServletPath();
    if (welcome!=null && path.endsWith("/"))
      request.getRequestDispatcher(path+welcome).forward(request,response);
    else
      chain.doFilter(request, response);
  }

  public void destroy() {}
}

5.2. RequestDispatchers vs Wrappers

The request wrapping features introduced in 2.3 are well defined within the section of the specification that discusses filters, where it is made clear that specific types of wrapped requests and responses may be passed along a filter chain and over RequestDispatchers:

SRV.6.2.2 ?... When a filter invokes the doFilter method on the containers filter chain implementation, the container must ensure that the request and response object that it passes to the next entity in the filter chain, or to the target web resource if the filter was the last in the chain, is the same object that was passed into the doFilter method by the calling filter. ?

?The same requirement of wrapper object identity applies to the calls from a servlet or a filter to RequestDispatcher.forward or RequestDispatcher.include, when the caller wraps the request or response objects. In this case, the request and response objects seen by the called servlet must be the same wrapper objects that were passed in by the calling servlet or filter..?

While this direct passing of wrapped objects is straight forward for filters, unfortunately this requirement proves ambiguous and undesirable when applied to RequestDispatchers. Fundamentally the container cannot guarantee that request and responses will be passed unwrapped through a RequestDispatcher as the enhanced Filter mechanism now allows filters and thus wrappers to applied to dispatching.

Furthermore, the specification of the RequestDispatch mechanism is very explicit about the treatment of path methods in a passed request:

SRV.8.4 ?The path elements of the request object exposed to the target servlet must reflect the path used to obtain the RequestDispatcher. The only exception to this is if the RequestDispatcher was obtained via the getNamedDispatcher method.?

Thus, as a user supplied wrapper is free to change the values return from the path methods, SRV.8.4 indicates that the container must override any such user supplied wrappers and force the values to be those specified by the RequestDispatcher.

Containers commonly implement the RequestDispatcher requirements by applying their own request wrapper during dispatch. Containers that respect SRV.6.2.2 ?wrap under? user supplied wrappers, while those that respect SRV.8.4 ?wrap over? user supplied wrappers. These two approaches can both be justified by referring to the specification, but result if very different behaviours.

5.2.1. ?Wrap Over? vs ?Wrap Under?

Consider the following HttpServletRequestWrapper. It has been written to assist converting a web application from a case insensitive environment to a case sensitive one, so it converts all path methods to return lowercase values so that mixed case bookmarks and static content continue to work. The filter has been written to cache the results of toLowercase to avoid multiple conversions:

public LCRequestWrapper extends HttpServletRequestWrapper
{
    LCRequestWrapper(HttpServletRequest r) {super(r);}

    public String getServletPath()
    {
        if (servletPath==null)
            servletPath=super.getServletPath().toLowercase();
        return servletPath;
    }
    
    public String getPathInfo()
    {
        if (pathInfo==null)
        {
            pathInfo=super.pathInfo();
            if (pathInfo!=null)
                pathInfo=pathInfo.toLowercase();
        }
        return servletPath;
    }

    private String servletPath;
    private String pathInfo;
}

This wrapper looks straight forward, but can have very unexpected behaviour if used in a ?wrap under? container. When a dispatch is done, the container will insert a request wrapper underneath this user supplied wrapper, so that the target resource will call the user wrapper directly. The user wrapper may call super methods which may return different values for the duration of the dispatch.

If the dispatching resource calls a path method before the request is forwarded to the target resource, then the target resource will see the cached value calculated before the dispatch, which will not be the correct value for the target.

If a path method is first called by the target resource, then the cached value will be set from dispatched values of the path. This value will be incorrectly returned to any call made by the dispatching servlet after the dispatch.

If ?wrap over? semantics are applied to this example, then the behaviour is well defined as the wrapped path methods are not visible during dispatch as they are hidden by the SRV.8.4 compliant wrapper.

While this example is somewhat contrived and can simply be fixed by removing the value cache, it does indicate that ?wrap under? semantics can be more complex than first appearances. To avoid such indeterminate behaviour, a wrapper must avoid having any fields that are derived from methods that may be changed during a request dispatch.

Unfortunately some values, such as streams and writers must be cached by a wrapper as they cannot be reproduced on each call. The following filter follows a similar pattern to most filters that wrap the responses stream or writer with a specialized version for compression or encryption etc.

public MyResponseWrapper extends HttpServletResponseWrapper
{
    MyResposneWrapper(HttpServletResponse r) {super(r);}
    
    public ServletOutputStream getOutputStream() throws IOException 
    {
        if (_writer!=null)
            throw new IllegalStateException("getWriter called");
        if (_out==null)
            _out=new MyOutputStream(super.getOutputStream());
        return _out;
    }  
    
    public PrintWriter getWriter() throws IOException 
    {
        if (_out!=null)
            throw new IllegalStateException("getOutputStream called");
        if (_writer==null)
            _writer=new MyWriter(super.getWriter());
        return _writer;
    }
    
    ServletOutputStream _out;
    PrintWriter _writer;
}

This wrapper contains the state of which type of output stream has been requested. Consider if this wrapper is passed to a servlet that calls getOutputStream before including of a resource that is generated using getWriter.

The call to getOutputStream will initialize the specialized MyOutputStream. If wrap under semantics are used during the include, then this wrapper will also be passed to the second resource. When getWriter is called an IllegalStateException will result. This implies that the user of RequestDispatch.include must be aware of the implementation details of the requested resource and can only use those with the same flavour of implementation.

With ?wrap over? semantics, the container is given the opportunity to hide the state of the including resource. The included resource can be passed a response wrapper provided by the container that itself wraps the MyResponseWrapper instance. When getWriter is called, it initially calls the wrapped getWriter which will throw an IllegalStateException. The container supplied wrapper can catch this exception and then call the wrapped getOutputStream and wrap the specialized stream returned in an OutputStreamWriter.

5.2.2. Resolution of RequestDispatchers vs Wrappers

The next version of the specification must address this conflict if portable filters and servlets are to be written using request and response wrapping. To resolve which part of the specification should prevail we need to examine the motivation for the requirement for direct passing of objects:

SRV.6.2.2 ?... In this model, the developer not only has the ability to override existing methods on the request and response objects, but to provide new API suited to a particular filtering task to a filter or target web resource down the chain.?

This close coupling of components is not best practise in an environment where the development of utility filters and wrappers is encouraged to intercept arbitrary requests. For example if a web application was written to assume the direct passing of a wrapper type from a dispatching servlet to a resource producing HTML, then the application of the common compression filter to all HTML resources would break this assumption and cause a class cast exception.

There exist several other mechanism to allow required coupling between web application components that are more flexible and not vulnerable to the extended filter mechanism. Static methods, request attributes, session attributes, ThreadLocals and unwinding of wrappers can all be used to achieve the aim of providing new API for a web application.

It is the author's hope that the next revision of the servlet specification will relax the object passing requirements of SRV.6.2.2 and discourage the associated closely couple programming model. The ?wrap over? semantics should be specifically required for RequestDispatching.

5.3. Distributable Web Applications

The specification of distributed HttpSessions remains very poor in the 2.4 specification with no clarifications made to any of the significant omissions in the specification:

  • Should the HttpSession.setAttribute method be considered pass-by-value or pass-by-reference? i.e. at what time should the session values be distributed. Non distributed sessions allow the value to be changed at any time by any thread or mechanism and the next call to getAttribute will return the updated value. However, unlike EJB's there is no uniform access path to session values nor transactional model, so such changes cannot simply be distributed. Many distribution implementation avoid this issue by changing the semantics for distributed sessions to be pass-by-value and only distributing session values when setAttribute is called.

  • How are bind and unbind events handled in a distributed session? Is bind called in every JVM that value is distributed to? Or is it only called in the JVM where the value was originally added and then distributed to other JVMs without further events.

  • How is non-gentle migration handled? If a node fails and no session passivation event was handled, should a session activation event be issued on the new node?

Until these issues are clarified, distribution will remain a very container specific mechanism.