2

How do I get the first preflighted POST request to a URL to complete successfully and send a response back to my jQuery $.ajax() method?

Issue

The first post request I send hangs when I am using CORS with Tomcat. Since my request is pre-flighted then it triggers CORS. The OPTIONS preflight request completes successfully yet the POST request stays pending, according to the Network tab in the Chrome developer window.

Oddly enough, the POST request is received by the servlet and the servlet completes the request. Yet the servlet, or something else I am unaware of, is not sending back a response to my client so the client hangs, waiting for a response to the original POST request.

Even weirder, the second POST request at the same URL completes successfully and a response is returned for the second POST request. There is no OPTIONS handshake before the second request because the browser cached the preflight request since I set cors.maxAge to 3600. The second request must be using the OPTIONS cached from the original preflight.

Other Similar SO Questions: There are many questions on SO that talk about CORS, a few that talk about using Tomcat with CORS. One from Ninefingers is great, but is for Python it appears. Another from James is good but it doesn't seem to apply since I use a newer version of jQuery AND I specify the cors.maxAge property.

My Setup

  1. I am using a REST API as a servlet in a Tomcat container on a separate server than my JS Aplication.
  2. I have a JavaScript UI defined with the WaveMaker editor (based on the Dojo JS Framework) on a separate domain.
  3. I use a CORS filter in my REST API servlet to allow the cross-domain requests.
  4. I am using jQuery 2.1.1 for my AJAX call.
  5. I am using Tomcat 8 for my REST API Server, Tomcat 7 for my WaveMaker Client app.
  6. I am using Vladimir Dzhuvinov's CORS filter here.

HTTP Headers

my request url: http://myRESTAPIHost:8080/myServlet/LinkObject

1. Initial OPTIONS request (preflight)

Accept:*/*
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:accept, content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:myRESTAPIHost:8080
Origin:http://myClientUIHost:8080
Referer:http://myClientUIHost:8080/myClientServlet/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36
Response Headersview source

2. Response to OPTIONS request (preflight)

Access-Control-Allow-Headers:accept, content-type
Access-Control-Allow-Methods:PUT, DELETE, GET, POST, OPTIONS, HEAD
Access-Control-Allow-Origin:http://myClientUIHost:8080
Access-Control-Max-Age:3600
Content-Length:0
Date:Fri, 13 Jun 2014 19:11:01 GMT
Server:Apache-Coyote/1.1
Vary:Origin

3. Initial POST request (hangs - no response)

Accept:application/json, text/javascript, */*; q=0.01
Content-Type:application/json;
Origin:http://myClientUIHost:8080
Referer:http://myClientUIHost:8080/myClientServlet/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36

4. (missing response)

5. Second POST request to the same URL

Accept:application/json, text/javascript, */*; q=0.01
Accept-Encoding:gzip,deflate,sdch
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:52
Content-Type:application/json;
Host:myRESTAPIHost:8080
Origin:http://myClientUIHost:8080
Referer:http://myClientUIHost:8080/ubmPrototype/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36

6. Response to second POST

Access-Control-Allow-Origin:http://myClientUIHost:8080
Access-Control-Expose-Headers:X-Custom-1, X-Custom-2
Content-Type:application/json;charset=UTF-8
Date:Fri, 13 Jun 2014 19:16:57 GMT
Server:Apache-Coyote/1.1
Transfer-Encoding:chunked
Vary:Origin

jQuery ajax call

objSave: function(restfulUrl, obj, opType, error, gridVar, dialog) {        
    $.ajax({
        type : opType,
        crossDomain : true,
        url : "http://myRESTAPIHost/myServlet/" + restfulUrl,
        data : JSON.stringify(obj),
        contentType : "application/json;",
        dataType : "json"
    }).done(function(data) {
        if(gridVar !== undefined && gridVar !== null)
            gridVar.update();
        if(dialog !== undefined && dialog !== null)
            dialog.hide();
    }).fail(function(){
        console.log(error);
    });
},

web.xml

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.1"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<display-name>MyCORSfilter</display-name>
<listener>
    <listener-class>
        org.springframework.web.context.ContextLoaderListener
    </listener-class>
</listener>
<listener>
    <listener-class>
        org.springframework.web.context.request.RequestContextListener
    </listener-class>
</listener>
<filter>
    <description>generated-persistence-filter</description>
    <filter-name>CORSFilter</filter-name>
    <filter-class>
        org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter
    </filter-class>
    <init-param>
        <param-name>entityManagerFactoryBeanName</param-name>
        <param-value>myServlet</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>MyCORSFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
    <!-- The CORS filter with parameters -->
    <filter-name>CORS</filter-name>
    <filter-class>com.thetransactioncompany.cors.CORSFilter</filter-class>

    <!-- Note: All parameters are options, if omitted the CORS 
         Filter will fall back to the respective default values.
      -->
    <init-param>
        <param-name>cors.allowGenericHttpRequests</param-name>
        <param-value>true</param-value>
    </init-param>

    <init-param>
        <param-name>cors.allowOrigin</param-name>
        <param-value>__all of my other hosts__, http://myClientUIHost:8080, http://myRESTAPIHost:8080</param-value>
    </init-param>

    <init-param>
        <param-name>cors.allowSubdomains</param-name>
        <param-value>false</param-value>
    </init-param>

    <init-param>
        <param-name>cors.supportedMethods</param-name>
        <param-value>GET, HEAD, POST, PUT, DELETE, OPTIONS</param-value>
    </init-param>

    <init-param>
        <param-name>cors.supportedHeaders</param-name>
        <param-value>*</param-value>
    </init-param>

    <init-param>
        <param-name>cors.exposedHeaders</param-name>
        <param-value>X-Custom-1, X-Custom-2</param-value>
    </init-param>

    <init-param>
        <param-name>cors.supportsCredentials</param-name>
        <param-value>false</param-value>
    </init-param>

    <init-param>
        <param-name>cors.maxAge</param-name>
        <param-value>3600</param-value>
    </init-param>

</filter>
<filter-mapping>
        <filter-name>CORS</filter-name>
        <url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
    <description>generated-servlet</description>
    <servlet-name>myServlet</servlet-name>
    <servlet-class>
        org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:myServlet-web-context.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet>
    <description>generated-resources-servlet</description>
    <servlet-name>Resource Servlet</servlet-name>
    <servlet-class>
        org.springframework.js.resource.ResourceServlet
    </servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>Resource Servlet</servlet-name>
    <url-pattern>/resources/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
    <servlet-name>myServlet Servlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
<welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

Community
  • 1
  • 1
Kent Bull
  • 1,144
  • 3
  • 21
  • 41
  • I have tried changing the wildcard on cors.supportedHeaders to `Origin, Content-Type, X-Requested-With, Access-Control-Allow-Origin` and that gave me an access error. Some sources I read mentioned that the wildcard could be the issue but I haven't seen anything convincing. – Kent Bull Jun 14 '14 at 19:52

1 Answers1

3

This is probably going to make some of you laugh.

The solution was to turn off the Web Protection in my antivirus program, Sophos AV.

See the link here on their forums for an exact description of what happened to me: http://community.sophos.com/t5/Sophos-EndUser-Protection/Sophos-9-0-7-breaks-browser-AJAX-CORS-support-in-Chrome-Firefox/m-p/46923/highlight/false#M17046

Here are two SO links from rghazarian and Dominik with the same problem and solution.

Facepalm!

Community
  • 1
  • 1
Kent Bull
  • 1,144
  • 3
  • 21
  • 41
  • Had the same issue! Unfortunately spent far too long looking at requests, responses and sockets before reading this. Thank you for the update. – MikeJ Aug 19 '14 at 16:18