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
- I am using a REST API as a servlet in a Tomcat container on a separate server than my JS Aplication.
- I have a JavaScript UI defined with the WaveMaker editor (based on the Dojo JS Framework) on a separate domain.
- I use a CORS filter in my REST API servlet to allow the cross-domain requests.
- I am using jQuery 2.1.1 for my AJAX call.
- I am using Tomcat 8 for my REST API Server, Tomcat 7 for my WaveMaker Client app.
- 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>