網關 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 爲什麼選擇以服務名 + 負載均衡的方式?
-
主要是寫死地址的話,今後如果 userservice 的地址變了,那麼又要去修改 yml 配置文件。
-
而 lb://userservice 可以讓程序員一眼認出這是哪個微服務,以後地址變了也無需修改 yml 配置文件。
上述配置詳解:
將 /user/**
開頭的請求,代理到 lb://userservice。
將 /card/**
開頭的請求,代理到 lb://cardservice。
lb 是負載均衡,根據服務名拉取服務列表,實現負載均衡。
-
http://127.0.0.1:10086/user/99
就算是/user/**
開頭的請求,不要把協議、ip 和端口計算在內。 -
有多少個需要配置的路由,都按上面的格式配置即可
三、GateWay 路由配置詳解
路由主要有四個配置:
-
路由 id(id)
-
路由目標(uri)
-
路由斷言(predicates):判斷路由的規則,
-
路由過濾器(filters):對請求或響應做處理
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 的值有兩種方式:
- 實現 Ordered 接口,並且重寫 getOrder 方法
@Component
public class GateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
}
@Override
public int getOrder() {
return -1;
}
}
- 使用 @Order 註解
@Order(-1)
@Component
public class GateWayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
}
}
五、過濾路由過濾器的執行順序
5.1 過濾器的種類
SpringCloudGateWay 中,有三種過濾器:
-
默認過濾器 default-filters
-
只對具體某個路由生效的局部過濾器 filters
-
使用 java 代碼編寫的全局過濾器 GlobalFilter
5.2 過濾器的執行順序
由上圖知過濾器的執行順序爲:默認過濾器 → 當前路由過濾器 → 全局過濾器。
六、網關的跨域問題
6.1 跨域的概念和原理
跨域:請求位置和被請求位置不同源就會發生跨域。
這裏的不同源包括兩個點:
-
域名不同:www.baidu.com 和 www.taobao.com。(IP 不同也是相同道理)
-
端口不同:127.0.0.1:8080 和 127.0.0.1:8081。
而瀏覽器又會禁止請求的發起者與服務端發生跨域 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