My last year's post "Enable CORS support in REST services with Spring 3.1" seems causing some confusion. I decided to create an example to show how to enable CORS with Spring rest api. The CorsFilter is same as before:
Below are 2 endpoints from EmployeeController.java:
The update method adds the header Access-Control-Allow-Origin with "*", but delete method doesn't. Therefore, the update method is enabled with CORS, but delete isn't. If delete endpoint is called, the following error will be shown in Chrome:
"cannot load http://localhost:8080/rest/employee/1. Origin http://127.0.0.1:8080 is not allowed by Access-Control-Allow-Origin."
However, the delete method is still invoked on the server side since the pre-flight request (OPTIONS)
allows DELETE method to be called.
The entire project can be downloaded from github. Following README to test it.
My intention was to disable/enable CORS support in each individual method by setting "Access-Control-Allow-Origin", but it seems not working as expected: Although the browser returns correct info, the method call is still invoked on the server side even Access-Control-Allow-Origin is not set. If you are allowed to enable all endpoints with CORS support, the code can be simplified as below:
The only difference is that addHeader("Access-Control-Allow-Origin") is moved out the if check. And then the update method can be simplified as:
The code can be downloaded from github too.
Search This Blog
Showing posts with label jQuery. Show all posts
Showing posts with label jQuery. Show all posts
Monday, June 3, 2013
Friday, June 8, 2012
Enable CORS support in REST services with Spring 3.1
We are working on a web project, which has 2 components, UI and web services. These 2 components are deployed to 2 different servers. UI uses jQuery to make ajax call to back end restful web services. Back end web services use Spring 3.1 MVC framework. We are facing the XSS (cross site scripting) issue since modern browsers don't allow XSS.
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:
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.
Subscribe to:
Posts (Atom)