SpringCloud使用Gateway组件作为路由器
一、说明:
参考:https://windmt.com/2018/05/07/spring-cloud-13-spring-cloud-gateway-router/
官网:https://cloud.spring.io/spring-cloud-gateway/reference/html/#shortcut-configuration
参考:https://www.cnblogs.com/crazymakercircle/p/11704077.html
https://www.cnblogs.com/crazymakercircle/p/11704077.html
1、getway系统架构
2、getway分成以下几部分
-
Route(路由):这是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配
-
Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数
-
Filter(过滤器):这是 GatewayFilter 的实例,我们可以使用它修改请求和响应。
3、getaway的请求流程
二、gateway接入注册中心
1、添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、添加配置
spring:
cloud:
gateway:
discovery:
locator:
enabled: true #开启gateway的客户端发现
routes: #路由规则
- id: course #路由规则
uri: /v1/api/course #映射地址
order: 1 #排序
predicates: #配置预处理器
- Path=/**
filters: #配置过滤器
- SetPath=
- RewritePath=/CONSUMER/(?<segment>.*), /$\{segment}
3、启动类设置为Eureka的客户端
@EnableDiscoveryClient
@SpringBootApplication
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
}
二、gateway在项目中的使用
1、gateway的Predicate规则
(1)内置规则说明和实例
# datetime :请求时间限制在之前,之间,之后
- After=2018-01-20T06:06:06+08:00[Asia/Shanghai]
- Before=2018-01-20T06:06:06+08:00[Asia/Shanghai]
- Between=2018-01-20T06:06:06+08:00[Asia/Shanghai], 2019-01-20T06:06:06+08:00[Asia/Shanghai]
# cookie :请求指定的Cookie
- Cookie=ityouknow, kee.e
# header :请求指定的Header的正则配置和指定名称
- Header=X-Request-Id, \d+
# host :请求指定的Host
- Host=**.ityouknow.com
# method :请求指定的方法
- Method=GET,POST,OPTIONS
# path :请求指定的路径
- Path=/foo/{segment}
# QueryParam :请求指定的参数值
- Query=channel_web
# RemoteAddr :请求指定的远程地址
- RemoteAddr=192.168.1.1/24
spring:
cloud:
gateway:
routes:
- id: host_foo_path_headers_to_httpbin
uri: http://ityouknow.com
predicates:
- Host=**.foo.org
- Path=/headers
- Method=GET
- Header=X-Request-Id, \d+
- Query=foo, ba.
- Query=baz
- Cookie=chocolate, ch.p
- After=2018-01-20T06:06:06+08:00[Asia/Shanghai]
2、gateway的Filter规则
spring:
cloud:
gateway:
routes:
- id: nameRoot
uri: http://nameservice
predicates:
- Path=/name/** # 当请求路径匹配到/name/**会将包含name和后边的字符串接去掉转发,
filters:
- StripPrefix=2 # 请求路径修改过滤器 StripPrefix=2就代表截取路径的个数,
- PrefixPath=/mypath # 跟StripPrefix相反,会添加后转发
3、自定义拦截器
spring:
cloud:
gateway:
routes:
- id: nameRoot
uri: http://nameservice
predicates:
- Path=/name/** # 当请求路径匹配到/name/**会将包含name和后边的字符串接去掉转发,
filters:
- StripPrefix=2 # 请求路径修改过滤器 StripPrefix=2就代表截取路径的个数,
- PrefixPath=/mypath # 跟StripPrefix相反,会添加后转发
- Auth # 自定义的权限过滤器
- IPForbid=0:0:0:0:0:0:0:1 #自定义的IP拦截器
- ResponseBody #自定义的响应结果拦截器
(1)自定义全局限流过滤器
<dependency>
<groupId>com.github.vladimir-bukhtoyarov</groupId>
<artifactId>bucket4j-core</artifactId>
<version>4.4.1</version>
</dependency>
通过token桶的方式设置时间段内限流
//=======全局限流过滤器=========//
//全局过滤器,实现GlobalFilter接口,和Ordered接口即可。
@Component
public class FluidControlGlobalGatewayFilter implements GlobalFilter, Ordered{
int capacity = 5;//桶的最大容量,即能装载 Token 的最大数量
int refillTokens = 1; //每次 Token 补充量
Duration duration = Duration.ofSeconds(1); //补充 Token 的时间间隔
private static final Map<String, Bucket> BUCKET_CACHE = new ConcurrentHashMap<>();
private Bucket createNewBucket(){
Refill refill = Refill.greedy(refillTokens, duration);
Bandwidth limit = Bandwidth.classic(capacity, refill);
return Bucket4j.builder().addLimit(limit).build();
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
Bucket bucket = BUCKET_CACHE.computeIfAbsent(ip, k -> createNewBucket());
System.out.println("IP: " + ip + ",has Tokens: " + bucket.getAvailableTokens());
if (bucket.tryConsume(1)){
return chain.filter(exchange);
}else{
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
return exchange.getResponse().setComplete();
}
}
@Override
public int getOrder(){
return -100;
}
}
//==========================
//全局过滤器,使用配置类形式,直接构造bean,使用注解完成Ordered接口功能,统计接口调用时间
@Configuration
public class GlobalGatewayFilterConfig{
//直接将简单的统计耗时加入过滤器
@Bean
@Order(-100)
public GlobalFilter elapsedGlobalFilter(){
return new FluidControlGlobalGatewayFilter();
}
}
(2)自定义统计耗时
/全局过滤器,使用配置类形式,直接构造bean,使用注解完成Ordered接口功能,统计接口调用时间
@Configuration
public class GlobalGatewayFilterConfig{
//直接将简单的统计耗时加入过滤器
@Bean
@Order(-100)
public GlobalFilter elapsedGlobalFilter(){
return (exchange, chain) -> {
//调用请求之前统计时间
Long startTime = System.currentTimeMillis();
return chain.filter(exchange).then().then(Mono.fromRunnable(() -> {
//调用请求之后统计时间
Long endTime = System.currentTimeMillis();
System.out.println(
exchange.getRequest().getURI().getRawPath()
+ ", cost time : " + (endTime - startTime) + "ms");
}));
};
}
}
(3)自定义简单权限过滤器
//全局权限过滤器
public class AuthGatewayFilter implements GatewayFilter, Ordered{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain){
//获取header的参数
String name = exchange.getRequest().getHeaders().getFirst("name");
String password = exchange.getRequest().getHeaders().getFirst("password");
boolean permitted = AuthUtil.isPermitted(name, password);//权限比较
if (permitted){
return chain.filter(exchange);
}else{
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
}
@Override
public int getOrder(){
return 10;
}
}
//简单权限
public final class AuthUtil{
private static Map<String, String> map = new HashMap<>();
private AuthUtil(){}
//程序启动的时候加载权限的信息,比如从文件、数据库中加载
public static void init(){
map.put("tom", "123456");
}
//简单判断
public static boolean isPermitted(String name, String password){
return map.containsKey(name) && map.get(name).equals(password);
}
}
//启动类初始化权限
@SpringBootApplication
public class GatewayApplication{
public static void main(String[] args){
SpringApplication springApplication = new SpringApplication(GatewayApplication.class);
springApplication.addListeners(new ApplicationListenerStarted());//增加监听器
springApplication.run(args);
}
private static class ApplicationListenerStarted
implements ApplicationListener<ApplicationStartedEvent>{
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent)
{
//权限初始化数据
AuthUtil.init();
}
}
}
//===========
@Component
public class AuthGatewayFilterFactory extends AbstractGatewayFilterFactory<Object>{
@Override
public GatewayFilter apply(Object config){
return new AuthGatewayFilter();
}
}
(4)自定义IP地址禁止过滤器
@Component
@Order(99)
public class IPForbidGatewayFilterFactory
extends AbstractGatewayFilterFactory<IPForbidGatewayFilterFactory.Config>
{
public IPForbidGatewayFilterFactory()
{
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder()
{
return Arrays.asList("forbidIp");
}
@Override
public GatewayFilter apply(Config config)
{
return (exchange, chain) -> {
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
if (config.getForbidIp().equals(ip))
{
return chain.filter(exchange);
}
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
};
}
static public class Config
{
private String forbidIp;
public String getForbidIp()
{
return forbidIp;
}
public void setForbidIp(String forbidIp)
{
this.forbidIp = forbidIp;
}
}
}
(5)自定义返回值过滤器
这个过滤器主要是处理如果服务端的接口返回的数据过长,或者有某些敏感字段导致响应的报文问题,乱码问题,等等
@Component
public class ResponseBodyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> {
@Override
public GatewayFilter apply(Object config) {
return new ModifyResponseGatewayFilter();
}
public class ModifyResponseGatewayFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange.mutate().response(decorate(exchange)).build());
}
@SuppressWarnings("unchecked")
ServerHttpResponse decorate(ServerWebExchange exchange) {
return new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
Class inClass = String.class;
Class outClass = String.class;
String originalResponseContentType = exchange
.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_TYPE,
originalResponseContentType);
ClientResponse clientResponse = ClientResponse
.create(exchange.getResponse().getStatusCode())
.headers(headers -> headers.putAll(httpHeaders))
.body(Flux.from(body)).build();
Mono modifiedBody = clientResponse.bodyToMono(inClass)
.flatMap(originalBody -> {
//TODO:此次可以对返回的body进行操作
System.out.println(originalBody);
return Mono.just(originalBody);
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody,
outClass);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(
exchange, exchange.getResponse().getHeaders());
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
Flux<DataBuffer> messageBody = outputMessage.getBody();
HttpHeaders headers = getDelegate().getHeaders();
if (!headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) {
messageBody = messageBody.doOnNext(data -> headers
.setContentLength(data.readableByteCount()));
}
return getDelegate().writeWith(messageBody);
}));
}
@Override
public Mono<Void> writeAndFlushWith(
Publisher<? extends Publisher<? extends DataBuffer>> body) {
return writeWith(Flux.from(body).flatMapSequential(p -> p));
}
};
}
@Override
public int getOrder() {
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER - 1;
}
}
}
(6)定义局部过滤器
//@Component
@Slf4j
public class UserIdCheckGateWayFilter implements GatewayFilter, Ordered
{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
String url = exchange.getRequest().getPath().pathWithinApplication().value();
log.info("请求URL:" + url);
log.info("method:" + exchange.getRequest().getMethod());
/* String secret = exchange.getRequest().getHeaders().getFirst("secret");
if (StringUtils.isBlank(secret))
{
return chain.filter(exchange);
}*/
//获取param 请求参数
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
//获取header
String userId = exchange.getRequest().getHeaders().getFirst("user-id");
log.info("userId:" + userId);
if (StringUtils.isBlank(userId))
{
log.info("*****头部验证不通过,请在头部输入 user-id");
//终止请求,直接回应
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
// 值越小,优先级越高
// int HIGHEST_PRECEDENCE = -2147483648;
// int LOWEST_PRECEDENCE = 2147483647;
@Override
public int getOrder()
{
return HIGHEST_PRECEDENCE;
}
}
//===========添加到过滤器
@Component
public class UserIdCheckGatewayFilterFactory extends AbstractGatewayFilterFactory<Object>
{
@Override
public GatewayFilter apply(Object config)
{
return new UserIdCheckGateWayFilter();
}
}
在配置文件中配置
- id: service_provider_demo_route_filter
uri: lb://service-provider-demo
predicates:
- Path=/filter/**
filters:
- RewritePath=/filter/(?<segment>.*), /provider/$\{segment}
- UserIdCheck
4、统一配置跨域请求
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowed-origins: "*"
allowed-headers: "*"
allow-credentials: true
allowed-methods:
- GET
- POST
三、gateway的高级应用
1、结合redis实现网关限流
(1)添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
(2)添加相关的配置
spring:
application:
name: cloud-gateway
redis: # redis的相关配置
host: localhost
password:
port: 6379
cloud:
gateway:
discovery:
locator:
enabled: true
routes: # 路由配置
- id: rate_limit_route
uri: /v1/api/course
filters: # 限速路由相关配置
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10 # 允许用户每秒处理多少个请求
redis-rate-limiter.burstCapacity: 20 #令牌桶的容量,允许在一秒钟内完成的最大请求数
key-resolver: "#{@userKeyResolver}" #使用 SpEL 按名称引用 bean
predicates:
- Method=GET
(3)添加配置类
public class Config {
// 根据请求参数中的user字段来作为路由
@Bean
public KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getQueryParams().getFirst("user"));
}
// 根据远程主机名称来作为路由
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getHostName());
}
//直接根据URL进行限流
@Bean
public KeyResolver apiKeyResolver() {
return exchange -> Mono.just(exchange.getRequest().getPath().value());
}
}
2、结合hystrix断路器
spring:
cloud:
gateway:
routes:
- id: ingredients
uri: lb://ingredients
predicates:
- Path=//ingredients/**
filters:
- name: Hystrix
args:
name: fetchIngredients
fallbackUri: forward:/fallback # 服务降级回调地址
- id: ingredients-fallback # 定义回调服务地址
uri: http://localhost:9994
predicates:
- Path=/fallback
(1)使用resilience4j方式
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
引入ReactiveResilience4JCircuitBreakerFactory
@Bean
public ReactiveResilience4JCircuitBreakerFactory defaultCustomizer() {
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom() //
.slidingWindowType(SlidingWindowType.TIME_BASED) // 滑动窗口的类型为时间窗口
.slidingWindowSize(60) // 时间窗口的大小为60秒
.minimumNumberOfCalls(5) // 在单位时间窗口内最少需要5次调用才能开始进行统计计算
.failureRateThreshold(50) // 在单位时间窗口内调用失败率达到50%后会启动断路器
.enableAutomaticTransitionFromOpenToHalfOpen() // 允许断路器自动由打开状态转换为半开状态
.permittedNumberOfCallsInHalfOpenState(5) // 在半开状态下允许进行正常调用的次数
.waitDurationInOpenState(Duration.ofSeconds(60)) // 断路器打开状态转换为半开状态需要等待60秒
.recordExceptions(Throwable.class) // 所有异常都当作失败来处理
.build();
ReactiveResilience4JCircuitBreakerFactory factory = new ReactiveResilience4JCircuitBreakerFactory();
factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
.timeLimiterConfig(TimeLimiterConfig.custom().timeoutDuration(Duration.ofMillis(500)).build())
.circuitBreakerConfig(circuitBreakerConfig).build());
return factory;
}
(2)使用hystrix实现
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
引入Hystrix工厂
@Bean
public HystrixCircuitBreakerFactory defaultConfig() {
HystrixCircuitBreakerFactory circuitBreakerFactory = new HystrixCircuitBreakerFactory();
circuitBreakerFactory.configureDefault(id -> HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(id)).andCommandPropertiesDefaults(
HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(4000)));
return circuitBreakerFactory;
}
3、配置重试RetryGatewayFilter
spring:
cloud:
gateway:
routes:
- id: retry_test
uri: lb://spring-cloud-producer
predicates:
- Path=/retry
filters:
- name: Retry
args:
retries: 3 #重试次数,默认值是 3 次
statuses: BAD_GATEWAY #HTTP 的状态返回码,取值请参考:org.springframework.http.HttpStatus
methods: GET #指定哪些方法的请求需要进行重试逻辑,默认值是 GET 方法
series: 5 #状态码配置,默认值是 SERVER_ERROR,值是 5,也就是 5XX(5 开头的状态码),共有5 个值
4、结合Ribbon实现负载均衡
- uri: lb://my-load-balanced-service 注意lb即表示使用了负载均衡
- NFLoadBalancerRuleClassName 采用Ribbon的负载均衡策略
- RandomRule 随机策略
- RetryRule 重试策略
- RoundRule 轮询策略
- WeightedResponseTimeRule 响应权重策略等等
server:
port: 8080
spring:
application:
name: gateway_server
cloud:
gateway:
default-filters:
routes:
- id: my_route
uri: lb://my-load-balanced-service
predicates:
- Path=/gateway/**
filters:
- StripPrefix=1
my-load-balanced-service:
ribbon:
listOfServers: localhost:1001, localhost:1002,localhost:1003
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
NFLoadBalancerPingClassName: com.yefengyu.gateway.loadbanlance.HealthExamination
健康检查
//=====客户端定义一个健康检查接口
@RestController
public class HealthController
{
@GetMapping("/heath")
@ResponseBody
public String heath()
{
return "ok";
}
}
//====gateway工程增加一个config类
@Configuration
public class MainConfig
{
@Bean
public RestTemplate restTemplate()
{
return new RestTemplate();
}
}
//===========新建一个健康检查类
@Component
public class HealthExamination implements IPing
{
@Autowired
private RestTemplate restTemplate;
@Override
public boolean isAlive(Server server)
{
String url = "http://" + server.getId() + "/heath";
try
{
ResponseEntity<String> heath = restTemplate.getForEntity(url, String.class);
if (heath.getStatusCode() == HttpStatus.OK)
{
System.out.println("ping " + url + " success and response is " + heath.getBody());
return true;
}
System.out.println("ping " + url + " error and response is " + heath.getBody());
return false;
}
catch (Exception e)
{
System.out.println("ping " + url + " failed");
return false;
}
}
}
自定义负载均衡策略
public class MyRule extends AbstractLoadBalancerRule{
private volatile int total;
private volatile int index;
List<Server> upList = new ArrayList<>();
public MyRule(){}
public Server choose(ILoadBalancer lb, Object key){
if (lb == null){
return null;
} else{
Server server = null;
while (server == null){
if (Thread.interrupted()){return null;}
List<Server> allList = lb.getAllServers();
int serverCount = allList.size();
if (serverCount == 0){return null;}
if (total == 0){
upList = lb.getReachableServers();
}
if (total < 3){
if (upList.size() != lb.getReachableServers().size()){
index = 0;
}
server = lb.getReachableServers().get(index);
total++;
}else{
total = 0;
index++;
if (index >= lb.getReachableServers().size())
{
index = 0;
}
}
if (server == null){
Thread.yield();
}else{
if (server.isAlive()){
return server;
}
server = null;
Thread.yield();
}
}
return server;
}
}
public Server choose(Object key){
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig){}
}
//将自定义策略配置到配置文件中即可
5、Gateway结合Swagger文档
@Component
public class MySwaggerResourceProvider implements SwaggerResourcesProvider {
/**
* swagger2默认的url后缀
*/
private static final String SWAGGER2URL = "/v2/api-docs";
/**
* 网关路由
*/
private final RouteLocator routeLocator;
/**
* 网关应用名称
*/
@Value("${spring.application.name}")
private String self;
@Autowired
public MySwaggerResourceProvider(RouteLocator routeLocator) {
this.routeLocator = routeLocator;
}
@Override
public List<SwaggerResource> get() {
List<SwaggerResource> resources = new ArrayList<>();
List<String> routeHosts = new ArrayList<>();
// 获取所有可用的host:serviceId
routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
.filter(route -> !self.equals(route.getUri().getHost()))
.subscribe(route -> routeHosts.add(route.getUri().getHost()));
// 记录已经添加过的server,存在同一个应用注册了多个服务在nacos上
Set<String> dealed = new HashSet<>();
routeHosts.forEach(instance -> {
// 拼接url,样式为/serviceId/v2/api-info,当网关调用这个接口时,会自动通过负载均衡寻找对应的主机
String url = "/" + instance.toLowerCase() + SWAGGER2URL;
// 无group时 使用默认
if (!dealed.contains(url)) {
dealed.add(url);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setUrl(url);
swaggerResource.setName(instance);
// 服务下拉列表读取映射
resources.add(swaggerResource);
}
});
return resources;
}
}
6、Gateway结合redis实现集群限流
内部原理是令牌桶算法,通过内置的redis的lua脚本,给每个访问的请求派发令牌,从而实现限流
(1)添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifatId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
(2)配置类
@Configuration
public class GatewayConfiguration {
//构建RequestRateLimiterGatewayFilterFactory
@Bean
@ConditionalOnBean({RedisRateLimiter.class, IpKeyResolver.class})
public RequestRateLimiterGatewayFilterFactory requestRateLimiterGatewayFilterFactory(RedisRateLimiter rateLimiter, IpKeyResolver keyResolver) {
return new RequestRateLimiterGatewayFilterFactory(rateLimiter, keyResolver);
}
}
(3)IpKeyResolver通过IP进行限流
@Component
public class IpKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest()
.getRemoteAddress()
.getAddress()
.getHostAddress());
}
@Bean
public HostAddrKeyResolver hostAddrKeyResolver() {
return new HostAddrKeyResolver();
}
}
(4)UriKeyResolver通过URL路径进行限流
public class UriKeyResolver implements KeyResolver {
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getURI().getPath());
}
@Bean
public UriKeyResolver uriKeyResolver() {
return new UriKeyResolver();
}
}
(5)UserIdKeyResolver通过UserId进行限流
public class UserIdKeyResolver implements KeyResolver {
@Bean
KeyResolver userKeyResolver() {
return exchange -> Mono.just(exchange.getRequest()
.getQueryParams().getFirst("userid"));
}
@Bean
public UserIdKeyResolver userIdKeyResolver() {
return new UserIdKeyResolver();
}
}
(6)添加配置项
server:
port: 8081
spring:
application:
name: gateway-limiter
cloud:
gateway:
routes:
- id: limit_route
uri: http://httpbin.org:80/get
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
filters:
- name: RequestRateLimiter
args:
key-resolver: '#{@IpKeyResolver}' #令牌桶解析对象名称
redis-rate-limiter.replenishRate: 1 # 令牌填充速率
redis-rate-limiter.burstCapacity: 3 #令牌桶的总量
redis:
host: localhost
port: 6379
database: 0
7、gateway跨域配置
(1)第一种方式,配置方式
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
# 允许携带认证信息
# 允许跨域的源(网站域名/ip),设置*为全部
# 允许跨域请求里的head字段,设置*为全部
# 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
# 跨域允许的有效期
allow-credentials: true
allowed-origins:
- "http://localhost:13009"
- "http://localhost:13010"
allowed-headers: "*"
allowed-methods:
- OPTIONS
- GET
- POST
max-age: 3600
# 允许response的head信息
# 默认仅允许如下6个:
# Cache-Control
# Content-Language
# Content-Type
# Expires
# Last-Modified
# Pragma
#exposed-headers:
(2)第二种方式,配置类方式
@Configuration
@ConditionalOnBean(GlobalCorsProperties.class)
public class CorsAutoConfiguration {
@Autowired
private GlobalCorsProperties globalCorsProperties;
@Bean
public CorsWebFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
globalCorsProperties.getCorsConfigurations().forEach((path,corsConfiguration)->source.registerCorsConfiguration(path, corsConfiguration));
return new CorsWebFilter(source);
}
}
8、Gateway的全局异常处理
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
/**
* 获取异常属性
*/
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
int code = 500;
Throwable error = super.getError(request);
if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
code = 404;
}
return response(code, this.buildMessage(request, error));
}
/**
* 指定响应处理方法为JSON处理的方法
* @param errorAttributes
*/
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
/**
* 根据code获取对应的HttpStatus
* @param errorAttributes
*/
@Override
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
int statusCode = (int) errorAttributes.get("code");
return HttpStatus.valueOf(statusCode);
}
/**
* 构建异常信息
* @param request
* @param ex
* @return
*/
private String buildMessage(ServerRequest request, Throwable ex) {
StringBuilder message = new StringBuilder("Failed to handle 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;
}
}
9、 自定义异常处理
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
/**
* 获取异常属性
*/
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
int code = 500;
Throwable error = super.getError(request);
if (error instanceof org.springframework.cloud.gateway.support.NotFoundException) {
code = 404;
}
return response(code, this.buildMessage(request, error));
}
/**
* 指定响应处理方法为JSON处理的方法
* @param errorAttributes
*/
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
/**
* 根据code获取对应的HttpStatus
* @param errorAttributes
*/
@Override
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
int statusCode = (int) errorAttributes.get("code");
return HttpStatus.valueOf(statusCode);
}
/**
* 构建异常信息
* @param request
* @param ex
* @return
*/
private String buildMessage(ServerRequest request, Throwable ex) {
StringBuilder message = new StringBuilder("Failed to handle 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;
}
}
@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> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public ErrorHandlerConfiguration(ServerProperties serverProperties,
ResourceProperties resourceProperties,
ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider.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.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}
10、整合Sentinel实现限流
<!--添加sentinel和gateway适配-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.7.1</version>
</dependency>
//配置限流拦截器
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
//添加sentinel拦截器
@Bean
@Order(-1)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
//设置限流规则
@PostConstruct
public void doInit() {
initGatewayRules();
}
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("demo2")
.setCount(2)
.setIntervalSec(2)
.setBurst(2)
.setParamItem(new GatewayParamFlowItem()
.setParseStrategy(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_CLIENT_IP)
)
);
GatewayRuleManager.loadRules(rules);
}
}
//配置自定义返回消息
public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
public JsonSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] datas = "{\"code\":403,\"msg\":\"API接口被限流\"}".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
return serverHttpResponse.writeWith(Mono.just(buffer));
}
}
11、结合Nacos实现动态路由
https://www.cnblogs.com/jian0110/p/12862569.html
(1)Nacos控制台添加一个gateway-route配置
- 选择json结构
[{
"id": "consumer-router",
"order": 0,
"predicates": [{
"args": {
"pattern": "/consume/**"
},
"name": "Path"
}],
"uri": "lb://nacos-consumer"
},{
"id": "provider-router",
"order": 2,
"predicates": [{
"args": {
"pattern": "/provide/**"
},
"name": "Path"
}],
"uri": "lb://nacos-provider"
}]
(2)gateway连接配置中心
- 添加nacos配置以及启动类增加服务发现
- 添加相关配置
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
spring.cloud.nacos.discovery.namespace=08ecd1e5-c042-410a-84d5-b0a8fbeed8ea
nacos.gateway.route.config.data-id=gateway-router
nacos.gateway.route.config.group=DEFAULT_GROUP
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApplication
{
public static void main( String[] args )
{
SpringApplication.run(GatewayApplication.class, args);
}
}
/**
* 路由类配置
*/
@Configuration
public class GatewayConfig {
public static final long DEFAULT_TIMEOUT = 30000;
public static String NACOS_SERVER_ADDR;
public static String NACOS_NAMESPACE;
public static String NACOS_ROUTE_DATA_ID;
public static String NACOS_ROUTE_GROUP;
@Value("${spring.cloud.nacos.discovery.server-addr}")
public void setNacosServerAddr(String nacosServerAddr){
NACOS_SERVER_ADDR = nacosServerAddr;
}
@Value("${spring.cloud.nacos.discovery.namespace}")
public void setNacosNamespace(String nacosNamespace){
NACOS_NAMESPACE = nacosNamespace;
}
@Value("${nacos.gateway.route.config.data-id}")
public void setNacosRouteDataId(String nacosRouteDataId){
NACOS_ROUTE_DATA_ID = nacosRouteDataId;
}
@Value("${nacos.gateway.route.config.group}")
public void setNacosRouteGroup(String nacosRouteGroup){
NACOS_ROUTE_GROUP = nacosRouteGroup;
}
}
(3)初始化路由
/**
*
* 通过nacos下发动态路由配置,监听Nacos中gateway-route配置
*
*/
@Component
@Slf4j
@DependsOn({"gatewayConfig"}) // 依赖于gatewayConfig bean
public class DynamicRouteServiceImplByNacos {
@Autowired
private DynamicRouteServiceImpl dynamicRouteService;
private ConfigService configService;
@PostConstruct
public void init() {
log.info("gateway route init...");
try{
configService = initConfigService();
if(configService == null){
log.warn("initConfigService fail");
return;
}
String configInfo = configService.getConfig(GatewayConfig.NACOS_ROUTE_DATA_ID, GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.DEFAULT_TIMEOUT);
log.info("获取网关当前配置:\r\n{}",configInfo);
List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
for(RouteDefinition definition : definitionList){
log.info("update route : {}",definition.toString());
dynamicRouteService.add(definition);
}
} catch (Exception e) {
log.error("初始化网关路由时发生错误",e);
}
dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID,GatewayConfig.NACOS_ROUTE_GROUP);
}
/**
* 监听Nacos下发的动态路由配置
* @param dataId
* @param group
*/
public void dynamicRouteByNacosListener (String dataId, String group){
try {
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
log.info("进行网关更新:\n\r{}",configInfo);
List<RouteDefinition> definitionList = JSON.parseArray(configInfo, RouteDefinition.class);
log.info("update route : {}",definitionList.toString());
dynamicRouteService.updateList(definitionList);
}
@Override
public Executor getExecutor() {
log.info("getExecutor\n\r");
return null;
}
});
} catch (NacosException e) {
log.error("从nacos接收动态路由配置出错!!!",e);
}
}
/**
* 初始化网关路由 nacos config
* @return
*/
private ConfigService initConfigService(){
try{
Properties properties = new Properties();
properties.setProperty("serverAddr",GatewayConfig.NACOS_SERVER_ADDR);
properties.setProperty("namespace",GatewayConfig.NACOS_NAMESPACE);
return configService= NacosFactory.createConfigService(properties);
} catch (Exception e) {
log.error("初始化网关路由时发生错误",e);
return null;
}
}
}
(4)实现动态路由规则
/**
* 动态更新路由网关service
* 1)实现一个Spring提供的事件推送接口ApplicationEventPublisherAware
* 2)提供动态路由的基础方法,可通过获取bean操作该类的方法。该类提供新增路由、更新路由、删除路由,然后实现发布的功能。
*/
@Slf4j
@Service
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private RouteDefinitionLocator routeDefinitionLocator;
/**
* 发布事件
*/
@Autowired
private ApplicationEventPublisher publisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
/**
* 删除路由
* @param id
* @return
*/
public String delete(String id) {
try {
log.info("gateway delete route id {}",id);
this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "delete success";
} catch (Exception e) {
return "delete fail";
}
}
/**
* 更新路由
* @param definitions
* @return
*/
public String updateList(List<RouteDefinition> definitions) {
log.info("gateway update route {}",definitions);
// 删除缓存routerDefinition
List<RouteDefinition> routeDefinitionsExits = routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {
routeDefinitionsExits.forEach(routeDefinition -> {
log.info("delete routeDefinition:{}", routeDefinition);
delete(routeDefinition.getId());
});
}
definitions.forEach(definition -> {
updateById(definition);
});
return "success";
}
/**
* 更新路由
* @param definition
* @return
*/
public String updateById(RouteDefinition definition) {
try {
log.info("gateway update route {}",definition);
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
} catch (Exception e) {
return "update fail,not find route routeId: "+definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
} catch (Exception e) {
return "update route fail";
}
}
/**
* 增加路由
* @param definition
* @return
*/
public String add(RouteDefinition definition) {
log.info("gateway add route {}",definition);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
}