spring gateway 笔记

  • Post author:
  • Post category:其他



目录


路由定义


配置路由谓词


配置路由过滤器


自定义全局过滤器


自定义全局异常处理器


http请求超时配置


跨域配置


官方网站


image-20211212142720197



执行流程

  1. 客户端请求进入网关

  2. 进入

    RoutePredicateHandlerMapping

    ,根据路由定义,创建路由对象,进行路由匹配,匹配通过往下执行,反之报错404

  3. 进入

    FilteringWebHandler

    ,执行过滤器链



时序图



核心类

class类 作用
RoutePredicateHandlerMapping 路由转发
RouteDefinitionRouteLocator 根据路由定义,创建路由对象
FilteringWebHandler 合并全局过滤器、默认过滤器、路由配置过滤器,排序,执行过滤器链
GatewayFilterFactory 创建过滤器
RoutePredicateFactory 创建谓词
DefaultGatewayFilterChain 执行过滤器链

路由定义

image-20211212155632907

public class Route implements Ordered {
    
    // 路由唯一标志,
    private final String id;
    
    // 路由转发对应微服务的地址
    private final URI uri;
    
    // 路由执行顺序编号
    private final int order;
    
    //谓词
    private final AsyncPredicate<ServerWebExchange> predicate;
    
    //网关过滤器集合
    private final List<GatewayFilter> gatewayFilters;
    
    //省略部分源代码
}

配置路由谓词

路径路由谓词工厂(PathRoutePredicateFactory)

支持配置多个路径

  • 简写方式

spring:
  cloud:
    gateway:
      routes:
      # '-'代表一个对象
      - id: path_route
        uri: lb://product-service
        predicates:
        #简写
        - Path=/a/get/{segment} # 如果请求路径为/a/get//1或/a/get/bar,则此路由匹配。多个谓词,以‘,’分隔开
  • 完整写法

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: lb://product-service
        predicates:
         - name: Path # 路由谓词工厂的前缀,如PathRoutePredicateFactory
           args: # args 下的参数,需要根据每个谓词工厂中的Config内部类的属性定义。
            patterns: /a/**,/b/**
  • 一个路由配置多个谓词

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: lb://product-service
        predicates:
         - name: Path # 路由谓词工厂的前缀,如PathRoutePredicateFactory
           args: # args 下的参数,需要根据每个谓词工厂中的Config内部类的属性定义。
            patterns: /a/**,/b/**
         - name: Query
           args:
            param: user
            regexp: zhangsan.*
            
            
######################简写方式##########################################
spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: lb://product-service
        predicates:
         - Path=/a/**,/b/**
         - Query=user,zhangsan.*

配置路由过滤器

StripPrefixGatewayFilterFactory

该工厂中有一个

parts

参数,该

parts

参数指示在将请求发送到下游之前要从请求中剥离的路径中的部分数

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: lb://product-service
        predicates:
         - Path=/a/**,/b/**
        filters: 
         - StripPrefix=1
#前端请求地址:http://localhost:8082/a/helloword,往下游转发前,会将原地址中的a去掉,变成http://product-service/helloword,往下转发。

RequestRateLimiterGatewayFilterFactory

用户请求速率限制器过滤器,起到限流作业

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: lb://product-service
        predicates:
         - Path=/a/**,/b/**
        filters: 
         - name: RequestRateLimiter
           args:
             redis-rate-limiter.replenishRate: 1
             redis-rate-limiter.burstCapacity: 5
             key-resolver: "#{@myKeyResolver}" # Spel表达式
  • redis-rate-limiter.replenishRate: 1,是您希望用户每秒允许多少个请求,而没有任何丢弃的请求。这是令牌桶被填充的速率。

  • redis-rate-limiter.burstCapacity是允许用户在一秒钟内执行的最大请求数。这是令牌桶可以容纳的令牌数。将此值设置为零将阻止所有请求。

  • key-resolver,表示按照什么策略限流,可以根据ip限流,等等。这个里面配置具体策略。

/**
 * @author:shixianqing
 * @Date:2021/12/9 11:11
 * @Description: 限制请求的标识,可以根据用户名,客户端ip,进行限流
 **/
@Component
@Slf4j
public class MyKeyResolver implements KeyResolver {
​
    @Override
    public Mono<String> resolve(ServerWebExchange exchange) {
        String hostAddress = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
        log.info("hostAddress:{}",hostAddress);
        return Mono.just(hostAddress);
    }
}

HystrixGatewayFilterFactory

熔断降级过滤器。

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: lb://product-service
        predicates:
         - Path=/a/**,/b/**
        filters: 
          - name: Hystrix
            args:
               name: fallbackcmd # RouteHystrixCommand 组名
               fallbackUri: forward:/fallback # 重定向自定义降级接口
# 降级超时配置
hystrix:
  command:
    fallbackcmd:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000
/**
* 降级接口
*/
@RestController
@Slf4j
public class FallbackController {
​
    @RequestMapping("fallback")
    @ResponseStatus
    public String fallback(ServerWebExchange exchange) throws Exception {
        Exception exception = exchange.getAttribute(ServerWebExchangeUtils.HYSTRIX_EXECUTION_EXCEPTION_ATTR);
        ServerWebExchange delegate = ((ServerWebExchangeDecorator) exchange).getDelegate();
        log.error("接口调用失败,URL={}", delegate.getRequest().getURI(), exception);
        if (exception instanceof HystrixTimeoutException) {
            log.error("msg {}", "接口调用超时");
            throw new BizException("100","接口调用超时");
        } else if (exception != null && exception.getMessage() != null) {
            log.error("msg {}", "接口调用失败: " + exception.getMessage());
            throw new BizException("100","接口调用失败,失败原因:"+exception.getMessage());
        } else {
            log.error("msg {}", "接口调用失败");
            throw new BizException("100","接口调用失败:"+exception.getMessage());
        }
    }
}

接口调用,出现降级,执行下面方法

    @Override
        protected Observable<Void> resumeWithFallback() {
            
            //没有配置降级接口,调用父类降级处理
            if (this.fallbackUri == null) {
                return super.resumeWithFallback();
            }
​
            // TODO: copied from RouteToRequestUrlFilter
            URI uri = exchange.getRequest().getURI();
            // TODO: assume always?
            boolean encoded = containsEncodedParts(uri);
            URI requestUrl = UriComponentsBuilder.fromUri(uri).host(null).port(null)
                    .uri(this.fallbackUri).scheme(null).build(encoded).toUri();
            //保存降级地址
            exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
            addExceptionDetails();
            //重新构造request,请求降级接口
            ServerHttpRequest request = this.exchange.getRequest().mutate()
                    .uri(requestUrl).build();
            ServerWebExchange mutated = exchange.mutate().request(request).build();
            return RxReactiveStreams.toObservable(getDispatcherHandler().handle(mutated));
        }
    }

自定义全局过滤器

自定义全局过滤器,可以做身份验证等一些安全功能。每个请求都会经过。

@Slf4j
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //鉴权
        List<String> name = exchange.getRequest().getHeaders().get("name");
        log.info("name:{}",name);
        String path = exchange.getRequest().getURI().getPath();
        //path:/a/helloword
        log.info("path:{}",path);
        String url = exchange.getRequest().getURI().toString();
        //url:http://localhost:8082/a/helloword
        log.info("url:{}",url);
        if (CollectionUtils.isEmpty(name)) {
            return Mono.error(new BizException("100","当前未登录"));
        }
        return chain.filter(exchange);
    }
​
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }
}

自定义全局异常处理器

@Component
@Order(-1)
@Slf4j
public class GlobalExceptionHandler implements WebExceptionHandler {
​
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
​
        //获取原始请求地址
        LinkedHashSet<URI> uris = exchange
                .getAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
        String url;
        if (CollectionUtils.isEmpty(uris)) {
            url = exchange.getRequest().getURI().toString();
        } else {
            url = uris.stream().findFirst().get().toString();
        }
        log.error("url:{},调用失败:",url,ex);
        R r = null;
        if (ex instanceof TimeoutException) {
            r = R.error("接口调用超时,请稍后重试");
        } else if (ex instanceof BizException) {
            BizException e = (BizException) ex;
            r = R.error(e.getMessage());
        } else if (ex instanceof ResponseStatusException) {
            ResponseStatusException e = (ResponseStatusException) ex;
            if (e.getStatus().is4xxClientError()) {
                r = R.error("服务不存在,请联系管理员");
            } else if (e.getStatus().is5xxServerError()) {
                r = R.error("系统异常,请联系管理员");
            }
        }else {
            r = R.error("接口调用失败,请稍后重试");
        }
        String responseStr = JSONObject.toJSONString(r);
        ServerHttpResponse response = exchange.getResponse();
        //处理响应乱码
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
        return response.writeWith(Mono.fromSupplier(() -> {
            DataBufferFactory factory = response.bufferFactory();
            try {
                return factory.wrap(responseStr.getBytes("UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
                return factory.wrap(new byte[0]);
            }
        }));
    }
}

http请求超时配置

全局超时

配置全局 http 超时:

connect-timeout

必须以毫秒为单位指定。

response-timeout

必须指定为 java.time.Duration

spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 1000
        response-timeout: 5s

每个路由超时

要配置每条路由超时:

connect-timeout

必须以毫秒为单位指定。

response-timeout

必须以毫秒为单位指定。

      - id: per_route_timeouts
        uri: https://example.org
        predicates:
          - name: Path
            args:
              pattern: /delay/{timeout}
        metadata:
          response-timeout: 200
          connect-timeout: 200

跨域配置

spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':
             # 允许携带认证信息
          # 允许跨域的源(网站域名/ip),设置*为全部
          # 允许跨域请求里的head字段,设置*为全部
          # 允许跨域的method, 默认为GET和OPTIONS,设置*为全部
          # 跨域允许的有效期
            allow-credentials: true
            allowedOrigins: "*"
            allowedHeaders: "*"
            allowedMethods: "*"

参考

GlobalCorsProperties

配置

官方网站


参考spring官网



版权声明:本文为weixin_38310717原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。