SpringCloudGateway服务网关的使用

SpringCloudGateway的使用

Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等响应式编程和事件流技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

1 网关的作用、概念

Gateway网关是服务的守门神,所有微服务的统一入口。

网关的核心功能:

  • 请求路由
  • 权限控制
  • 限流

权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。

路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。

限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

在SpringCloud中网关的实现包括两种:

  • gateway
  • zuul

Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

Spring MVC found on classpath, which is incompatible with 
Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.
...
Parameter 0 of method modifyRequestBodyGatewayFilterFactory in 
org.springframework.cloud.gateway.config.GatewayAutoConfiguration
 required a bean of type 'org.springframework.http.codec.ServerCodecConfigurer' 
 that could not be found.

spring cloud gateway是基于webflux的,如果非要web支持的话需要导入spring-boot-starter-webflux而不是spring-boot-start-web。

2 Gateway的路由功能

基本步骤:

  1. 创建SpringBoot工程gateway,引入网关依赖
  2. 编写启动类
  3. 编写基础配置和路由规则
  4. 启动网关服务进行测试
1)创建gateway服务,引入依赖
<!--网关-->
<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.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>

样例:

    <dependencies>
        <!--导入gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <!--引入nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>

        <!--指明依赖,避免依赖冲突-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.3</version>
        </dependency>
         <!-- 排除 Spring Boot 依赖的日志包冲突 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!-- Spring Boot 集成 log4j2 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>
    </dependencies>
2)编写启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GatewayApplication {

	public static void main(String[] args) {
		SpringApplication.run(GatewayApplication.class, args);
	}
}
3)编写基础配置和路由规则

application.yml:

server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求

上面的配置文件表明,只要路径是以/user/开头,就去注册中心寻找服务名为userserivce的地址,然后将请求转发给该服务。
注意:测试的时候,我们应该访问的是gateway网关的地址,不能直接访问目标服务(userservice)的地址,这样网关才有效果。

4)重启测试

重启网关,访问http://localhost:10010/user/1时,符合/user/**规则,请求转发到uri:http://userservice/user/1,得到了结果:

总结:

网关搭建步骤:

  1. 创建项目,引入nacos服务发现和gateway依赖

  2. 配置application.yml,包括服务基本信息、nacos地址、路由

gateway(SpringCloud生态的,zuul是netflix的)的配置文件:

  • 网关本地:
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.101.65:8848
      discovery:
        namespace: ${spring.profiles.active}
        group: xuecheng-plus-project
      config:
        namespace: ${spring.profiles.active}
        group: xuecheng-plus-project
        file-extension: yaml
        refresh-enabled: true
        shared-configs:
          - data-id: logging-${spring.profiles.active}.yaml
            group: xuecheng-plus-common
            refresh: true


  profiles:
    active: dev
  • nacos上网关的配置:
server:
  port: 63010 # 网关端口
spring:
  cloud:
    gateway:
#      filter:
#        strip-prefix:
#          enabled: true
      routes: # 网关路由配置
        - id: content-api # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://content-api # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/content/** # 这个是按照路径匹配,只要以/content/开头就符合要求
#          filters:
#            - StripPrefix=1
        - id: system-api
          # uri: http://127.0.0.1:8081
          uri: lb://system-api
          predicates:
            - Path=/system/**
#          filters:
#            - StripPrefix=1
        - id: media-api
          # uri: http://127.0.0.1:8081
          uri: lb://media-api
          predicates:
            - Path=/media/**
#          filters:
#            - StripPrefix=1

路由配置包括:

  1. 路由id:路由的唯一标示

  2. 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡

  3. 路由断言(predicates):判断路由的规则,

  4. 路由过滤器(filters):对请求或响应做处理

3 Gateway的过滤功能

3.1 路由过滤器种类、使用

SpringCloudGateway服务网关的使用_第1张图片
此处以请求头过滤器为例:

  • 请求头过滤器:

需求:给所有进入userservice的请求添加一个请求头:Truth=itcast is freaking awesome!
key - value

spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
        - Path=/user/** 
        filters: # 过滤器
        - AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头

当前过滤器写在userservice路由下,因此仅仅对访问userservice的请求有效。

默认过滤器:

如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下:

spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
        - Path=/user/**
      default-filters: # 默认过滤项
      - AddRequestHeader=Truth, Itcast is freaking awesome! 

其他的断言,断言不仅仅对Path处理,还可以包括其他:
SpringCloudGateway服务网关的使用_第2张图片
例如,断言Header:

server:
  port: 1000
spring:
  application:
    name: gateway # 注册到nacos的服务名称
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # nacos地址
    # 配置路由过滤
    gateway:
      routes:
        - id: userService
          uri: lb://user-service # nacos上注册的服务名称
          predicates:
            - Path=/user/**
            - Header=X-design,222 # 设置header断言

路径为/user/**的请求中Header中必须包含=X-design,且值为222
总结

过滤器的作用是什么?

① 对路由的请求或响应做加工处理,比如添加请求头

② 配置在路由下的过滤器只对当前路由的请求生效

defaultFilters的作用是什么?

① 对所有路由都生效的过滤器

3.2 全局过滤器【自己实现个性化拦截】

之前的过滤器,网关提供了31种,但每一种过滤器的作用都是固定的。如果我们希望拦截请求,做自己的业务逻辑则没办法实现。此时就应该使用全局过滤器GlobalFilter

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。

定义方式是实现GlobalFilter接口。

public interface GlobalFilter {
    /**
     *  处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
     *
     * @param exchange 请求上下文,里面可以获取Request、Response等信息
     * @param chain 用来把请求委托给下一个过滤器 
     * @return {@code Mono} 返回标示当前过滤器业务结束
     */
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

在filter中编写自定义逻辑,可以实现下列功能:

  • 登录状态判断
  • 权限校验
  • 请求限流等

实例:

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:

  • 参数中是否有authorization,

  • authorization参数值是否为admin

如果同时满足则放行,否则拦截

代码实现:

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求参数
        MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
        // 2.获取authorization参数
        String auth = params.getFirst("authorization");
        // 3.校验
        if ("admin".equals(auth)) {
            // 放行
            return chain.filter(exchange);
        }
        // 4.拦截
        // 4.1.禁止访问,设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
        // 4.2.结束处理
        return exchange.getResponse().setComplete();
    }
}

检验header:

@Order(-1)
@Component
public class AuthFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求头
        HttpHeaders headers = exchange.getRequest().getHeaders();
        //请求参数
//        MultiValueMap queryParams = exchange.getRequest().getQueryParams();
        //2.获取authorization参数
        String authorization = headers.getFirst("authorization");
        //3.校验
        if("admin".equals(authorization)){
            //校验通过,放行
           return chain.filter(exchange);
        }
        //4.拦截
        //4.1 禁止访问,设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
        //4.2 结束处理
        return exchange.getResponse().setComplete();
    }
}

如果想要构建过滤器链,只需要创建多个Filter,并且配置好对应的Order就可以了

3.3 过滤器的执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、GlobalFilter

请求路由后,会将当前路由过滤器和DefaultFilter、GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:

排序的规则是什么呢?

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
  • 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
  • 当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。

order值可以为负数

详细内容,可以查看源码:

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。

org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链

4 解决跨域问题

4.1 什么是跨域问题

跨域:域名不一致就是跨域,主要包括:

  • 域名不同: www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com

  • 域名相同,端口不同:localhost:8080和localhost8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题

解决方案:CORS。不知道的可以查看https://www.ruanyifeng.com/blog/2016/04/cors.html

4.2 解决跨域问题

4.2.1 模拟跨域问题

首先需要本地启动一个nacos,将三个服务都注册到本地nacos上

①在car-service模块中,定义一个html,通过js向网关发起get请求,因为服务端口不同,所以会产生跨域问题
SpringCloudGateway服务网关的使用_第3张图片

car-service的static下的index.html

DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js">script>
head>
<body>
    <div id="show">div>
    <input type="button" onclick="btnClick()" value="get_button">
<script>
    function btnClick(){
        $.get("http://localhost:1000/user/test", function (msg){
            alert(msg)
        })
    }
script>
body>
html>

②网关模块
依赖:

<dependencies>
   <!--导入gateway-->
   <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-starter-gateway</artifactId>
       <version>2.2.5.RELEASE</version>
   </dependency>
   <!--引入nacos-->
   <dependency>
       <groupId>com.alibaba.cloud</groupId>
       <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
       <version>2.2.5.RELEASE</version>
   </dependency>

   <!--指明依赖,避免依赖冲突-->
   <dependency>
       <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpclient</artifactId>
       <version>4.5.3</version>
   </dependency>
</dependencies>

application.yml:

server:
  port: 1000
spring:
  application:
    name: gateway # 注册到nacos的服务名称
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # nacos地址
    # 配置路由过滤
    gateway:
      routes:
        - id: userService
          uri: lb://user-service # nacos上注册的服务名称
          predicates:
            - Path=/user/**
#            - Header=X-design,222 # 设置header断言

③user-service模块【访问的目的地】
application.yml:

server:
  port: 8081

spring:
  application:
    name: user-service

  cloud:
    nacos:
      server-addr: localhost:8848

controller:

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("/test")
    public String testUser(){
        return "userService success...";
    }
}

④输入url,点击按钮:
SpringCloudGateway服务网关的使用_第4张图片
⑤解决,通过gateway解决:

添加以下配置即可

      # 跨域处理
      globalcors:
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截的问题
        cors-configurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8082"

yml完整配置:

server:
  port: 1000
spring:
  application:
    name: gateway # 注册到nacos的服务名称
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 # nacos地址
    # 配置路由过滤
    gateway:
      routes:
        - id: userService
          uri: lb://user-service # nacos上注册的服务名称
          predicates:
            - Path=/user/**
#            - Header=X-design,222 # 设置header断言
      # 跨域处理
      globalcors:
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截的问题
        cors-configurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求
              - "http://localhost:8082"

重新点击按钮,访问页面:
SpringCloudGateway服务网关的使用_第5张图片

可以看到,跨域问题被解决了

4.2.2 解决问题

其他配置:
在application.yml中配置:

spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求 
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

你可能感兴趣的:(框架,java,spring,cloud,微服务,过滤器链,filterChain)