Рубрики
Без рубрики

Фабрики предикатов маршрутизации Spring Cloud Gateway

Узнайте, как добавить пользовательские фабрики предикатов в Spring Cloud Gateway и использовать их для определения маршрутов с использованием произвольной логики.

Автор оригинала: Philippe Sevestre.

1. введение

В предыдущей статье мы рассмотрели, что такое шлюз Spring Cloud и как использовать встроенные предикаты для реализации основных правил маршрутизации. Однако иногда этих встроенных предикатов может быть недостаточно. Например, по какой-то причине наша логика маршрутизации может потребовать поиска в базе данных.

В этих случаях Spring Cloud Gateway позволяет нам определять пользовательские предикаты. После определения мы можем использовать их как любой другой предикат, то есть мы можем определять маршруты с помощью fluent API и/или DSL.

2. Анатомия предиката

В двух словах, предикат в Spring Cloud Gateway-это объект, который проверяет, соответствует ли данный запрос заданному условию. Для каждого маршрута мы можем определить один или несколько предикатов, которые, если они будут удовлетворены, будут принимать запросы для настроенного бэкенда после применения любых фильтров.

Прежде чем написать наш предикат, давайте взглянем на исходный код существующего предиката или, точнее, на код существующего PredicateFactory. Как уже следует из названия, Spring Cloud Gateway использует популярный шаблон Factory Method в качестве механизма для поддержки создания экземпляров Предиката расширяемым способом.

Мы можем выбрать любую из встроенных фабрик предикатов, доступных в пакете org.springframework.cloud.gateway.handler.predicate модуля spring-cloud-gateway-core . Мы можем легко определить существующие, так как все их имена заканчиваются на Route PredicateFactory . Заголовок маршрутизатора PredicateFactory является хорошим примером:

public class HeaderRoutePredicateFactory extends 
  AbstractRoutePredicateFactory {

    // ... setup code omitted
    @Override
    public Predicate apply(Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange exchange) {
                // ... predicate logic omitted
            }
        };
    }

    @Validated
    public static class Config {
        public Config(boolean isGolden, String customerIdCookie ) {
          // ... constructor details omitted
        }
        // ...getters/setters omitted
    }
}

Есть несколько ключевых моментов, которые мы можем наблюдать при реализации:

  • Он расширяет Абстрактный Route PredicateFactory , который, в свою очередь, реализует интерфейс Route PredicateFactory , используемый шлюзом
  • Метод apply возвращает экземпляр фактического Предиката – a GatewayPredicate в этом случае
  • Предикат определяет внутренний класс Config , который используется для хранения статических параметров конфигурации, используемых логикой тестирования

Если мы посмотрим на другие доступные PredicateFactory, мы увидим, что основной шаблон в основном тот же:

  1. Определите класс Config для хранения параметров конфигурации
  2. Расширьте Abstract Route PredicateFactory , используя класс конфигурации в качестве параметра шаблона
  3. Переопределите метод apply , возвращая предикат , реализующий желаемую логику тестирования

3. Реализация пользовательской фабрики предикатов

Для нашей реализации предположим следующий сценарий: для данного API вызов мы должны выбрать один из двух возможных бэкэндов. “Золотые” клиенты, которые являются нашими самыми ценными клиентами, должны быть перенаправлены на мощный сервер с доступом к большему объему памяти, большему количеству процессоров и быстрым дискам. Не золотые клиенты переходят на менее мощный сервер, что приводит к более медленному времени отклика.

Чтобы определить, исходит ли запрос от золотого клиента, нам нужно будет вызвать службу, которая принимает CustomerID , связанный с запросом, и возвращает его статус. Что касается CustomerID , в нашем простом сценарии мы предположим, что он доступен в файле cookie.

Со всей этой информацией мы теперь можем написать наш пользовательский предикат. Мы сохраним существующее соглашение об именовании и назовем наш класс Golden Customer Route PredicateFactory :

public class GoldenCustomerRoutePredicateFactory extends 
  AbstractRoutePredicateFactory {

    private final GoldenCustomerService goldenCustomerService;
    
    // ... constructor omitted

    @Override
    public Predicate apply(Config config) {        
        return (ServerWebExchange t) -> {
            List cookies = t.getRequest()
              .getCookies()
              .get(config.getCustomerIdCookie());
              
            boolean isGolden; 
            if ( cookies == null || cookies.isEmpty()) {
                isGolden = false;
            } else {                
                String customerId = cookies.get(0).getValue();                
                isGolden = goldenCustomerService.isGoldenCustomer(customerId);
            }              
            return config.isGolden() ? isGolden : !isGolden;           
        };        
    }
    
    @Validated
    public static class Config {        
        boolean isGolden = true;        
        @NotEmpty
        String customerIdCookie = "customerId";
        // ...constructors and mutators omitted   
    }    
}

Как мы видим, реализация довольно проста. Ваш метод apply возвращает лямбду, которая реализует требуемую логику, используя переданный ему серверный веб-обмен|/. Во-первых, он проверяет наличие CustomerID cookie. Если он не может его найти, то это нормальный клиент. В противном случае мы используем значение cookie для вызова метода is Golden Customer service.

Затем мы объединяем тип клиента с настроенным параметром is//для определения возвращаемого значения. Это позволяет нам использовать один и тот же предикат для создания обоих маршрутов, описанных ранее, просто изменив значение параметра isGolden //.

4. Регистрация фабрики пользовательских предикатов

После того, как мы закодировали наш пользовательский predicatefactory, нам нужен способ сделать Spring Cloud Gateway осведомленным о том, если. Поскольку мы используем Spring, это делается обычным способом: мы объявляем компонент типа Golden Customer Route PredicateFactory .

Поскольку ваш тип реализует Route PredicateFactory through является базовым классом, он будет выбран Spring во время инициализации контекста и доступен для Spring Cloud Gateway.

Здесь мы создадим наш боб, используя класс @Configuration :

@Configuration
public class CustomPredicatesConfig {
    @Bean
    public GoldenCustomerRoutePredicateFactory goldenCustomer(
      GoldenCustomerService goldenCustomerService) {
        return new GoldenCustomerRoutePredicateFactory(goldenCustomerService);
    }
}

Мы предполагаем, что здесь у нас есть подходящая Золотая реализация обслуживания клиентов , доступная в контексте весны. В нашем случае у нас есть просто фиктивная реализация, которая сравнивает значение CustomerID с фиксированным значением — не реалистично, но полезно для демонстрационных целей.

5. Использование пользовательского предиката

Теперь, когда у нас есть предикат “Золотой клиент”, реализованный и доступный для Spring Cloud Gateway, мы можем начать использовать его для определения маршрутов. Сначала мы будем использовать fluent API для определения маршрута, а затем сделаем это декларативно, используя YAML.

5.1. Определение маршрута с помощью Fluent API

Fluent API – популярный выбор дизайна, когда нам приходится программно создавать сложные объекты. В нашем случае мы определяем маршруты в @Bean , который создает объект Route Locator с помощью Route Locator Builder и нашей пользовательской фабрики предикатов:

@Bean
public RouteLocator routes(RouteLocatorBuilder builder, GoldenCustomerRoutePredicateFactory gf ) {
    return builder.routes()
      .route("golden_route", r -> r.path("/api/**")
        .uri("https://fastserver")
        .predicate(gf.apply(new Config(true, "customerId"))))
      .route("common_route", r -> r.path("/api/**")
        .uri("https://slowserver")
        .predicate(gf.apply(new Config(false, "customerId"))))                
      .build();
}

Обратите внимание, как мы использовали две различные конфигурации Config в каждом маршруте. В первом случае первым аргументом является true , поэтому предикат также оценивается как true , когда у нас есть запрос от золотого клиента. Что касается второго маршрута, мы передаем false в конструкторе, поэтому наш предикат вернет true для не золотых клиентов.

5.2. Определение маршрута в YAML

Мы можем достичь того же результата, что и раньше, декларативным способом, используя свойства или файлы YAML. Здесь мы будем использовать YAML, так как его немного легче читать:

spring:
  cloud:
    gateway:
      routes:
      - id: golden_route
        uri: https://fastserver
        predicates:
        - Path=/api/**
        - GoldenCustomer=true
      - id: common_route
        uri: https://slowserver
        predicates:
        - Path=/api/**
        - name: GoldenCustomer
          args:
            golden: false
            customerIdCookie: customerId

Здесь мы определили те же маршруты, что и раньше, используя два доступных параметра для определения предикатов. Первый, golden_route , использует компактное представление,которое принимает форму Predicate=[param [, param]+] . Предикат здесь имя предиката, которое автоматически выводится из имени заводского класса путем удаления суффикса RoutePredicateFactory . После знака “=” у нас есть параметры, используемые для заполнения связанного экземпляра Config .

Этот компактный синтаксис хорош, когда наш предикат требует только простых значений, но это может быть не всегда так. Для этих сценариев мы можем использовать длинный формат, изображенный на втором маршруте. В этом случае мы предоставляем объект с двумя свойствами: name и args . name содержит имя предиката, а args используется для заполнения экземпляра Config . Поскольку на этот раз args является объектом, наша конфигурация может быть настолько сложной, насколько это необходимо.

6. Тестирование

Теперь давайте проверим, все ли работает так, как ожидалось, используя curl для тестирования нашего шлюза. Для этих тестов мы настроили наши маршруты точно так же, как показано ранее, но мы будем использовать общедоступные httpbin.org сервис в качестве нашего фиктивного бэкенда. Это довольно полезный сервис, который мы можем использовать, чтобы быстро проверить, работают ли наши правила должным образом, доступный как онлайн, так и в виде образа docker, который мы можем использовать локально.

Наша тестовая конфигурация также включает стандартный addRequestHeader фильтр. Мы используем его для добавления пользовательского заголовка Goldencustomer в запрос со значением, соответствующим результату предиката. Мы также добавляем фильтр stripPrefix , так как мы хотим удалить/ api из URI запроса перед вызовом серверной части.

Во-первых, давайте протестируем сценарий “общий клиент”. Когда наш шлюз запущен и работает, мы используем curl для вызова httpbin ‘s headers API, который будет просто повторять все полученные заголовки:

$ curl http://localhost:8080/api/headers
{
  "headers": {
    "Accept": "*/*",
    "Forwarded": "proto=http;host=\"localhost:8080\";for=\"127.0.0.1:51547\"",
    "Goldencustomer": "false",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.55.1",
    "X-Forwarded-Host": "localhost:8080",
    "X-Forwarded-Prefix": "/api"
  }
}

Как и ожидалось, мы видим, что заголовок Golden customer был отправлен со значением false . Давайте попробуем сейчас с “Золотым” клиентом:

$ curl -b customerId=baeldung http://localhost:8080/api/headers
{
  "headers": {
    "Accept": "*/*",
    "Cookie": "customerId=baeldung",
    "Forwarded": "proto=http;host=\"localhost:8080\";for=\"127.0.0.1:51651\"",
    "Goldencustomer": "true",
    "Host": "httpbin.org",
    "User-Agent": "curl/7.55.1",
    "X-Forwarded-Host": "localhost:8080",
    "X-Forwarded-Prefix": "/api"
  }
}

На этот раз Goldencustomer является true , поскольку мы отправили CustomerID cookie со значением , которое наш фиктивный сервис распознает как действительное для золотого клиента.

7. Заключение

В этой статье мы рассмотрели, как добавить пользовательские фабрики предикатов в Spring Cloud Gateway и использовать их для определения маршрутов с использованием произвольной логики.

Как обычно, весь код доступен на GitHub .