一、簡介
Spring Cloud GateWay 是Spring官方基於Spring5.0 SpringBoot2.0 和 Project Reactor等技術開發的網關,Spring Cloud GateWay zhi在爲旨在爲微服務架構提供一種簡單有效的、統一的API路由管理方式。它不僅提供統一的路由方式,並且基於Filter鏈的方式提供了網關的基本功能,例如:安全、監控/埋點和限流等。
在Spring Cloud GateWay 有幾個概念需要了解一下:
Route:是網關的基礎元素,有ID 目標URI 、斷言、過濾器組成。當請求到達網關時由GateWay Handler Mapping 通過斷言進行路由匹配(Mapping) 當斷言爲真時匹配到路由。
Predicate: Predicate 時 java8 提供的一個函數。輸入類型是Spring Framework ServerWebExchange,它允許開發人員匹配來自HTTP的請求。
Filter: Filter 是GateWay 中的過濾器,可以在請求發出前後進行一些業務的處理。
二、簡單使用
創建一個springBoot的maven項目,添加下面的依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version> 2.2.2.RELEASE</version>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR4</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在配置文件進行如下配置即可進行路由轉發
server:
port: 2001
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
routes:
- id: path_route
uri: https://www.baidu.com
predicates:
- Path=/user/**
這樣只要訪問 http://localhost:2001/user 就會跳轉到 https://www.baidu.com
注意:-path中的user/** 的** 表示任意層級
三、整合Eureka
1. 整合 Eureka 路由
引入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>2.2.2.RELEASE</version>
</dependency>
配置文件的配置如下
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
routes:
- id: eureka-client-user-service
uri: lb://eureka-client-user-service
predicates:
- Path=/user/**
eureka:
instance:
prefer-ip-address: true
hostname: ${spring.cloud.client.ip-address}
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
client:
serviceUrl:
defaultZone: http://tttt:123456@localhost:8761/eureka/
注意:uri 以lb://開頭(lb 代表從註冊中心獲取服務),後面接的就是需要轉發到的服務名稱,這個服務名稱必須跟Eureka中的對應,否則會找不到服務。
這時訪問 http://localhost:2001/user/hello 就會轉發到 eureka-client-user-service 的 /user/hello 的接口中。
2. 整合Eureka的默認路由
Spring Cloud GateWay 可以爲所有的服務進行轉發操作,我們只需要在訪問路徑上指定要訪問的服務即可,通過這種方式就不用爲每個服務都去配置轉發規則,當新加了服務就不用去配置路由規則和重啓網關,配置如下:
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true
# 可以通過小寫的服務名訪問的配置
lower-case-service-id: true
注意:如果在配置文件中不設置 lower-case-service-id: true 只能通過大寫的服務名訪問,設置了之後只能通過小寫的服務名訪問。
四、路由斷言工廠的使用
1. path 路由斷言工廠
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
routes:
- id: eureka-client-user-service
uri: lb://eureka-client-user-service
predicates:
- Path=/user/**
2. Qyery 路由斷言工廠
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
routes:
- id: query_route
uri: https://www.baidu.com
predicates:
- Query=foo,ba.
如果請求包含一個值與ba匹配的foo查詢參數,則次路由將會匹配,因此第二個參數是正則表達式。
測試連接:http://localhost:2001/?foo=bar
3. Method 路由斷言工廠
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
routes:
- id: method_route
uri: https://www.baidu.com
predicates:
- Method=GET
只要是get請求都會被匹配。
其他更多的斷言工廠方法請參考官方文檔進行學習。
4. 自定義斷言工廠
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.function.Predicate;
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(Config.class);
}
@Override
public Predicate<ServerWebExchange> apply(Config config) {
return exchange->{
System.err.println("進入了 CheckAuthRoutePredicateFactory "+config.getName());
if (config.getName().equals("1234")){
return true;
}
return false;
};
}
public static class Config{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
cloud:
gateway:
routes:
- id: customer_route
uri: https://www.baidu.com
predicates:
- name: CheckAuth
args:
name: 1234
五、過濾器使用
1. AddRequestHeader 過濾器的使用
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://www.baidu.com
filters:
- AddRequestHeader=X-Request-Foo, Bar
匹配成功後將在headers裏添加 X-Request-Foo:Bar,並將其傳遞到後端服務。
2. RemoveRequestHeader 過濾器的使用
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://www.baidu.com
predicates:
- name: CheckAuth
args:
name: 456
filters:
- RemoveRequestHeader=X-Request-Foo, Bar
3. SetSatus 過濾器工廠
cloud:
gateway:
routes:
- id: add_request_header_route
uri: https://www.baidu.com
predicates:
- name: CheckAuth
args:
name: 456
filters:
- SetSatus=401
更多的過濾器工廠請參考官方文檔。
4. 自定義過濾器工廠
package com.example.gateway.springcloudgateway.common;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
@Component
public class CheckAuth2GatewayFilterFactory extends AbstractGatewayFilterFactory<CheckAuth2GatewayFilterFactory.Config> {
public CheckAuth2GatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
System.err.println("進入了 CheckAuth2GatewayFilterFactory"+config.getName());
ServerHttpRequest request = exchange.getRequest().mutate().build();
return chain.filter(exchange.mutate().request(request).build());
};
}
public static class Config{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
使用
filters:
- name: CheckAuth2
args:
name: 123
5. 全局過濾器使用
import com.alibaba.fastjson.JSONObject;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
@Component
public class IPCheckFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
HttpHeaders headers = exchange.getRequest().getHeaders();
if(getIp(headers).equals("127.0.0.2")){
ServerHttpResponse response = exchange.getResponse();
String data = "非法請求";
JSONObject jsonObject = new JSONObject();
jsonObject.put("data",data);
byte[] datas = new byte[0];
try {
datas = data.getBytes(String.valueOf(StandardCharsets.UTF_8));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
DataBuffer buffer = response.bufferFactory().wrap(datas);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type","application/json;charset=UFT-8");
Mono<Void> voidMono = response.writeWith(Mono.just(buffer));
return voidMono;
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
public String getIp(HttpHeaders headers){
return "127.0.0.1";
}
}
六、熔斷回退
引入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
配置文件進行如下配置
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
routes:
- id: eureka-client-user-service
uri: lb://eureka-client-user-service
predicates:
- Path=/user/**
filters:
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallback
上面配置了一個Hystrix的過濾器,該過濾器會使用 Hystrix 熔斷與回退,原理是將請求包裝成RouteHystrixCommand執行。
fallbackuri:是發生熔斷時回退的uri 地址,目前只支持forward 模式的uri。如果服務被降級後該請求會被轉發到該uri
@GetMapping("/fallback")
public String fallback(){
return "fallback";
}
七、跨域問題
package com.example.gateway.springcloudgateway.common;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
@Component
public class CoresConfig {
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (null != requestMethod) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
}
八、統一異常處理
自定義異常處理邏輯代碼如下:
package com.example.gateway.springcloudgateway.common;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.*;
import java.util.HashMap;
import java.util.Map;
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
/**
* 獲取異常屬性
* @param request
* @param options
* @return
*/
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
int code = 500;
Throwable error = super.getError(request);
if(error instanceof NotFoundException){
code = 404;
}
return response(code,this.buildMessage(request,error));
}
/**
* 制定相應處理方法爲json數據格式
* @param errorAttributes
* @return
*/
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(),this::renderErrorResponse);
}
/**
* 根據code獲取對應的httpStatus
* @param errorAttributes
* @return
*/
@Override
protected int getHttpStatus(Map<String, Object> errorAttributes) {
int statusCode = (int) errorAttributes.get("code");
HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
return httpStatus.value();
}
/**
* 構建異常信息
* @param request
* @param ex
* @return
*/
private String buildMessage(ServerRequest request, Throwable ex) {
StringBuffer message = new StringBuffer("fail to handler request [");
message.append(request.methodName());
message.append(" ");
message.append(request.uri());
message.append("]");
if(ex != null){
message.append(": ");
message.append(ex.getMessage());
}
return message.toString();
}
/**
* 構建返回的json 數據格式
* @param status
* @param errorMessage
* @return
*/
public static Map<String,Object> response(int status,String errorMessage){
Map<String,Object> map = new HashMap<>();
map.put("code",status);
map.put("message",errorMessage);
map.put("data",null);
return map;
}
}
覆蓋默認的配置代碼如下:
package com.example.gateway.springcloudgateway.common;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import java.util.Collections;
import java.util.List;
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolverList;
private final ServerCodecConfigurer serverCodecConfigurer;
public ErrorHandlerConfiguration(ServerProperties serverProperties, ApplicationContext applicationContext, ResourceProperties resourceProperties,
ObjectProvider<List<ViewResolver>> viewResolverProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolverList = viewResolverProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes){
JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,this.resourceProperties,
this.serverProperties.getError(),this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolverList);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}
九、重試機制
spring:
application:
name: spring-cloud-gateway
cloud:
gateway:
routes:
- id: eureka-client-user-service
uri: lb://eureka-client-user-service
predicates:
- Path=/user/**
filters:
- name: Retry
args:
retries: 3
series: SERVER_ERROR
retries:重試次數,默認值是3次
series:狀態碼配置(分段),符合某段狀態碼纔會進行重試邏輯,默認值是 SERVER_ERROR 值是5,也就是5XX(5開頭的狀態碼),共有5個值,代碼如下:
public static enum Series {
INFORMATIONAL(1),
SUCCESSFUL(2),
REDIRECTION(3),
CLIENT_ERROR(4),
SERVER_ERROR(5);
}