SpringCloud中级(二) ——Gateway新一代网关

概述简介

官网:

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/

概述

在这里插入图片描述
在这里插入图片描述
一句话:Spring Cloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架

能干嘛?

  • 路由
  • 鉴权
  • 流量控制
  • 熔断
  • 日志监控
  • 过滤
    。。。

微服务架构中网关在哪里?
服务提供者和消费者都是微服务。
服务提供者:被其他微服务调用的微服务
服务消费者:调用的其他微服务的微服务
在这里插入图片描述

有了Zuul了怎么又出来了gateway

1.neflix不太靠谱,zuul2.0一直跳票,迟迟不发布
在这里插入图片描述
2.SpringCloud Gateway具有如下特性
在这里插入图片描述
3.Gateway与Zuul的区别
在这里插入图片描述

模型比较

Zuul1.x模型:
在这里插入图片描述
在这里插入图片描述
GateWay模型
在这里插入图片描述
gateway基于webflux,而webflux基于netty,netty的I/O操作时异步的,netty模型是同步非阻塞

三大核心概念

  • Route(路由)
    路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
  • Predicate(断言)
    参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
  • Filter(过滤)
    指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

在这里插入图片描述

Gateway工作流程

在这里插入图片描述
在这里插入图片描述
核心逻辑:路由转发+执行过滤器链

入门配置

新建一个子模块cloud-gateway-gateway9527
依赖:

 <!--gateway-->
<!--        springcloud版本要为Hoxton.SR6 gateway为2.2.3会报下面的异常-->
<!--        Correct the classpath of your application so that it contains a single, compatible version of reactor.netty.resources.ConnectionProvider-->
<!--        把springcloud版本改成Hoxton.SR1-->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-gateway</artifactId>
 </dependency>

启动类:

@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
    public static void main(String[] args) {
            SpringApplication.run( GateWayMain9527.class,args);
        }
}

9527网关如何做路由映射???
看看服务提供者8001的controller的访问地址,我们目前不想暴露8001端口,希望在8001外面套一层9527
配置文件:

server:
  port: 9527
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
          uri: http://localhost:8001   #匹配后提供服务的路由地址
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由
        - id: payment_routh2
          uri: http://localhost:8001
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由
eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

测试

启动Eureka,服务提供者8001,网关9527
访问:
直接访问提供者的接口:
http://localhost:8001/payment/get/1
再通过网关访问,效果一样
http://localhost:9527/payment/get/1

Gateway网关路由有两种配置方式

第一种:在配置文件yml中配置

见前面步骤

第二种:代码中注入RouteLocator的Bean

官网案例:
在这里插入图片描述
示例:通过9527网关访问到外网的百度新闻网址()

http://news.baidu.com/guonei
http://news.baidu.com/game

编码:

@Configuration
public class GateWayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        //id->path_rote_atguigu  相当于配置文件里配的id
        //第二个地址表示localhost:9527/guonei,将转发到地址http://news.baidu.com/guonei
        routes.route("path_rote_atguigu",
                r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
        routes.route("path_rote_atguigu1",
                r -> r.path("/game").uri("http://news.baidu.com/game")).build();
        return routes.build();
    }
}

启动:Eureka以及网关模块。
访问:
http://localhost:9527/game
http://localhost:9527/guonei
均能转发成功。

通过微服务名实现动态路由

上面的案例做完,有一些问题。
第一,地址写死,如:localhost:8001 。
第二,服务提供者不可能只有一台机器,所以要实现负载均衡。

以前:
在这里插入图片描述
现在:架构还是那个架构,但是不再使用ribbon做负载均衡,而是使用网关,对外暴露统一的服务接口,就是9527网关服务端口,由网关实现负载均衡
在这里插入图片描述

示例

默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。
启动:一个eureka7001+两个服务提供者8001/8002
在这里插入图片描述
配置:
1.开启从注册中心动态创建路由的功能,利用微服务名进行路由
2.将写死的地址,改成从配置中心获取,lb,表示启用Gateway的负载均衡功能。

server:
  port: 9527
spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service  #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由
        - id: payment_routh2
#          uri: http://localhost:8001
          uri: lb://cloud-payment-service  #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由
eureka:
  instance:
    hostname: cloud-gateway-service
  client:
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka

测试:
发现实现了负载均衡的功能。

http://localhost:9527/payment/lb

在这里插入图片描述
在这里插入图片描述
解决了地址写死的问题,也解决了负载均衡的问题。
需要说明gateway的负载均衡底层使用的就是ribbon。

代码中注入RouteLocator的Bean

上面的是使用配置文件的方式,现在展示一下使用硬编码的方式

@Configuration
public class GateWayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
        routes.route("payment_routh",
                r -> r.path("/payment/get/** ").uri("lb://cloud-payment-service")).build();
        routes.route("payment_routh2",
                r -> r.path("/payment/lb/**").uri("lb://cloud-payment-service")).build();
        return routes.build();
    }
}

配置文件:

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由

Predicate断言的使用

是什么

通过路由断言判断路由是否可用,匹配成功进行下一步处理,否则则失败。
启动网关子模块。
在这里插入图片描述
Route Predicate Factories这个是什么东东?
在这里插入图片描述
在这里插入图片描述

常用的Route Predicate

在这里插入图片描述

After Route Predicate

意思要在指定时间之后,路由才生效

获取当前时间时区:

public class ZonedDateTimeDemo {
    public static void main(String[] args) {
        //默认时区
        ZonedDateTime zonedDateTime=ZonedDateTime.now();
        System.out.println(zonedDateTime);
        //2020-07-05T12:19:02.779+08:00[Asia/Shanghai]
    }
}

测试:
加上 - After=2020-07-05T13:19:02.779+08:00[Asia/Shanghai]

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service  #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由
            - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效
        - id: payment_routh2
#          uri: http://localhost:8001
          uri: lb://cloud-payment-service  #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由
            - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效

因为当前是时间,已经过去了,所以按照预期,重启服务,肯定能访问接口lb和get。
重启网关。
访问http://localhost:9527/payment/lb
在这里插入图片描述

把lb接口对应的-After时间调后一小时:- After=2020-07-05T13:19:02.779+08:00[Asia/Shanghai]
按照预期应该是访问不了改接口了
在这里插入图片描述
同理断言Before Route Predicate,Between Route Predicate用法是和After Route Predicate一样的。

- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
- Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] ,  2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
Cookie Route Predicate

断言Cookie,满足指定的cookie,才能访问路由
在这里插入图片描述
示例
配置:
添加 - Cookie=username,liuzhihui #断言Cookie key=username value=liuzhihui 满足这个断言才能访问

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service  #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由
            - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效
        - id: payment_routh2
#          uri: http://localhost:8001
          uri: lb://cloud-payment-service  #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由
            - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效
            - Cookie=username,liuzhihui #断言Cookie key=username value=liuzhihui 满足这个断言,才能访问路由

测试:接口http://localhost:9527/payment/lb,现在这个接口要能成功访问必须满足两个条件。
第一在指定的时间之后访问;
第二要带上指定的Cookie

使用curl来测试,curl就是postman底层操作,postman只不过是图形界面操作。
首先,不带cookie访问:curl http://localhost:9527/payment/lb 直接报错了。注意这个属于get请求
在这里插入图片描述
然后,带上cookie访问:curl http://localhost:9527/payment/lb --cookie "username=liuzhihui"
在这里插入图片描述

加入curl返回中文乱码:https://blog.csdn.net/leedee/article/details/82685636

Header Route Predicate

请求头断言,满足指定的请求头,才能访问路由。
在这里插入图片描述
示例:

- Header=X-Request-Id, \d+   #请求头中要有X-Request-Id属性并且值为整数的正则表达式

在这里插入图片描述
重启网关。
测试:curl http://localhost:9527/payment/lb --cookie "username=liuzhihui" -H "X-Request-Id:123"
在这里插入图片描述

Host Route Predicate

host断言
在这里插入图片描述
示例:
只要请求头host满足: - Host=**.atguigu.com,**.liuzhihui.com,即可转发路由

spring:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
      routes:
        - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#          uri: http://localhost:8001   #匹配后提供服务的路由地址
          uri: lb://cloud-payment-service  #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
          predicates:
            - Path=/payment/get/**   #断言,路径相匹配的进行路由
            - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效
        - id: payment_routh2
#          uri: http://localhost:8001
          uri: lb://cloud-payment-service  #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由
            - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效
            - Cookie=username,liuzhihui    #断言Cookie key=username value=liuzhihui 满足这个断言才能访问
            - Header=X-Request-Id, \d+   #请求头中要有X-Request-Id属性并且值为整数的正则表达式
            - Host=**.atguigu.com,**.liuzhihui.com

测试:

curl http://localhost:9527/payment/lb --cookie "username=liuzhihui" -H "X-Request-Id:1"  -H  "Host: aaa.liuzhihui.com"
curl http://localhost:9527/payment/lb --cookie "username=liuzhihui" -H "X-Request-Id:1"  -H  "Host: www.liuzhihui.com"
curl http://localhost:9527/payment/lb --cookie "username=liuzhihui" -H "X-Request-Id:1"  -H  "Host: www.atguigu.com"

在这里插入图片描述

Method Route Predicate

接口的请求方式:Get,Post
示例:是get请求,才能进行路由转发

- Method=GET #接口的请求方式

测试:

curl http://localhost:9527/payment/lb -X GET  --cookie "username=liuzhihui" -H "X-Request-Id:1"  -H  "Host: www.atguigu.com"
curl http://localhost:9527/payment/lb -X POST --cookie "username=liuzhihui" -H "X-Request-Id:1"  -H  "Host: www.atguigu.com"

在这里插入图片描述

Path Route Predicate

请求路径正则匹配指定值。没啥好说的一直都在用。

- Path=/payment/get/**   #断言,路径相匹配的进行路由
Query Route Predicate

查询参数断言

- Query=username, \d+ #要有参数名称并且是正整数才能路由

测试:

predicates:
     - Path=/payment/lb/**   #断言,路径相匹配的进行路由
     - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效
     - Cookie=username,liuzhihui    #断言Cookie key=username value=liuzhihui 满足这个断言才能访问
     - Header=X-Request-Id, \d+   #请求头中要有X-Request-Id属性并且值为整数的正则表达式
     - Host=**.atguigu.com,**.liuzhihui.com
     - Method=GET #接口的请求方式
     - Query=username, \d+ #要有参数名称并且是正整数才能路由
     - Query=age, \d+ #要有参数名称并且是正整数才能路由
curl "http://localhost:9527/payment/lb?username=1&age=1" -X GET  --cookie "username=liuzhihui" -H "X-Request-Id:1"  -H  "Host: www.atguigu.com"

在这里插入图片描述

小总结

说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。
更多的路由请参考官网:

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/#gateway-request-predicates-factories

Filter的使用

是什么

路由过滤器可用于修改进入Http的请求和返回的Http响应,路由过滤器只能指定路由进行使用。spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。

官网:

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/#gatewayfilter-factories

生命周期:在业务逻辑之pre;在业务逻辑之后post
种类:GatewayFilter单一过滤器 GlobalFilter全局过滤器

例如:
该路由的意思是,前端发送请过来,如果路径包含/api/xxx,则符合断言规则,然后经过过滤器,将路径改为/renren-fast/xxx

  - id: admin_route
    uri: lb://renren-fast
    predicates:
      - Path=/api/**
    filters:
      ##路径重写
      - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}

实际工作中内置的过滤器用的并不多,用的更多的是自定义过滤器。

自定义全局GlobalFilter

首先要实现两接口impiemerts GlobalFilter ,Ordered
能干嘛?
全局日志记录
统一网关鉴权
。。。。

案例:
网关子模块里,新建一个过滤器包
在这里插入图片描述

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        log.info("*********come in MyLogGateWayFilter: "+new Date());
        String username = exchange.getRequest().getQueryParams().getFirst("username");
        if(StringUtils.isEmpty(username)){
            log.info("*****用户名为Null 非法用户,(┬_┬)");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
            return exchange.getResponse().setComplete();
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

配置文件:
将之前的断言注释了,只留- Path 避免干扰

          predicates:
            - Path=/payment/lb/**   #断言,路径相匹配的进行路由
#            - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效
#            - Cookie=username,liuzhihui    #断言Cookie key=username value=liuzhihui 满足这个断言才能访问
#            - Header=X-Request-Id, \d+   #请求头中要有X-Request-Id属性并且值为整数的正则表达式
#            - Host=**.atguigu.com,**.liuzhihui.com
#            - Method=GET #接口的请求方式
#            - Query=username, \d+ #要有参数名称并且是正整数才能路由
#            - Query=age, \d+ #要有参数名称并且是正整数才能路由

重启网关。
正确的访问:http://localhost:9527/payment/lb?username=z3
在这里插入图片描述
错误的访问:

http://localhost:9527/payment/lb

在这里插入图片描述

网关统一配置跨域

什么是跨域?
跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域;
在这里插入图片描述
跨域流程?
非简单请求(put,delete)等,需要先发送预检请求,询问是否允许跨域。
在这里插入图片描述

跨域问题:如下图所示
在这里插入图片描述

解决跨域的方式一:
跨域的根本原因,目标网站跟想要发远程请求的网站不在同一域;
nginx把它们转化为同一域;
具体做法:比如有一台nginx服务器,将前端项目也部署到里面,后台的网关也让nginx代理
能行,但是太麻烦了
在这里插入图片描述
解决跨域的方式二:
网关统一配置跨域,在网关处添加一个过滤器,所有请求过来,我都给它配置允许跨域。

@Configuration
public class GulimallCorsConfiguration {
    /**
     * springboot提供了跨域的过滤器CorsWebFilter
     * @return
     */
    @Bean
    public CorsWebFilter corsWebFilter(){
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();

        //配置跨域
        //允许哪些都跨域
        corsConfiguration.addAllowedHeader("*");
        //允许哪些请求方式跨域
        corsConfiguration.addAllowedMethod("*");
        //允许哪些请求来源跨域
        corsConfiguration.addAllowedOrigin("*");
        //是否允许携带cookies跨域
        corsConfiguration.setAllowCredentials(true);

        //注册跨域配置
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration);

        return new CorsWebFilter(urlBasedCorsConfigurationSource);
    }
}

再次测试需要跨域的接口:
发现会有两次请求,其中一次是预检请求,并且发现请求头添加上了配置的跨域信息,但是是不携带数据的;
另一个就是真实的请求了,携带有真实的数据
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值