12-SpringCloud GateWay


GateWay和Zuul說明

Zuul開發人員窩里斗,實屬明日黃花

重點關注Gate Way

GateWay是什么

上一代zuul 1.x官網

Gateway官網

概述

  • Cloud全家桶中有個很重要的組件就是網關,在1.x版本中都是采用的Zuul網關;
  • 但在2.x版本中,zuul的升級一直跳票,SpringCloud最后自己研發了一個網關替代Zuul,那就是SpringCloud Gateway—句話:gateway是原zuul1.x版的替代

  • Gateway是在Spring生態系統之上構建的API網關服務,基於Spring 5,Spring Boot 2和Project Reactor等技術。
  • Gateway旨在提供一種簡單而有效的方式來對API進行路由,以及提供一些強大的過濾器功能,例如:熔斷、限流、重試等。
  • SpringCloud Gateway是Spring Cloud的一個全新項目,基於Spring 5.0+Spring Boot 2.0和Project Reactor等技術開發的網關,它旨在為微服務架構提供—種簡單有效的統一的API路由管理方式。
  • SpringCloud Gateway作為Spring Cloud 生態系統中的網關,目標是替代Zuul,在Spring Cloud 2.0以上版本中,沒有對新版本的Zul 2.0以上最新高性能版本進行集成,仍然還是使用的Zuul 1.x非Reactor模式的老版本。而為了提升網關的性能,SpringCloud Gateway是基於WebFlux框架實現的,而WebFlux框架底層則使用了高性能的Reactor模式通信框架Netty。
  • Spring Cloud Gateway的目標提供統一的路由方式且基於 Filter鏈的方式提供了網關基本的功能,例如:安全,監控/指標,和限流。

作用

  • 方向代理
  • 鑒權
  • 流量控制
  • 熔斷
  • 日志監控

微服務架構中網關的位置

GateWay非阻塞異步模型

我們為什么選擇Gateway

有Zuull了怎么又出來Gateway?我們為什么選擇Gateway?

  • netflix不太靠譜,zuul2.0一直跳票,遲遲不發布。
  • 一方面因為Zuul1.0已經進入了維護階段,而且Gateway是SpringCloud團隊研發的,是親兒子產品,值得信賴。而且很多功能Zuul都沒有用起來也非常的簡單便捷。
  • `Gateway是基於異步非阻塞模型上進行開發的,性能方面不需要擔心。雖然Netflix早就發布了最新的Zuul 2.x,但Spring Cloud貌似沒有整合計划。而且Netflix相關組件都宣布進入維護期;不知前景如何?
  • 多方面綜合考慮Gateway是很理想的網關選擇。
  • SpringCloud Gateway具有如下特性
  • 基於Spring Framework 5,Project Reactor和Spring Boot 2.0進行構建;
  • 動態路由:能夠匹配任何請求屬性;
  • 可以對路由指定Predicate (斷言)和Filter(過濾器);
  • 集成Hystrix的斷路器功能;
  • 集成Spring Cloud 服務發現功能;
  • 易於編寫的Predicate (斷言)和Filter (過濾器);
  • 請求限流功能;
  • 支持路徑重寫。
  • SpringCloud Gateway與Zuul的區別
  • 在SpringCloud Finchley正式版之前,Spring Cloud推薦的網關是Netflix提供的Zuul。
  • Zuul 1.x,是一個基於阻塞I/O的API Gateway。
  • Zuul 1.x基於Servlet 2.5使用阻塞架構它不支持任何長連接(如WebSocket)Zuul的設計模式和Nginx較像,每次I/О操作都是從工作線程中選擇一個執行,請求線程被阻塞到工作線程完成,但是差別是Nginx用C++實現,Zuul用Java實現,而JVM本身會有第-次加載較慢的情況,使得Zuul的性能相對較差。
  • Zuul 2.x理念更先進,想基於Netty非阻塞和支持長連接,但SpringCloud目前還沒有整合。Zuul 2.x的性能較Zuul 1.x有較大提升。在性能方面,根據官方提供的基准測試,Spring Cloud Gateway的RPS(每秒請求數)是Zuul的1.6倍。
  • Spring Cloud Gateway建立在Spring Framework 5、Project Reactor和Spring Boot2之上,使用非阻塞API。
  • Spring Cloud Gateway還支持WebSocket,並且與Spring緊密集成擁有更好的開發體驗

Zuul1.x模型

Springcloud中所集成的Zuul版本,采用的是Tomcat容器,使用的是傳統的Serviet IO處理模型。

Servlet的生命周期?servlet由servlet container進行生命周期管理。

  • container啟動時構造servlet對象並調用servlet init()進行初始化;
  • container運行時接受請求,並為每個請求分配一個線程(一般從線程池中獲取空閑線程)然后調用service);
  • container關閉時調用servlet destory()銷毀servlet。

上述模式的缺點:

Servlet是一個簡單的網絡IO模型,當請求進入Servlet container時,Servlet container就會為其綁定一個線程,在並發不高的場景下這種模型是適用的。但是一旦高並發(如用Jmeter進行壓測),線程數量就會上漲,而線程資源代價是昂貴的(上線文切換,內存消耗大)嚴重影響請求的處理時間。在一些簡單業務場景下,不希望為每個request分配一個線程,只需要1個或幾個線程就能應對極大並發的請求,這種業務場景下servlet模型沒有優勢。

所以Zuul 1.X是基於servlet之上的一個阻塞式處理模型,即Spring實現了處理所有request請求的一個servlet (DispatcherServlet)並由該servlet阻塞式處理處理。所以SpringCloud Zuul無法擺脫servlet模型的弊端。

Gateway模型

WebFlux是什么

官方文檔

傳統的Web框架,比如說: Struts2,SpringMVC等都是基於Servlet APl與Servlet容器基礎之上運行的。

但是在Servlet3.1之后有了異步非阻塞的支持。而WebFlux是一個典型非阻塞異步的框架,它的核心是基於Reactor的相關API實現的。相對於傳統的web框架來說,它可以運行在諸如Netty,Undertow及支持Servlet3.1的容器上。非阻塞式+函數式編程(Spring 5必須讓你使用Java 8)。

 

Spring WebFlux是Spring 5.0 引入的新的響應式框架,區別於Spring MVC,它不需要依賴Servlet APl,它是完全異步非阻塞的,並且基於Reactor來實現響應式流規范。

Spring Cloud Gateway requires the Netty runtime provided by Spring Boot and Spring Webflux. It does not work in a traditional Servlet Container or when built as a WAR.link

Gateway工作流程

三大核心概念

  1. Route(路由) - 路由是構建網關的基本模塊,它由ID,目標URI,一系列的斷言和過濾器組成,如斷言為true則匹配該路由;
  2. Predicate(斷言) - 參考的是Java8的java.util.function.Predicate,開發人員可以匹配HTTP請求中的所有內容(例如請求頭或請求參數),如果請求與斷言相匹配則進行路由;
  1. Filter(過濾) - 指的是Spring框架中GatewayFilter的實例,使用過濾器,可以在請求被路由前或者之后對請求進行修改。

web請求,通過一些匹配條件,定位到真正的服務節點。並在這個轉發過程的前后,進行一些精細化控制。

predicate就是我們的匹配條件;而fliter,就可以理解為一個無所不能的攔截器。有了這兩個元素,再加上目標uri,就可以實現一個具體的路由了

Gateway工作流程

官網總結

Clients make requests to Spring Cloud Gateway. If the Gateway Handler Mapping determines that a request matches a route, it is sent to the Gateway Web Handler. This handler runs the request through a filter chain that is specific to the request. The reason the filters are divided by the dotted line is that filters can run logic both before and after the proxy request is sent. All “pre” filter logic is executed. Then the proxy request is made. After the proxy request is made, the “post” filter logic is run.link

客戶端向Spring Cloud Gateway發出請求。然后在Gateway Handler Mapping 中找到與請求相匹配的路由,將其發送到GatewayWeb Handler。

Handler再通過指定的過濾器鏈來將請求發送到我們實際的服務執行業務邏輯,然后返回。

 

過濾器之間用虛線分開是因為過濾器可能會在發送代理請求之前(“pre”)或之后(“post")執行業務邏輯。

 

Filter在“pre”類型的過濾器可以做參數校驗、權限校驗、流量監控、日志輸出、協議轉換等,在“post”類型的過濾器中可以做響應內容、響應頭的修改,日志的輸出,流量監控等有着非常重要的作用。

 

核心邏輯:路由轉發 + 執行過濾器鏈。

Gateway搭建

新建工程

新建Module - cloud-gateway-gateway9527

修改POM.xml

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>com.dance.springcloud</artifactId>
        <groupId>com.dance</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-gateway-gateway9527</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--gateway-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- dependent on common modules -->
        <dependency>
            <groupId>com.dance</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!--一般基礎配置類-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

新建yml配置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服務提供者provider注冊進eureka服務列表內
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

新建主啟動類

package com.dance.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;


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

網關如何做路由映射

cloud-provider-payment8001看看controller的訪問地址

  • get
  • lb

我們目前不想暴露8001端口,希望在8001外面套一層9527

YML新增網關配置

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      routes:
        - id: payment_routh #payment_route    #路由的ID,沒有固定規則但要求唯一,建議配合服務名
          uri: http://localhost:8001          #匹配后提供服務的路由地址
          #uri: lb://cloud-payment-service #匹配后提供服務的路由地址
          predicates:
            - Path=/payment/get/**         # 斷言,路徑相匹配的進行路由

        - id: payment_routh2 #payment_route    #路由的ID,沒有固定規則但要求唯一,建議配合服務名
          uri: http://localhost:8001          #匹配后提供服務的路由地址
          #uri: lb://cloud-payment-service #匹配后提供服務的路由地址
          predicates:
            - Path=/payment/lb/**         # 斷言,路徑相匹配的進行路由

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服務提供者provider注冊進eureka服務列表內
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

測試

  • 啟動Eureka集群
  • 啟動8001-cloud-provider-payment8001
  • 啟動9527網關
  • 訪問說明
  • 兩者訪問成功,返回相同結果

Gateway配置路由的兩種方式

在配置文件yml中配置,見上一章節(就是上面配置的yml) 下面的這種方式,是除了yml配置之外的類配置方式

代碼中注入RouteLocator的Bean

官方案例 - link

RemoteAddressResolver resolver = XForwardedRemoteAddressResolver
    .maxTrustedIndex(1);

...

.route("direct-route",
    r -> r.remoteAddr("10.1.1.1", "10.10.1.1/24")
        .uri("https://downstream1")
.route("proxied-route",
    r -> r.remoteAddr(resolver, "10.10.1.1", "10.10.1.1/24")
        .uri("https://downstream2")
)

百度國內新聞網址,需要外網 - http://news.baidu.com/guonei

自己寫一個

業務需求 - 通過9527網關訪問到外網的百度新聞網址

編碼

cloud-gateway-gateway9527業務實現

package com.dance.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();

        /**
         * 第一個參數:path_route_atguigu 相當於yml中的id
         * 第二個參數是一個函數:
         *  path:/guonei 相當於yml中匹配的路勁
         *  uri 是一樣的
         */
        routes.route("path_route_atguigu",
                r -> r.path("/guonei")
                        .uri("http://news.baidu.com/guonei")).build();

        return routes.build();
    }
}

測試

瀏覽器輸入http://localhost:9527/guonei,返回http://news.baidu.com/guonei相同的頁面。

GateWay配置動態路由

默認情況下Gateway會根據注冊中心注冊的服務列表,以注冊中心上微服務名為路徑創建動態路由進行轉發,從而實現動態路由的功能(不寫死一個地址)。

修改POM.xml

<!--eureka-client-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

如果沒有添加注冊中心就添加注冊中心

修改yml配置

需要注意的是uri的協議為lb,表示啟用Gateway的負載均衡功能。

lb://serviceName是spring cloud gateway在微服務中自動為我們創建的負載均衡uri。

server:
  port: 9527

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #開啟從注冊中心動態創建路由的功能,利用微服務名進行路由
      routes:
        - id: payment_routh #payment_route    #路由的ID,沒有固定規則但要求唯一,建議配合服務名
          #          uri: http://localhost:8001          #匹配后提供服務的路由地址
          uri: lb://cloud-payment-service #匹配后提供服務的路由地址
          predicates:
            - Path=/payment/get/**         # 斷言,路徑相匹配的進行路由

        - id: payment_routh2 #payment_route    #路由的ID,沒有固定規則但要求唯一,建議配合服務名
          #          uri: http://localhost:8001          #匹配后提供服務的路由地址
          uri: lb://cloud-payment-service #匹配后提供服務的路由地址
          predicates:
            - Path=/payment/lb/**         # 斷言,路徑相匹配的進行路由

eureka:
  instance:
    hostname: cloud-gateway-service
  client: #服務提供者provider注冊進eureka服務列表內
    service-url:
      register-with-eureka: true
      fetch-registry: true
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka

測試

  1. 啟動Eureka集群
  2. 啟動8001和8002
  1. 瀏覽器輸入地址,不停刷新頁面,8001/8002兩個端口切換。
  1. http://localhost:9527/payment/lb

GateWay常用的Predicate

官方文檔

Route Predicate Factories是什么

Spring Cloud Gateway matches routes as part of the Spring WebFlux HandlerMapping infrastructure. Spring Cloud Gateway includes many built-in route predicate factories. All of these predicates match on different attributes of the HTTP request. You can combine multiple route predicate factories with logical and statements.link

Spring Cloud Gateway將路由匹配作為Spring WebFlux HandlerMapping基礎架構的一部分。

Spring Cloud Gateway包括許多內置的Route Predicate工廠。所有這些Predicate都與HTTP請求的不同屬性匹配。多個RoutePredicate工廠可以進行組合。

 

Spring Cloud Gateway創建Route 對象時,使用RoutePredicateFactory 創建 Predicate對象,Predicate 對象可以賦值給Route。Spring Cloud Gateway包含許多內置的Route Predicate Factories。

所有這些謂詞都匹配HTTP請求的不同屬性。多種謂詞工廠可以組合,並通過邏輯and。

常用的Route Predicate Factory

  1. The After Route Predicate Factory
  2. The Before Route Predicate Factory
  1. The Between Route Predicate Factory
  2. The Cookie Route Predicate Factory
  1. The Header Route Predicate Factory
  2. The Host Route Predicate Factory
  1. The Method Route Predicate Factory
  2. The Path Route Predicate Factory
  1. The Query Route Predicate Factory
  2. The RemoteAddr Route Predicate Factory
  1. The weight Route Predicate Factory

討論幾個Route Predicate Factory

The After Route Predicate Factory

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #開啟從注冊中心動態創建路由的功能,利用微服務名進行路由
      routes:
        - id: payment_routh #payment_route    #路由的ID,沒有固定規則但要求唯一,建議配合服務名
          #          uri: http://localhost:8001          #匹配后提供服務的路由地址
          uri: lb://cloud-payment-service #匹配后提供服務的路由地址
          predicates:
            - Path=/payment/get/**         # 斷言,路徑相匹配的進行路由
            - After=2021-08-29T18:06:05.572+08:00[Asia/Shanghai] # 只有在這個時間之后訪問的請求才會被映射,可以做預發布

測試

通過cmd訪問

成功

C:\Users\ext.caiyuanqing>curl http://localhost:9527/payment/get/1
{"code":200,"message":"查詢成功,serverPort:  8002","data":{"id":1,"serial":"dance"}}
C:\Users\ext.caiyuanqing>

修改時間:- After=2021-08-29T19:06:05.572+08:00[Asia/Shanghai] ,往后調一個小時,再次訪問

失敗

C:\Users\ext.caiyuanqing>curl http://localhost:9527/payment/get/1
{"timestamp":"2021-08-29T10:10:59.912+0000","path":"/payment/get/1","status":404,"error":"Not Found","message":null,"requestId":"62b8c040","trace":"org.springframework.web.server.ResponseStatusException: 404 NOT_FOUND\r\n\tat org.springframework.web.reactive.resource.ResourceWebHandler.lambda$handle$0(ResourceWebHandler.java:325)\r\n\tSuppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: \nError has been observed at the following site(s):\n\t|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]\n\t|_ checkpoint ⇢ HTTP GET \"/payment/get/1\" [ExceptionHandlingWebHandler]\nStack trace:\r\n\t\tat org.springframework.web.reactive.resource.ResourceWebHandler.lambda$handle$0(ResourceWebHandler.java:325)\r\n\t\tat reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)\r\n\t\tat reactor.core.publisher.Mono.subscribe(Mono.java:4105)\r\n\t\tat reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75)\r\n\t\tat reactor.core.publisher.MonoFlatMap$FlatMapMain.onComplete(MonoFlatMap.java:174)\r\n\t\tat reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:96)\r\n\t\tat reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:359)\r\n\t\tat reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:211)\r\n\t\tat reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:139)\r\n\t\tat reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:63)\r\n\t\tat reactor.core.publisher.Mono.subscribe(Mono.java:4105)\r\n\t\tat reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:172)\r\n\t\tat reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)\r\n\t\tat reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150)\r\n\t\tat reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)\r\n\t\tat reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76)\r\n\t\tat reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:274)\r\n\t\tat reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:851)\r\n\t\tat reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:121)\r\n\t\tat reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2186)\r\n\t\tat reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:162)\r\n\t\tat reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:1994)\r\n\t\tat reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:1868)\r\n\t\tat reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:90)\r\n\t\tat reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54)\r\n\t\tat reactor.core.publisher.Mono.subscribe(Mono.java:4105)\r\n\t\tat reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:441)\r\n\t\tat reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:211)\r\n\t\tat reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:139)\r\n\t\tat reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:63)\r\n\t\tat reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55)\r\n\t\tat reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)\r\n\t\tat reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55)\r\n\t\tat reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)\r\n\t\tat reactor.core.publisher.Mono.subscribe(Mono.java:4105)\r\n\t\tat reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:172)\r\n\t\tat reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)\r\n\t\tat reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55)\r\n\t\tat reactor.netty.http.server.HttpServerHandle.onStateChange(HttpServerHandle.java:64)\r\n\t\tat reactor.netty.tcp.TcpServerBind$ChildObserver.onStateChange(TcpServerBind.java:226)\r\n\t\tat reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:441)\r\n\t\tat reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:89)\r\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)\r\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)\r\n\t\tat io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)\r\n\t\tat reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:167)\r\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)\r\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)\r\n\t\tat io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)\r\n\t\tat io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:438)\r\n\t\tat io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:326)\r\n\t\tat io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:300)\r\n\t\tat io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:253)\r\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)\r\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)\r\n\t\tat io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:352)\r\n\t\tat io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1422)\r\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:374)\r\n\t\tat io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:360)\r\n\t\tat io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:931)\r\n\t\tat io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)\r\n\t\tat io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:700)\r\n\t\tat io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:635)\r\n\t\tat io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:552)\r\n\t\tat io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:514)\r\n\t\tat io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1050)\r\n\t\tat io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)\r\n\t\tat io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)\r\n\t\tat java.lang.Thread.run(Thread.java:748)\r\n"}
C:\Users\ext.caiyuanqing>

可以通過下述方法獲得上述格式的時間戳字符串

package time;

import java.time.ZonedDateTime;


public class T2
{
    public static void main(String[] args)
    {
        ZonedDateTime zbj = ZonedDateTime.now(); // 默認時區
        System.out.println(zbj);

       //2021-08-29T18:06:05.572+08:00[Asia/Shanghai]
    }
}

The Between Route Predicate Factory

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #開啟從注冊中心動態創建路由的功能,利用微服務名進行路由
      routes:
        - id: payment_routh #payment_route    #路由的ID,沒有固定規則但要求唯一,建議配合服務名
          #          uri: http://localhost:8001          #匹配后提供服務的路由地址
          uri: lb://cloud-payment-service #匹配后提供服務的路由地址
          predicates:
            - Path=/payment/get/**         # 斷言,路徑相匹配的進行路由
#            - After=2021-08-29T19:06:05.572+08:00[Asia/Shanghai] # 只有在這個時間之后訪問的請求才會被映射,可以做預發布
            - Between=2021-08-29T18:06:05.572+08:00[Asia/Shanghai],2021-08-29T19:06:05.572+08:00[Asia/Shanghai] # 只有在兩個時間之間才能訪問

The Cookie Route Predicate Factory

spring:
  application:
    name: cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true #開啟從注冊中心動態創建路由的功能,利用微服務名進行路由
      routes:
        - id: payment_routh #payment_route    #路由的ID,沒有固定規則但要求唯一,建議配合服務名
          #          uri: http://localhost:8001          #匹配后提供服務的路由地址
          uri: lb://cloud-payment-service #匹配后提供服務的路由地址
          predicates:
            - Path=/payment/get/**         # 斷言,路徑相匹配的進行路由
#            - After=2021-08-29T19:06:05.572+08:00[Asia/Shanghai] # 只有在這個時間之后訪問的請求才會被映射,可以做預發布
            - Between=2021-08-29T18:06:05.572+08:00[Asia/Shanghai],2021-08-29T19:06:05.572+08:00[Asia/Shanghai] # 只有在兩個時間之間才能訪問
            - Cookie=chocolate,ch.p # 指定Cookie中必須攜帶key為 chocolate,值必須為 ch.p : 點的意思是正則中的意思 意味着匹配任意一個字符 比如 chip chap都是合法的

The cookie route predicate factory takes two parameters, the cookie name and a regular expression.

This predicate matches cookies that have the given name and whose values match the regular expression.

后面可以寫正則表達式 .匹配任意字符

測試

# 該命令相當於發get請求,且沒帶cookie
curl http://localhost:9527/payment/get/1

# 帶cookie的
curl http://localhost:9527/payment/get/1 --cookie "chocolate=chap"

The Header Route Predicate Factory

 
        

The header route predicate factory takes two parameters, the header name and a regular expression.

This predicate matches with a header that has the given name whose value matches the regular expression.

測試

# 帶指定請求頭的參數的CURL命令
curl http://localhost:9527/payment/get/1 --cookie "chocolate=chap" -H "X-Request-Id:123"

其它的,舉一反三。

小結

說白了,Predicate就是為了實現一組匹配規則,讓請求過來找到對應的Route進行處理。還可以讓一些非法請求攔截到外面

GateWay的Filter

官方文檔

Route filters allow the modification of the incoming HTTP request or outgoing HTTP response in some manner. Route filters are scoped to a particular route. Spring Cloud Gateway includes many built-in GatewayFilter Factories.

路由過濾器可用於修改進入的HTTP請求和返回的HTTP響應,路由過濾器只能指定路由進行使用。Spring Cloud Gateway內置了多種路由過濾器,他們都由GatewayFilter的工廠類來產生。

Spring Cloud Gateway的Filter:

生命周期:

  • pre
  • post

種類(具體看官方文檔):

  • GatewayFilter - 有31種
  • GlobalFilter - 有10種

常用的GatewayFilter:

  1. AddRequestParameter
  2. GatewayFilter

自定義全局GlobalFilter

兩個主要接口介紹:

  1. GlobalFilter
  2. Ordered

能干什么:

  1. 全局日志記錄
  2. 統一網關鑒權

修改網關工程

GateWay9527項目添加MyLogGateWayFilter類:

package com.dance.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());

        // 獲取第一個 參數為uname的參數
        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;
    }
}

測試

  1. 啟動Eureka集群
  2. 啟動8001,8002工程
  1. 啟動網關
  1. 啟動之前記得將配置規則都改回去
  1. 瀏覽器測試
  1. http://localhost:9527/payment/lb - 訪問異常
  2. http://localhost:9527/payment/lb?uname=abc - 正常訪問

訪問異常地址

后端打印

訪問正常地址

測試成功,達到用戶鑒權效果,完結撒花~

作者:彼岸舞

時間:2021\08\29

內容關於:Spring Cloud H版

本文屬於作者原創,未經允許,禁止轉發


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM