经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
Spring Cloud Gateway编码实现任意地址跳转
来源:cnblogs  作者:程序员欣宸  时间:2023/6/28 8:51:07  对本文有异议

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 作为《Spring Cloud Gateway实战》系列的第十四篇,本文会继续发掘Spring Cloud Gateway的潜力,通过编码体验操控网关的乐趣,开发出一个实用的功能:让Spring Cloud Gateway应用在收到请求后,可以按照业务的需要跳转到任意的地址去

一般路由规则

  • 先来看一个普通的路由规则,如下所示,意思是将所有/hello/**的请求转发到http://127.0.0.1:8082这个地址去:
  1. spring:
  2. application:
  3. name: hello-gateway
  4. cloud:
  5. gateway:
  6. routes:
  7. - id: path_route
  8. uri: http://127.0.0.1:8082
  9. predicates:
  10. - Path=/hello/**
  • 上述规则的功能如下图所示,假设这就是生产环境的样子,192.168.50.99:8082是提供服务的后台应用:
    在这里插入图片描述

特殊规则

  • 以上是常规情况,但也有些特殊情况,要求SpringCloud Gateway把浏览器的请求转发到不同的服务上去
  • 如下图所示,在之前的环境中增加了另一个服务(即蓝色块),假设蓝色服务代表测试环境
    在这里插入图片描述
  • 浏览器发起的/hello/str请求中,如果header中带有tag-test-user,并且值等于true,此时要求SpringCloud Gateway把这个请求转发到测试环境
  • 如果浏览器的请求header中没有tag-test-user,SpringCloud Gateway需要像以前那样继续转发到192.168.50.99:8082
  • 很明显,上述需求难以通过配置来实现,因为转发的地址和转发逻辑都是围绕业务逻辑来定制的,这也就是本篇的目标:对同一个请求path,可以通过编码转发到不同的地方去
  • 实现上述功能的具体做法是:自定义过滤器

设计

  • 编码之前先设计,把关键点想清楚再动手
  • 今天咱们要开发一个SpringCloud Gateway应用,里面新增一个自定义过滤器
  • 实现这个功能需要三个知识点作为基础,也就是说,您会通过本篇实战掌握以下知识点:
  1. 自定义过滤器
  2. 自定义过滤器的配置参数和bean的映射
  3. 编码构造Route实例
  • 用思维导图将具体工作内容展开,如下图所示,咱们就按部就班的实现吧:
    在这里插入图片描述

源码下载

名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本篇的源码在spring-cloud-tutorials文件夹下,如下图红框所示:
    在这里插入图片描述- spring-cloud-tutorials内部有多个子项目,本篇的源码在gateway-dynamic-route文件夹下,如下图红框所示:
    在这里插入图片描述

编码

  • 新建名为gateway-dynamic-route的maven工程,其pom.xml内容如下:
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>spring-cloud-tutorials</artifactId>
  7. <groupId>com.bolingcavalry</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>gateway-dynamic-route</artifactId>
  12. <dependencies>
  13. <dependency>
  14. <groupId>com.bolingcavalry</groupId>
  15. <artifactId>common</artifactId>
  16. <version>${project.version}</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>org.springframework.cloud</groupId>
  20. <artifactId>spring-cloud-starter-gateway</artifactId>
  21. </dependency>
  22. </dependencies>
  23. <build>
  24. <plugins>
  25. <!-- 如果父工程不是springboot,就要用以下方式使用插件,才能生成正常的jar -->
  26. <plugin>
  27. <groupId>org.springframework.boot</groupId>
  28. <artifactId>spring-boot-maven-plugin</artifactId>
  29. <configuration>
  30. <mainClass>com.bolingcavalry.gateway.GatewayDynamicRouteApplication</mainClass>
  31. </configuration>
  32. <executions>
  33. <execution>
  34. <goals>
  35. <goal>repackage</goal>
  36. </goals>
  37. </execution>
  38. </executions>
  39. </plugin>
  40. </plugins>
  41. </build>
  42. </project>
  • 启动类是普通的SpringBoot启动类:
  1. package com.bolingcavalry.gateway;
  2. import org.springframework.boot.SpringApplication;
  3. import org.springframework.boot.autoconfigure.SpringBootApplication;
  4. @SpringBootApplication
  5. public class GatewayDynamicRouteApplication {
  6. public static void main(String[] args) {
  7. SpringApplication.run(GatewayDynamicRouteApplication.class,args);
  8. }
  9. }
  • 接下来是本篇的核心,自定义过滤器类,代码中已经添加了详细的注释,有几处要注意的地方稍后会提到:
  1. package com.bolingcavalry.gateway.filter;
  2. import lombok.Data;
  3. import lombok.ToString;
  4. import lombok.extern.slf4j.Slf4j;
  5. import org.springframework.cloud.gateway.filter.GatewayFilter;
  6. import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
  7. import org.springframework.cloud.gateway.route.Route;
  8. import org.springframework.http.HttpHeaders;
  9. import org.springframework.http.HttpMethod;
  10. import org.springframework.http.server.reactive.ServerHttpRequest;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.util.MultiValueMap;
  13. import org.springframework.web.util.UriComponentsBuilder;
  14. import java.net.URI;
  15. import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
  16. @Component
  17. @Slf4j
  18. public class BizLogicRouteGatewayFilterFactory extends AbstractGatewayFilterFactory<BizLogicRouteGatewayFilterFactory.BizLogicRouteConfig> {
  19. private static final String TAG_TEST_USER = "tag-test-user";
  20. public BizLogicRouteGatewayFilterFactory() {
  21. super(BizLogicRouteConfig.class);
  22. }
  23. @Override
  24. public GatewayFilter apply(BizLogicRouteConfig config) {
  25. return (exchange, chain) -> {
  26. // 本次的请求对象
  27. ServerHttpRequest request = exchange.getRequest();
  28. // 调用方请求时的path
  29. String rawPath = request.getURI().getRawPath();
  30. log.info("rawPath [{}]", rawPath);
  31. // 请求头
  32. HttpHeaders headers = request.getHeaders();
  33. // 请求方法
  34. HttpMethod httpMethod = request.getMethod();
  35. // 请求参数
  36. MultiValueMap<String, String> queryParams = request.getQueryParams();
  37. // 这就是定制的业务逻辑,isTestUser等于ture代表当前请求来自测试用户,需要被转发到测试环境
  38. boolean isTestUser = false;
  39. // 如果header中有tag-test-user这个key,并且值等于true(不区分大小写),
  40. // 就认为当前请求是测试用户发来的
  41. if (headers.containsKey(TAG_TEST_USER)) {
  42. String tageTestUser = headers.get(TAG_TEST_USER).get(0);
  43. if ("true".equalsIgnoreCase(tageTestUser)) {
  44. isTestUser = true;
  45. }
  46. }
  47. URI uri;
  48. if (isTestUser) {
  49. log.info("这是测试用户的请求");
  50. // 从配置文件中得到测试环境的uri
  51. uri = UriComponentsBuilder.fromHttpUrl(config.getTestEnvUri() + rawPath).queryParams(queryParams).build().toUri();
  52. } else {
  53. log.info("这是普通用户的请求");
  54. // 从配置文件中得到正式环境的uri
  55. uri = UriComponentsBuilder.fromHttpUrl(config.getProdEnvUri() + rawPath).queryParams(queryParams).build().toUri();
  56. }
  57. // 生成新的Request对象,该对象放弃了常规路由配置中的spring.cloud.gateway.routes.uri字段
  58. ServerHttpRequest serverHttpRequest = request.mutate().uri(uri).method(httpMethod).headers(httpHeaders -> httpHeaders = httpHeaders).build();
  59. // 取出当前的route对象
  60. Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
  61. //从新设置Route地址
  62. Route newRoute =
  63. Route.async().asyncPredicate(route.getPredicate()).filters(route.getFilters()).id(route.getId())
  64. .order(route.getOrder()).uri(uri).build();
  65. // 放回exchange中
  66. exchange.getAttributes().put(GATEWAY_ROUTE_ATTR,newRoute);
  67. // 链式处理,交给下一个过滤器
  68. return chain.filter(exchange.mutate().request(serverHttpRequest).build());
  69. };
  70. }
  71. /**
  72. * 这是过滤器的配置类,配置信息会保存在此处
  73. */
  74. @Data
  75. @ToString
  76. public static class BizLogicRouteConfig {
  77. // 生产环境的服务地址
  78. private String prodEnvUri;
  79. // 测试环境的服务地址
  80. private String testEnvUri;
  81. }
  82. }
  • 上述代码中要注意的地方如下:
  1. BizLogicRouteConfig是过滤器的配置类,可以在使用过滤器时在配置文件中配置prodEnvUri和testEnvUri的值,在代码中可以通过这两个字段取得配置值
  2. 过滤器的工厂类名为BizLogicRouteGatewayFilterFactory,按照规则,过滤器的名字是BizLogicRoute
  3. 在apply方法中,重新创建ServerHttpRequest和Route对象,它们的参数可以按照业务需求随意设置,然后再将这两个对象设置给SpringCloud gateway的处理链中,接下来,处理链上的其他过滤拿到的就是新的ServerHttpRequest和Route对象了

配置

  • 假设生产环境地址是http://127.0.0.1:8082,测试环境地址是http://127.0.0.1:8087,整个SpringCloud Gateway应用的配置文件如下,可见使用了刚刚创建的过滤器,并且为此过滤器配置了两个参数:
  1. server:
  2. #服务端口
  3. port: 8086
  4. spring:
  5. application:
  6. name: gateway-dynamic-route
  7. cloud:
  8. gateway:
  9. routes:
  10. - id: path_route
  11. uri: http://0.0.0.0:8082
  12. predicates:
  13. - Path=/hello/**
  14. filters:
  15. - name: BizLogicRoute
  16. args:
  17. prodEnvUri: http://127.0.0.1:8082
  18. testEnvUri: http://127.0.0.1:8087
  • 至此,编码完成了,启动这个服务

开发和启动后台服务,模拟生产和测试环境

  • 接下来开始验证功能是否生效,咱们要准备两个后台服务:
  1. 模拟生产环境的后台服务是provider-hello,监听端口是8082,其/hello/str接口的返回值是Hello World, 2021-12-12 10:53:09
  2. 模拟测试环境的后台服务是provider-for-test-user,监听端口是8087,其/hello/str接口的返回值是Hello World, 2021-12-12 10:57:11 (from test enviroment)(和生产环境相比,返回内容多了个(from test enviroment)),对应Controller参考如下:
  1. package com.bolingcavalry.provider.controller;
  2. import com.bolingcavalry.common.Constants;
  3. import org.springframework.web.bind.annotation.*;
  4. import java.text.SimpleDateFormat;
  5. import java.util.Date;
  6. import java.util.Map;
  7. @RestController
  8. @RequestMapping("/hello")
  9. public class Hello {
  10. private String dateStr(){
  11. return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
  12. }
  13. /**
  14. * 返回字符串类型
  15. * @return
  16. */
  17. @GetMapping("/str")
  18. public String helloStr() {
  19. return Constants.HELLO_PREFIX + ", " + dateStr() + " (from test enviroment)";
  20. }
  21. }
  • 以上两个服务,对应的代码都在我的Github仓库中,如下图红框所示:
    在这里插入图片描述
  • 启动gateway-dynamic-routeprovider-helloprovider-for-test-user服务
  • 此时,SpringCloud gateway应用和两个后台服务都启动完成,情况如下图,接下来验证刚才开发的过滤器能不能像预期那样转发:
    在这里插入图片描述

验证

  • 用postman工具向gateway-dynamic-route应用发起一次请求,返回值如下图红框所示,证明这是provider-hello的响应,看来咱们的请求已经正常到达:
    在这里插入图片描述
  • 再发送一次请求,如下图,这次在header中加入键值对,得到的结果是provider-for-test-user的响应
    在这里插入图片描述
  • 至此,过滤器的开发和验证已经完成,通过编码,可以把外部请求转发到任何咱们需要的地址去,并且支持参数配置,这个过滤器还有一定的可配置下,减少了硬编码的比率,如果您正在琢磨如何深度操控SpringCloud Gateway,希望本文能给您一些参考;

欢迎关注博客园:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...

原文链接:https://www.cnblogs.com/bolingcavalry/p/17507693.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号