CodeMed
CodeMed

Reputation: 9201

Why can't AngularJS app consume REST service on different server?

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

Answers (1)

meriton
meriton

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

Related Questions