使用 Zuul 作为代理访问 Spring REST 服务

1、概览

本文将带你了解如何在 UI (前端)应用中使用 Zuul 作为代理与 Spring REST 服务通信,使用 Zuul 代理的目的是为了统一处理 CORSSame Origin Policy 问题。

Zuul 是 Netflix 基于 JVM 的路由和服务器端负载均衡器。Spring Cloud 与嵌入式 Zuul 代理进行了很好的集成。

2、Maven 配置

首先,在 UI 应用的 pom.xml 中添加 Spring Cloud Zuul 依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>

最新版本可在 此处 找到。

3、Zuul Properties

接着,在 application.yml 中对 Zuul 进行配置:

zuul:
  routes:
    foos:
      path: /foos/**
      url: http://localhost:8081/spring-zuul-foos-resource/foos

如上:

  • 代理了资源服务器 Foos
  • UI 应用上所有以 /foos/ 开头的请求都将转发到 Foos 资源服务器 http://loclahost:8081/spring-zuul-foos-resource/foos/

4、API

API 是一个简单的 Spring Boot 应用,监听 8081 端口。

首先,定义资源 DTO 类,Foo

public class Foo {
    private long id;
    private String name;

    // Get、Set 方法
}

以及,Controller:

@RestController
public class FooController {

    @GetMapping("/foos/{id}")
    public Foo findById(
      @PathVariable long id, HttpServletRequest req, HttpServletResponse res) {
        return new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));
    }
}

5、UI 应用

UI 应用也是一个简单的 Spring Boot 应用,监听 8080 端口:

首先,创建主页 index.html,使用 AngularJS:

<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 不应起作用。

不过,使用 Zuul 代理后,就可以通过代理以相对路径的形式访问 Foo 资源,Zuul 代理会将这些请求路由到实际的 API 服务。

最后,启动应用:

@EnableZuulProxy
@SpringBootApplication
public class UiApplication extends SpringBootServletInitializer {

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

除了简单的 Spring Boot 注解外,还通过 @EnableZuulProxy 注解启用了 Zuul 代理。

6、测试路由

测试 UI 应用,如下:

@Test
public void whenSendRequestToFooResource_thenOK() {
    Response response = RestAssured.get("http://localhost:8080/foos/1");
 
    assertEquals(200, response.getStatusCode());
}

7、自定义 Zuul Filter

有多种 Zuul Filter 可供选择,也可以创建自己的自定义 Filter:

@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;
    }
    // ...
}

如上这个简单的 Filter 只是在请求中添加了一个名为 Test 的 Header。当然,你也可以根据需要在这里添加复杂的请求。

8、测试自定义 Zuul Filter

最后,来测试一下自定义 Filter 是否正常工作。

首先,修改 Foos 资源服务器上的 FooController

@RestController
public class FooController {

    @GetMapping("/foos/{id}")
    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"));
}

9、总结

本文介绍了如何在前端应用中使用 Zuul 作为代理请求 REST API 服务,


Ref:https://www.baeldung.com/spring-rest-with-zuul-proxy