重新定义cloud 第17章 spring cloud gateway上篇

╰+攻爆jí腚メ 2022-12-21 11:00 201阅读 0赞

gateway 概述

  • 基于 spring 5.0
  • spring boot 2.0
  • project reactor
  • 提供简单,有效且统一, api路由 管理方式
  • 基于 filter 链的方式 提供了 网关基本的 功能

    • 安全
    • 监控/埋点
    • 限流
    • 协议适配
    • 协议转发
    • 安全策略 waf
    • 防刷
    • 流量
    • 监控日志
  • 网关的 核心 Filter 以及filter chain (filter 责任链)
  • 路由 route(包含)

    • id
    • 目的 url
    • 断言工厂
    • 一组filter
    • 断言为真,则 说明url和 配置的路由匹配
  • 断言 predicate

    • spring 5.0 的 serverWebExchange
    • 允许开发者 去定义匹配 来自于 http Request中的任何信息(请求头,参数)
  • 过滤器 filter

    • 一个标准的spring filter
    • gateWay filter
    • globbal filter
    • 过滤器 filter 将会 对 请求和响应 进行 修改处理。

工作原理

  • 默认端口,80,https 443
  • 启动容器只支持 netty
  • gateway client
  • spring cloud gateway

    • HttpWebHandlerAdapter 组装网关上下文
    • DispatcheerHandler 分发处理器,循环遍历 Mapping,获取 Handler
    • Route Predicate Handler Mapping 断言处理器,判断是否可用
    • 断言成功
    • Filtering Web Handler 创建过滤器,调用过滤链

      • pre filter
      • post filter
  • proxied service 代理服务器

入门案例

  • 协议适配

    • 路由信息转发
  • 协议转发
  • 本案例演示,Path路由断言工厂 实现 url 直接转发

xml

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-actuator</artifactId>
  5. </dependency>
  6. <!--Spring Cloud Gateway的Starter-->
  7. <dependency>
  8. <groupId>org.springframework.cloud</groupId>
  9. <artifactId>spring-cloud-starter-gateway</artifactId>
  10. </dependency>
  11. </dependencies>

主启动类

  1. @SpringBootApplication
  2. public class SpringCloudGatewayApplication {
  3. /** * 基本的转发 * 当访问http://localhost:8080/jd * 转发到http://jd.com * @param builder * @return */
  4. @Bean // Locator n. 定位器,探测器
  5. public RouteLocator custom Route Locator(RouteLocatorBuilder builder) {
  6. return builder.routes()
  7. //basic proxy
  8. .route(r ->r.path("/jd")
  9. .uri("http://jd.com:80/").id("jd_route")
  10. ).build();
  11. } //第一种配置
  12. public static void main(String[] args) {
  13. SpringApplication.run(SpringCloudGatewayApplication.class, args);
  14. }
  15. }
  • 第二种配置。application.yml

    spring:
    cloud:

    1. gateway:
    2. routes: #当访问http://localhost:8080/baidu,直接转发到https://www.baidu.com/
    3. - id: baidu_route
    4. uri: http://baidu.com:80/
    5. predicates:
    6. - Path=/baidu

application.yml

  1. server:
  2. port: 8080
  3. spring:
  4. application:
  5. name: spring-cloud-gateway
  6. cloud:
  7. gateway:
  8. routes: #当访问http://localhost:8080/baidu,直接转发到https://www.baidu.com/
  9. - id: baidu_route
  10. uri: http://baidu.com:80/
  11. predicates:
  12. - Path=/baidu
  13. logging: ## Spring Cloud Gateway的日志配置
  14. level:
  15. org.springframework.cloud.gateway: TRACE
  16. org.springframework.http.server.reactive: DEBUG
  17. org.springframework.web.reactive: DEBUG
  18. reactor.ipc.netty: DEBUG
  19. management:
  20. endpoints:
  21. web:
  22. exposure:
  23. include: '*'
  24. security:
  25. enabled: false
  26. trace
  27. /treɪs/ /treɪs/
  28. v. (通过调查)找到;追溯;追踪;沿(特定路径)走;映描;(尤指用手指、脚趾)画;勾画出轮廓
  29. n. 痕迹;踏出来的小路;(大脑上的学习或记忆)痕迹;语迹;跟踪;微量;丝毫;描记线;交线;缰绳

开启端点

  • 如上配置
  • 提供了 gateway actuator
  • 关于 filter和 routes 信息查询,制定 route信息更新的 Rest API
  • http://localhost:8080/actuator/gateway/routes

    1. [{
    2. "route_id": "jd_route", //jd的转发 断言是这个类
    3. "route_object": {
    4. "predicate": "org.springframework.cloud.gateway.support.ServerWebExchangeUtils$$Lambda$293/70528019@27137286"
    5. },
    6. "order": 0
    7. }, {
    8. "route_id": "baidu_route",
    9. "route_definition": {
    10. "id": "baidu_route",
    11. "predicaates": [{
    12. "name": "Path",
    13. "args": {
    14. "_genkey_0": "/baidu" //本地的请求
    15. }
    16. }],
    17. "filters": [],
    18. "uri": "http://baidu.com:80/", //转发的路径
    19. "order": 0
    20. },
    21. "order": 0
    22. }]

路由 断言

  • 路由匹配 是 spring webFlux的 Handler Mapping 为 基础实现的
  • 有许多的 路由断言 工厂
  • 路由断言工厂会根据配置的路由规则,对 请求进行 断言匹配
  • 匹配成功进行下一步处理,否则 断言失败,返回错误信息

after路由器断言工厂

  • after route predicate factory
  • 取UTC 时间格式 UTC一般指协调世界时。协调世界时,又称世界统一时间、世界标准时间、国际协调时间。
  • 当请求进来的当前时间 在配置的 UTF 时间之后,会匹配成功。否则失败。
  • 当前请求在,配置时间之后。才会成功、

路由配置代码

  1. @SpringBootApplication
  2. public class SCGatewayApplication {
  3. @Bean
  4. public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
  5. //生成比当前时间早一个小时的UTC时间
  6. ZonedDateTime minusTime = LocalDateTime.now().minusHours(1).atZone(ZoneId.systemDefault());
  7. return builder.routes()
  8. .route("after_route", r -> r.after(minusTime)
  9. .uri("http://baidu.com"))
  10. .build();
  11. }
  12. public static void main(String[] args) {
  13. SpringApplication.run(SCGatewayApplication.class, args);
  14. }
  15. }
  16. 如果:.plusHours(1) 就会失败,因为当前请求的时间,在之前了。
  • 或者

    spring:
    cloud:

    1. gateway:
    2. routes: #当访问http://localhost:8080/baidu,直接转发到https://www.baidu.com/
    3. - id: baidu_route
    4. uri: http://baidu.com:80/
    5. predicates:
    6. - Path=/baidu #要加百度的访问
    7. - After=2020-11-13T14:26:38.584+08:00[Asia/Shanghai]

UTC工具类

  1. /** * 生成UTC时间 */
  2. public class UtcTimeUtil {
  3. public static void main(String[] args) {
  4. ZonedDateTime time= ZonedDateTime.now();
  5. System.out.println("zonedDateTime:"+time);
  6. String maxTime=ZonedDateTime.now().plusHours(1).format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
  7. System.out.println("maxTime:"+maxTime);
  8. //用这个会出现:Handler Terminated 处理程序终止 无法跳转
  9. String minTime=ZonedDateTime.now().minusHours(1).format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
  10. System.out.println("minTime:"+minTime);
  11. DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
  12. String str=time.format(formatter);
  13. System.out.println(str);
  14. }
  15. }

测试

  • 如果使用类配置,直接访问:http://localhost:8081
  • 如果用类配置,不会配置请求http://localhost:8081/baidu
  • r.path(“/baidu”)
    1. .uri("http://baidu.com").id("aaa")

before 路由器断言 工厂

  • 当请求进来的时间,在 路由器断言工厂之前 ,会匹配成功

    1. @Bean
    2. public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    3. //加一天,保证当前的请求,是在 之前的。
    4. //Handler Terminated(如果当前的请求在 之后)
    5. ZonedDateTime datetime = LocalDateTime.now().plusDays(1).atZone(ZoneId.systemDefault());
    6. return builder.routes()
    7. .route("before_route", r -> r.before(datetime)
    8. .uri("http://baidu.com"))
    9. .build();
    10. }
  • http://localhost:8080 会转发到baidu

Between断言工厂

代码控制

  1. @SpringBootApplication
  2. public class SCGatewayApplication {
  3. @Bean
  4. public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
  5. ZonedDateTime datetime1 = LocalDateTime.now().minusDays(1).atZone(ZoneId.systemDefault());
  6. ZonedDateTime datetime2 = LocalDateTime.now().plusDays(1).atZone(ZoneId.systemDefault());
  7. return builder.routes()
  8. .route("between_route", r -> r.between(datetime1,datetime2)
  9. .uri("http://baidu.com"))
  10. .build();
  11. }
  12. public static void main(String[] args) {
  13. SpringApplication.run(SCGatewayApplication.class, args);
  14. }
  15. }

yaml控制

  1. spring:
  2. cloud:
  3. gateway:
  4. routes:
  5. - id: between_route
  6. uri: http://xujin.org
  7. predicates:
  8. - name: Between
  9. args:
  10. datetime1: 2018-03-15T00:02:48.513+08:00[Asia/Shanghai]
  11. datetime2: 2018-03-15T02:02:48.516+08:00[Asia/Shanghai]
  • 会取, key 和 value
  • 当请求携带的 cookie 和 cookied 断言工厂中配置的 cookie 一致,匹配成功

代码控制

  1. @Bean
  2. public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
  3. return builder.routes()
  4. .route("cookie_route", r -> r.cookie("chocolate", "ch.p")
  5. .uri("http://localhost:8071/test/cookie"))
  6. .build();
  7. }

yaml 控制

  1. server:
  2. port: 8080
  3. spring:
  4. application:
  5. name: spring-cloud-gateway
  6. cloud:
  7. gateway:
  8. routes:
  9. - id: between_route
  10. uri: http://xujin.org
  11. predicates:
  12. - Cookie=chocolate, ch.p

请求到的代码

  1. @GetMapping("/test/cookie")
  2. public String testGateway(HttpServletRequest request, HttpServletResponse response){
  3. Cookie[] cookies = request.getCookies();
  4. if (cookies != null) {
  5. for (Cookie cookie : cookies) {
  6. System.out.println(cookie.getName()+":"+cookie.getValue());
  7. }
  8. }
  9. return "Spring Cloud Gateway,Hello world!";
  10. }

测试

  • http://localhost:8080/
  • header 增加:key为:Cookie ,value: chocolate=ch.p
  • 只有用这个headr头 才能访问

header 路由断言工厂

header 断言的两种方法

  • 根据 路由的 header 信息断言

    @Bean

    1. public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    2. return builder.routes()
    3. .route("header_route", r -> r.header("X-Request-Id", "xujin")
    4. .uri("http://localhost:8071/test/head"))
    5. .build();
    6. }
  • 或者

    spring:
    cloud:

    1. gateway:
    2. routes:
    3. - id: header_route
    4. uri: http://localhost:8071/test/head
    5. predicates:
    6. - Header=X-Request-Id, xujin

被请求的项目

  1. /** * 测试Head路由断言工厂 * @param request * @param response * @return */
  2. @GetMapping("/test/head")
  3. public String testGatewayHead(HttpServletRequest request, HttpServletResponse response){
  4. String head=request.getHeader("X-Request-Id");
  5. return "return head info:"+head;
  6. }

Host 路由断言工厂

  • 对请求中的 Host 进行断言处理,断言成功则进行路由转发。

    @Bean

    1. public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    2. return builder.routes()
    3. .route("host_route", r -> r.host("**.baidu.com:8080")
    4. .uri("http://jd.com"))
    5. .build();
    6. }

    server:
    port: 8080
    spring:
    application:

    1. name: spring-cloud-gateway

    cloud:

    1. gateway:
    2. routes:
    3. - id: header_route
    4. uri: http://jd.com
    5. predicates:
    6. - Host=**.baidu.com:8080
  • 更改host文件,把vip.baidu.com映射到:127.0.0.1

  • 访问:http://vip.baidu.com:8080 即可跳转到京东

Method 断言工厂

  • 根据路由信息配置的 method对 请求方法 是 Get 或者 Post 等 进行断言

    @Bean

    1. public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    2. return builder.routes()
    3. .route("method_route", r -> r.method("GET")
    4. .uri("http://jd.com"))
    5. .build();
    6. }

    server:
    port: 8080
    spring:
    application:

    1. name: spring-cloud-gateway

    cloud:

    1. gateway:
    2. routes:
    3. - id: header_route
    4. uri: http://jd.com
    5. predicates:
    6. - Method=GET
  • 访问:http://localhost:8080/ 即可。

Query 断言工厂

  • 从请求中 获取两个参数,和 Query 断言路由器中的配置 进行匹配
  • http://localhost:8080/?foo=baz 和 r.query(“foo”,“baz”) 配置对比

    @Bean

    1. public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    2. return builder.routes()
    3. .route("query_route", r -> r.query("foo","baz")
    4. .uri("http://baidu.com"))
    5. .build();
    6. }

    server:
    port: 8080
    spring:
    application:

    1. name: spring-cloud-gateway

    cloud:

    1. gateway:
    2. routes:
    3. - id: header_route
    4. uri: http://jd.com
    5. predicates:
    6. - Query=foo, baz

RemoteAddr 路由器断言工厂

  • ipv4 或 v6 网段,或者IP
  • 当请求的 IP 在网段内,匹配成功,否则失败

    @Bean

    1. public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    2. return builder.routes()
    3. .route("remoteaddr_route", r -> r.remoteAddr("127.0.0.1")
    4. .uri("http://baidu.com"))
    5. .build();
    6. }

    spring:
    cloud:

    1. gateway:
    2. routes:
    3. - id: remoteaddr_route
    4. uri: http://baidu.com
    5. predicates:
    6. - RemoteAddr=127.0.0.1
  • 访问:http://localhost:8080/ 不行。

  • 访问:http://127.0.0.1:8080/ 转发成功
  • 或者 配本地的IP,也是可以测试的。

Spring cloud gateway 的 内置 filter

  • 路由过滤器 允许 以某种方式 修改请求进来的 http请求
  • 或 返回的 http 响应
  • 主要作用:处理 特定路由
  • 过滤器 将近20多个
  • 分为7类

    • Header
    • Paramter
    • Path
    • Status
    • Redirect跳转
    • Hystrix熔断
    • RateLimiter

AddRequestHeader 过滤器工厂

  • 对匹配的请求 加上 Header

    @Bean

    1. public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
    2. return builder.routes()
    3. .route("add_request_header_route", r ->
    4. r.path("/test").filters(f ->
    5. f.addRequestHeader
    6. ("X-Request-Acme", "ValueB"))
    7. .uri("http://localhost:8071/test/head"))
    8. .build();
    9. }

    /* @param request @param response @return */

    1. @GetMapping("/test/head")
    2. public String testGatewayHead(HttpServletRequest request, HttpServletResponse response){
    3. String head=request.getHeader("X-Request-Acme");
    4. return "return head info:"+head;
    5. }
  • 访问:http://localhost:8080/test

  • 返回:return head info:ValueB

AddRequestParameter过滤器

  • 对 匹配上的请求路由 添加 请求参数

    @Bean

    1. public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
    2. return builder.routes()
    3. .route("add_request_parameter_route", r ->
    4. r.path("/addRequestParameter").filters(f -> f.addRequestParameter("example", "ValueB"))
    5. .uri("http://localhost:8071/test/addRequestParameter"))
    6. .build();
    7. }

    @GetMapping(“/test/addRequestParameter”)

    1. public String addRequestParameter(HttpServletRequest request, HttpServletResponse response){
    2. String parameter=request.getParameter("example");
    3. return "return addRequestParameter info:"+parameter;
    4. }
  • 访问:http://localhost:8080/addRequestParameter

  • 返回:return addRequestParameter info:ValueB

RewritePath过滤器

  • RewritePath 替代 Zuul 的 StripPrefix

    1. path: /demo/**
    2. stripPrefix: true
    3. url: http://demo.com
  • 所有 /demo/xxxx的请求 转发给 http://demo.com/xxx ,去掉demo 前缀
  • gateway使用的是 RewritePath

    @Bean

    1. public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
    2. return builder.routes()
    3. .route("rewritepath_route", r ->
    4. r.path("/foo/**").filters(f -> f.rewritePath("/foo/(?<segment>.*)","/$\\{segment}"))
    5. .uri("http://www.baidu.com"))
    6. .build();
    7. }
  • 访问:http://localhost:8080/foo/cache/sethelp/help.html

  • 将会去掉:/foo。http://www.baidu.com/cache/sethelp/help.html
  • /foo/(?.*) 替换为 /$\{segment}

Add Response Header过滤器

  • 对网关 返回的 响应添加 header

    @Bean

    1. public RouteLocator testRouteLocator(RouteLocatorBuilder builder) {
    2. return builder.routes()
    3. .route("add_request_header_route", r ->
    4. r.path("/test").filters(f -> f.addResponseHeader("X-Response-Foo", "Bar"))
    5. .uri("http://www.baidu.com"))
    6. .build();
    7. }

    //很神奇,去掉www,后转发成功了,之后加上www也可以了。
    //好吧,确实不能加 www,缓存问题

  • http://localhost:8080/test

  • 转发的京东 请写:http://jd.com:80/

StripPrefix过滤器

  • StripPrefix Gateway Filter Factory
  • 针对请求 url前缀进行处理的 filter工厂,用于去除前缀
  • Prefix Path Gateway Filter Factory 是用于增加前缀

    server:
    port: 8080
    spring:
    application:

    1. name: spring-cloud-gateway

    spring:
    cloud:

    1. gateway:
    2. routes:
    3. - id: baidu_route
    4. uri: http://www.baidu.com
    5. predicates:
    6. - Path=/baidu/test/**
    7. filters:
    8. - StripPrefix=2

    logging:
    level:

    1. org.springframework.cloud.gateway: TRACE
    2. org.springframework.http.server.reactive: DEBUG
    3. org.springframework.web.reactive: DEBUG
    4. reactor.ipc.netty: DEBUG
  • 访问:http://localhost:8080/baidu/test

  • 跳转到: http://www.baidu.com 。去除:/baidu/test/

Retry过滤器

  • 对网络请求 进行重试
  • 使用 重试 filter 进行重试

    @Bean

    1. public RouteLocator retryRouteLocator(RouteLocatorBuilder builder) {
    2. return builder.routes()
    3. .route("retry_route", r -> r.path("/test/retry")
    4. .filters(f ->f.retry(config -> config.setRetries(2).setStatuses(HttpStatus.INTERNAL_SERVER_ERROR)))
    5. .uri("http://localhost:8071/retry?key=abc&count=2"))
    6. .build();
    7. }
  • 设置重试 次数为 两次

  • 代理服务 调用失败时,设置 返回的状态码 为 500 (服务器内部错误)

重试到 action的代码

  1. ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>();
  2. @GetMapping("/retry")
  3. public String testRetryByException(@RequestParam("key") String key, @RequestParam(name = "count") int count) {
  4. AtomicInteger num = map.computeIfAbsent(key, s -> new AtomicInteger());
  5. //对请求或重试次数计数
  6. int i = num.incrementAndGet();
  7. log.warn("重试次数: "+i);
  8. //计数i小于重试次数2抛出异常,让Spring Cloud Gateway进行重试
  9. if (i < count) {
  10. throw new RuntimeException("Deal with failure, please try again!");
  11. }
  12. //当重试两次时候,清空计数,返回重试两次成功
  13. map.clear();
  14. return "重试"+count+"次成功!";
  15. }

测试

  • http://localhost:8080/test/retry
  • 第一次:AtomicInteger num 为0,num.incrementAndGet(); 为1
  • 第二次:AtomicInteger num 为1,num.incrementAndGet(); 为2
  • key=abc 作为:ConcurrentHashMap 的 key 统计 计数

hystrix 过滤器

  • 熔断,自我保护,服务降级,快速失败
  • gateway调用后端服务,后端服务一致异常。对服务进行降级。

xml

  1. <dependencies>
  2. <!-- Spring Cloud Gateway Starter-->
  3. <dependency>
  4. <groupId>org.springframework.cloud</groupId>
  5. <artifactId>spring-cloud-starter-gateway</artifactId>
  6. </dependency>
  7. <!-- hystrix的Starter-->
  8. <dependency>
  9. <groupId>org.springframework.cloud</groupId>
  10. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  11. </dependency>
  12. </dependencies>

yaml

  1. server:
  2. port: 8080
  3. spring:
  4. application:
  5. name: spring-cloud-gateway
  6. spring:
  7. cloud:
  8. gateway:
  9. routes:
  10. - id: prefix_route
  11. uri: http://localhost:8071/test/Hystrix?isSleep=true
  12. predicates:
  13. - Path=/test/Hystrix
  14. filters:
  15. - name: Hystrix # Hystrix Filter的名称
  16. args: # Hystrix配置参数
  17. name: fallbackcmd #HystrixCommand的名字
  18. fallbackUri: forward:/fallback #fallback对应的uri
  19. #Hystrix的fallbackcmd的时间
  20. hystrix.command.fallbackcmd.execution.isolation.thread.timeoutInMilliseconds: 5000
  21. logging:
  22. level:
  23. org.springframework.cloud.gateway: TRACE
  24. org.springframework.http.server.reactive: DEBUG
  25. org.springframework.web.reactive: DEBUG
  26. reactor.ipc.netty: DEBUG

本项目的回退

  1. @RestController
  2. public class FallbackController {
  3. @GetMapping("/fallback")
  4. public String fallback() {
  5. return "Spring Cloud Gateway Fallback!";
  6. }
  7. }

远程请求的 接口

  1. @GetMapping("/test/Hystrix")
  2. public String index(@RequestParam("isSleep") boolean isSleep) throws InterruptedException {
  3. log.info("issleep is " + isSleep);
  4. //isSleep为true开始睡眠,睡眠时间大于Gateway中的fallback设置的时间
  5. if (isSleep) {
  6. TimeUnit.MINUTES.sleep(10); //这里怎么睡成分钟了
  7. }
  8. return "No Sleep";
  9. }

测试

  • http://localhost:8080/test/Hystrix
  • 返回:Spring Cloud Gateway Fallback!
  • 如果改成睡4秒,返回为:No Sleep

发表评论

表情:
评论列表 (有 0 条评论,201人围观)

还没有评论,来说两句吧...

相关阅读