(六)springcloud 服務網關-Spring Cloud Gateway


Spring Cloud Gateway is built upon Spring Boot 2.0, Spring WebFlux, and Project Reactor. As a consequence many of the familiar synchronous libraries (Spring Data and Spring Security, for example) and patterns you may not apply when using Spring Cloud Gateway. If you are unfamiliar with these projects we suggest you begin by reading their documentation to familiarize yourself with some of the new concepts before working with Spring Cloud Gateway.

必須要補 Spring WebFlux 和 Project 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 built as a WAR.

Netty 也要補

Spring Cloud Gateway功能:

  • 基於Spring Framework 5,Project Reactor和Spring Boot 2.0構建
  • 能夠匹配任何請求屬性上的路由。
  • 謂詞和過濾器特定於路線。
  • Hystrix斷路器集成。
  • Spring Cloud DiscoveryClient集成
  • 易於編寫謂詞和過濾器
  • 請求率限制
  • 路徑重寫

初體驗

依賴:

之前學的東西都直接用進來,去服務發現上注冊,從集中配置上拉配置文件

<!-- 網關依賴 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

配置(bootstrap.yml):

  • 如果包含starter,但不想啟用網關功能:spring.cloud.gateway.enabled=false(默認為true)
server:
  port: 9001

spring:
  application:
    name: gateway-server
  profiles:
    active: dev
  cloud:
    config:
      label: master
      profile: ${spring.profiles.active}
      discovery:
        service-id: config-server
        enabled: true

eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:8761/eureka/

啟動類:

@SpringCloudApplication
@RestController
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }

    @Bean
    public RouteLocator myRoutes(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(p -> p
                        .path("/get")
                        .filters(f -> f.addRequestHeader("Hello", "World"))
                        .uri("http://localhost:4001/config/param"))
                .build();
    }
}

概念

Route:路由網關的基本構建塊。它由ID,目標URI,謂詞集合和過濾器集合定義。如果聚合謂詞為真,則匹配路由

Predicate:這是一個Java 8函數斷言。輸入類型是Spring Framework ServerWebExchange。這允許開發人員匹配來自HTTP請求的任何內容,例如標頭或參數。

Filters:這些是使用特定工廠構建的Spring Framework GatewayFilter實例。這里,可以在發送下游請求之前或之后修改請求和響應。

工作流程:

客戶端向Spring Cloud Gateway發出請求。如果網關處理程序映射確定請求與路由匹配,則將其發送到網關Web處理程序。此處理程序運行通過特定於請求的過濾器鏈發送請求。濾波器被虛線划分的原因是濾波器可以在發送代理請求之前或之后執行邏輯。執行所有“預”過濾器邏輯,然后進行代理請求。在發出代理請求之后,執行“post”過濾器邏輯。

在沒有端口的路由中定義的URI將分別為HTTP和HTTPS URI獲取默認端口設置為80和443。

Spring Cloud Gateway將路由作為Spring WebFlux HandlerMapping基礎結構的一部分進行匹配。Spring Cloud Gateway包含許多內置的Route Predicate工廠。所有這些 Predicate 都匹配HTTP請求的不同屬性。多路線謂詞工廠可以組合並通過邏輯組合and

Predicate

Predicate來自於java8的接口。Predicate 接受一個輸入參數,返回一個布爾值結果。該接口包含多種默認方法來將Predicate組合成其他復雜的邏輯(比如:與,或,非)。可以用於接口請求參數校驗、判斷新老數據是否有變化需要進行更新操作。add–與、or–或、negate–非。

配置:

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]

內置的Predicate

1、請求時間匹配

  • After Route Predicate Factory
    • - After=2017-01-20T17:42:47.789-07:00[America/Denver]
    • 與2017年1月20日17:42 Mountain Time(Denver)之后的所有請求相匹配
  • Before Route Predicate Factory
    • - Before=2017-01-20T17:42:47.789-07:00[America/Denver]
    • 與2017年1月20日17:42 Mountain Time(Denver)之前的所有請求相匹配
  • After Route Predicate Factory
    • - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
    • 與2017年1月20日17:42之后和2017年1月21日17:42之前的任何請求相匹配

2、Cookie匹配

  • Cookie Route Predicate Factory
    • - Cookie=chocolate, ch.p
    • 匹配請求具有名為chocolatewho的值與ch.p正則表達式匹配的cookie

3、Header匹配

  • Header Route Predicate Factory
    • - Header=X-Request-Id, \d+
    • 請求頭X-Request-Id其值與\d+正則表達式匹配(具有一個或多個數字的值),則此路由匹配

4、Host匹配

  • Host Route Predicate Factory
    • - Host=**.somehost.org,**.anotherhost.org
    • This route would match if the request has a Host header has the value www.somehost.org or beta.somehost.org or www.anotherhost.org.
    • - {sub}.myhost.org
    • 將URI模板變量(sub如上例中定義的)提取為名稱和值的映射,並將其放在ServerWebExchange.getAttributes()帶有定義的鍵的位置ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE

5、Method匹配

  • Method Route Predicate Factory
    • - Method=GET
    • 匹配請求方法為GET的請求

6、Path匹配

  • Path Route Predicate Factory
    • - Path=/foo/{segment},/bar/{segment}
    • This route would match if the request path was, for example: /foo/1 or /foo/bar or /bar/baz.
    • 將URI模板變量(segment如上例中定義的)提取為名稱和值的映射,並將其放在ServerWebExchange.getAttributes()帶有定義的鍵的位置ServerWebExchangeUtils.URI_TEMPLATE_VARIABLES_ATTRIBUTE

7、QueryParam匹配

  • Query Route Predicate Factory
    • - Query=baz
    • This route would match if the request contained a baz query parameter.
    • - Query=foo, ba.
    • 請求包含foo其值與ba.regexp 匹配的查詢參數,則此路由將匹配,因此bar並且baz將匹配

8、Remote匹配

  • RemoteAddr Route Predicate Factory
    • - RemoteAddr=192.168.1.1/24
    • This route would match if the remote address of the request was, for example, 192.168.1.10.****

示例:

1、時間匹配

@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
  ZonedDateTime zonedDateTime = ZonedDateTime.now().plusDays(-1);
  String url = "http://localhost:4001/config/param";
  return builder.routes()
    .route("before-predicate",
           predicateSpec -> predicateSpec
           .before(zonedDateTime)
           .uri(url))
    .build();
}

2、Cookie匹配

@Bean
public RouteLocator routers(RouteLocatorBuilder builder) {
  String url = "http://localhost:4001/config/param";
  return builder.routes()
    .route(predicateSpec -> predicateSpec
           .cookie("cookieName", "^\\d+$")
           .uri(url))
    .build();
}

3、Host匹配

@Bean
public RouteLocator routers(RouteLocatorBuilder builder) {
  String url = "http://localhost:4001/config/param";
  return builder.routes()
    .route(predicateSpec -> predicateSpec.host("localhost").uri(url))
    .build();
}

4 ...

Filter

過濾器類型

1、按執行順序分

pre過濾器:轉發之前執行。參數校驗、權限校驗、流量監控、日志輸出、協議轉換等

post過濾器:轉發之后執行。響應內容、響應頭的修改,日志的輸出,流量監控等

2、按作用范圍分

global filter:所有路由

gateway filter:指定的路由

GatewayFilter

1、AddRequestHeaderGatewayFilterFactory:在匹配的請求上添加請求頭,如示例中,添加請求頭:X-Request-Foo;值:bar

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        filters:
        # 添加請求頭 X-Request-Foo:Bar
        - AddRequestParameter=X-Request-Foo, Bar

2、AddRequestParameterGatewayFilterFactory:在匹配的請求上添加請求參數

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        filters:
        # 添加請求參數 foo=bar; 在query串中添加
        - AddRequestParameter=foo, bar

3、AddResponseHeaderGatewayFilterFactory:在匹配的請求上添加響應頭

spring:
  cloud:
    gateway:
      routes:
      - id: add_response_header_route
        uri: https://example.org
        filters:
        # 添加響應頭 X-Response-Foo:Bar
        - AddResponseHeader=X-Response-Foo, Bar

4、HystrixGatewayFilterFactory:將斷路器引入網關路由

spring:
  cloud:
    gateway:
      routes:
      - id: hystrix_route
        uri: https://example.org
        filters:
        - Hystrix=myCommandName

5、HystrixGatewayFilterFactory:將斷路器引入網關路由,保護您的服務免受級聯故障的影響,並允許您在下游故障時提供回退響應。

6、PrefixPathGatewayFilterFactory:將為/mypath所有匹配請求的路徑添加前綴

7、PreserveHostHeaderGatewayFilterFactory:設置路由過濾器將檢查的請求屬性,以確定是否應發送原始主機頭,而不是http客戶端確定的主機頭。

8、RequestRateLimiterGatewayFilterFactory:RateLimiter實現來確定是否允許當前請求繼續。如果不是,HTTP 429 - Too Many Requests則返回(默認情況下)狀態。

9、RedirectToGatewayFilterFactory: takes a status and a url parameter. The status should be a 300 series redirect http code, such as 301. The url should be a valid url. This will be the value of the Location header

10、RemoveRequestHeaderGatewayFilterFactory:接受一個name參數。它是要刪除的標頭的名稱

11、RemoveResponseHeaderGatewayFilterFactory:接受一個name參數。它是要刪除的標頭的名稱

12、...

Global Filter

所有實例GlobalFilter和所有路由特定實例添加GatewayFilter到過濾器鏈中。這個組合的過濾器鏈按org.springframework.core.Ordered接口排序,可以通過實現getOrder()方法或使用@Order注釋來設置。

1、LoadBalancerClientFilter:交換屬性查找一個URI ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR。如果url有一個lb方案(即lb://myservice),它將使用Spring Cloud LoadBalancerClient將名稱(myservice在前面的示例中)解析為實際的主機和端口,並替換相同屬性中的URI。未修改的原始URL將附加到ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR屬性中的列表中。過濾器還將查看ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR屬性以查看它是否等於lb,然后應用相同的規則。

2、...

DiscoveryClient Route Definition Locator

The Gateway can be configured to create routes based on services registered with a DiscoveryClient compatible service registry.

1、set spring.cloud.gateway.discovery.locator.enabled=true and make sure a DiscoveryClient implementation is on the classpath and enabled (such as Netflix Eureka, Consul or Zookeeper).

2、Configuring Predicates and Filters For DiscoveryClient Routes

3、自定義DiscoveryClient路由使用的謂詞和/或過濾器,可以通過設置spring.cloud.gateway.discovery.locator.predicates[x]和來自定義spring.cloud.gateway.discovery.locator.filters[y]

spring.cloud.gateway.discovery.locator.predicates[0].name:Path
spring.cloud.gateway.discovery.locator.predicates[0].args [pattern]:“'/'+ serviceId +'/ **'”
spring.cloud.gateway.discovery.locator.predicates[1].name:Host
spring.cloud.gateway.discovery.locator.predicates[1].args [pattern]:“'**。foo.com'”
spring.cloud.gateway.discovery.locator.filters[0].name:Hystrix
spring.cloud.gateway.discovery.locator.filters[0].args [name]:serviceId
spring.cloud.gateway.discovery.locator.filters[1].name:RewritePath
spring.cloud.gateway.discovery.locator.filters[1].args [regexp]:“'/'+ serviceId +'/(?<remaining>.*)'”
spring.cloud.gateway.discovery.locator.filters[1].args [replacement]:“'/ $ {remaining}'”

動態路由

監控端點:

GET /actuator/gateway/globalfilters:檢索應用於所有路由的全局過濾器

GET /actuator/gateway/routefilters:查看路由過濾器

POST /actuator/gateway/refresh:刷新路由緩存

GET /actuator/gateway/routes:檢索網關中定義的路由

GET /actuator/gateway/routes/{id}:檢索特定路徑的路由信息

POST/DELETE /gateway/routes/{id_route_to_create}:創建或刪除特定路由

1、初始化路由

無論是yml還是代碼,這些配置最終都是被封裝到RouteDefinition對象中。

一個RouteDefinition有個唯一的ID,如果不指定,就默認是UUID,多個RouteDefinition組成了gateway的路由系統。

所有路由信息在系統啟動時就被加載裝配好了,並存到了內存里

涉及到的相關bean

RouteDefinitionRepository: 從存儲器中加載路由
public interface RouteDefinitionLocator {

	Flux<RouteDefinition> getRouteDefinitions();
  
}

2、實現自己的加載邏輯,這里從redis中加載

添加依賴:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>

創建RedisRouteDefinitionRepository,實現RouteDefinitionRepository

@Component
@Slf4j
@AllArgsConstructor
@SuppressWarnings("all")
public class RedisRouteDefinitionRepository implements RouteDefinitionRepository {

    private static final String ROUTE_KEY = "gateway_route_key";

    private final RedisTemplate redisTemplate;

    @Override
    public Flux<RouteDefinition> getRouteDefinitions() {
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinition.class));
        List<RouteDefinition> values = redisTemplate.opsForHash().values(ROUTE_KEY);
        log.debug("redis 中路由定義條數: {}, {}", values.size(), values);
        return Flux.fromIterable(values);
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> route) {
        return route.flatMap(r -> {
            log.info("保存路由信息{}", r);
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.opsForHash().put(ROUTE_KEY, r.getId(), r);
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        routeId.subscribe(id -> {
            log.info("刪除路由信息{}", id);
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.opsForHash().delete(ROUTE_KEY, id);
        });
        return Mono.empty();
    }
}

路由的定義可以任何來源初始化到redis,這里只模擬

@PostConstruct
public void main() {
  RouteDefinition definition = new RouteDefinition();
  definition.setId("id");
  URI uri = UriComponentsBuilder.fromHttpUrl("http://localhost:4001/config/param").build().toUri();
  definition.setUri(uri);

  //定義第一個斷言
  PredicateDefinition predicate = new PredicateDefinition();
  predicate.setName("Path");

  Map<String, String> predicateParams = new HashMap<>(8);
  predicateParams.put("pattern", "/get");
  predicate.setArgs(predicateParams);

  //定義Filter
  FilterDefinition filter = new FilterDefinition();
  filter.setName("AddRequestHeader");
  Map<String, String> filterParams = new HashMap<>(8);
  //該_genkey_前綴是固定的,見org.springframework.cloud.gateway.support.NameUtils類
  filterParams.put("_genkey_0", "header");
  filterParams.put("_genkey_1", "addHeader");
  filter.setArgs(filterParams);

  FilterDefinition filter1 = new FilterDefinition();
  filter1.setName("AddRequestParameter");
  Map<String, String> filter1Params = new HashMap<>(8);
  filter1Params.put("_genkey_0", "param");
  filter1Params.put("_genkey_1", "addParam");
  filter1.setArgs(filter1Params);

  definition.setFilters(Arrays.asList(filter, filter1));
  definition.setPredicates(Arrays.asList(predicate));

  System.out.println("definition:" + JSONUtil.toJsonStr(definition));
  redisTemplate.setKeySerializer(new StringRedisSerializer());
  redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(RouteDefinition.class));
  redisTemplate.opsForHash().put(ROUTE_KEY, "router1", definition);
}

啟動網關,裝配RedisRouteDefinitionRepository后,spring調用main(),創建一條路由

GET /actuator/gateway/routes:查看當前路由信息

3、也可以使用http請求實現,這里就不寫了。


免責聲明!

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



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