Gateway服务网关(二)

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
简介: Gateway服务网关

过滤器

关于过滤器这块我举个例子,更多的内容请小伙伴自己查阅文档

官方文档:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

举一个用的比较多的过滤器:

StripPrefix

顾名思义,除去前缀的过滤器,将匹配的请求的前缀去除,将去除后的请求转发给下游服务

spring:
  cloud:
    gateway:
      # 路由配置
      routes:
        # 路由id, 保证唯一性
        - id: my-goods
          # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名
          uri: lb://my-goods
          # 断言
          predicates:
            # 匹配goods开头的请求
            - Path=/api/goods/**
          filters:
            # 1表示去除一个前缀
            - StripPrefix=1

组合来看,意思是当客户端发起请求:http://localhost:5555/api/goods/get-goods, 匹配该路由,然后将第一个前缀api去除,然后转发给商品服务,转发的路径为:/goods/get-goods

测试

自定义断言工厂

上面提到过:所有的断言工厂都是继承于AbstractRoutePredicateFactory, 并且命名规则为:XxxRoutePredicateFactory, 比如Path的类名为:PathRoutePredicateFactory

我们现在就来尝试实现一个自定义的请求头断言工厂吧

编写代码

package com.my.micro.service.gateway.filter;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
/**
 * @author Zijian Liao
 * @since 1.0.0
 */
@Component
public class MyHeaderRoutePredicateFactory extends AbstractRoutePredicateFactory<MyHeaderRoutePredicateFactory.Config> {
    /**
     * Header key.
     */
    public static final String HEADER_KEY = "header";
    /**
     * Regexp key.
     */
    public static final String REGEXP_KEY = "regexp";
    public MyHeaderRoutePredicateFactory() {
        super(MyHeaderRoutePredicateFactory.Config.class);
    }
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(HEADER_KEY, REGEXP_KEY);
    }
    @Override
    public Predicate<ServerWebExchange> apply(MyHeaderRoutePredicateFactory.Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange exchange) {
                // 获取请求头
                List<String> values = exchange.getRequest().getHeaders()
                        .getOrDefault(config.header, Collections.emptyList());
                if (values.isEmpty()) {
                    return false;
                }
                // 判断请求头中的值是否与配置匹配
                return values.stream()
                        .anyMatch(value -> value.matches(config.regexp));
            }
            @Override
            public String toString() {
                return String.format("Header: %s=%s ", config.header, config.regexp);
            }
        };
    }
    public static class Config {
        private String header;
        private String regexp;
        public String getHeader() {
            return header;
        }
        public void setHeader(String header) {
            this.header = header;
        }
        public String getRegexp() {
            return regexp;
        }
        public void setRegexp(String regexp) {
            this.regexp = regexp;
        }
    }
}

编写配置

spring:
  cloud:
    gateway:
      # 路由配置
      routes:
        # 路由id, 保证唯一性
        - id: my-goods
          # 路由的地址,格式:协议://服务名 lb: load balance,my-goods: 商品服务名
          uri: lb://my-goods
          # 断言
          predicates:
            # 匹配goods开头的请求
            - Path=/api/goods/**
            # 匹配header为name=aljian的请求
            - MyHeader=name,ajian
          filters:
            # 1表示去除一个前缀
            - StripPrefix=1

测试

直接在浏览器中访问

改用postman访问

自定义过滤器

自定义过滤器的方式与自定义断言工厂的方式大致相同,所以过滤器继承于AbstractGatewayFilterFactory或者AbstractNameValueGatewayFilterFactory, 命名规则为XxxGatewayFilterFactory

比如内置的添加请求头过滤器

public class AddRequestHeaderGatewayFilterFactory
    extends AbstractNameValueGatewayFilterFactory {
  @Override
  public GatewayFilter apply(NameValueConfig config) {
    return new GatewayFilter() {
      @Override
      public Mono<Void> filter(ServerWebExchange exchange,
          GatewayFilterChain chain) {
        // 获取到需要添加的header value
        String value = ServerWebExchangeUtils.expand(exchange, config.getValue());
        // 将header添加到request中
        ServerHttpRequest request = exchange.getRequest().mutate()
            .header(config.getName(), value).build();
        // 重新构建出一个exchange
        return chain.filter(exchange.mutate().request(request).build());
      }
      @Override
      public String toString() {
        return filterToStringCreator(AddRequestHeaderGatewayFilterFactory.this)
            .append(config.getName(), config.getValue()).toString();
      }
    };
  }
}

全局过滤器

以上内容都是针对于每一个router,Spring Cloud Gateway提供了一个针对所有router的全局过滤器

实现方式如下

package com.my.micro.service.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
 * @author Zijian Liao
 * @since 1.0.0
 */
@Slf4j
@Component
public class MyGlobalFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        log.info("进入全局过滤器,请求路径为:{}", path);
        // 编写任何你想要实现的逻辑,比如权限校验
        return chain.filter(exchange);
    }
}

测试

自定义异常处理器

小伙伴应该发现了,在遇到错误时,Spring Cloud Gateway返回给客户端的异常并不优雅,所以我们需要自定义异常处理

编写自定义异常处理器

package com.my.micro.service.gateway.exception;
import com.my.micro.service.gateway.result.BaseResult;
import lombok.extern.slf4j.Slf4j;
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.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.lang.NonNull;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
/**
 * @author Zijian Liao
 */
@Slf4j
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
    /**
     * Create a new {@code DefaultErrorWebExceptionHandler} instance.
     *
     * @param errorAttributes    the error attributes
     * @param resourceProperties the resources configuration properties
     * @param errorProperties    the error configuration properties
     * @param applicationContext the current application context
     */
    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                                          ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }
    @NonNull
    @Override
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        Throwable throwable = getError(request);
        return ServerResponse.status(HttpStatus.OK)
                .contentType(MediaType.APPLICATION_JSON)
                .body(BodyInserters.fromValue(this.handleError(throwable)));
    }
    private BaseResult<Void> handleError(Throwable throwable){
        return BaseResult.failure(throwable.getMessage());
    }
}

BaseResult

package com.my.micro.service.gateway.result;
import lombok.Data;
/**
 * @author Zijian Liao
 * @since 1.0.0
 */
@Data
public class BaseResult<T> {
    private Integer code;
    private String message;
    public BaseResult(Integer code, String message){
        this.code = code;
        this.message = message;
    }
    public static <T> BaseResult<T> failure(String  message){
        return new BaseResult<>(-1, message);
    }
}

编写配置类

package com.my.micro.service.gateway.exception;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
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.context.annotation.Primary;
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.stream.Collectors;
/**
 * @author Zijian Liao
 * @since 1.0.0
 */
@Configuration
public class ExceptionConfiguration {
    @Primary
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes, ServerProperties serverProperties, ResourceProperties resourceProperties,
                                                             ObjectProvider<ViewResolver> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer,
                                                             ApplicationContext applicationContext) {
        DefaultErrorWebExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,
                resourceProperties, serverProperties.getError(), applicationContext);
        exceptionHandler.setViewResolvers(viewResolversProvider.orderedStream().collect(Collectors.toList()));
        exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

测试

小结

本编介绍了关于微服务架构中——客户端如何访问的解决方案:Spring Cloud Gateway

其中介绍了Gateway的三个核心概念:Route,Predicate,Filter。并演示了如何配置及使用他们,还讲解了如何自定义Predicate和Filter。

最后介绍了Spring Cloud Gateway的全局过滤器,以及如何实现自定义异常处理。

以上

希望大家有所收获,我们下期再见~

gittee: https://gitee.com/lzj960515/my-micro-service-demo

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
1月前
|
安全 5G 网络性能优化
|
2月前
|
监控 负载均衡 安全
微服务(五)-服务网关zuul(一)
微服务(五)-服务网关zuul(一)
|
11天前
|
负载均衡 Java 应用服务中间件
Gateway服务网关
Gateway服务网关
24 1
Gateway服务网关
|
1月前
|
前端开发 Java API
vertx学习总结5之回调函数及其限制,如网关/边缘服务示例所示未来和承诺——链接异步操作的简单模型响应式扩展——一个更强大的模型,特别适合组合异步事件流Kotlin协程
本文是Vert.x学习系列的第五部分,讨论了回调函数的限制、Future和Promise在异步操作中的应用、响应式扩展以及Kotlin协程,并通过示例代码展示了如何在Vert.x中使用这些异步编程模式。
47 5
vertx学习总结5之回调函数及其限制,如网关/边缘服务示例所示未来和承诺——链接异步操作的简单模型响应式扩展——一个更强大的模型,特别适合组合异步事件流Kotlin协程
|
2月前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
102 5
|
2月前
|
测试技术 微服务
微服务(八)-服务网关zuul(四)
微服务(八)-服务网关zuul(四)
|
2月前
|
监控 前端开发 Java
微服务(七)-服务网关zuul(三)
微服务(七)-服务网关zuul(三)
|
2月前
|
负载均衡 前端开发 安全
微服务(六)-服务网关zuul(二)
微服务(六)-服务网关zuul(二)
|
3月前
|
安全 API
【Azure API 管理】APIM Self-Host Gateway 自建本地环境中的网关数量超过10个且它们的出口IP为同一个时出现的429错误
【Azure API 管理】APIM Self-Host Gateway 自建本地环境中的网关数量超过10个且它们的出口IP为同一个时出现的429错误
|
4月前
|
监控 负载均衡 Java
深入理解Spring Cloud中的服务网关
深入理解Spring Cloud中的服务网关