1. 什么是API網關
API網關:是一個服務器,是系統的唯一入口。同時也可以實現服務的路由、負載均衡、鑒權、限流、熔斷等功能。
API網關出現的原因是微服務架構的出現,不同的微服務一般會有不同的網絡地址,而外部客戶端可能需要調用多個服務的接口才能完成一個業務需求,如果讓客戶端直接與各個微服務通信,會有以下的問題:
客戶端請求多個微服務,各個服務ip不一樣,增加客戶端復雜度。
存在跨域,在一定場景下處理相對復雜。
認證復雜,每個服務都需要獨立認證。
與微服務耦合太強,微服務變更,客戶端需要變更
2. 使用API網關的好處
所有的外部請求都會先經過API 網關這一層。也就是說,API 的實現方面更多的考慮業務邏輯,而安全、性能、監控可以交由 API 網關來做,這樣既提高業務靈活性又不缺安全性。
- 易於監控
- 統一認證
- 減少客戶端與微服務交互,解耦接口依賴
3. 常用網關
Nginx+lua
Zuul Zuul是一種提供動態路由、監視、彈性、安全性等功能的邊緣服務。Zuul是Netflix出品的一個基於JVM路由和服務端的負載均衡器。
SpringCoud Gateway Spring Cloud GateWay是Spring Cloud的⼀個全新項⽬【SpringCloud公司開發的】,⽬標是取代Netflix Zuul。
3. gateWay入門使用
3.-1 基本概念
-
斷言:用於進行條件判斷,只有斷言都返回真,才會真正的執行路由。
-
路由:路由是構建網關的基本模塊,它由ID,目標URI,斷言Predicates集合,過濾器Filters集合組成,如果斷言為true,則匹配該路由。
-
過濾器:可以在請求被路由前后修改請求和響應的內容。基於過濾器可以實現:安全,監控,限流等問題。
3.0 創建一個服務
ip配置為80端口,當訪問這個服務,就是訪問我們的getWay。
# 服務端口
server.port=80
# 服務名
spring.application.name=service-gateway
3.1 請求轉發的實現
導入依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
添加配置文件
spring:
application:
name: springcloud-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 當有了注冊中心時,網關也是一個服務,所以需要注冊到注冊中心去。
gateway:
discovery:
locator:
enabled: true # 讓gateway從nacos中獲取服務信息
routes: # routes 是路由,一個GateWay可以包含多個route
- id: to-user # 路由的id,必須唯一。用於區別其他Route。
uri: lb://springcloud-consumer # 路由指向的目的地 uri。 lb 動態路由,因為有了注冊中心,uri就不是是寫,而是寫它的服務實例名,lb代表負載均衡。
order: 1 # 路由的優先級,數字越小代表路由的優先級越高。
predicates: # predicates 是斷言,一個ateWay可以包含多個predicate
- Path=/user/** # 斷言規則,用於進行條件判斷,只有斷言都返回真,才會真正的執行路由。
#filters: # 過濾器,用於在請求的傳遞過程中,對請求和響應做一些手腳
路由介紹:
Route:主要由 路由id、目標uri、斷言集合和過濾器集合組成。
- 路由標識,要求唯一,名稱任意(默認值 uuid,一般不用,需要自定義)
- uri:請求最終被轉發到的目標地址
- order: 路由優先級,數字越小,優先級越高
- predicates:斷言數組,即判斷條件,如果返回值是boolean,則轉發請求到 uri 屬性指定的服務中
- filters:過濾器數組,在請求傳遞過程中,對請求做一些修改
斷言介紹:
Spring Cloud Gateway包括許多內置的路由斷言工廠。所有這些斷言都與HTTP請求的不同屬性匹配。您可以將多個路由斷言工廠與邏輯 and 語句結合使用。
Spring Cloud Gateway 中的斷言命名都是有規范的,格式:“xxx + RoutePredicateFactory”,比如權重斷言 WeightRoutePredicateFactory,那么配置時直接取前面的 “Weight”。
也可以自定義斷言工廠,用到的時候百度吧。
過濾器介紹:
過濾器按區域
划分,可分為全局,局部兩種過濾器。
- 局部GatewayFilter:會應用到單個路由上
- 全局GlobalFilter:會應用到所有路由上
過濾器按作用點
划分,可分為Pre(前置),Post(后置)兩種過濾器。
- Pre:在請求轉發到后端微服務之前執行。
- Post:在請求執行完成之后執行。
Spring Cloud Gateway 中的過濾器命名都是有規范的,格式: "xxx + “GatewayFilterFactory”,比如PrefixPathGatewayFilterFactory,那么配置時直接取前面的 “PrefixPath”。
在Spring Cloud Gateway中內置了很多局部Filter。
3.2 介紹用得比較多的內置局部過濾器——Path路徑過濾器
Path相關過濾器可以實現URL重寫,通過重寫URL可以實現隱藏真實路徑提高安全性。
Path相關過濾器采用路徑正則表達式參數和替換參數,使用Java正則表達式來靈活地重寫請求路徑。
Gateway過濾器-自定義局部過濾器
自定義(局部)網關過濾器:https://www.bilibili.com/video/BV1R7411774f?p=42&spm_id_from=pageDriver
因為Gateway提供了很多Gateway內置網關(局部)過濾器,所以一般很少使用Gateway自定義網關過濾器。
在Spring Cloud Gateway中內置了很多全局Filter。
全局:會應用到所有的路由上。通過全局過濾器可以實現對權限的統一校驗,安全性驗證等功能。
多個 GlobalFilter 可以通過 @Order 或者 getOrder() 方法指定執行順序,order值越小,執行的優先級越高。
Gateway過濾器-自定義全局過濾器:
常見需求:用戶需要登錄了才放行。
1. 當客戶端第一次請求服務時,服務端對用戶進行信息認證(登錄)
2. 認證通過,將用戶信息進行加密形成token,返回給客戶端,作為登錄憑證
3. 以后每次請求,客戶端都攜帶認證的token
4. 服務端對token進行解密,判斷是否有效
// 自定義類實現 GlobalFilter , Ordered 接口,加上@Component注解即可;
@Component
public class MyCustomerGlobalFilter implements GlobalFilter ,Ordered {
// 參數1:是一個響應交互的契約。提供對HTTP請求和響應的訪問,並公開額外的服務器端處理相關屬性和特性,如請求屬性。
// 參數2:用於承載請求相關的屬性和請求體,Spring Cloud Gateway中底層使用Netty處理網絡請求。
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 判斷token,並驗證賬號密碼
String token = request.getQueryParams().getFirst("token"); // 獲取請求對象中參數名為 token 的值,獲取第一個參數的值。
if (StringUtils.isBlank(token)) {
System.out.println("鑒權失敗");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
if(token.equale("abc")){
// 放行
return chain.filter(exchange);
}
}
// 該方法用於聲明該過濾器執行的優先級
@Override
public int getOrder() { // 返回值越低,表示過濾器執行的優先級越高。
return 0;
}
}
4. 跨域問題的解決
方法一:配置類方式
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
方法二:配置文件方式
spring:
cloud:
gateway:
globalcors: # 全局的跨域處理
add-to-simple-url-handler-mapping: true # 解決options請求被攔截問題
corsConfigurations:
'[/**]':
allowedOrigins: # 允許哪些網站的跨域請求 # 需要 注意的是在springboot2.4之前的版本是使用allowed-origins: "*",在springboot2.4之后的版本是 allowed-origin-patterns: "*"。
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允許的跨域ajax的請求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允許在請求中攜帶的頭信息
allowCredentials: true # 是否允許攜帶cookie
maxAge: 360000 # 這次跨域檢測的有效期
5. Nacos+Spring Cloud Gateway動態路由配置
將網關的一系列配置寫到項目的配置文件中,一旦路由策略發生改變必須要重啟項目,這樣維護成本很高,特別是服務網關作為系統的中心點,一旦重啟出現問題,影響面將是十分巨大的。
因此,我們將網關的配置存放到配置中心中,這樣由配置中心統一管理,一旦路由發生改變,只需要在配置中心修改即可,降低風險且實時失效。
https://www.cnblogs.com/jian0110/p/12862569.html
6. 自定義全局異常處理器
來源:https://blog.csdn.net/a745233700/article/details/122917167
一旦路由的微服務下線或者失聯了,Spring Cloud Gateway直接返回了一個錯誤頁面,如下圖:
顯然這種異常信息不友好,前后端分離架構中必須定制返回的異常信息。傳統的Spring Boot 服務中都是使用 @ControllerAdvice 來包裝全局異常處理的,但是由於服務下線,請求並沒有到達。
因此必須在網關中也要定制一層全局異常處理,這樣才能更加友好的和客戶端交互。
pring Cloud Gateway提供了多種全局處理的方式,今天只介紹其中一種方式,實現還算比較優雅:
直接創建一個類 GlobalErrorExceptionHandler,實現 ErrorWebExceptionHandler,重寫其中的 handle 方法,代碼如下:
/**
* 用於網關的全局異常處理
* @Order(-1):優先級一定要比ResponseStatusExceptionHandler低
*/
@Slf4j
@Order(-1)
@Component
@RequiredArgsConstructor
public class GlobalErrorExceptionHandler implements ErrorWebExceptionHandler {
private final ObjectMapper objectMapper;
@SuppressWarnings({"rawtypes", "unchecked", "NullableProblems"})
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse response = exchange.getResponse();
if (response.isCommitted()) {
return Mono.error(ex);
}
// JOSN格式返回
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
if (ex instanceof ResponseStatusException) {
response.setStatusCode(((ResponseStatusException) ex).getStatus());
}
return response.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory();
try {
//todo 返回響應結果,根據業務需求,自己定制
CommonResponse resultMsg = new CommonResponse("500",ex.getMessage(),null);
return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMsg));
}
catch (JsonProcessingException e) {
log.error("Error writing response", ex);
return bufferFactory.wrap(new byte[0]);
}
}));
}
}
6. gateway整合sentinel
https://www.bilibili.com/video/BV1pF41147Aa?p=60&vd_source=61b6fb4e547748656e36b17ee95125fb
https://blog.csdn.net/a745233700/article/details/122917160