When Tomcat, HTTPS, and Excel collide

One of the main reasons that we started publishing a blog at YD was to try to give back to the community when we Figured Stuff Out. Here’s one such story about a strange interaction among Excel, Tomcat, and HTTPS.

Buggy Behavior

When our users tried to open an .xls file (via Internet Explorer into Excel) that originated from our application, they would receive the popup below stating “The Web site you want to view requests identification. Please choose a certificate.” Even more unfortunately, Excel would display the popup 3-20 times before they could get to the data at all.

Certificate Error Image

Needless to say, this was annoying our customers.

We were totally confused and at the time we could find no explanation of this behavior on the web. No other content was exhibiting this sort of problem while originating from our application. HTTPS via Apache Server was working fine. We only had two other clues:

  • We could not recreate the problem by connecting to Tomcat via plain HTTP. (Makes sense given the message.) Of course, unencrypted HTTP was not an option in production.
  • If you chose to Save the file when prompted rather than to Open it, you could use it later with no annoying popups.

Wait, WebDAV?!

So we started some desperation debugging and fired up Ethereal (now Wireshark) to sniff the packets coming from Excel. We then noticed that Excel was sending several OPTIONS requests (one for each sub-directory of the file output) back to our server once Excel received the file. Using cURL, the most incriminating request/response looked like this:

user@server:~> curl -i -X OPTIONS https://app.example.com/APP/
HTTP/1.1 403
Date: Wed, 20 Jun 2007 15:36:26 GMT
Server: Apache/1.3.34 Ben-SSL/1.57 (Unix) mod_jk/1.2.23
Transfer-Encoding: chunked
Content-Type: text/plain

At this point, our guess was that Excel was trying to do some WebDAV “cleverness” by checking whether the file was some sort of WebDAV resource. Excel seemed to descend the “directory tree” of the .xls file looking for any WebDAV features listed in the OPTIONS. (We guess.)

Admittedly, despite the cURL output above, we don’t really support PUT and DELETE. Also, 403 is an inappropriate status to be returning for an OPTIONS request. On the other hand, our allowed HTTP request types are pretty standard, and didn’t include any of those fancy WebDAV ones.

We found that the same request directed to the normal URL of our application gave a more sensible response:

user@server:~> curl -i -X OPTIONS https://app.example.com/APP/main
HTTP/1.1 200 OK
Date: Wed, 20 Jun 2007 15:36:32 GMT
Server: Apache/1.3.34 Ben-SSL/1.57 (Unix) mod_jk/1.2.23
Content-Length: 0
Content-Type: text/plain

Altogether, something fishy was going on…

Tomcat Trickery

Why the two different OPTIONS responses? A quick look at our web-app configuration (web.xml) showed us that we had nothing mapped to the root web-application path, https://app.example.com/APP/. All normal traffic to our application was intentionally sent to https://app.example.com/APP/main instead. Therefore, requests to https://app.example.com/APP/ were sent to the default servlet defined in $CATALINA_HOME/conf/web.xml.

We hadn’t changed the default server servlet since installing Tomcat, so the requests were being handled by org.apache.catalina.servlets.DefaultServlet, which is by default set to “readOnly” mode. We tried looking in the
source code for DefaultServlet
. The code was specifying a 403 error for PUT and DELETE when in “readOnly” mode. Unable to glean much more information from the source code, we then made the logical leap that something similar must be happening for OPTIONS. (Of course, it also doesn’t make sense for OPTIONS to report that PUT and DELETE are allowable when in readOnly mode, but that is a complaint for another time.)

Finally, a Fix

This left us with two options to eliminate the 403 response:

  1. Write our own default servlet that returned a 200 status and the correct request methods (POST, GET, OPTIONS, HEAD, maybe TRACE) for the OPTIONS call.
  2. Provide a servlet mapping to the root web-app directory that then gave correct OPTIONS output.

We opted for the second path. (No need to add extra jar files and configuration to every deployed Tomcat instance. Plus, the DefaultServlet was not terribly extensible.) We ended up redirecting all requests to the web-app directory to our standard servlet, which already had correct OPTIONS behavior.

Hope that helps y’all.