不同于zuul(基于servlet),spring-cloud-gateway 基于webflux
1.基于zookeeper作为服务注册与发现中心的spring-cloud-gateway的使用
使用zookeeper作为服务注册中心参考我的这篇文章spring-cloud使用zookeeper作为服务注册发现中心(下面会使用到该文章所建工程)
新建工程gateway
com.wl.springcloud
gateway
1.0-SNAPSHOT
pom.xml
4.0.0
com.wl.springcloud
gateway
1.0-SNAPSHOT
gateway
http://www.example.com
UTF-8
1.8
1.8
2.0.3.RELEASE
2.0.3.RELEASE
2.0.8.RELEASE
2.0.3.RELEASE
2.0.3.RELEASE
com.wl.springcloud.gateway.GatewayApplication
org.springframework.boot
spring-boot-starter-webflux
${spring-boot-version}
org.springframework.boot
spring-boot-autoconfigure
${spring-boot-version}
org.springframework.boot
spring-boot-starter-actuator
${spring-boot-version}
org.springframework.cloud
spring-cloud-starter-config
${spring-cloud-config-version}
org.springframework.cloud
spring-cloud-starter-zookeeper-discovery
2.0.0.RELEASE
org.apache.httpcomponents
httpclient
org.apache.zookeeper
zookeeper
org.apache.zookeeper
zookeeper
3.4.10
org.springframework.cloud
spring-cloud-starter-gateway
2.0.3.RELEASE
io.netty
netty-all
4.1.32.Final
org.springframework.boot
spring-boot-starter-test
${spring-boot-version}
test
org.springframework.boot
spring-boot-maven-plugin
${spring-boot-version}
${MainClass}
JAR
repackage
org.apache.maven.plugins
maven-compiler-plugin
3.1
1.8
src/main/resources
**/*.*
*.*
src/main/java
**/*.*
*.*
注意这里手动导入了netty-all依赖,如果没有这个依赖会报Caused by: java.lang.ClassNotFoundException: io.netty.resolver.DefaultAddressResolverGroup
application.yml(修改了zookeeper工程的服务id为zookeeper1,服务id与path路径相同时StripPrefix无效)
server:
port: 8080
spring:
cloud:
zookeeper:
connect-string: 192.168.245.130:2181
gateway:
discovery:
locator:
enabled: true
routes:
- id: zookeeper1
uri: lb://zookeeper1
predicates:
- Path= /zookeeper/**
filters:
- StripPrefix=0
#配置多个路由
# - id: clientId
# uri: lb://clientId
# predicates:
# - Path= /zookeeper/*
# filters:
# - StripPrefix=0
1.spring.cloud.gateway.discovery.locator.enable=true表示启用服务注册发现组件(可以通过服务id进行转发),默认为false
2.id表示注册到zookeeper上的服务实例id即spring-application-name
3.uri 与zuul中的url配置基本相同,可以是http路径也可以是服务id。与zuul直接使用服务id不同,spring-cloud-gateway需要写成lb://服务id
4.Path 与zuul中path相同 。这里表示以/zookeeper 开始的路径将转发到zookeeper1服务上
5.StripPrefix表示去掉前缀的数量。例如StripPrefix=1 则请求 /name/bar/foo 转发到目标服务的路径为/bar/foo。以此类推值为2则转发到目标服务的路径为/foo。注意StripPrefix=0等价于zuul中stripPrefix=false,StripPrefix=1等价于zuul中stripPrefix=true
6.predicates(路由方式)、filters(过滤器)配置。下面介绍过滤器时会进行详细说明。这两个配置主要针对org.springframework.cloud.gateway.filter.factory、org.springframework.cloud.gateway.handler.predicate中的类,Path、StripPrefix分别是PathRoutePredicateFactory、StripPrefixGatewayFilterFactory的前缀,当然还有更多的pridicate 和 filter
启动类
package com.wl.springcloud.gateway;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
/**
* Created by Administrator on 2019/4/22.
*/
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class, //不使用数据库
GsonAutoConfiguration.class //spring-boot2.0.0以上版本需要引入高版本的gson依赖,如果不引用gson依赖需要加此属性
},scanBasePackages = "com.wl")
public class GatewayApplication {
private static final Logger logger = LoggerFactory.getLogger(GatewayApplication.class);
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class);
}
}
启动gateway和zookeeper(注意修改spring.application.name=zookeeper1)工程
浏览器输入http://localhost:8080/zookeeper/zookeeper
2.spring-cloud-gateway 过滤器
2.1 GatewayFilter
GatewayFilter是路由绑定的过滤器,只会在绑定的路由中执行
自定义GatewayFilter有两种方式:1.通过实现GatewayFilter 2.通过自定义GatewayFilterFactory然后通过RouteDefinitionRouteLocator类中的loadGatewayFilters方法加载到过滤器链中
方式一
过滤器
package com.wl.springcloud.gateway.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.ipc.netty.ByteBufFlux;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Administrator on 2019/4/22.
*/
public class AuthGateWayFilter implements GatewayFilter,Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String token = request.getQueryParams().getFirst("token");
if(token != null && !token.equals("")){
return chain.filter(exchange);
}
ServerHttpResponse response = exchange.getResponse();
//设置http响应状态码
response.setStatusCode(HttpStatus.BAD_REQUEST);
//设置响应头信息Content-Type类型
response.getHeaders().add("Content-Type","application/json");
//设置返回json数据
return response.writeAndFlushWith(Flux.just(ByteBufFlux.just(response.bufferFactory().wrap(getWrapData()))));
//直接返回(没有返回数据)
// return response.setComplete().then();
//设置返回的数据(非json格式)
// return response.writeWith(Flux.just(response.bufferFactory().wrap("".getBytes())));
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
private byte[] getWrapData() {
Map map = new HashMap<>();
map.put("code","1");
map.put("msg","token is empty or illegal");
try {
return new ObjectMapper().writeValueAsString(map).getBytes();
} catch (JsonProcessingException e) {
//
}
return "".getBytes();
}
}
这是一个自定义的校验token的过滤器,如果token为空则返回{"msg":"token is empty or illegal","code":"1"}的json数据
1.如果不需要返回数据则直接response.setComplete()
2.如果返回的不是json格式的数据则response.writeWith(Flux.just(response.bufferFactory().wrap(data)))或response.writeWith(Mono.just(response.bufferFactory().wrap(data)))
3.如果返回的是application/json格式的数据则response.writeAndFlushWith(Flux.just(ByteBufFlux.just(response.bufferFactory().wrap(getWrapData()))))或
response.writeAndFlushWith(Mono.just(ByteBufMono.just(response.bufferFactory().wrap(getWrapData()))))
4.Flux 与Mono 的区别参考https://www.jianshu.com/p/611f3667c4d2
5.注意writeWith与writeAndFlushWith的参数的泛型区别,所以在writeAndFlushWith需要使用Flux包装两次
将过滤器绑定在某一个路由上
package com.wl.springcloud.gateway.config;
import com.wl.springcloud.gateway.filter.AuthGateWayFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.GatewayFilterSpec;
import org.springframework.cloud.gateway.route.builder.PredicateSpec;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.route.builder.UriSpec;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.function.Function;
/**
* Created by Administrator on 2019/4/23.
*/
@Configuration
public class GatewayFilterConfig {
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route(new Function() {
@Override
public Route.AsyncBuilder apply(PredicateSpec predicateSpec) {
return predicateSpec.path("/zookeeper/**").filters(new Function() {
@Override
public UriSpec apply(GatewayFilterSpec gatewayFilterSpec) {
return gatewayFilterSpec.filter(new AuthGateWayFilter()).stripPrefix(0);
}
}).uri("lb://zookeeper1").order(0).id("zookeeper1");
}
}).build();
}
}
这里将过滤器绑定在了/zookeeper/**这个路由上
浏览器输入http://localhost:8080/zookeeper/zookeeper
浏览器输入http://localhost:8080/zookeeper/zookeeper?token=123
方式二 使用GatewayFilterFactory 参考StripPrefixGatewayFilterFactory
package com.wl.springcloud.gateway.filter.factory;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
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.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.ipc.netty.ByteBufFlux;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by Administrator on 2019/4/23.
*/
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory {
private final String KEY = "token";
public AuthGatewayFilterFactory() {
super(Config.class);
}
@Override
public List shortcutFieldOrder() {
return Arrays.asList("enabled");
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if(!config.enabled){
return chain.filter(exchange);
}
ServerHttpRequest request = exchange.getRequest();
String token = request.getQueryParams().getFirst(KEY);
if(token != null && !token.equals("")){
return chain.filter(exchange);
}
ServerHttpResponse response = exchange.getResponse();
//设置http响应状态码
response.setStatusCode(HttpStatus.BAD_REQUEST);
//设置响应头信息Content-Type类型
response.getHeaders().add("Content-Type","application/json");
//设置返回json数据
return response.writeAndFlushWith(Flux.just(ByteBufFlux.just(response.bufferFactory().wrap(getWrapData()))));
//直接返回(没有返回数据)
// return response.setComplete().then();
//设置返回的数据(非json格式)
// return response.writeWith(Flux.just(response.bufferFactory().wrap("".getBytes())));
}
private byte[] getWrapData() {
Map map = new HashMap<>();
map.put("code","1");
map.put("msg","token is empty or illegal");
try {
return new ObjectMapper().writeValueAsString(map).getBytes();
} catch (JsonProcessingException e) {
//
}
return "".getBytes();
}
};
}
public static class Config {
private boolean enabled;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
}
修改配置
server:
port: 8080
spring:
cloud:
zookeeper:
connect-string: 192.168.245.130:2181
gateway:
discovery:
locator:
enabled: true
routes:
- id: zookeeper1
uri: lb://zookeeper1
predicates:
- Path= /zookeeper/**
filters:
- StripPrefix=0
- Auth=true
#配置多个路由
# - id: zookeeper1
# uri: lb://zookeeper1
# predicates:
# - Path= /zookeeper/**
# filters:
# - StripPrefix=0
配置添加了- Auth=true
注意filters配置里面的StripPrefix和Auth是StripPrefixGatewayFilterFactory和AuthGatewayFilterFactory的前缀,分别代表了两个GatewayFilterFactory的名称,后面的值0和true会通过
@Override
public List shortcutFieldOrder() {
return Arrays.asList("enabled");
}
public static final String PARTS_KEY = "parts";
@Override
public List shortcutFieldOrder() {
return Arrays.asList(PARTS_KEY);
}
传递到其内部类Config的属性中(属性名称与Arrays.asList() 中的字符串相同)即StripPrefixGatewayFilterFactory.Config中的属性名称parts与Arrays.asList(PARTS_KEY)中的PARTS_KEY相同;AuthGatewayFilterFactory.Config中的属性名称enabled与Arrays.asList("enabled")相同
注意不要忘记AuthGatewayFilterFactory类上的@Component注解
配置- Auth=true之后会通过RouteDefinitionRouteLocator类中的loadGatewayFilters方法加载到过滤器链中。
更多GatewayFilterFactory在org.springframework.cloud.gateway.filter.factory包中
相同的 predicates的配置对应的有RoutePredicateFactory接口,Path是PathRoutePredicateFactory的前缀,代表了PathRoutePredicateFactory的名称,后面的值/zookeeper/**会通过
@Override
public List shortcutFieldOrder() {
return Arrays.asList(PATTERN_KEY, MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY);
}
传递到其内部类Config的pattern属性中。可以看到其内部类中有三个属性名称分别对应的与Arrays.asList(PATTERN_KEY, MATCH_OPTIONAL_TRAILING_SEPARATOR_KEY)中的参数名称相同
更多的RoutePredicateFactory在org.springframework.cloud.gateway.handler.predicate中
其它的RoutePredicateFactory和GatewayFilterFactory以此类推!
将之前的过滤器注释掉并重启应用
浏览器输入http://localhost:8080/zookeeper/zookeeper
2.2GlobalFilter
顾名思义为全局的过滤器
自定义GlobalFilter非常简单只需实现GlobalFilter即可
package com.wl.springcloud.gateway.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
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.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.ipc.netty.ByteBufFlux;
import java.util.HashMap;
import java.util.Map;
/**
* Created by Administrator on 2019/4/24.
*/
@Component
public class GlobalAuthFilter implements GlobalFilter,Ordered {
@Override
public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String token = request.getQueryParams().getFirst("token");
if(token != null && !token.equals("")){
return chain.filter(exchange);
}
ServerHttpResponse response = exchange.getResponse();
//设置http响应状态码
response.setStatusCode(HttpStatus.BAD_REQUEST);
//设置响应头信息Content-Type类型
response.getHeaders().add("Content-Type","application/json");
//设置返回json数据
return response.writeAndFlushWith(Flux.just(ByteBufFlux.just(response.bufferFactory().wrap(getWrapData()))));
//直接返回(没有返回数据)
// return response.setComplete().then();
//设置返回的数据(非json格式)
// return response.writeWith(Flux.just(response.bufferFactory().wrap("".getBytes())));
}
private byte[] getWrapData() {
Map map = new HashMap<>();
map.put("code","1");
map.put("msg","token is empty or illegal");
map.put("filter","global");
try {
return new ObjectMapper().writeValueAsString(map).getBytes();
} catch (JsonProcessingException e) {
//
}
return "".getBytes();
}
@Override
public int getOrder() {
return 0;
}
}
配置增加一个/abc/**的路由
server:
port: 8080
spring:
cloud:
zookeeper:
connect-string: 192.168.245.130:2181
gateway:
discovery:
locator:
enabled: true
routes:
- id: zookeeper1
uri: lb://zookeeper1
predicates:
- Path= /zookeeper/**
filters:
- StripPrefix=0
- Auth=true
#配置多个路由
- id: zookeeper1
uri: lb://zookeeper1
predicates:
- Path= /abc/**
filters:
- StripPrefix=1
浏览器输入
http://localhost:8080/abc/zookeeper/zookeeper
加上token
3.HystrixGatewayFilterFactory配置服务熔断、降级(由名字可见是绑定路由的过滤器)
加入依赖
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
2.0.3.RELEASE
修改配置
server:
port: 8080
spring:
cloud:
zookeeper:
connect-string: 192.168.245.130:2181
gateway:
discovery:
locator:
enabled: true
routes:
- id: zookeeper1
uri: lb://zookeeper1
predicates:
- Path= /zookeeper/**
filters:
- StripPrefix=0
- Auth=true
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallbackUri
#配置多个路由
- id: zookeeper1
uri: lb://zookeeper1
predicates:
- Path= /abc/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/fallbackUri
fallbackUri是服务降级后访问的路径
package com.wl.springcloud.gateway.fallback;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
/**
* Created by Administrator on 2019/5/24.
*/
@RestController
public class GatewayFallback {
@RequestMapping(value = "/fallbackUri",produces = {MediaType.APPLICATION_JSON_VALUE})
public Object fallbackUri(ServerWebExchange exchange){
return "服务降级";
}
}
hystrix超时配置
hystrix:
command:
fallbackcmd:
execution:
isolation:
strategy: SEMAPHORE
thread:
timeoutInMilliseconds: 2000
shareSecurityContext: true
command后fallbackcmd与上面Hystrix args中的name绑定
Spring-cloud-gateway更多GatewayFilterFactory 参考https://www.cnblogs.com/liukaifeng/p/10055863.html
Spring Cloud GateWay动态路由配置参考https://blog.csdn.net/lazasha/article/details/84942823
使用eureka作为服务注册发现中心参考我的另一篇文章spring-cloud-starter-netflix-zuul(原spring-cloud-starter-zuul)的使用