(六)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.html

必需要補 Spring WebFlux 和 Project Reactor 的技術java

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.react

Netty 也要補web

Spring Cloud Gateway功能:

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

初體驗

依賴:正則表達式

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

<!-- 網關依賴 -->
<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):spring

  • 若是包含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/

啓動類:bootstrap

@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請求的任何內容,例如標頭或參數。cookie

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

一、請求時間匹配

  • 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以前的任何請求相匹配

二、Cookie匹配

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

三、Header匹配

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

四、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

五、Method匹配

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

六、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

七、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將匹配

八、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.****

示例:

一、時間匹配

@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();
}

二、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();
}

三、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

過濾器類型

一、按執行順序分

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

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

二、按做用範圍分

global filter:全部路由

gateway filter:指定的路由

GatewayFilter

一、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

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

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

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

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

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

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

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

六、PrefixPathGatewayFilterFactory:將爲/mypath全部匹配請求的路徑添加前綴

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

八、RequestRateLimiterGatewayFilterFactory:RateLimiter實現來肯定是否容許當前請求繼續。若是不是,HTTP 429 - Too Many Requests則返回(默認狀況下)狀態。

九、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

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

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

十二、...

Global Filter

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

一、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,而後應用相同的規則。

二、...

DiscoveryClient Route Definition Locator

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

一、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).

二、Configuring Predicates and Filters For DiscoveryClient Routes

三、自定義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}:建立或刪除特定路由

一、初始化路由

不管是yml仍是代碼,這些配置最終都是被封裝到RouteDefinition對象中。

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

全部路由信息在系統啓動時就被加載裝配好了,並存到了內存裏

涉及到的相關bean

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

    Flux<RouteDefinition> getRouteDefinitions();
  
}

二、實現本身的加載邏輯,這裏從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:查看當前路由信息

三、也可使用http請求實現,這裏就不寫了。

相關文章
相關標籤/搜索