Bug 58765

Summary: Default behavior change in tomcat 8.0.29-30 context root redirect process
Product: Tomcat 8 Reporter: Amb <jrivard>
Component: CatalinaAssignee: Tomcat Developers Mailing List <dev>
Status: RESOLVED FIXED    
Severity: normal    
Priority: P2    
Version: 8.0.30   
Target Milestone: ----   
Hardware: PC   
OS: Linux   

Description Amb 2015-12-24 05:42:18 UTC
Changes to tomcat 8.0.29-30 break applications that modify attributes on the HttpSession object on the initial root context url request, and possibly other places.  This behavior change is partially described in bug 58660.

Behaviors seen:

In 8.0.28, a request to the application context "/context" would result in a redirect to "/context/".  This handling was handed in a way that was invisible to the application code.  

In 8.0.29, this request is processed and the application generates a response.

In 8.0.30, this request is processed by the application, but generates a 302 to "/context/"

8.0.29-30 both break my application in similar but distinct ways.  The cause is that the application processes the initial request in a filter, modifies the session object, and then issues a redirect to itself.  Because the JSESSION cookie path is set to "/context/" and not "/context", the session seen on the subsequent handling of the redirect does not have access to the same session object as on the first request.  In a real application that depends on similar behavior this breaks the application in significant ways.

Another way to think of this is that it shouldn't be possible for the application to access an HttpSession that doesn't match the browser's session cookie.  As of 8.0.30 this implied contract is broken because on the initial request to the "/context" url, application code has access to an effectively bogus HttpSession instance.

I have created a test application that shows the different behaviors:

https://github.com/jrivard/tomcat-root-redir-test

The behavior can be reverted to the 8.0.28 style by setting the context parameter mapperContextRootRedirectEnabled=true.

I recommend the default of this setting - or some other equivalent - be changed to false.  

This issue can also be corrected by changing the context parameter sessionCookiePathUsesTrailingSlash=false, however this has a potentially negative security impact so I don't think this default should be changed.

In any case, the default behavior for this execution path should not be changing  on point releases.  My understanding is there is a similar issue in the 7.x and 9.x branch but I haven't tested them.
Comment 1 Konstantin Kolinko 2015-12-24 13:00:31 UTC
1. Behaviour can change between minor versions. Actually you are asking for such a change.

The old behaviour was not removed. You can opt-in for the old behaviour by setting mapperContextRootRedirectEnabled="true" either on your own web application or globally in conf/context.xml file.

2. Relying on mapperContextRootRedirectEnabled="true" is bad.

1) It is a Tomcat-specific feature.

2) It is application responsibility to process requests. Relying on Tomcat here is wrong. E.g. it is incompatible with RemoteIpValve.

http://tomcat.apache.org/tomcat-8.0-doc/config/valve.html#Remote_IP_Valve

You filter calls sendRedirect(). You have to change that call to correctly process requests to the root of web application (getServletPath() is "" and getPathInfo() is null) by appending '/' to the request URI.


The code to test for this condition looks like the following:

  if (request.getServletPath().length() == 0 && request.getPathInfo() == null)

The code to append '/' to requestURI looks like the following:

  StringBuilder location = new StringBuilder(requestURI);
  location.append('/');
  if (request.getQueryString() != null) {
      location.append('?');
      location.append(request.getQueryString());
  }
  response.sendRedirect(response.encodeRedirectURL(location.toString()));


Note, that handling such URIs by yourself will improve the network latency of your application.

Old behaviour:
step 1: request to /test, responds with redirect to /test/
step 2: request to /test/, your filter responds with redirect to /test/?value=random
step 3: request to /test/?value=random

Now you can send the correct redirect on step 1, skipping step 2 and omitting one 302 response round trip.



BTW, the following filter mapping in your WEB-INF/web.xml is wrong:

  <url-pattern>*</url-pattern>

I guess you meant '/*' here.

The '*' pattern does not end with '/*'. Thus it has to be used as exact match, not as a prefix match. See chapter 12.2 Specification of Mappings of the Servlet 3.1 specification.
Comment 2 Mark Thomas 2015-12-28 08:42:44 UTC
Fixed in 8.0.x (for 8.0.31 onwards) and 7.0.x (for 7.0.68 onwards).

The default was also changed in trunk and 6.0.x but no release had been made with the previous default so this bug did not affect any release of those branches.