網關 GateWay 的使用詳解、路由、過濾器、跨域配置

作者:流水武 qin

原文:https://blog.csdn.net/qq_44749491/article/details/126560999

一、網關的基本概念

SpringCloudGateway 網關是所有微服務的統一入口。

1.1 它的主要作用是:

1.2 相比於 Zuul 的優勢:

SpringCloudGateway 基於 Spring5 中提供的 WebFlux,是一種響應式編程的實現,性能更加優越。

Zuul 的實現方式比較老式,基於 Servlet 的實現,它是一種阻塞式編程,在高併發下性能性能不佳。

拓展:

其實 Nginx 也可以作爲網關,但是要使用 Nginx 自主實現網關的相關功能,還需要藉助 lua 腳本語言

學習成本是比較高的,現在一般也不會使用它來做網關,但是隻按性能來講 Nginx,性能是最高的。

1.3 SpringCloudGateway 架構圖:

微服務只接收來自網關的請求,而其它直接訪問微服務本身的請求拒絕。

這樣可以極大保護微服務免受不法侵害。

同時在請求壓力激增時,可以實施服務限流,保護微服務集羣。

二、SpringBoot 中配置 GateWay

2.1 引入 GateWay 的 Maven 依賴

<!--網關 起步依賴-->
<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>

2.2 配置 application.yml 文件

server:
  port: 10086   # 網關端口
spring:
  application:
    name: gateway # 服務名稱
  cloud:
    nacos:
      server-addr: localhost:8848    # nacos 地址
    gateway:
      routes:         # 網關路由配置
        - id: user-service      # 路由id,自定義,只要唯一即可
          # uri: http://127.0.0.1:8081   # 路由的目標地址 (直接寫死地址的方式,不推薦)
          uri: lb://userservice    # 路由的目標地址 lb是負載均衡,後面跟服務名稱(推薦)
          predicates:       # 路由斷言,判斷請求是否符合路由規則的條件
            - Path=/user/**      # 按照路徑匹配,以/user/開頭的請求就符合要求
        - id: card-service
          uri: lb://cardservice
          predicates:
            - Path=/card/**

gateway 配置中的注意點:

routes 後面的路由可以配置多個,相當於配置個數組,一個 - 開頭的配置就是其中的一個數組元素。

uri 爲什麼選擇以服務名 + 負載均衡的方式?

上述配置詳解:

將 /user/**開頭的請求,代理到 lb://userservice。

將 /card/**開頭的請求,代理到 lb://cardservice。

lb 是負載均衡,根據服務名拉取服務列表,實現負載均衡。

三、GateWay 路由配置詳解

路由主要有四個配置:

3.1 路由 id

當前路由的唯一標識。

3.1 路由目標

路由的目標地址,http 代表固定地址,lb 代表根據服務名負載均衡。

一般都不會選擇寫死 http 固定地址的方式。而是選擇可維護性更強的 lb 根據服務名負載均衡的方式。

具體優勢如上所言。

3.3 路由斷言

路由斷言主要用來判斷路由的規則。

配置文件中寫的斷言規則只是字符串,這些字符串會被 Predicate Factory 讀取並處理。

例如Path=/user/**是按照路徑匹配,這個規則是由

org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory類來處理。

像這樣的斷言工廠在 SpringCloudGateway 還有十幾個:

實際使用時,根絕業務要求選擇使用即可。

不過一般來講,最常用的是使用 Path 這種斷言工廠,僅用它就能滿足常見的需求了。

關於 Path 斷言工廠的補充:

Path=/card/**代表 以 / card / 路徑開頭的多級路徑請求,這麼寫多級路徑請求和一級路徑請求都生效。

Path=/card/*代表 以 / card / 路徑開頭的一級路徑請求,這麼寫多級路徑請求將不會生效。

斷言工廠官方文檔:

https://docs.spring.io/spring-cloud-gateway/docs/3.1.4-SNAPSHOT/reference/html/#gateway-request-predicates-factories

今後如果有複雜的斷言工廠配置,可以參照官網文檔上的例子去實現。

3.4 路由過濾器(filters)

路由過濾器對請求或響應做處理。

客戶端請求先找到路由,路由匹配時經過過濾器層層篩選,最終訪問到微服務。

當然微服務的請求反悔時,也會經過過濾器的篩選,只不過我們一般只對請求過濾,而不會對響應過濾。

SpringCloudGateWay 目前已經提供了 34 種不同的過濾器工廠。

常用的幾個有:

3.4.1 請求頭過濾器配置示例(局部過濾器)
spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
        - Path=/user/** 
        filters:         # 過濾器配置
        - AddRequestHeader=token, test # 添加請求頭

上述過濾器的含義:

給所有進入 userservice 的請求添加一個請求頭。

請求頭的 key 爲 token,value 爲 test。

由於當前前過濾器寫在微服務的 userservice 路由下,因此僅僅對訪問微服務 userservice 的請求有效。

3.4.2 默認過濾器配置示例(全局過濾器)
spring:
  cloud:
    gateway:
      routes:
        - id: user-service 
          uri: lb://userservice 
          predicates: 
          - Path=/user/**
      default-filters:      # 默認過濾器配置
        - AddRequestHeader=token, test  # 添加請求頭

default-filters 的配置和 routes 平級。

只要配置在 default-filters 下面的過濾器,會對 routes 配置的所有路由都生效。

過濾器工廠官方文檔:

https://docs.spring.io/spring-cloud-gateway/docs/3.1.4-SNAPSHOT/reference/html/#gateway-request-predicates-factories

今後如果有複雜的斷言工廠配置,可以參照官網文檔上的例子去實現。

四、自定義全局路由過濾器

有時候 SpringCloudGateWay 提供的過濾器工廠不能滿足自己的要求。

可能有時候需要在過濾時做一些其它的邏輯操作。

那麼這時候可以選擇使用 java 代碼自定義全局過濾器。

代碼示例:

@Component
public class GateWayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // 1.獲取請求參數 
        //1.這裏的request並不是servlet中的request  
        //2.返回值是一個多鍵的map集合、也就是說這個map集合的鍵可以重複
        MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
        // 2.獲取userName參數
        String userName = params.getFirst("userName");
        // 3.校驗
        if ("root".equals(userName)) {
            // 放行
            return chain.filter(exchange);
        }
        // 4.攔截
        // 4.1.禁止訪問,設置狀態碼
        exchange.getResponse().setStatusCode(500);
        // 4.2.結束處理
        return exchange.getResponse().setComplete();
    }

    @Override
    public int getOrder() {
        return -1;
    }
}

當有多個過濾器時,Order 的值決定了過濾器的執行順序。

數值越大優先級越低, 負的越多, 優先級越高。

設置 Order 的值有兩種方式:

  1. 實現 Ordered 接口,並且重寫 getOrder 方法
@Component
public class GateWayFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

    }

    @Override
    public int getOrder() {
        return -1;
    }
}
  1. 使用 @Order 註解
@Order(-1)
@Component
public class GateWayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

    }
}

五、過濾路由過濾器的執行順序

5.1 過濾器的種類

SpringCloudGateWay 中,有三種過濾器:

5.2 過濾器的執行順序

由上圖知過濾器的執行順序爲:默認過濾器 → 當前路由過濾器 → 全局過濾器。

六、網關的跨域問題

6.1 跨域的概念和原理

跨域:請求位置和被請求位置不同源就會發生跨域。

這裏的不同源包括兩個點:

而瀏覽器又會禁止請求的發起者與服務端發生跨域 AJAX 請求。

如果發生了跨域請求,服務器端是能夠正常響應的,但是響應的結果會被瀏覽器攔截。

6.2 跨域常見解決方案

使用 CORS 方式。

CORS 是一個 W3C 標準,全稱是 "跨域資源共享"(Cross-origin resource sharing)。

它允許瀏覽器向跨源服務器,發出 XMLHttpRequest 請求,從而克服了 AJAX 只能同源使用的限制。

6.3 gateway 中如何解決跨域問題

方式一:配置 application.yml 文件:

spring:
  cloud:
    gateway:
      globalcors: # 全局的跨域配置
        add-to-simple-url-handler-mapping: true # 解決options請求被攔截問題
               # options請求 就是一種詢問服務器是否瀏覽器可以跨域的請求
               # 如果每次跨域都有詢問服務器是否瀏覽器可以跨域對性能也是損耗
               # 可以配置本次跨域檢測的有效期maxAge
               # 在maxAge設置的時間範圍內,不去詢問,統統允許跨域
        corsConfigurations:
          '[/**]':
            allowedOrigins:   # 允許哪些網站的跨域請求 
              - "http://localhost:8090"
            allowedMethods:   # 允許的跨域ajax的請求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*"  # 允許在請求中攜帶的頭信息
            allowCredentials: true # 允許在請求中攜帶cookie
            maxAge: 360000    # 本次跨域檢測的有效期(單位毫秒)
                  # 有效期內,跨域請求不會一直髮option請求去增大服務器壓力

方式二:使用編碼方式定義配置類:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Configuration
public class CorsConfig {
    private static final String MAX_AGE = "18000L";

    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            // 使用SpringMvc自帶的跨域檢測工具類判斷當前請求是否跨域
            if (!CorsUtils.isCorsRequest(request)) {
                return chain.filter(ctx);
            }
            HttpHeaders requestHeaders = request.getHeaders();                                  // 獲取請求頭
            ServerHttpResponse response = ctx.getResponse();                                    // 獲取響應對象
            HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();          // 獲取請求方式對象
            HttpHeaders headers = response.getHeaders();                                        // 獲取響應頭
            headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());   // 把請求頭中的請求源(協議+ip+端口)添加到響應頭中(相當於yml中的allowedOrigins)
            headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
            if (requestMethod != null) {
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());    // 允許被響應的方法(GET/POST等,相當於yml中的allowedMethods)
            }
            headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");          // 允許在請求中攜帶cookie(相當於yml中的allowCredentials)
            headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");                // 允許在請求中攜帶的頭信息(相當於yml中的allowedHeaders)
            headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);                           // 本次跨域檢測的有效期(單位毫秒,相當於yml中的maxAge)
            if (request.getMethod() == HttpMethod.OPTIONS) {                                    // 直接給option請求反回結果
                response.setStatusCode(HttpStatus.OK);
                return Mono.empty();
            }
            return chain.filter(ctx);                                                           // 不是option請求則放行
        };
    }

}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/TGNmJiLJ8mJANb2Zp9qVyg