首頁 > 軟體

淺析getway閘道器

2022-07-08 18:06:02

一、閘道器基本概念

1、API閘道器介紹

API閘道器出現的原因是微服務架構的出現,不同的微服務一般會有不同的網路地址,而外部使用者端可能需要呼叫多個服務的介面才能完成一個業務需求,如果讓使用者端直接與各個微服務通訊,會有以下的問題:

(1)使用者端會多次請求不同的微服務,增加了使用者端的複雜性。

(2)存在跨域請求,在一定場景下處理相對複雜。

(3)認證複雜,每個服務都需要獨立認證。

(4)難以重構,隨著專案的迭代,可能需要重新劃分微服務。例如,可能將多個服務合併成一個或者將一個服務拆分成多個。如果使用者端直接與微服務通訊,那麼重構將會很難實施。

(5)某些微服務可能使用了防火牆/瀏覽器不友好的協定,直接存取會有一定的困難。

以上這些問題可以藉助API閘道器解決。API閘道器是介於使用者端和伺服器端之間的中間層,所有的外部請求都會先經過API閘道器這一層。也就是說,API的實現方面更多的考慮業務邏輯,而安全、效能、監控可以交由API閘道器來做,這樣既提高業務靈活性又不缺安全性

(6)getway可以實現nginx的請求轉發和跨域(@CrossOrigin也可以實現跨域)。

負載均衡:把請求平均分配到多臺伺服器上。叢集部署,部署2臺service-edu服務,只有埠號不同,專案都一樣。

2、Spring Cloud Gateway

Spring cloud gateway是spring官方基於Spring 5.0、Spring Boot2.0和Project Reactor等技術開發的閘道器,Spring Cloud Gateway旨在為微服務架構提供簡單、有效和統一的API路由管理方式,Spring Cloud Gateway作為Spring Cloud生態系統中的閘道器,目標是替代Netflix Zuul,其不僅提供統一的路由方式,並且還基於Filer鏈的方式提供了閘道器基本的功能,例如:安全、監控/埋點、限流等。

閘道器和服務都在Nacos註冊,註冊之後通過Getway閘道器在存取相應的服務

3、Spring Cloud Gateway核心概念

閘道器提供API全託管服務,豐富的API管理功能,輔助企業管理大規模的API,以降低管理成本和安全風險,包括協定適配、協定轉發、安全策略、防刷、流量、監控紀錄檔等貢呢。一般來說閘道器對外暴露的URL或者介面資訊,我們統稱為路由資訊。如果研發過閘道器中介軟體或者使用過Zuul的人,會知道閘道器的核心是Filter以及Filter Chain(Filter責任鏈)。Sprig Cloud Gateway也具有路由和Filter的概念。下面介紹一下Spring Cloud Gateway中幾個重要的概念。

(1)路由。路由是閘道器最基礎的部分,路由資訊有一個ID、一個目的URL、一組斷言和一組Filter組成。如果斷言路由為真,則說明請求的URL和設定匹配

(2)斷言。Java8中的斷言函數。Spring Cloud Gateway中的斷言函數輸入型別是Spring5.0框架中的ServerWebExchange。Spring Cloud Gateway中的斷言函數允許開發者去定義匹配來自於http request中的任何資訊,比如請求頭和引數等,簡單來講就是一個匹配規則,如果匹配到就到Handler去處理。

(3)過濾器。一個標準的Spring webFilter。Spring cloud gateway中的filter分為兩種型別的Filter,分別是Gateway Filter和Global Filter。過濾器Filter將會對請求和響應進行修改處理,統一例外處理,統一跨域處理等。

如上圖所示,Spring cloud Gateway發出請求。然後再由Gateway Handler Mapping中找到與請求相匹配的路由,將其傳送到Gateway web handler。Handler再通過指定的過濾器鏈將請求傳送到我們實際的服務執行業務邏輯,然後返回。

二、getway閘道器例子

1、在infrastructure模組下建立api_gateway模組

2、在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>infrastructure</artifactId>
        <groupId>com.stu</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>api_gateway</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.stu</groupId>
            <artifactId>common-utils</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <!--gson-->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
        </dependency>

        <!--服務呼叫-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

</project>

3、編寫application.properties組態檔

# 伺服器埠
server.port=8222
# 服務名
spring.application.name=service-gateway
# nacos服務地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
#使用服務發現路由,通過openfeign找到服務(nginx是通過組態檔的路徑匹配發現服務)
spring.cloud.gateway.discovery.locator.enabled=true
#服務路由名小寫
#spring.cloud.gateway.discovery.locator.lower-case-service-id=true
#設定service-edu服務
#設定路由id(id可以隨便命名,建議用服務名稱)
spring.cloud.gateway.routes[0].id=service-edu
#設定路由的uri
spring.cloud.gateway.routes[0].uri=lb://service-edu
#設定路由斷言,代理servicerId為auth-service的/auth/路徑
spring.cloud.gateway.routes[0].predicates= Path=/eduservice/**
#設定service-edu服務
spring.cloud.gateway.routes[1].id=service-msm
## 服務名
#spring.application.name=service-msm
spring.cloud.gateway.routes[1].uri=lb://service-msm
spring.cloud.gateway.routes[1].predicates= Path=/edumsm/**

yml檔案:

server:
  port: 8222
spring:
  application:
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
      - id: SERVICE-ACL
        uri: lb://SERVICE-ACL
        predicates:
        - Path=/*/acl/** # 路徑匹配
      - id: SERVICE-EDU
        uri: lb://SERVICE-EDU
        predicates:
        - Path=/eduservice/** # 路徑匹配
      - id: SERVICE-UCENTER
        uri: lb://SERVICE-UCENTER
        predicates:
        - Path=/ucenter/** # 路徑匹配
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

4、編寫啟動類

package com.stu.getway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient//Nacos註冊
public class ApiGetwayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGetwayApplication.class,args);
    }
}

5、測試

通過nginx設定存取(8001)

通過getway設定存取(8222)

三、閘道器相關設定

1、閘道器解決跨域問題

(1)建立設定類

package com.stu.getway.config;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
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.codec.ServerCodecConfigurer;
import org.springframework.http.codec.support.DefaultServerCodecConfigurer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Mono;
/**
 * <p>
 * 處理跨域
 * </p>
 *
 * @author qy
 * @since 2019-11-21
 */
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
/**
 * description:
 *
 * @author wangpeng
 * @date 2019/01/02
 */
@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);
    }
}

2、全域性Filter,統一處理會員登入與外部不允許存取的服務

package com.stu.getway.filter;
import com.google.gson.JsonObject;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
 * <p>
 * 全域性Filter,統一處理會員登入與外部不允許存取的服務
 * </p>
 *
 * @author qy
 * @since 2019-11-21
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    private AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();
        //校驗使用者必須登入
        if(antPathMatcher.match("/api/**/auth/**", path)) {
            List<String> tokenList = request.getHeaders().get("token");
            if(null == tokenList) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response);
            } else {
//                Boolean isCheck = JwtUtils.checkToken(tokenList.get(0));
//                if(!isCheck) {
                    ServerHttpResponse response = exchange.getResponse();
                    return out(response);
//                }
            }
        }
        //內部服務介面,不允許外部存取
        if(antPathMatcher.match("/**/inner/**", path)) {
            ServerHttpResponse response = exchange.getResponse();
            return out(response);
        }
        return chain.filter(exchange);
    }
    @Override
    public int getOrder() {
        return 0;
    }
    private Mono<Void> out(ServerHttpResponse response) {
        JsonObject message = new JsonObject();
        message.addProperty("success", false);
        message.addProperty("code", 28004);
        message.addProperty("data", "鑑權失敗");
        byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        //response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //指定編碼,否則在瀏覽器中會中文亂碼
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
}

3、自定義例外處理

服務閘道器呼叫服務時可能會有一些異常或服務不可用,它返回錯誤資訊不友好,需要我們覆蓋處理

ErrorHandlerConfig

package com.stu.getway.handler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;
import java.util.Collections;
import java.util.List;
/**
 * 覆蓋預設的例外處理
 *
 * @author yinjihuan
 *
 */
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfig {
    private final ServerProperties serverProperties;
    private final ApplicationContext applicationContext;
    private final ResourceProperties resourceProperties;
    private final List<ViewResolver> viewResolvers;
    private final ServerCodecConfigurer serverCodecConfigurer;
    public ErrorHandlerConfig(ServerProperties serverProperties,
                                     ResourceProperties resourceProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                        ServerCodecConfigurer serverCodecConfigurer,
                                     ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }
    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes,
                this.resourceProperties,
                this.serverProperties.getError(),
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

JsonExceptionHandler

package com.stu.getway.handler;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.*;
import java.util.HashMap;
import java.util.Map;
/**
 * 自定義例外處理
 *
 * <p>異常時用JSON代替HTML異常資訊<p>
 *
 * @author yinjihuan
 *
 */
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                                ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }
    /**
     * 獲取異常屬性
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> map = new HashMap<>();
        map.put("success", false);
        map.put("code", 20005);
        map.put("message", "閘道器失敗");
        map.put("data", null);
        return map;
    }
    /**
     * 指定響應處理方法為JSON處理的方法
     * @param errorAttributes
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }
    /**
     * 根據code獲取對應的HttpStatus
     * @param errorAttributes
     */
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return 200;
    }
}

到此這篇關於getway閘道器的文章就介紹到這了,更多相關getway閘道器內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com