为啥 AngularJS 应用程序不能在不同的服务器上使用 REST 服务?

Posted

技术标签:

【中文标题】为啥 AngularJS 应用程序不能在不同的服务器上使用 REST 服务?【英文标题】:Why can't AngularJS app consume REST service on different server?为什么 AngularJS 应用程序不能在不同的服务器上使用 REST 服务? 【发布时间】:2016-03-12 18:25:36 【问题描述】:

如何从运行在不同服务器上的 AngularJS 应用调用 Spring Boot REST api? 具体来说,如何修改以下代码来完成此操作?

我正在使用 the tutorial at this link 学习 Spring Boot。该应用程序的第一部分和第二部分运行完美。在part three of the tutorial (which is at this link) 中,我们将REST api 移动到不同的资源服务器,原型为localhost:9000

本教程使用 groovy 制作资源服务器应用程序,但我正在尝试将其修改为 Java。

当我定义/home/username/workspacename/resource/src/main/java/com/example/ResourceApplication.java 时,我可以访问localhost:9000 的Web 服务,如下所示。

但是,在 localhost:8080 上运行的 AngularJS 应用程序无法从 localhost:9000 访问 Web 服务。当我然后添加CorsFilter.java 如下所示并使用kill $(lsof -t -i:9000)mvn spring-boot:run 重新启动服务时,我无法再查看localhost:9000 的数据,并且我仍然无法从应用程序访问Web 服务localhost:8080

我没有对代码进行任何其他更改,我停在教程中创建CorsFilter.java 并重新启动应用程序的位置。 那么如何更改下面的代码以从localhost:9000 获取Web 服务,以便从localhost:8080 的gui 应用程序访问?

资源服务器中两个更改类的代码如下:

/home/username/workspacename/resource/src/main/java/com/example/ResourceApplication.java 是:

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 是:

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() 


hello.js 文件在 GUI 应用程序中localhost:8080 的代码是:

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;
          );
        

    ); 

正在进行的研究:


按照@meriton 的要求,我在firefox 中打开了开发者工具,然后请求localhost:8080。当我放大到网络选项卡以获取 GET 请求的响应标头以调用嵌入在 localhost:8080 请求中的 localhost:9000 时,我得到了以下请求标头:

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: *  

并且Network选项卡中还显示了以下请求标头:

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  

【问题讨论】:

“我无法再查看 localhost:9000 的数据”是什么意思?你有错误吗?如果是这样,什么错误? @meriton 我只是得到一个白色的空白页。浏览器没有错误。我将使用开发人员工具查看这些日志中显示的内容。我还应该去哪里看? 开发者工具。具体来说,javascript 控制台,如果服务器响应异常,则网络选项卡。如果 javascript 控制台上没有错误,并且响应看起来有效,则必须调试发送 rest 请求并处理响应的代码。 @meriton 我刚刚在 Firefox 中调用了localhost:9000,并打开了开发者工具。调试器选项卡显示no sourcesno stack frames。当我从网络选项卡再次调用它时,日志列出了对 / 的调用,响应为 0kb。 什么是 HTTP 状态和响应的标头?也就是说,这听起来像是您的服务器行为不端。 【参考方案1】:

过滤器是 servlet 的拦截器,它可以选择自己处理请求,或者使用 chain.doFilter(req, res) 将其传递给过滤器链中的下一个过滤器或 servlet。您的过滤器仅传递选项请求,所有其他请求(例如 GET 请求)既不传递也不响应:

if (request.getMethod().equals("OPTIONS")) 
  try chain.doFilter(req, res); 
  catch (IOException e) e.printStackTrace(); 
  catch (ServletException e) e.printStackTrace();
 
else         

由于从未写入响应,因此它保持为空,这解释了服务器回复的 0KB 响应。

至于解决这个问题,您应该正确转录示例代码,即教程代码

if (request.getMethod()!='OPTIONS') 
  chain.doFilter(req, res)
 else 

应该变成

if (!request.getMethod().equals("OPTIONS")) 
  chain.doFilter(req, res)
 else 

(注意否定!)或者更好

if (request.getMethod().equals("OPTIONS")) 
    // preflight request
 else 
    chain.doFilter(req, res)

但是,使用CORS-Support provided by Spring MVC 可能比自己实现它更容易。

【讨论】:

谢谢。你发现了我的错误。我把!= 变成了string.equals。应该是!string.equals 我在同一个教程中还有另一个问题。您是否也愿意对此发表评论?这是链接:***.com/questions/34235409/…

以上是关于为啥 AngularJS 应用程序不能在不同的服务器上使用 REST 服务?的主要内容,如果未能解决你的问题,请参考以下文章

AngularJS:为啥在说“服务”时使用“工厂”?

为啥图像网格不能在 angularjs ng-repeat 中扩展但 javascript 工作正常?

为啥我应该在 AngularJS 中选择使用服务而不是工厂? [复制]

AngularJS 两个不同的 $injector

为啥我在 AngularJs 帖子中收到 400 个错误请求?

AngularJS路由为啥不显示