This week's book giveaway is in the OCAJP forum. We're giving away four copies of OCA Java SE 8 Programmer I Study Guide 1Z0-808 and have Jeanne Boyarsky & Scott Selikoff on-line! See this thread for details.
I have tomcat running behind a load balancer which removes the https. Tomcat receives http and when it gets a request for a directory without a trailing slash Tomcat issues a 302 to the same (http) url with the forward slash added. This fails since the request comes back from the client and is not https. So I am wondering if there is a configuration to have tomcat either not redirect at all or to do a server-side redirect, or to configure all redirects to use https?
As I've said from time to time, a web server is not a file server. A web server accepts Uniform Resource Locators (URLs). It parses the URL and determines what to do, based on that information.
When a URL doesn't match any of the URL patterns defined in the application's web.xml file, the URL is delegated to Tomcat's own global web.xml file. As shipped, it is set up to send the URL request to Tomcat's built-in default servlet (which is what the web I indicated describes).
The unresolved URL's post-context part is then converted to a resource path relative to the WAR root. It is this process that makes Tomcat "serve files". The resource is located and a check is made to see the resource is a JSP. If so, the JSP cache is checked, and if the JSP hasn't been compiled yet, the JSP is compiled into the cache and the cached JSP code (which is a servlet class) is passed the URL.
If the resource is not a JSP, the default servlet takes the resource location. opens the resource, and copies it to the HTTP Response output stream, after first sending the appropriate response headers. If the resource could not be found, a "404" error response is generated, instead.
But one thing did not ring true about your problem. You assert that Tomcat is generating a "302" response. This didn't make sense, since it would effectively send the request back to the client when in fact, the default behavior for a URL that parses to a directory location is to list the directory (except in cases where listing is suppressed by configuration option).
I've been looking at the DefaultServlet source code. I can see where the directory listing function is, but I cannot see any place at all where the DefaultServlet does a redirection.
In other words, I think the "302" is coming from the software that's front-ending Tomcat, and not Tomcat itself.
this is for anyone else hitting this specific error. I got this same error when using load balancer and Tomcat is indeed generating the 302 response. Tim is correct about the way Tomcat is handling things, but fails to see the ultimate reason why Tomcat generates this error.
So, your web application is behind load balancer that takes in HTTPS from browser and sends the request forward as HTTP. This is the key to understanding our problem.
- user writes url in browser, eg. https://www.domain.com/folder - load balancer takes in the request and sends it forward as HTTP
- like the HTTP-protocol dictates, Tomcat starts to check if the folder-resource is some predefined file or application and thus checks if there are files such https://www.domain.com/folder.jpg, https://www.domain.com/folder.gif etc.
- Of course, because the folder-path is indeed directory, Tomcat does not find any file
- Tomcat redirects browser and adds slash-character to the end of the url, because the resource probably is directory. The problem is, as Tomcat thinks the browser is using HTTP instead of HTTPS (because the load balancer hides this fact), it redirects the browser to http://www.domain.com/folder/ instead of https://www.domain.com/folder/
And thus we hit the problem. I'm not exactly sure what way would be the best to handle this, but I have requested the person in charge of the load balancer to add the trailing slash if user has omitted it. I have to admit that this offends what the HTTP-protocol dictates but it works for us.
There are a couple of problems with this theory. Before I made my last pompous pronouncement, I looked at Tomcat's actual code. If there's a redirect in the directory-listing function I'm too blind to see it (which isn't impossible. ). On the other hand, a protocol negotiation would be done in cases where a URL pattern-matches a transport constraint in web.xml, whether the request targets a directory or not.
Tomcat itself doesn't pay attention to "http" or "https" in the URL as such. That's mainly a convenience for the client. If a client wants a URL whose absolute path begins with "http:", then it knows to send data formatted according to the HTTP protocol, in clear text and aim it at the server's port 80 (unless overridden to something like 8080). If the protocol for the URL is defined as "https:", then the client will negotiate a secure (encrypted) channel, and transmit the request to the server port 443 (again, assuming no override). You can send HTTP to port 443 and HTTPS to port 80 by coding it into your URLs, and Tomcat will complain because the data isn't in the right shape for the indicated target port.
There's nothing inherently "wrong" about a URL ending in a trailing slash, although I admit I don't find it aesthetic. But the URL is not a filesystem path, it's a resource identifier. Some apps even get designed to take the same basic URL and process the "bare" version of it through one code function and the trailing-slash version through a completely different code function. Not something I really recommend, though.
In the cast of Tomcat, as I said before, the "index" function is processed by the DefaultServlet unless you customize Tomcat. According to the J2EE spec, all resources in a WAR are in a ZIP-format file, not independent filesystem objects, so the DefaultServlet doesn't actually use the java.io.File list(), isFile() or isDirectory() methods, but it does do the resource-based equivalent. And, you might be interested to know, it does include a trailing slash when creating a listing URL for a parenting ("directory") resource. It also does a URL rewrite to retain session identity. So even when a URL lacking a trailing slash comes in, if it refers to a directory resource, the sub-directory URLs will be returned with trailing slashes.
I've just got the problem with redirects to slash behind Nginx too. Here's my thought on why this happens
In order the redirects to use proper protocol behind a balancer one should use RemoteIpValve in tomcat. It turns on "isSecure" flag when request comes from the balancer and so regular redirects work properly in webapps.
But it looks like this valve isn't used by tomcat for the /app => /app/ redirects, probably because they are processed before any valves.
Here's an excerpt from AccessValve reference:
"Some requests may be handled by Tomcat before they are passed to a container. These include redirects from /foo to /foo/ and the rejection of invalid requests. "
While this is an old post, I figured it would benefit from an answer since one is not available.
Tomcat is indeed the one that sends back a 302 response when a trailing slash is missing from the URL.
There are a couple of ways to handle this. You can either write a redirect rule when you receive the HTTP request from the browser (due to the location header pointing to HTTP) and convert the request to HTTPS, OR you change the location header on Nginx (that was sent from Tomcat) on the fly so that http is not the scheme that is sent back on the header (to the browser). I prefer the latter since it saves a round trip and does not make a HTTP call where HTTPS is required.
You can take option B by using the proxy_redirect directive from Nginx. This directive will rewrite a location header from an upstream server (such as tomcat) before returning a response to a client. Under an appropriate location, place this directive under a location rule.