Spring REST 与 Zuul 代理

http://www.baeldung.com/spring-rest-with-zuul-proxy
作者: Eugen Paraschiv
译者: http://oopsguy.com

1、概述

在本文中,我们将探讨前端应用与单独部署的 REST API 之间的通信。

本文旨在解决 CORS 和浏览器的同源策略限制,允许 UI 调用 API,即使它们不是同源

我们将创建两个独立的应用 —— 一个 UI 应用和一个简单的 REST API,之后使用 UI 应用中的 Zuul 代理来代理 REST API 的调用。

Zuul 是 Netflix 开源的一个基于 JVM 的路由和服务端负载均衡器。Spring Cloud 与内嵌式 Zuul 代理可以很好地集成工作,本次我们也将使用他们。

2、Maven 配置

首先,我们需要添加一个来自 Spring Cloud 的 zuul 依赖到 UI 应用的 pom.xml 中:

1
2
3
4
5
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.0.4.RELEASE</version>
</dependency>

3、Zuul Properties

接下来,我们需要配置 Zuul,由于使用 Spring Boot,我们将在 application.yml 中进行配置:

1
2
3
4
5
zuul:
routes:
foos:
path: /foos/**
url: http://localhost:8081/spring-zuul-foos-resource/foos

注意:

  • 代理的目标为资源服务器 Foos
  • 来自 UI 应用 URL 以 /foos/ 开头的所有请求都将路由到 Foos 资源服务器:http:// loclahost:8081/spring-zuul-foos-resource/foos/

4、API

API 应用是一个简单的 Spring Boot 应用。

在本文中,我们考虑将 API 部署至运行在 8081 端口上的服务器中。

首先定义资源 DTO:

1
2
3
4
5
6
public class Foo {
private long id;
private String name;

// standard getters and setters
}

和一个简单的控制器:

1
2
3
4
5
6
7
8
9
10
@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));
}
}

5、UI 应用

UI 应用同样是一个简单的 Spring Boot 应用。

在本文中,我们考虑将 UI 部署至运行在 8080 端口上的服务器中。

index.html 中使用了一点 AngularJS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<html>
<body ng-app="myApp" ng-controller="mainCtrl">
<script src="angular.min.js"></script>
<script src="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>
<a href="#" ng-click="getFoo()">New Foo</a>
</div>
</body>
</html>

这里最重要的地方是我们如何使用相对 URL 来访问 API!

请记住,API 应用未部署在与 UI 应用相同的服务器上,因此无法通过相对 URL 调用,如果没有使用代理,该 UI 应用将无法正常工作。

然而,如果使用代理服务器,我们可以通过 Zuul 代理来访问 Foo 资源,该代理配置为将这些请求路由到实际部署的 API。

最后,引导应用:

1
2
3
4
5
6
7
8
@EnableZuulProxy
@SpringBootApplication
public class UiApplication extends SpringBootServletInitializer {

public static void main(String[] args) {
SpringApplication.run(UiApplication.class, args);
}
}

除了简单的引导注解,我们还使用 Zuul 代理启用注解,这种方式非常方便,而且干净简洁。

6、测试路由

接下来来测试一下 UI 应用,如下所示:

1
2
3
4
5
6
@Test
public void whenSendRequestToFooResource_thenOK() {
Response response = RestAssured.get("http://localhost:8080/foos/1");

assertEquals(200, response.getStatusCode());
}

7、自定义 Zuul Filter

Zuul 提供了多个 Zuul 过滤器,我们也可以创建自己自定义的过滤器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component
public class CustomZuulFilter extends ZuulFilter {

@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("Test", "TestSample");
return null;
}

@Override
public boolean shouldFilter() {
return true;
}
// ...
}

这个简单的过滤器只是在请求中添加了一个名为 Test 的 header —— 当然,我们可以根据自己需要的复杂程度来扩展请求信息。

8、测试自定义 Zuul Filter

最后,让我们测试以确保自定义的过滤器能够正常工作 —— 首先在 Foos 资源服务器上修改 FooController

1
2
3
4
5
6
7
8
9
10
11
12
13
@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));
}
}

开始测试:

1
2
3
4
5
6
7
@Test
public void whenSendRequest_thenHeaderAdded() {
Response response = RestAssured.get("http://localhost:8080/foos/1");

assertEquals(200, response.getStatusCode());
assertEquals("TestSample", response.getHeader("Test"));
}

9、结论

在本文中,我们主要使用了 Zuul 将来自 UI 应用的请求路由到 REST API,成功解决了 CORS 和同源策略问题,我们还定制和扩充了 HTTP 请求。

本教程的完整实现可以在项目 GitHub 中找到 — 这是一个基于 Maven 的项目,所以应该很容易导入和运行。

原文项目示例代码

https://github.com/eugenp/tutorials/tree/master/spring-zuul