SpringGateway网关

SpringGateway网关

奈非框架简介

早期(2020年前)奈非提供的微服务组件和框架受到了很多开发者的欢迎

这些框架和SpringCloud Alibaba的对应关系我们要了解

现在还有很多旧项目维护是使用奈非框架完成的微服务架构

Nacos对应Eureka都是注册中心

Dubbo对应Ribbon+feign都是实现微服务远程RPC调用的组件

Sentinel对应Hystrix都是做项目限流熔断降级的组件

SpringGateway对应Zuul都是网关组件

Gateway框架不是阿里写的,是Spring提供的

什么是网关

"网"指网络,"关"指关口或关卡

网关:就是指网络中的关口\关卡

网关就是当前微服务项目的"统一入口"

程序中的网关就是当前微服务项目对外界开放的统一入口

所有外界的请求都需要先经过网关才能访问到我们的程序

提供了统一入口之后,方便对所有请求进行统一的检查和管理

在这里插入图片描述

网关的主要功能有

  • 将所有请求统一经过网关
  • 网关可以对这些请求进行检查
  • 网关方便记录所有请求的日志
  • 网关可以统一将所有请求路由到正确的模块\服务上

“路由"的近义词就是"分配”

Spring Gateway简介

我们使用Spring Gateway作为当前项目的网关框架

Spring Gateway是Spring自己编写的,也是SpringCloud中的组件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Spring Gateway官网

https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/

网关实例项目git地址

https://gitee.com/jtzhanghl/gateway-demo.git

为什么选择 Gateway

在这里插入图片描述

Gateway 的特性

在这里插入图片描述

Gateway 与 Zuul 的区别

在这里插入图片描述
在这里插入图片描述

Gateway 的三大核心概念

Route (路由)

在这里插入图片描述

Predicate (断言)

在这里插入图片描述

Filter (过滤)

在这里插入图片描述

总结

在这里插入图片描述

Gateway 的工作流程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

简单网关演示

SpringGateway网关是一个依赖,不是一个软件

所以我们要使用它的话,必须先创建一个SpringBoot项目

这个项目也要注册到Nacos注册中心,因为网关项目也是微服务项目的一个组成部分

beijing和shanghai是编写好的两个项目

gateway项目就是网关项目,需要添加相关配置

<dependencies>
    <!--   SpringGateway的依赖   -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <!--   Nacos依赖   -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--   网关负载均衡依赖    -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
</dependencies>

我们从yml文件配置开始添加

server:
  port: 9000
spring:
  application:
    name: gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      # route就是路由的意思,下面就是配置路由信息
      # 一个网关项目大多会配置很多路由
      # 所以这个网关配置是一个List集合类型
      routes:
        # List类型元素赋值时,每个元素都要以"-"开头,在这个"-"之后,
        # 编写的所有内容,都是同一个对象的属性值
        # id设置当前路由的名称,也是唯一标识,和其它配置没有对应关系,注意不能和之后的id名称重复即可
        - id: gateway-beijing
          # uri属性配置的是路由目标服务器的名称,"beijing"指注册到Nacos名称为"beijing"的模块
          # lb就是负载均衡LoadBalance的缩写,标识路由支持负载均衡
          uri: lb://beijing
          # predicate是断言的意思,断言指某些条件满足时,执行某些操作
          # predicates配置也是一个List类型的属性,所以它赋值也要以"-"开头
          predicates:
            # 下面是断言的内容,Path表示判断路径,"/bj/**"表示判断路径是否以"/bj/"开头
            # 当断言条件满足时,就会按上面uri的配置,路由到该服务器模块
            # ↓   P是大写的!!!!!
            - Path=/bj/**

先启动nacos

再启动beijing

最后启动gateway

网关多路由配置

上面只配置了一个beijing的路由设置

下面我们修改yml文件也实现shanghai的路由设置

gateway:
  # route就是路由的意思,下面就是配置路由信息
  # 一个网关项目大多会配置很多路由
  # 所以这个网关配置是一个List集合类型
  routes:
    - id: gateway-shanghai
      uri: lb://shanghai
      predicates: 
        - Path=/sh/**
   # beijing配置略

在保证nacos启动的情况下

beijing服务器如果启动无需重启

启动shanghai项目

最后重启网关

测试网关路由到两个模块的效果

http://localhost:9000/bj/show可以访问beijing服务器的资源

http://localhost:9000/sh/show可以访问shanghai服务器的资源

以此类推,再有很多服务器时,我们都可以仅使用9000端口号来将请求路由到正确的服务器

就实现了gateway成为项目的统一入口的效果

Gateway配置路由的两种方式

  • 在配置文件yml中配置
  • 代码中注入RouteLocator的Bean

以第二种为例, 业务需求 - 通过端口为9527网关访问到百度网址
百度国内新闻网址,需要外网 - http://baidu.com

编码: 创建 config.GateWayConfig 类
在这里插入图片描述

package com.atguigu.springcloud.config;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class GateWayConfig {
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) {
        RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();

        routes.route("path_route_atguigu",
                r -> r.path("/guonei")
                        .uri("http://baidu.com")).build();

        return routes.build();
    }
}

浏览器输入http://localhost:9527/guonei,返回http://baidu.com相同的页面。

动态路由

网关项目的配置会随着微服务模块数量增多而变得复杂,维护的工作量也会越来越大

所以我们希望gateway能够设计一套默认情况下自动路由到每个模块的路由规则

这样的话,不管当前项目有多少个路由目标,都不需要维护yml文件了

这就是我们SpringGateway的动态路由功能

在这里插入图片描述

配置文件中开启即可

gateway:
  discovery:
    locator:
      # 开启动态路由功能,默认值是关闭的
      # 动态路由规则:在网关端口号后,先编写要路由目标服务器注册到Naocs的名称
      # 在编写访问这个服务器的具体路径
      # 例如要访问 localhost:9001/bj/show  -> localhost:9000/beijing/bj/show
      enabled: true

按上面修改完配置之后

我们可以重启gateway来测试动态路由路径是否生效

动态路由生成规则为:在网关端口号后先写要路由到的目标服务器在nacos注册的名称,再编写具体路径

内置断言

我们上面章节在网关配置中使用了predicates(断言)的配置

断言的意思就是判断某个条件是否满足

我们之前使用了Path断言,判断请求的路径是不是满足条件,例如是不是/sh/** /bj/**

如果路径满足这个条件,就路由到指定的服务器

但是Path实际上只是SpringGateway提供的多种内置断言中的一种
在这里插入图片描述

还有很多其它断言

  • After
  • Before
  • Between
  • Cookie
  • Header
  • Host
  • Method
  • Path
  • Query
  • Remoteaddr

时间相关

After,Before,Between

判断当前时间在指定时间之前,之后或之间的操作

如果条件满足可以执行路由操作,否则拒绝访问

表示时间的格式比较特殊,先使用下面代码获得时间

ZonedDateTime.now()

运行程序输出,可获得当前时间,这个时间的格式可能是

2023-04-21T11:13:54.967+08:00[Asia/Shanghai]

下面在yml配置中添加新的断言配置

使用After设置必须在指定时间之后访问

routes:
  - id: gateway-shanghai
    uri: lb://shanghai
    predicates:
      - Path=/sh/**
      # After是时间断言,判断当前请求访问时的时间是否晚于配置的时间
      # 如果成立正常访问,如果判断不成立,返回404错误,多个断言之间是"与"的关系
      - After=2023-04-21T11:21:30.967+08:00[Asia/Shanghai]

使用Before设置必须在指定时间之前才能访问服务

否则发生404错误拒绝访问

predicates:
  - Path=/sh/**
  - Before=2023-04-21T11:23:20.967+08:00[Asia/Shanghai]

使用Between设置必须在指定时间之间访问

predicates:
  - Path=/sh/**
  - Between=2023-04-21T11:25:25.967+08:00[Asia/Shanghai],2023-04-21T11:25:45.967+08:00[Asia/Shanghai]

要求指定参数的请求

Query断言,判断是否包含指定的参数名称,包含参数名称才能通过路由
在这里插入图片描述

predicates:
  - Path=/sh/**
  # Query断言判断请求中是否包含指定参数名称,这里设置为username,如果没有发生404错误
  - Query=username

重启gateway测试

必须是包含username参数的请求才能访问到指定的页面

例如:http://localhost:9000/sh/show?username=tom

带 Cookie 参数的断言

在这里插入图片描述

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://example.org
        predicates:
        - Cookie=username, yyx

不带 Cookie 访问
在这里插入图片描述
携带 Cookie 访问
在这里插入图片描述

带 Header 参数的断言

在这里插入图片描述

spring:
  cloud:
    gateway:
      routes:
      - id: header_route
        uri: https://example.org
        predicates:
        - Header=X-Request-Id, \d+  # 请求头要有 X-Request-Id 属性并且值为整数的正则表达式

在这里插入图片描述

Host 断言

在这里插入图片描述

Method 断言

在这里插入图片描述

内置过滤器

Gateway还提供的内置过滤器

不要和我们学习的filter混淆

内置过滤器允许我们在路由请求到目标资源的同时,对这个请求进行一些加工或处理

在这里插入图片描述

常见过滤器也有一些

AddRequestParameter过滤器

我们给大家演示一下AddRequestParameter过滤器

它的作用是在请求中添加参数和它对应的值

routes:
  - id: gateway-shanghai
    uri: lb://shanghai
    filters:
      # 如果路由顺利成功,这个内置过滤器会自动在请求中添加下面的参数
      # 属性名称为age,默认值18,如果请求中包含age的值,这个值就不会生效了
      - AddRequestParameter=age,18
    predicates:
      - Path=/sh/**
      # Query断言判断请求中是否包含指定参数名称,这里设置为username,如果没有发生404错误
      - Query=username

在shanghai的控制器方法中添加代码接收username,age的值

@GetMapping("/show")
public String show(String username,Integer age){
    // 2023-04-21T11:13:54.967+08:00[Asia/Shanghai]
    System.out.println(ZonedDateTime.now());
    return "这里是上海!username:"+username+",age:"+age;
}

重启shanghai和gateway进行测试

http://localhost:9000/sh/show?username=jerry

因为过滤器的存在,控制器可以获取网关过滤器添加的参数值

在这里插入图片描述

自定义过滤器

创建类 filter.MyLogGateWayFilter

package com.atguigu.springcloud.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Date;

@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 uname = exchange.getRequest().getQueryParams().getFirst("uname");

        if(uname == null)
        {
            log.info("******* 用户名为null,非法用户,o(╥﹏╥)o");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;  // 数字越小,过滤器优先级越高
    }
}

启动项目,正常访问携带参数,不携带报错
正常访问如:http://localhost:9527/payment/lb?uname=123
错误访问如:http://localhost:9527/payment/lb

路由配置的设计规则

路由规则解释

路由规则一定是在开发之前就设计好的

一般可以使用约定好的路径开头来实现的

例如

gateway项目

如果路径以 /bj开头,就是要访问beijing项目

如果路径以 /sh开头.就是养访问shanghai项目

csmall项目

如果路径是 /base/business开头的, 就去找nacos-business服务器

如果路径是 /base/cart开头的, 就去找nacos-cart服务器

如果路径是 /base/order开头的, 就去找nacos-order服务器

如果路径是 /base/stock开头的, 就去找nacos-stock服务器

在这里插入图片描述

csmall项目网关

创建网关项目,然后父子相认

修改子项目pom文件和依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>cn.tedu</groupId>
        <artifactId>csmall</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.tedu</groupId>
    <artifactId>gateway</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gateway</name>
    <description>Demo project for Spring Boot</description>
    <dependencies>
        <!-- web实例 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--  Nacos注册依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
        </dependency>
    </dependencies>

</project>

也删除test测试文件夹

application.properties换为yml

配置如下

server:
  port: 19000
spring:
  application:
    name: gateway-server
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      discovery:
        locator:
          # 开启动态路由
          enabled: true
  main:
    # 防止SpringMVC和SpringGateway依赖冲突的配置
    web-application-type: reactive

网关项目的knife4j配置

我们希望配置网关之后,在使用knife4j测试时

就不来回切换端口号了

我们需要在网关项目中配置Knife4j才能实现

而这个配置是固定的,

只要是网关项目配置各个子模块的knife4j功能,就直接复制这几个类即可

csmall-finish中直接复制config\controller\filter

cn.tedu.gateway.config

SwaggerProvider

@Component
public class SwaggerProvider implements SwaggerResourcesProvider {
    /**
     * 接口地址
     */
    public static final String API_URI = "/v2/api-docs";
    /**
     * 路由加载器
     */
    @Autowired
    private RouteLocator routeLocator;
    /**
     * 网关应用名称
     */
    @Value("${spring.application.name}")
    private String applicationName;

    @Override
    public List<SwaggerResource> get() {
        //接口资源列表
        List<SwaggerResource> resources = new ArrayList<>();
        //服务名称列表
        List<String> routeHosts = new ArrayList<>();
        // 获取所有可用的应用名称
        routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
                .filter(route -> !applicationName.equals(route.getUri().getHost()))
                .subscribe(route -> routeHosts.add(route.getUri().getHost()));
        // 去重,多负载服务只添加一次
        Set<String> existsServer = new HashSet<>();
        routeHosts.forEach(host -> {
            // 拼接url
            String url = "/" + host + API_URI;
            //不存在则添加
            if (!existsServer.contains(url)) {
                existsServer.add(url);
                SwaggerResource swaggerResource = new SwaggerResource();
                swaggerResource.setUrl(url);
                swaggerResource.setName(host);
                resources.add(swaggerResource);
            }
        });
        return resources;
    }
}

cn.tedu.gateway.controller

SwaggerController类

@RestController
@RequestMapping("/swagger-resources")
public class SwaggerController {
    @Autowired(required = false)
    private SecurityConfiguration securityConfiguration;
    @Autowired(required = false)
    private UiConfiguration uiConfiguration;
    private final SwaggerResourcesProvider swaggerResources;
    @Autowired
    public SwaggerController(SwaggerResourcesProvider swaggerResources) {
        this.swaggerResources = swaggerResources;
    }
    @GetMapping("/configuration/security")
    public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
    }
    @GetMapping("/configuration/ui")
    public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
        return Mono.just(new ResponseEntity<>(
                Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
    }
    @GetMapping("")
    public Mono<ResponseEntity> swaggerResources() {
        return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
    }
}

cn.tedu.gateway.filter

SwaggerHeaderFilter类

@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
    private static final String HEADER_NAME = "X-Forwarded-Prefix";

    private static final String URI = "/v2/api-docs";

    @Override
    public GatewayFilter apply(Object config) {
        return (exchange, chain) -> {
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();
            if (!StringUtils.endsWithIgnoreCase(path,URI )) {
                return chain.filter(exchange);
            }
            String basePath = path.substring(0, path.lastIndexOf(URI));
            ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            return chain.filter(newExchange);
        };
    }
}

gateway项目删除test文件夹

测试网关路由效果,和knife4j效果

启动Nacos\Seata\Sentinel

启动cart\stock\order\business

最后启动gateway

可以通过19000端口测试各个业务模块的功能

http://localhost:19000/nacos-stock/doc.html

http://localhost:19000/nacos-cart/doc.html

http://localhost:19000/nacos-order/doc.html

http://localhost:19000/nacos-business/doc.html

如果不使用网关一切正常,但是启动网关访问失败的话,就是gateway项目配置问题

Gateway和SpringMvc依赖冲突问题和解决

之前网关的演示项目我们添加的网关依赖

<!-- Spring Gateway 网关依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

当前csmall项目需要配置knife4j的路由配置,需要编写一个控制器

所以我们添加了SpringMVC的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

这两个依赖在同一个项目中时,默认情况下启动会报错

SpringMVC框架依赖中自带一个Tomcat服务器

而SpringGateway框架中自带一个Netty的服务器

在启动项目时,两个框架中包含的服务器都想占用相同端口,因为争夺端口号的主动权而发生冲突

导致启动服务时报错

要想能够正常启动必须在yml文件配置

spring:
  main:
    web-application-type: reactive

reactive:反应的

添加这个配置之后,会Tomcat服务器会变成非阻塞的运行

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值