【Spring Cloud】网关 Gateway ❍ ❍ ❍ ❍ ❍

前言

最近有一个新的需求,需要手动配置一个没有 Nacos 的 Gateway 网关。大大小小踩了不少坑,今天就全面复盘一下。

Spring Cloud Gateway 简介

Spring Cloud Gateway是一个功能强大、灵活易用的API网关服务,它为微服务架构中的服务间通信提供了统一的入口和出口。

主要功能和特点

  1. 路由转发:Spring Cloud Gateway可以根据配置将请求转发到不同的目标服务,支持动态路由配置,灵活适应不同的场景需求。
  2. 过滤器链:通过过滤器链对请求和响应进行处理,支持在请求被路由前、后进行预处理或后处理,实现日志记录、认证、授权、请求转发、限流等功能。
  3. 断路器:集成了 Hystrix 断路器,可以在服务不可用或超时时进行熔断,保障服务的稳定性。
  4. 集成性:作为Spring Cloud项目的一部分,Spring Cloud Gateway天然集成了Spring Cloud组件,如服务注册与发现(Eureka、Consul等)、负载均衡(Ribbon)、断路器(Hystrix)等。
  5. 响应式编程模型:基于Project Reactor实现的响应式编程模型,支持异步和非阻塞的IO操作,提高了系统的性能和吞吐量。

由于篇幅和自身业务用到的知识面等因素,我只从两个方面来复盘这次的业务。

1、动态路由。

2、过滤器链。

Gateway 动态路由

Gateway动态路由是指在网关服务运行时,能够动态地添加、修改和删除路由规则,而无需重新启动网关服务。这个功能的出现是为了应对不断变化的业务需求和路由规则,使得网关服务更加灵活和可扩展。

动态路由的主要思想是将路由规则存储在外部配置中,例如数据库、ZooKeeper、或者其他配置中心。网关服务在启动时从外部配置中加载这些路由规则,并将其应用到请求转发过程中。这样,当业务需求发生变化时,我们可以通过在外部配置中心中添加、修改或删除路由规则,来实现动态路由的管理。

1、如何定义一个路由

  1. ID(标识符) :路由的唯一标识符,用于在路由配置中区分不同的路由。
  2. URI(统一资源标识符) :指定了路由请求应该转发到的目标地址。可以是一个具体的 URL,也可以是一个负载均衡的服务名称。
  3. 谓词(Predicates) :谓词定义了路由应该何时生效。谓词通常根据请求的特征(如路径、请求方法、请求头等)来匹配请求,只有满足谓词条件的请求才会被路由到目标地址。
  4. 过滤器(Filters) :过滤器用于在请求被路由之前或之后对请求进行修改或过滤。可以使用过滤器来实现请求的修改、添加认证、限流、重定向等功能。
  5. 顺序(Order) :路由的顺序属性指定了路由应该被应用的顺序。通常,路由会按照它们的顺序依次匹配请求,因此顺序属性很重要,可以确保路由按照预期的顺序应用。

例子:

java 复制代码
[
    {
        "id": "route1",
        "uri": "http://localhost:8081",
        "predicates": [
            {
                "name": "Path",
                "args": {
                    "pattern": "/service1/**"
                }
            }
        ],
        "filters": [
            {
                "name": "RewritePath",
                "args": {
                    "regexp": "/service1/(?<remaining>.*)",
                    "replacement": "/$\{remaining}"
                }
            }
        ]
    },
    {
        "id": "route2",
        "uri": "http://localhost:8082",
        "predicates": [
            {
                "name": "Path",
                "args": {
                    "pattern": "/service2/**"
                }
            }
        ]
    }
]

2、如何加载动态路由

编写自定义的RouteDefinitionLocator接口的实现,用于从外部源加载路由规则。

java 复制代码
@Component
public class CustomRouteDefinitionLocator implements RouteDefinitionLocator {
    
    @Autowired
    private ExternalConfigurationSource externalConfigurationSource;
​
    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        // 从外部配置中心加载路由规则
        List<RouteDefinition> definitions = externalConfigurationSource.loadRouteDefinitions();
        return Flux.fromIterable(definitions);
    }
}

定义一个外部配置源,用于加载路由规则。例如,可以实现一个从数据库或配置文件中读取路由规则的类。

java 复制代码
@Component
public class ExternalConfigurationSource {
​
    // 从外部源加载路由定义
    public List<RouteDefinition> loadRouteDefinitions() {
        // 从数据库或配置文件等外部源加载路由规则
        // 返回路由定义的列表
    }
}

3、如何管理动态路由

java 复制代码
/**
 * 路由管理服务接口,用于管理动态路由规则
 */
public interface RouteService {
​
    /**
     * 获取所有路由规则
     * @return 包含所有路由规则的 Flux 对象
     */
    public Flux<RouteDefinition> getAllRoutes();
​
    /**
     * 根据路由 ID 删除指定路由规则
     * @param routeId 要删除的路由规则的唯一标识符
     * @return 删除操作完成后的 Mono 对象
     */
    public Mono<Void> deleteRouteById(String routeId);
​
    /**
     * 刷新所有路由规则,使得新的路由规则立即生效
     * @return 刷新操作完成后的 Mono 对象
     */
    public Mono<Void> refreshAllRoutes();
​
    /**
     * 根据路由 ID 刷新指定路由规则,使得新的路由规则立即生效
     * @param routeId 要刷新的路由规则的唯一标识符
     * @return 刷新操作完成后的 Mono 对象
     */
    public Mono<Void> refreshRouteById(String routeId);
​
    /**
     * 添加新的路由规则
     * @param routeDefinition 要添加的路由规则对象
     * @return 添加操作完成后的 Mono 对象,包含添加后的路由规则信息
     */
    public Mono<ResponseEntity<?>> addRoute(RouteDefinition routeDefinition);
​
}
​
java 复制代码
@Service
public class RouteServiceImpl implements RouteService {
​
    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
​
    @Autowired
    private RouteDefinitionLocator routeDefinitionLocator;
​
    /**
     * 查询全部路由
     * @return 包含全部路由定义的 Flux 对象
     */
    @Override
    public Flux<RouteDefinition> getAllRoutes() {
        return routeDefinitionLocator.getRouteDefinitions();
    }
​
    /**
     * 根据路由 ID 删除路由
     * @param routeId 要删除的路由的唯一标识符
     * @return 删除操作完成后的 Mono 对象
     */
    @Override
    public Mono<Void> deleteRouteById(String routeId) {
        return routeDefinitionWriter.delete(Mono.just(routeId));
    }
​
    /**
     * 刷新全部路由
     * @return 刷新操作完成后的 Mono 对象
     */
    @Override
    public Mono<Void> refreshAllRoutes() {
        return routeDefinitionLocator.getRouteDefinitions()
                .flatMap(routeDefinition -> routeDefinitionWriter.save(Mono.just(routeDefinition)))
                .then();
    }
​
    /**
     * 根据路由 ID 刷新指定路由
     * @param routeId 要刷新的路由的唯一标识符
     * @return 刷新操作完成后的 Mono 对象
     */
    @Override
    public Mono<Void> refreshRouteById(String routeId) {
        return routeDefinitionLocator.getRouteDefinitions()
                .filter(routeDefinition -> routeDefinition.getId().equals(routeId))
                .flatMap(routeDefinition -> routeDefinitionWriter.save(Mono.just(routeDefinition)))
                .then();
    }
​
    /**
     * 添加新的路由
     * @param routeDefinition 要添加的路由定义对象
     * @return 添加操作完成后的 Mono 对象,包含添加后的路由信息
     */
    @Override
    public Mono<ResponseEntity<?>> addRoute(RouteDefinition routeDefinition) {
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        return Mono.just(ResponseEntity.ok().build());
    }
}
​

Gateway 过滤器链

1、一个简单的全局过滤器

java 复制代码
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
​
/**
 * 全局过滤器示例类,实现了 GlobalFilter 和 Ordered 接口。
 */
public class SimpleFilter implements GlobalFilter, Ordered {
​
    /**
     * 过滤器逻辑方法,在此方法中可以实现对请求的处理逻辑。
     * @param exchange ServerWebExchange 对象,表示当前的请求和响应的上下文信息。
     * @param chain GatewayFilterChain 对象,用于将请求交给下一个过滤器处理。
     * @return Mono<Void> 对象,表示异步处理结果的流。
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 在此处编写过滤器的逻辑处理代码
        return null;
    }
​
    /**
     * 获取过滤器的顺序,数值越小优先级越高。
     * @return 整数值,表示过滤器的顺序。
     */
    @Override
    public int getOrder() {
        return 0;
    }
}

2、讲解几个概念

Spring WebFlux 框架 是什么?

Spring WebFlux 是 Spring Framework 5 引入的一种新的反应式编程模型,用于构建基于异步和非阻塞的响应式应用程序。与传统的基于 Servlet 的 Spring MVC 框架不同,Spring WebFlux 提供了一种更加灵活和高效的编程方式,适用于处理高并发、高吞吐量的应用场景。

Spring WebFlux 的核心思想是响应式编程,它基于 Reactor 库实现了反应式流处理。在 Spring WebFlux 中,所有的输入和输出都被抽象为流(Flux 和 Mono),这使得开发者可以更容易地处理异步数据流,实现非阻塞的事件驱动编程。

Spring WebFlux 框架提供了基于注解的编程模型,允许开发者使用常见的 Spring 注解来定义控制器、路由和其他组件。同时,它也支持函数式编程风格,允许开发者使用函数式端点来定义路由和处理器。

ServerWebExchange 是什么? ServerWebExchange 是 Spring WebFlux 框架中的核心接口,用于表示一次客户端与服务器之间的 HTTP 交换。

ServerWebExchange 的作用是什么? 它封装了 HTTP 请求和响应,并提供了操作请求和响应的方法,以及与请求处理相关的上下文信息。

ServerWebExchange 的主要功能和特点

  • 封装了 HTTP 请求和响应,提供了统一的处理接口。
  • 可以对请求和响应进行修改和定制化处理。
  • 包含了与请求处理相关的上下文信息,如路由信息、路径变量、查询参数等。
  • 提供了属性存储机制,允许在请求处理过程中传递和共享数据。

ServerWebExchange 的组成

  • ServerHttpRequest:请求对象

    • 作用和功能: 代表客户端发送的 HTTP 请求,包含了请求的方法、URL、头部信息、参数等。
    • 常用方法和属性: getMethod()getURI()getHeaders()getQueryParams() 等。
  • ServerHttpResponse:响应对象

    • 作用和功能: 代表服务器发送的 HTTP 响应,包含了响应的状态码、头部信息、响应体等。
    • 常用方法和属性: setStatusCode()getHeaders()writeWith()writeAndFlushWith() 等。
  • Attributes:属性对象

    • 作用和功能: 用于存储和传递与请求处理相关的属性信息,提供了属性的增删改查操作。
    • 常用方法和属性: getAttribute()setAttribute()removeAttribute()getAttributeOrDefault() 等。

ServerWebExchange 的使用

  • 如何获取 ServerWebExchange 对象? 可以通过 WebFlux 框架提供的处理器或过滤器中的方法参数获取。

  • ServerWebExchange 对象的常见用法和场景

    • 从请求中获取信息,如路径、参数、头部等。
    • 修改请求和响应,如重定向、添加头部信息等。
    • 传递和共享属性,如在拦截器或过滤器中传递数据。

3、过滤器 Filter 可以实现什么功能

修改请求路径,原始请求的 Path 修改为 "/test"

java 复制代码
@Component
public class TestFilter implements GlobalFilter, Ordered {
 
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String newServletPath = "/test"; // 修改请求路径
        ServerHttpRequest newRequest = request.mutate().path(newServletPath).build();
        return chain.filter(exchange.mutate().request(newRequest).build());
    }
}

修改请求数据,过滤器会拦截传入的请求,并修改请求体中的JSON数据,向其中添加一个名为 "userId" 的新属性。

java 复制代码
@Component
public class ModifyRequestBodyGlobalFilter implements GlobalFilter {
​
    @Autowired
    private ObjectMapper objectMapper;
​
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
​
        // 从请求中获取请求体
        Flux<DataBuffer> body = request.getBody();
​
        // 将请求体中的数据转换成字符串
        return DataBufferUtils.join(body)
                .flatMap(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);
                    String bodyString = new String(bytes, StandardCharsets.UTF_8);
​
                    try {
                        // 解析JSON数据
                        JsonNode jsonNode = objectMapper.readTree(bodyString);
                        Assert.isTrue(jsonNode instanceof ObjectNode, "JSON格式异常");
                        ObjectNode objectNode = (ObjectNode) jsonNode;
​
                        // 在JSON数据中添加新属性
                        objectNode.put("userId", "123");
​
                        // 将修改后的JSON数据转换回字节数组
                        byte[] modifiedBytes = objectMapper.writeValueAsBytes(objectNode);
​
                        // 创建新的请求体数据缓冲区
                        DataBuffer modifiedDataBuffer = exchange.getResponse().bufferFactory().wrap(modifiedBytes);
​
                        // 创建新的请求对象,并将修改后的请求体设置进去
                        ServerHttpRequest modifiedRequest = new ServerHttpRequestDecorator(request) {
                            @Override
                            public Flux<DataBuffer> getBody() {
                                return Flux.just(modifiedDataBuffer);
                            }
                        };
​
                        // 使用修改后的请求对象创建新的ServerWebExchange
                        ServerWebExchange modifiedExchange = exchange.mutate().request(modifiedRequest).build();
​
                        // 继续执行过滤器链
                        return chain.filter(modifiedExchange);
                    } catch (IOException e) {
                        log.error("Failed to modify request body", e);
                        return Mono.error(e);
                    }
                });
    }
}

修改路由,根据路由的元数据中的 targetUris 列表中随机选择一个目标 URI,并修改当前的路由为选定的 URI。

java 复制代码
@Component
public class RandomUriRoutingFilter implements GlobalFilter, Ordered {
​
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        if (route != null) {
            List<String> targetUris = (List<String>) route.getMetadata().get("targetUris");
            if (targetUris != null && !targetUris.isEmpty()) {
                String selectedUri = getRandomUri(targetUris);
                try {
                    URI uri = new URI(selectedUri);
                    Route newRoute = Route.async()
                            .asyncPredicate(route.getPredicate())
                            .filters(route.getFilters())
                            .id(route.getId())
                            .order(route.getOrder())
                            .uri(uri).build();
                    exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, newRoute);
                } catch (URISyntaxException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return chain.filter(exchange);
    }
​
    private String getRandomUri(List<String> targetUris) {
        Random random = new Random();
        int randomIndex = random.nextInt(targetUris.size());
        return targetUris.get(randomIndex);
    }
​
    @Override
    public int getOrder() {
        return 0;
    }
}
​

修改响应体。

java 复制代码
@Component
public class ModifyResponseFilter implements GatewayFilter {
​
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取当前路由信息
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        if (route != null) {
            // 在响应中修改需要的内容
            RewriteFunction<String, String> rewriteFunction = (exchange1, response) ->
                    response.map(body -> body.toUpperCase()); // 示例:将响应体转换为大写
            // 使用 ModifyResponseBodyGatewayFilterFactory 创建修改响应体的过滤器
            ModifyResponseBodyGatewayFilterFactory filterFactory = new ModifyResponseBodyGatewayFilterFactory();
            GatewayFilter modifyResponseBodyFilter = filterFactory.apply(rewriteFunction);
​
            // 执行过滤器链
            return modifyResponseBodyFilter.filter(exchange, chain);
        }
        return chain.filter(exchange);
    }
}

总结

一定要多思考,如果人永远待在舒适圈的话,人永远不会成长。共勉

觉得作者写的不错的,值得你们借鉴的话,就请点一个免费的赞吧!这个对我来说真的很重要。૮(˶ᵔ ᵕ ᵔ˶)ა

相关推荐
qq_353233538937 分钟前
【原创】java+ssm+mysql校园疫情防控管理系统设计与实现
java·mvc·javaweb·ssm框架·bs·疫情防控
代码调试4 小时前
Springboot校园失物招领平台
java·spring boot
camellias_5 小时前
SpringBoot(二十三)SpringBoot集成JWT
java·spring boot·后端
tebukaopu1485 小时前
springboot如何获取控制层get和Post入参
java·spring boot·后端
昔我往昔5 小时前
SpringBoot 创建对象常见的几种方式
java·spring boot·后端
武昌库里写JAVA5 小时前
机器学习笔记2 - 机器学习的一般流程
spring boot·spring·毕业设计·layui·课程设计
q567315235 小时前
用 PHP或Python加密字符串,用iOS解密
java·python·ios·缓存·php·命令模式
灭掉c与java5 小时前
第三章springboot数据访问
java·spring boot·后端
啊松同学5 小时前
【Java】设计模式——工厂模式
java·后端·设计模式
枫叶_v6 小时前
【SpringBoot】20 同步调用、异步调用、异步回调
java·spring boot·后端