1. 概述
本文將探討前端應用程序與部署在不同位置的 REST API 之間的通信。
目標是在解決瀏覽器 CORS(跨域資源共享)和同一來源策略限制的情況下,允許 UI 調用 API,即使 UI 和 API 沒有共享相同的源。
我們將創建一個兩個獨立的應用程序——UI 應用程序和一個簡單的 REST API,並使用 UI 應用程序中的 Zuul 代理 來代理對 REST API 的調用。
Zuul 是 Netflix 基於 JVM 的路由器和服務器端負載均衡器。Spring Cloud 具有與嵌入式 Zuul 代理的良好集成——我們將在這裏使用它。
2. Maven 配置
首先,我們需要將 Spring Cloud zuul 支持的依賴添加到我們 UI 應用程序的 pom.xml 中:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>最新版本可以在這裏找到:這裏。
3. Zuul 屬性
接下來,我們需要配置 Zuul。由於我們使用了 Spring Boot,我們將通過在 application.yml 文件中進行配置:
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 端口的服務器上的API。
首先,讓我們定義我們使用的資源的基準DTO:
public class Foo {
private long id;
private String name;
// standard getters and setters
}一個簡單的控制器:
@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上的服務器上部署的API。
讓我們從主文件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);
}
}除了簡單的 Boot 註解之外,我們還使用了 Zuul 代理的 enable-style 註解,這非常酷、簡潔和清晰。
6. 測試路由
現在,讓我們測試我們的 UI 應用程序,方法如下:
@Test
public void whenSendRequestToFooResource_thenOK() {
Response response = RestAssured.get("http://localhost:8080/foos/1");
assertEquals(200, response.getStatusCode());
}7. 自定義 Zuul 過濾器
有多個 Zuul 過濾器可用,我們也可以創建自己的自定義過濾器:
@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”的標題——當然,我們也可以根據需要增加複雜性,增強我們的請求。
8. 測試自定義 Zuul 過濾器
最後,讓我們測試以確保我們的自定義過濾器正常工作——首先,我們將修改我們的 FooController 在 Foos 資源服務器上:
@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 將 UI 應用程序的請求路由到 REST API。我們成功地解決了 CORS 和同源策略問題,並能夠自定義和增強傳輸中的 HTTP 請求。