The back end rest web services support HTTP methods GET, POST, PUT and DELETE. The first problem we solved was how to call HTTP method GET from UI using jQuery. Since I used JSONP before, it is a natural solution to me to use JSONP again. My coworker already blog the solution here: http://www.iceycake.com/2012/06/xml-json-jsonp-web-service-endpoints-spring-3-1.
However, JSONP only supports GET, and you can't set http headers with JSONP request. I also realized that JSONP is kind of out-of-dated too. So how to support other methods like PUT, POST?
CORS, Cross-Origin Resource Sharing, defines a mechanism to enable client-side cross-origin requests. CORS are widely supported by modern browsers like FireFox, Chrome, Safari, and IE.
The UI code is fairly simple:
<html>If you just run the above javascript and call the backend, you may notice that the PUT request is changed to OPTIONS. The browser sends an OPTIONS request to the server, and the server needs to send response with correct headers. So in order to return the correct response, a filter is created (thanks to the link https://gist.github.com/2232095):
<head>
<script type="text/javascript" src="jquery.js"></script>
<script type="application/javascript">
(function($) {
var url = 'http://localhost:8080/employee/id';
$.ajax({
type: 'put',
url: url,
async: true,
contentType: 'application/json',
data: '{"id": 1, "name": "John Doe"}',
success: function(response) {
alert("success");
},
error: function(xhr) {
alert('Error! Status = ' + xhr.status + " Message = " + xhr.statusText);
}
});
})(jQuery);
</script>
</head>
<body>
<!-- we will add our HTML content here -->
</body>
</html>
package com.zhentao;
import java.io.IOException;The web.xml needs adding the following too:
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
public class CorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
// CORS "pre-flight" request
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.addHeader("Access-Control-Allow-Headers", "Content-Type");
response.addHeader("Access-Control-Max-Age", "1800");//30 min
}
filterChain.doFilter(request, response);
}
}
<filter>However, this isn't enough yet. The original post didn't address it. You still need to change your rest web services to return Accept-Controll-Allow-Origin header since HTTP is stateless. Here is what I did with Spring Rest api:
<filter-name>cors</filter-name>
<filter-class>com.zhentao.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>cors</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
@RequestMapping(value = "/employee/{id}", method = RequestMethod.PUT, consumes = {"application/json"})
public ResponseEntity<Employee> create(@Valid @RequestBody Employee employee, @PathVariable String id)
employee.setId(id);//some logic here
HttpHeaders headers = new HttpHeaders();
headers.add("Access-Control-Allow-Origin", "*");
ResponseEntity<Employee> entity = new ResponseEntity<Employee>(headers, HttpStatus.CREATED);
return entity;
}Update: it seems my post caused some confusion, and I created another post and fully working examples for CORS. See here.
This comment has been removed by the author.
ReplyDeleteReally usefull. Thanks!
ReplyDeletehey, don't know if you'd see this, but I am having trouble to get sending as json type working as your example. Also the CORSfilter is never hit. Could you give any more tip on how the CORSfilter works?
ReplyDeleteSee the url pattern for CORS filter. It is /*. So, for every request, the cors filter is called (which checks if the request is of type OPTIONS and adds appropriate headers). The filter then returns the response to the browser.The browser can now make the actual POST/PUT call to the server and this time you wont get cross domain error because the filter added the required headers to the response previously
DeleteWhy do you say I have to change my REST API code if I define the new headers using the Filter? The filter already adds the COSR headers to the response. If the filter doesn't do it what is it doing there? Just wondering... because I've seen several posts about this subject and yours is the only one that requires changing code. However I haven't tested any of those solutions (just researching, for now).
ReplyDeletefilter is adding headers for OPTION request (requests with method option), but not for POST and GET.
ReplyDeleteHowever you can add to filter handling of GET, POST and also as well
Great tips.
ReplyDeleteJust a remark. The Cors doesnt' works without add in the filter :
response.addHeader("Access-Control-Allow-Headers", "Content-Type, x-http-method-override");
Without the header x-http-method-override, the GET resquest wasn't sent after the OPTION request.
You could always change it to
ReplyDeleteresponse.addHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
Nice post, we took a slightly different approach rather than creating an implementation as above. Thought it would be worth a mention: http://stackoverflow.com/questions/11096195/spring-and-http-options-request/16021886#16021886
ReplyDeleteThank you for this post, I learned a lot about CORS + Spring here. Although I used the suggestion by Jim Gough above. :)
ReplyDeleteSame issue I have faced,but front end is EXT.js in my situation.
ReplyDeleteI have achieved this issue by adding
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With,Content-Type"); in header.
Thx This concept is really useful.
I am facing same problem, I am using spring javaconfig anotations, AbstractAnnotationConfigDispatcherServletInitializer instead of web.xml.
ReplyDeleteCould anyone provide me reinvent code.
The fact that the company I'm working with has forced me to use a blog post from 6 years ago and hasn't been bumped in 3 years makes me sad.
ReplyDeleteThank you for make this available.