在本文中,我们将探讨前端应用与单独部署的 REST API 之间的通信。
本文旨在 解决 CORS 和浏览器的同源策略限制,允许 UI 调用 API,即使它们不是同源 。
基本上,我们将创建两个独立的应用程序 — 一个 UI 应用程序和一个简单的 REST API,我们将使用 UI 应用程序中的 Zuul 代理 来代理对 REST API 的调用。
Zuul 是 Netflix 的一个基于 JVM 的路由和服务端负载均衡器。Spring Cloud 与内嵌式 Zuul 代理可以很好地集成工作 — 本次我们也将使用他们。
首先,我们需要添加一个来自 Spring Cloud 的 zuul 支持到我们的 UI 应用程序的 pom.xml 中:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> <version>1.0.4.RELEASE</version> </dependency>
接下来,我们需要配置 Zuul,由于我们使用 Spring Boot,我们将在 application.yml 中进行配置:
zuul: routes: foos: path: /foos/** url: http://localhost:8081/spring-zuul-foos-resource/foos
注意:
我们的 API 应用程序是一个简单的 Spring Boot 应用程序。
在本文中,我们考虑将 API 部署至运行在 8081 端口上的服务器中。
首先定义我们要使用的资源的 DTO:
public class Foo{ private long id; private String name; // standard getters and setters }
和一个简单的控制器:
@Controller public class FooController{ @RequestMapping(method = RequestMethod.GET, value = "/foos/{id}") @ResponseBody public Foo findById( @PathVariable long id, HttpServletRequest req, HttpServletResponse res) { return new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4)); } }
我们的 UI 应用程序也是一个简单的 Spring Boot 应用程序。
在本文中,我们考虑将 UI 部署至运行在 8080 端口上的服务器中。
我们从 index.html 开始 — 使用了一点 AngularJS:
<html> <bodyng-app="myApp"ng-controller="mainCtrl"> <scriptsrc="angular.min.js"></script> <scriptsrc="angular-resource.min.js"></script> <script> var app = angular.module('myApp', ["ngResource"]); app.controller('mainCtrl', function($scope,$resource,$http){ $scope.foo = {id:0 , name:"sample foo"}; $scope.foos = $resource("/foos/:fooId",{fooId:'@id'}); $scope.getFoo = function(){ $scope.foo = $scope.foos.get({fooId:$scope.foo.id}); } }); </script> <div> <h1>Foo Details</h1> <span>{{foo.id}}</span> <span>{{foo.name}}</span> <ahref="#"ng-click="getFoo()">New Foo</a> </div> </body> </html>
这里最重要的地方是我们如何使用相对 URL 来访问 API!
请记住,API 应用程序未部署在与 UI 应用程序相同的服务器上, 因此无法通过相对 URL 工作 ,如果没有使用代理,该 UI 应用程序将无法正常工作。
然而,如果使用代理服务器,我们可以通过 Zuul 代理来访问 Foo 资源,该代理配置为将这些请求路由到实际部署 API 的位置。
最后,引导应用程序:
@EnableZuulProxy @SpringBootApplication public class UiApplicationextends SpringBootServletInitializer{ public static void main(String[] args){ SpringApplication.run(UiApplication.class, args); } }
除了简单的引导注解,请注意,我们使用 Zuul 代理的注解启用方式,这非常酷,而且干净简洁。
现在,让我们来测试 UI 应用程序,如下所示:
@Test public void whenSendRequestToFooResource_thenOK(){ Response response = RestAssured.get("http://localhost:8080/foos/1"); assertEquals(200, response.getStatusCode()); }
有多个 Zuul 过滤器 可以使用,我们也可以创建自己自定义的过滤器:
@Component public class CustomZuulFilterextends ZuulFilter{ @Override public Object run(){ RequestContext ctx = RequestContext.getCurrentContext(); ctx.addZuulRequestHeader("Test", "TestSample"); return null; } @Override public boolean shouldFilter(){ return true; } // ... }
这个简单的过滤器只是在请求中添加了一个名为 Test 的头部 — 当然,我们可以根据我们需要的复杂程度来扩充我们的请求。
最后,让我们测试以确保我们自定义的过滤器能够正常工作 — 首先我们将在 Foos 资源服务器上修改 FooController :
@Controller public class FooController{ @GetMapping("/foos/{id}") @ResponseBody public Foo findById( @PathVariable long id, HttpServletRequest req, HttpServletResponse res) { if (req.getHeader("Test") != null) { res.addHeader("Test", req.getHeader("Test")); } return new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4)); } }
现在,让我们开始测试:
@Test public void whenSendRequest_thenHeaderAdded(){ Response response = RestAssured.get("http://localhost:8080/foos/1"); assertEquals(200, response.getStatusCode()); assertEquals("TestSample", response.getHeader("Test")); }
在这篇文章中,我们主要使用了 Zuul 将来自 UI 应用程序的请求路由到 REST API。我们成功地解决了 CORS 和同源策略,我们还定制和扩充了 HTTP 请求。
本教程的完整实现可以在 项目 GitHub 中找到 — 这是一个基于 Maven 的项目,所以应该很容易导入和运行。