Spring Cloud Netflix - Zuul

1. Overview

In this article, we’ll explore the communication between a front-end application and a REST API that are deployed separately.
The goal is to work around CORS and the Same Origin Policy restriction of the browser and allow the UI to call the API even though they don’t share the same origin.
We’ll basically create two separate applications – a UI application and a simple REST API, and we’ll use the Zuul proxy in the UI application to proxy calls to the REST API.
Zuul is a JVM based router and server side load balancer by Netflix. And Spring Cloud has a nice integration with an embedded Zuul proxy – which is what we’ll use here.

2. Maven Configuration

First, we need to add a dependency to the zuul support from Spring Cloud to our UI application’s 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

Next – we need to configure Zuul, and since we’re using Spring Boot, we’re going to do that in the application.yml:
1
2
3
4
5
zuul:
  routes:
    foos:
      path: /foos/**
      url: http://localhost:8081/spring-zuul-foos-resource/foos
Note that:
  • We are proxying to our resource server Foos.
  • All requests from the UI that starts with “/foos/” will be routed to our FoosResource server at http://loclahost:8081/spring-zuul-foos-resource/foos/

4. The API

Our API application is a simple Spring Boot app.
Within this article, we’re going to consider the API deployed in a server running on port 8081.
Let’s first define the basic DTO for the Resource we’re going to be using:
1
2
3
4
5
6
public class Foo {
    private long id;
    private String name;
    // standard getters and setters
}
And a simple controller:
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. The UI Application

Our UI application is also a simple Spring Boot application.
Within this article, we’re going to consider the API deployed in a server running on port 8080.
Let’s start with the main index.html – using a bit of 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>
The most important aspect here is how we’re accessing the API using relative URLs!
Keep in mind that the API application isn’t deployed on the same server as the UI application, so relative URLs shouldn’t work, and won’t work without the proxy.
With the proxy, however, we’re accessing the Foo resources through the Zuul proxy, which is of course configured to route these requests to wherever the API is actually deployed.
And finally, the actually Boot enabled application:
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);
    }
}
Beyond the simple Boot annotation, notice that we’re using the enable-style of annotation for the Zuul proxy as well, which is pretty cool, clean and concise.

6. Test The Routing

Now – let’s test our UI application – as follows:
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. A Custom Zuul Filter

There are multiple Zuul filters available, and we can also create our own custom one:
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;
    }
    // ...
}
This simple filter just adds a header called “Test” to the request – but of course, we can get as complex as we need to here augment our requests.

8. Test Custom Zuul Filter

Finally, let’s test make sure our custom filter is working – first we will modify our FooController at Foos resource server:
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));
    }
}
Now – let’s test our it out:
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. Conclusion

In this write-up, we focused on using Zuul to route requests from a UI application to a REST API. We successfully worked around CORS and the same-origin policy and we also managed to customize and augment the HTTP request in transit.
The full implementation of this tutorial can be found in the GitHub project – this is a Maven-based project, so it should be easy to import and run as it is.

Reference:

Kommentare

Beliebte Posts