Reputation: 9201
How do I call a Spring Boot REST api from an AngularJS app running on a different server? Specifically, how do I modify the code below to accomplish this?
I am learning Spring Boot using the tutorial at this link. Part one and Part two of the app run perfectly. In part three of the tutorial (which is at this link), we move the REST api to a different resource server, which is prototyped as localhost:9000
.
The tutorial makes the resource server app in groovy, but I am trying to modify it to be in Java.
I am able to access the web service at localhost:9000
when I define /home/username/workspacename/resource/src/main/java/com/example/ResourceApplication.java
as shown below.
However, the AngularJS app running on localhost:8080
cannot access the web service from localhost:9000
. When I then add CorsFilter.java
as shown below and restart the service with kill $(lsof -t -i:9000)
and mvn spring-boot:run
, I can no longer view the data at localhost:9000
, and I also am still not able access the web service from the app in localhost:8080
.
I did not make any other changes to the code, and I stopped at the point in the tutorial where CorsFilter.java
is created and the app is restarted. So how do I change the code below to get the web service from localhost:9000
to be accessible from the gui app at localhost:8080
?
The code for the two changed classes in the resource server is as follows:
/home/username/workspacename/resource/src/main/java/com/example/ResourceApplication.java
is:
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@SpringBootApplication
@RestController
public class ResourceApplication {
@RequestMapping("/")
public Map<String,Object> home() {
Map<String,Object> model = new HashMap<String,Object>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "Hello World");
return model;
}
public static void main(String[] args) {
SpringApplication.run(ResourceApplication.class, args);
}
}
/home/username/workspacename/resource/src/main/java/com/example/CorsFilter.java
is:
package com.example;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.annotation.Order;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
class CorsFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
response.setHeader("Access-Control-Max-Age", "3600");
if (request.getMethod().equals("OPTIONS")) {
try {chain.doFilter(req, res);}
catch (IOException e) {e.printStackTrace();}
catch (ServletException e) {e.printStackTrace();}
}
else { }
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
And the code for the hello.js
file in the GUI app at localhost:8080
is:
angular.module('hello', [ 'ngRoute' ])
.config(function($routeProvider, $httpProvider) {
$routeProvider.when('/', {
templateUrl : 'home.html',
controller : 'home'
}).when('/login', {
templateUrl : 'login.html',
controller : 'navigation'
}).otherwise('/');
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
})
.controller('home', function($scope, $http) {
$http.get('http://localhost:9000/').success(function(data) {
$scope.greeting = data;
})
})
.controller('navigation',
function($rootScope, $scope, $http, $location) {
var authenticate = function(credentials, callback) {
var headers = credentials ? {authorization : "Basic "
+ btoa(credentials.username + ":" + credentials.password)
} : {};
$http.get('user', {headers : headers}).success(function(data) {
if (data.name) {
$rootScope.authenticated = true;
} else {
$rootScope.authenticated = false;
}
callback && callback();
}).error(function() {
$rootScope.authenticated = false;
callback && callback();
});
}
authenticate();
$scope.credentials = {};
$scope.login = function() {
authenticate($scope.credentials, function() {
if ($rootScope.authenticated) {
$location.path("/");
$scope.error = false;
} else {
$location.path("/login");
$scope.error = true;
}
});
};
$scope.logout = function() {
$http.post('logout', {}).success(function() {
$rootScope.authenticated = false;
$location.path("/");
}).error(function(data) {
$rootScope.authenticated = false;
});
}
});
ONGOING RESEARCH:
As per @meriton's request, I opened the developer tools in firefox and then requested localhost:8080
. When I zoomed in to Network tab for the response headers for the GET request for the call to localhost:9000
embedded in the localhost:8080
request, I got the following request headers:
Access-Control-Allow-Headers: x-requested-with
Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS, DELETE
Access-Control-Max-Age: 3600
Content-Length: 0
Date: Tue, 08 Dec 2015 00:41:01 GMT
Server: Apache-Coyote/1.1
access-control-allow-origin: *
And the following request headers were also shown in the Network
tab:
Host: localhost:9000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
X-Requested-With: XMLHttpRequest
Referer: http://localhost:8080/
Origin: http://localhost:8080
Connection: keep-alive
Upvotes: 2
Views: 1645
Reputation: 70574
A Filter is an interceptor for servlet that can choose to handle the request itself, or pass it one to the next filter or servlet in the filter chain using chain.doFilter(req, res)
. Your filter only passes on option requests, all other requests (such as a GET request) are neither passed on or otherwise responded to:
if (request.getMethod().equals("OPTIONS")) {
try {chain.doFilter(req, res);}
catch (IOException e) {e.printStackTrace();}
catch (ServletException e) {e.printStackTrace();}
}
else { }
as the response is never written to, it remains empty, which explains the 0KB response the server replies with.
As for fixing this, you should either transscribe the example code correctly, i.e. the tutorial code
if (request.getMethod()!='OPTIONS') {
chain.doFilter(req, res)
} else {
}
should become
if (!request.getMethod().equals("OPTIONS")) {
chain.doFilter(req, res)
} else {
}
(note the negation!) or better yet
if (request.getMethod().equals("OPTIONS")) {
// preflight request
} else {
chain.doFilter(req, res)
}
However, it may be easier to use the CORS-Support provided by Spring MVC rather than implementing it yourself.
Upvotes: 1