经过2天的研究终于下手写下这篇文章哈哈
Gateway
是什么
Spring Cloud 全家桶中有个很重要的组件:网关。在 1.x 版本中使用的是 Zuul 网关,但是到了 2.x,由于Zuul的升级不断跳票,Spring Cloud 自己研发了一套网关组件:Spring Cloud Gateway。
Spring Cloud Gateway基于 Spring Boot 2.x,Spring WebFlux 和 Project Reactor 构建,使用了 Webflux 中的 reactor-netty 响应式编程组件,底层使用了 Netty 通讯框架。
做什么
反向代理 --后台接口统一暴露出口
鉴权 --统一验证token有效性,没效果直接返回
流量控制 --流量大的时候很容易导致服务器奔溃,所以需要控制流量
熔断 --当下游服务不可用时快速返回错误信息,而不是继续访问下游服务
日志监控 --谁访问了那个接口
架构图
怎么用
Gateway的三大概念
Route(路由):路由是构建网关的基本模块,它由 ID、目标 URI、一系列的断言和过滤器组成,如果断言为 true 则匹配该路由
Predicate(断言):参考的是 Java8 中的 java.util.function.Predicate。开发人员可以匹配 HTTP 请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
Filter(过滤):指的是 Spring 框架中 GatewayFilter 的实例,使用过滤器,可以在请求被路由之前或之后对请求进行修改
以上三个概念的流程图
springCloud Gateway工作流程图
说明
客户端向 Spring Cloud Gateway 发出请求。如果网关处理程序映射确定请求与路由匹配,则将其发送到网关 Web 处理程序。该处理程序通过特定于请求的过滤器链来运行请求。 筛选器由虚线分隔的原因是,筛选器可以在发送代理请求之前和之后运行逻辑。所有 “前置“ 过滤器逻辑均被执行,然后发出代理请求,发出代理请求后,将运行“ 后置 ”过滤器逻辑。
总结
路由转发 + 执行过滤器链
路由配置(两种方式)
- 在application.yml中配置
server:
port: 5510
spring:
cloud:
gateway:
discovery:
locator:
#表明gateway开启服务注册和发现的功能,并且spring cloud gateway自动根据服务发现为每一个服务创建了一个router,这个router将以服务名开头的请求路径转发到对应的服务。
lower-case-service-id: true
enabled: true
routes:
- id: iot
predicates:
- Path=/test/**
Host="**.abc.org"
uri: http://www.baidu.com
filters:
- StripPrefix= 1 #过滤几个前缀/为间隔
AddResponseHeader="X-TestHeader",foobar
- id: test-servier
predicates:
- Path=/iot/config/**
uri: lb://nacos-provider-example
filters:
- StripPrefix= 1 #过滤几个前缀/为间隔
- 通过编程方式
// static imports from GatewayFilters and RoutePredicates
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route(r -> r.host("**.abc.org").and().path("/test/**")
.filters(f ->{
f.addResponseHeader("X-TestHeader", "foobar"));
f.stripPrefix(1);
}
.uri("http://httpbin.org:80")
)
.route(r -> r.path("/iot/config/**")
.filters(f ->
f.stripPrefix(1))
.uri("lb://nacos-provider-example #" )
)
.build();
}
- 亲测以上两种配置方式完全可以完成路由的转发,当然配置文件的方式配置出来的东西让我们更加直观
routes配置讲解
- id ---> 区分唯一值
- uri ---> 要转发的地址
- http://127.0.0.1:8888 写死了地址,只能调用一个服务
- lb:/nacos-provider-example #负载均衡
- Predicate ---> 断言,即判断某个路由是否跟你规定的内容相匹配匹配就放行,当然我们还可以自定义自己的判断规则
- 自定义predicate
- 凡是自定义predicate可以继承AbstractRoutePredicateFactory类
- 命名规范: 功能名+RoutePredicateFactory,如果定义token参数拦截的predicate命名为TokenRoutePredicateFactory
- 自定义类内部一般需要实现apply接口,创建自定义存参对象,
- 在配置文件内引入有两种方式:(都是在predicates属性下加入,以TokenRoutePredicateFactory为例)
方式一:(该方式不支持list数据结构)
- Token=arg1,arg2
方式二:
- name: Token
args:
name: token
desc: token-demo -
系统提供的predicate,我就不一一介绍了
import com.alibaba.nacos.client.naming.utils.CollectionUtils;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
@Component
public class TokenRoutePredicateFactory extends AbstractRoutePredicateFactory<TokenRoutePredicateFactory.TokenConfig> {
public TokenRoutePredicateFactory() {
super(TokenConfig.class);
}
//要告诉转换为list,默认转换为map数据类型
@Override
public ShortcutType shortcutType() {
return ShortcutType.GATHER_LIST;
}
//重写方法,将args的参数转换到那个list中("Config中指定的list容器命名"),如果指定,会匹配不到,所以一下mothods会没有值
@Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList("methods");
}
//自定义判断逻辑
@Override
public Predicate<ServerWebExchange> apply(TokenConfig config) {
return ((param)-> {
if (!CollectionUtils.isEmpty(config.methods) && config.methods.stream().anyMatch(method->method.equalsIgnoreCase(param.getRequest().getMethod().name()))){
return true;
}
return false;
});
}
static class TokenConfig{
private List<String> methods = new ArrayList();
public List<String> getMethods() {
return methods;
}
public void setMethods(List<String> methods) {
this.methods = methods;
}
}
}
-
filter ---> 网关经常需要对路由请求进行过滤,进行一些操作,如鉴权之后构造头部之类的,过滤的种类很多,如增加请求头、增加请求 参数 、增加响应头和断路器等等功能
- 系统内置的过滤类,这里也不一一介绍,可以去官方文档学些下:
https://cloud.spring.io/spring-cloud-gateway/2.2.x/reference/html/#gatewayfilter-factories
- 全局filter,比如在网关中拦截token判断该token有效性,所有我们需要自定义一个全局filter,需要实现GlobalFilter
/** * Token 校验全局过滤器 */ @Component public class AuthorizeFilter implements GlobalFilter, Ordered { private static final Logger log = LoggerFactory.getLogger( AuthorizeFilter.class ); private static final String AUTHORIZE_TOKEN = "token"; //自定义过滤的逻辑 @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst( AUTHORIZE_TOKEN ); if ( StringUtils.isBlank( token )) { log.info( "token is empty ..." ); exchange.getResponse().setStatusCode( HttpStatus.UNAUTHORIZED ); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } }
- 自定filter过滤工厂,和predicate工厂很相似,命名规范是一样的: 过滤名字+GatewayFilterFactory
@Component public class CustomerGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomerGatewayFilterFactory.Config> { private static final String COUNT_START_TIME = "countStartTime"; //这里也是跟config中的值做绑定 @Override public List<String> shortcutFieldOrder() { return Arrays.asList("enabled"); } public CustomerGatewayFilterFactory() { super(Config.class); log.info("Loaded GatewayFilterFactory [CustomerGatewayFilterFactory]"); } @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { if (!config.isEnabled()) { //如果需要自定义返回一些内容 final ServerHttpResponse response = exchange.getResponse(); byte[] bytes = "{\"code\":\"99999\",\"message\":\"非法访问,没有检测到token~~~~~~\"}".getBytes(StandardCharsets.UTF_8); DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes); //设置请求头,不然会乱码 response.getHeaders().set("content-type","charset=utf-8") response.writeWith(Flux.just(buffer)); return chain.filter(exchange); } exchange.getAttributes().put(COUNT_START_TIME, System.currentTimeMillis()); return chain.filter(exchange).then( Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute(COUNT_START_TIME); if (startTime != null) { StringBuilder sb = new StringBuilder(exchange.getRequest().getURI().getRawPath()) .append(": ") .append(System.currentTimeMillis() - startTime) .append("ms"); sb.append(" params:").append(exchange.getRequest().getQueryParams()); log.info(sb.toString()); } }) ); }; } public static class Config { /** * 控制是否开启统计 */ private boolean enabled; public Config() {} public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } } }
yaml配置
filters: - Customer=true
以上就是springcloud gateway 详细的配置讲解了写了3天,从0-1学习真的难,首先自己找资料,写demo做测试. 然后把成功的方案在脑子里面编辑,怎么写到博客上面,希望能帮助大家,不懂可以留言大家一起交流,或者加我wx: 770219891