|
Despite the numerous clarifications (and in some cases because of them), there are still some significant problems and ambiguities in the 2.4 specification. 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:
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:
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() {} } 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:
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:
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. 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. 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:
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. 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:
Until these issues are clarified, distribution will remain a very container specific mechanism. |
|
||||||
© 2003 Core Developers Network Ltd "Core Developers Network", the stylized apple logo and "Core Associates" are trademarks of Core Developers Network Ltd. All other trademarks are held by their respective owners. Core Developers Network Ltd is not affiliated with any of the respective trademark owners. |