一、简介
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);
}