1.網關簡介
大家都都知道在微服務架構中,一個系統會被拆分為很多個微服務。那麼作為用戶端(pc androud ios 平闆)要如何去調用這麼多的微服務呢?如果沒有網關的存在,我們隻能在用戶端記錄每個微服務的位址,然後分别去調用。 axios.get(ip:port/url) axios.get(ip:port/url)
這樣的架構,會存在着諸多的問題:
- 用戶端多次請求不同的微服務,增加用戶端代碼或配置編寫的複雜性
- 認證複雜,每個服務都需要獨立認證。
- 存在跨域請求,在一定場景下處理相對複雜。
(跨域: 浏覽器的ajax從一個位址通路另一個位址:
協定://ip:port 如果三則有一個不同,則會出現跨域問題。
http://192.168.10.11:8080 ----->https://192.168.10.11:8080
http://127.0.0.1:8080--->http://localhost:8080 跨域
)
上面的這些問題可以借助API網關來解決。
所謂的API網關,就是指系統的統一入口,它封裝了應用程式的内部結構,為用戶端提供統一服 務,一些與業務本身功能無關的公共邏輯可以在這裡實作,諸如認證、鑒權、監控(黑白名單)、路由轉發等等。 添加上API網關之後,系統的架構圖變成了如下所示:
在業界比較流行的網關,有下面這些:
- Ngnix+lua
使用nginx的反向代理和負載均衡可實作對api伺服器的負載均衡及高可用
lua是一種腳本語言,可以來編寫一些簡單的邏輯, nginx支援lua腳本
- Kong
基于Nginx+Lua開發,性能高,穩定,有多個可用的插件(限流、鑒權等等)可以開箱即用。 問題:
隻支援Http協定;二次開發,自由擴充困難;提供管理API,缺乏更易用的管控、配置方式。
- Zuul 1.0(慢 servlet 2.0 ) zuul2.0 沒出來。
Netflix開源的網關,功能豐富,使用JAVA開發,易于二次開發 問題:缺乏管控,無法動态配
置;依賴元件較多;處理Http請求依賴的是Web容器,性能不如Nginx
- Spring Cloud Gateway
Spring公司為了替換Zuul而開發的網關服務,将在下面具體介紹。
注意:SpringCloud alibaba技術棧中并沒有提供自己的網關,我們可以采用Spring Cloud Gateway來做網關
2. Gateway簡介
Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等術開發的網關,它旨在為微服務架構提供一種簡單有效的統一的 API 路由管理方式。它的目标是替代 Netflix Zuul,其不僅提供統一的路由方式,并且基于 Filter 鍊的方式提供了網關基本的功能,例如:安全,監控和限流。
優點:
- 性能強勁:是第一代網關Zuul的1.6倍
- 功能強大:内置了很多實用的功能,例如轉發、監控、限流等
- 設計優雅,容易擴充
缺點:
- 其實作依賴Netty與WebFlux,不是傳統的Servlet程式設計模型,學習成本高
- 不能将其部署在Tomcat、Jetty等Servlet容器裡,隻能打成jar包執行 web.Jar
- 需要Spring Boot 2.0及以上的版本,才支援
3.Gateway快速入門
3.1建立一個my-gateway的工程并加入依賴
3.2建立啟動類
3.3修改配置檔案
server: port: 8000 spring: nacos: server-addr: localhost:8848 application: name: my-gateway cloud: gateway: routes: - id: shop-product uri: lb://shop-product order: 0 predicates: - Path=/product/** # - After=2020-12-31T23:59:59.789+08:00[Asia/Shanghai] # - Header=token, \d+ #- Age=18,50 filters: # - SetStatus=700 # - StripPrefix=1 - id: shop-order uri: lb://shop-order order: 0 predicates: - Path=/order/**
3.4測試
4.增強版
現在在配置檔案中寫死了轉發路徑的位址, 前面我們已經分析過位址寫死帶來的問題, 接下來我們從注冊中心擷取此位址。
4.1加入nacos依賴
4.2在主啟動類上加入服務發現的注解
4.3修改application.yml的配置檔案
5.簡寫版
5.1修改application.yml的配置檔案
server:
port: 8000
spring:
cloud:
gateway:
discovery:
locator:
enabled: true
nacos:
server-addr: localhost:8848
application:
name: my-gateway
6.基本概念
路由(Route) 是 gateway 中最基本的元件之一,表示一個具體的路由資訊載體。主要定義了下面的幾個資訊:
- id,路由辨別符,差別于其他 Route。
- uri,路由指向的目的地 uri,即用戶端請求最終被轉發到的微服務。
- order,用于多個 Route 之間的排序,數值越小排序越靠前,比對優先級越高。
- predicate,斷言的作用是進行條件判斷,隻有斷言都傳回真,才會真正的執行路由。
- filter,過濾器用于修改請求和響應資訊。
6.1執行流程
執行流程大體如下:
1. Gateway Client向Gateway Server發送請求
2. 請求首先會被HttpWebHandlerAdapter進行提取組裝成網關上下文
3. 然後網關的上下文會傳遞到DispatcherHandler,它負責将請求分發給 RoutePredicateHandlerMapping
4. RoutePredicateHandlerMapping負責路由查找,并根據路由斷言判斷路由是否可用
5. 如果過斷言成功,由FilteringWebHandler建立過濾器鍊并調用
6. 請求會一次經過PreFilter--微服務--PostFilter的方法,最終傳回響應
6.3 斷言
Predicate(斷言, 謂詞) 用于進行條件判斷,隻有斷言都傳回真,才會真正的執行路由。
斷言就是說: 在 什麼條件下 才能進行路由轉發
6.4内置路由斷言工廠
SpringCloud Gateway包括許多内置的斷言工廠,所有這些斷言都與HTTP請求的不同屬性比對體如下:
基于Datetime類型的斷言工廠
此類型的斷言根據時間做判斷,主要有三個:
AfterRoutePredicateFactory: 接收一個日期參數,判斷請求日期是否晚于指定日期
BeforeRoutePredicateFactory: 接收一個日期參數,判斷請求日期是否早于指定日期
BetweenRoutePredicateFactory: 接收兩個日期參數,判斷請求日期是否在指定時間段内
-After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
基于遠端位址的斷言工廠 RemoteAddrRoutePredicateFactory:
接收一個IP位址段,判斷請求主機位址是否在位址段中
-RemoteAddr=192.168.1.1/24
基于Cookie的斷言工廠
CookieRoutePredicateFactory:接收兩個參數,cookie 名字和一個正規表達式。 判斷請求
cookie是否具有給定名稱且值與正規表達式比對。
-Cookie=chocolate, ch.
基于Header的斷言工廠
HeaderRoutePredicateFactory:接收兩個參數,标題名稱和正規表達式。 判斷請求Header是否
具有給定名稱且值與正規表達式比對。 key value
-Header=X-Request-Id, \d+
基于Host的斷言工廠
HostRoutePredicateFactory:接收一個參數,主機名模式。判斷請求的Host是否滿足比對規則。
-Host=**.testhost.org
基于Method請求方法的斷言工廠
MethodRoutePredicateFactory:接收一個參數,判斷請求類型是否跟指定的類型比對。
-Method=GET
基于Path請求路徑的斷言工廠
PathRoutePredicateFactory:接收一個參數,判斷請求的URI部分是否滿足路徑規則。
-Path=/foo/{segment}基于Query請求參數的斷言工廠
QueryRoutePredicateFactory :接收兩個參數,請求param和正規表達式, 判斷請求參數是否具
有給定名稱且值與正規表達式比對。
-Query=baz, ba.
基于路由權重的斷言工廠
WeightRoutePredicateFactory:接收一個[組名,權重], 然後對于同一個組内的路由按照權重轉發
routes:
-id: weight_route1 uri: host1 predicates:
-Path=/product/**
-Weight=group3, 1
-id: weight_route2 uri: host2 predicates:
-Path=/product/**
-Weight= group3, 9
- 内置路由斷言工廠的使用
7.自定義路由斷言工廠
7.1在配置檔案中,添加一個Age的斷言配置
7.2自定義一個斷言工廠, 實作斷言方法
package com.yyh.gateway.predicates; import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory; import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.server.ServerWebExchange; import javax.validation.constraints.NotNull; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.List; import java.util.function.Predicate; /** * @program: springcloud-parent * @description: 自定義斷言 * @author: * @create: 2021-07-08 16:06 **/ @Component public class AgeRoutePredicateFactory extends AbstractRoutePredicateFactory<AgeRoutePredicateFactory.Config> { public AgeRoutePredicateFactory() { super(AgeRoutePredicateFactory.Config.class); } public List<String> shortcutFieldOrder() { return Arrays.asList("minAge", "maxAge"); } public Predicate<ServerWebExchange> apply(AgeRoutePredicateFactory.Config config) { return new GatewayPredicate() { public boolean test(ServerWebExchange serverWebExchange) { ServerHttpRequest request=serverWebExchange.getRequest(); String age = request.getQueryParams().getFirst("age"); if(StringUtils.isEmpty(age)){ return false; }else { Integer a=Integer.parseInt(age); if(a>= config.getMinAge()&&a<= config.getMaxAge()){ return true; }else { return false; } } } }; } @Validated public static class Config { @NotNull private Integer minAge; @NotNull private Integer maxAge; public Integer getMinAge() { return minAge; } public void setMinAge(Integer minAge) { this.minAge = minAge; } public Integer getMaxAge() { return maxAge; } public void setMaxAge(Integer maxAge) { this.maxAge = maxAge; } } }
7.3測試
8.過濾器
1 作用: 過濾器就是在請求的傳遞過程中,對請求和響應做一些手腳
2 生命周期: Pre Post
3 分類: 局部過濾器(作用在某一個路由上) 全局過濾器(作用全部路由上)
在Gateway中, Filter的生命周期隻有兩個:“pre” 和 “post”。
PRE: 這種過濾器在請求被路由之前調用。我們可利用這種過濾器實作身份驗證、在叢集中選擇請求的微服務、記錄調試資訊等。
POST:這種過濾器在路由到微服務以後執行。這種過濾器可用來為響應添加标準的HTTP Header、收集統計資訊和名額、将響應從微服務發送給用戶端等。
Gateway 的Filter從作用範圍可分為兩種: GatewayFilter與GlobalFilter。
- GatewayFilter:應用到單個路由或者一個分組的路由上。
- GlobalFilter:應用到所有的路由上。
8.1内置局部過濾器
在SpringCloud Gateway中内置了很多不同類型的網關路由過濾器。
https://www.cnblogs.com/zhaoxiangjun/p/13042189.html
Spring Cloud Gateway 内置的過濾器工廠
Spring Cloud Gateway 内置的過濾器工廠
内置的過濾器工廠
這裡簡單将Spring Cloud Gateway内置的所有過濾器工廠整理成了一張表格。如下:
過濾器工廠 | 作用 | 參數 |
---|---|---|
AddRequestHeader | 為原始請求添加Header | Header的名稱及值 |
AddRequestParameter | 為原始請求添加請求參數 | 參數名稱及值 |
AddResponseHeader | 為原始響應添加Header | Header的名稱及值 |
DedupeResponseHeader | 剔除響應頭中重複的值 | 需要去重的Header名稱及去重政策 |
Hystrix | 為路由引入Hystrix的斷路器保護 HystrixCommand的名稱 | |
FallbackHeaders | 為fallbackUri的請求頭中添加具體的異常資訊 | Header的名稱 |
PrefixPath | 為原始請求路徑添加字首 | 字首路徑 |
PreserveHostHeader | 為請求添加一個preserveHostHeader=true的屬性,路由過濾器會檢查該屬性以決定是否要發送原始的Host | 無 |
RequestRateLimiter | 用于對請求限流,限流算法為令牌桶 | keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus |
RedirectTo | 将原始請求重定向到指定的URL | http狀态碼及重定向的url |
RemoveHopByHopHeadersFilter | 為原始請求删除IETF組織規定的一系列Header | 預設就會啟用,可以通過配置指定僅删除哪些Header |
RemoveRequestHeader | 為原始請求删除某個Header | Header名稱 |
RemoveResponseHeader | 為原始響應删除某個Header | Header名稱 |
RewritePath | 重寫原始的請求路徑 | 原始路徑正規表達式以及重寫後路徑的正規表達式 |
RewriteResponseHeader | 重寫原始響應中的某個Header | Header名稱,值的正規表達式,重寫後的值 |
SaveSession | 在轉發請求之前,強制執行WebSession::save操作 | 無 |
secureHeaders | 為原始響應添加一系列起安全作用的響應頭 | 無,支援修改這些安全響應頭的值 |
SetPath | 修改原始的請求路徑 | 修改後的路徑 |
SetResponseHeader | 修改原始響應中某個Header的值 | Header名稱,修改後的值 |
SetStatus | 修改原始響應的狀态碼 | HTTP 狀态碼,可以是數字,也可以是字元串 |
StripPrefix | 用于截斷原始請求的路徑 | 使用數字表示要截斷的路徑的數量 |
Retry | 針對不同的響應進行重試 | retries、statuses、methods、series |
RequestSize | 設定允許接收最大請求包的大小。如果請求包大小超過設定的值,則傳回 413 Payload Too Large | 請求包大小,機關為位元組,預設值為5M |
ModifyRequestBody | 在轉發請求之前修改原始請求體内容 | 修改後的請求體内容 |
ModifyResponseBody | 修改原始響應體的内容 | 修改後的響應體内容 |
Default | 為所有路由添加過濾器 | 過濾器工廠名稱及值 |
8.2内置局部過濾器的使用
8.3測試
9.全局過濾器
全局過濾器作用于所有路由, 無需配置。通過全局過濾器可以實作對權限的統一校驗,安全性驗證等功能。
9.1 内置全局過濾器
SpringCloud Gateway内部也是通過一系列的内置全局過濾器對整個路由轉發進行處理如下
9.2 自定義全局過濾器
内置的過濾器已經可以完成大部分的功能,但是對于企業開發的一些業務功能處理,還是需要我們自己編寫過濾器來實作的,那麼我們一起通過代碼的形式自定義一個過濾器,去完成統一的權限校驗。
開發中的鑒權邏輯:
- 當用戶端第一次請求服務時,服務端對使用者進行資訊認證(登入)
- 認證通過,将使用者資訊進行加密形成token,傳回給用戶端aaaa,作為登入憑證
- 以後每次請求,用戶端都攜帶認證的token
- 服務端對token進行解密,判斷是否有效。
如上圖,對于驗證使用者是否已經登入鑒權的過程可以在網關統一檢驗。
檢驗的标準就是請求中是否攜帶token憑證以及token的正确性
9.4例子
下面的我們自定義一個GlobalFilter,去校驗所有請求的請求參數中是否包含“token”,如何不包含請求參數“token”則不轉發路由,否則執行正常的邏輯
自定義全局過濾器 要求:必須實作GlobalFilter,Order接口
package com.yyh.gateway.filter; 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.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @program: springcloud-parent * @description: 登入的全局過濾器 * @author: 苑銀輝 * @create: 2021-07-08 16:51 **/ @Component public class LoginFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request= exchange.getRequest(); String token = request.getHeaders().getFirst("token"); if(!StringUtils.isEmpty(token)){ if("admin".equals(token)){ return chain.filter(exchange); } } exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } @Override public int getOrder() { return 0; } }
測試