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

Фабрики веб-фильтров Spring Cloud Gateway

Узнайте о встроенных фабриках веб-фильтров Spring Cloud Gateway и о том, как их использовать в расширенных сценариях

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

1. введение

Spring Cloud Gateway – это интеллектуальная прокси-служба, часто используемая в микросервисах. Он прозрачно централизует запросы в единой точке входа и направляет их в соответствующую службу. Одной из его наиболее интересных особенностей является концепция filters ( Web Filter или Gateway Filter ).

Веб-фильтр, вместе с Фабриками предикатов , включает в себя полный механизм маршрутизации. Spring Cloud Gateway предоставляет множество встроенных Веб-фильтров фабрик, которые позволяют взаимодействовать с HTTP-запросами до достижения проксируемой службы и HTTP-ответами до доставки результата клиенту . Также возможно реализовать пользовательские фильтры .

В этом уроке мы сосредоточимся на встроенных веб-фильтрах фабриках, включенных в проект, и на том, как их использовать в расширенных случаях использования.

2. Фабрики веб-фильтров

Фабрики Web Filter (или Gateway Filter ) позволяют изменять входящие HTTP-запросы и исходящие HTTP-ответы. В этом смысле он предлагает набор интересных функций для применения до и после взаимодействия с нижестоящими службами.

Архитектура фабрик веб-фильтров Spring Cloud Gateway

Сопоставление обработчиков управляет запросом клиента. Он проверяет, соответствует ли он какому-либо настроенному маршруту. Затем он отправляет запрос веб-обработчику для выполнения определенной цепочки фильтров для этого маршрута. Пунктирная линия разделяет логику между логикой до и после фильтрации. Фильтры доходов запускаются перед запросом прокси-сервера. Выходные фильтры вступают в действие, когда они получают ответ прокси-сервера. Фильтры предоставляют механизмы для изменения процесса между ними.

3. Внедрение Фабрик веб-Фильтров

Давайте рассмотрим наиболее важные Веб-фильтры фабрики, включенные в проект Spring Cloud Gateway. Есть два способа их реализации, используя YAML или Java DSL . Мы покажем примеры того, как реализовать и то, и другое.

3.1. HTTP-запрос

Встроенный Веб-фильтр фабрики позволяют взаимодействовать с заголовками и параметрами HTTP-запроса . Мы можем добавить (addRequestHeader), map |/(Заголовок запроса карты) , установить или заменить (setRequestHeader), и удалить |/(RemoveRequestHeader) значения заголовка и отправить их проксированной службе. Исходный заголовок хоста также может быть сохранен ( PreserveHostHeader ).

Таким же образом мы можем добавить (AddRequestParameter) и удалить (Удалить параметр запроса) параметры, которые будут обработаны нижестоящей службой. Давайте посмотрим, как это сделать:

- id: add_request_header_route
  uri: https://httpbin.org
  predicates:
  - Path=/get/**
  filters:
  - AddRequestHeader=My-Header-Good,Good
  - AddRequestHeader=My-Header-Remove,Remove
  - AddRequestParameter=var, good
  - AddRequestParameter=var2, remove
  - MapRequestHeader=My-Header-Good, My-Header-Bad
  - MapRequestHeader=My-Header-Set, My-Header-Bad
  - SetRequestHeader=My-Header-Set, Set 
  - RemoveRequestHeader=My-Header-Remove
  - RemoveRequestParameter=var2

Давайте проверим, все ли работает так, как ожидалось. Для этого мы будем использовать curl и общедоступный httpbin.org :

$ curl http://localhost:8080/get
{
  "args": {
    "var": "good"
  },
  "headers": {
    "Host": "localhost",
    "My-Header-Bad": "Good",
    "My-Header-Good": "Good",
    "My-Header-Set": "Set",
  },
  "origin": "127.0.0.1, 90.171.125.86",
  "url": "https://localhost:8080/get?var=good"
}

Мы можем видеть ответ curl как следствие настроенных фильтров запросов. Они добавляют My-Header-Good со значением Good и сопоставляют его содержимое с My-Header-Bad. Они удаляют My-Header-Remove и устанавливают новое значение в My-Header-Set . В разделах args и url мы видим новый параметр var . Кроме того, последний фильтр удаляет параметр var2 .

Кроме того, мы можем изменить тело запроса до достижения проксируемой службы . Этот фильтр можно настроить только с помощью нотации Java DSL. В приведенном ниже фрагменте просто в верхнем регистре указано содержимое тела ответа:

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
     return builder.routes()
       .route("modify_request_body", r -> r.path("/post/**")
         .filters(f -> f.modifyRequestBody(
           String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, 
           (exchange, s) -> Mono.just(new Hello(s.toUpperCase()))))
         .uri("https://httpbin.org"))
       .build();
}

Чтобы проверить фрагмент, давайте выполним curl с опцией -d , чтобы включить тело “Содержимое” :

$ curl -X POST "http://localhost:8080/post" -i -d "Content"
"data": "{\"message\":\"CONTENT\"}",
"json": {
    "message": "CONTENT"
}

Мы видим, что содержимое тела теперь в верхнем регистре CONTENT в результате фильтра.

3.2. HTTP-ответ

Аналогично, мы можем изменить заголовки ответов с помощью add ( AddResponseHeader ), set или replace ( SetResponseHeader ), remove ( RemoveResponseHeader ) и rewrite ( RewriteResponseHeader ). Другой функцией над ответом является dedupe ( Заголовок ответа Dedupe) для перезаписи стратегий и предотвращения их дублирования. Мы можем избавиться от специфичных для бэкенда деталей, касающихся версии, местоположения и хоста, используя другую встроенную фабрику ( RemoveLocationResponseHeader ).

Давайте рассмотрим полный пример:

- id: response_header_route
  uri: https://httpbin.org
  predicates:
  - Path=/header/post/**
  filters:
  - AddResponseHeader=My-Header-Good,Good
  - AddResponseHeader=My-Header-Set,Good
  - AddResponseHeader=My-Header-Rewrite, password=12345678
  - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
  - AddResponseHeader=My-Header-Remove,Remove
  - SetResponseHeader=My-Header-Set, Set
  - RemoveResponseHeader=My-Header-Remove
  - RewriteResponseHeader=My-Header-Rewrite, password=[^&]+, password=***
  - RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,

Давайте использовать curl для отображения заголовков ответов:

$ curl -X POST "http://localhost:8080/header/post" -s -o /dev/null -D -
HTTP/1.1 200 OK
My-Header-Good: Good
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
My-Header-Rewrite: password=***
My-Header-Set: Set

Аналогично HTTP-запросу, мы можем изменить тело ответа . В этом примере мы перезаписываем тело ответа PUT:

@Bean
public RouteLocator responseRoutes(RouteLocatorBuilder builder) {
    return builder.routes()
      .route("modify_response_body", r -> r.path("/put/**")
        .filters(f -> f.modifyResponseBody(
          String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE, 
          (exchange, s) -> Mono.just(new Hello("New Body"))))
        .uri("https://httpbin.org"))
      .build();
}

Давайте используем конечную точку PUT для проверки функциональности:

$ curl -X PUT "http://localhost:8080/put" -i -d "CONTENT"
{"message":"New Body"}

3.3. Путь

Одной из функций, предоставляемых встроенным веб-фильтром factories, является взаимодействие с путями, настроенными клиентом . Можно задать другой путь ( setPath ), перезаписать ( RewritePath ), добавить префикс ( Путь префикса ) и strip ( stripPrefix ), чтобы извлечь только его части. Помните, что фильтры выполняются в порядке, основанном на их позициях в файле YAML. Давайте посмотрим, как настроить маршруты:

- id: path_route
  uri: https://httpbin.org
  predicates:
  - Path=/new/post/**
  filters:
  - RewritePath=/new(?/?.*), $\{segment}
  - SetPath=/post

Оба фильтра удаляют вложенный путь /new до достижения проксированной службы. Давайте выполним curl:

$ curl -X POST "http://localhost:8080/new/post" -i
"X-Forwarded-Prefix": "/new"
"url": "https://localhost:8080/post"

Мы также могли бы использовать stripPrefix factory. С помощью stripPrefix=1, мы можем избавиться от первого подпутья при обращении в нижестоящую службу.

3.4. Связанные со статусом HTTP

Перенаправление на принимает два параметра: статус и URL. Статус должен представлять собой серию из 300 HTTP-кодов перенаправления, а URL-адрес должен быть действительным. setStatus принимает один статус параметра, который может быть HTTP-кодом или его строковым представлением. Давайте рассмотрим несколько примеров:

- id: redirect_route
  uri: https://httpbin.org
  predicates:
  - Path=/fake/post/**
  filters:
  - RedirectTo=302, https://httpbin.org
- id: status_route
  uri: https://httpbin.org
  predicates:
  - Path=/delete/**
  filters:
  - SetStatus=401

Первый фильтр действует поверх патча /fake/post , и клиент перенаправляется на https://httpbin.org со статусом HTTP 302 :

$ curl -X POST "http://localhost:8080/fake/post" -i
HTTP/1.1 302 Found
Location: https://httpbin.org

Второй фильтр определяет путь /delete , и устанавливается статус HTTP 401 :

$ curl -X DELETE "http://localhost:8080/delete" -i
HTTP/1.1 401 Unauthorized

3.5. Ограничение размера Запроса

Наконец, мы можем ограничить ограничение размера запроса ( Размер запроса ). Если размер запроса превышает лимит, шлюз отклоняет доступ к службе :

- id: size_route
  uri: https://httpbin.org
  predicates:
  - Path=/anything
  filters:
  - name: RequestSize
    args:
       maxSize: 5000000

4. Расширенные варианты использования

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

4.1. Автоматический выключатель

Spring Cloud Gateway имеет встроенный Веб-фильтр завод для Автоматического выключателя возможности . Фабрика допускает различные резервные стратегии и конфигурацию маршрута DSL Java. Давайте рассмотрим простой пример:

- id: circuitbreaker_route
  uri: https://httpbin.org
  predicates:
  - Path=/status/504
  filters:
  - name: CircuitBreaker
  args:
     name: myCircuitBreaker
     fallbackUri: forward:/anything
  - RewritePath=/status/504, /anything

Для конфигурации автоматического выключателя мы использовали Упругость 4 J , добавив spring-cloud-starter-circuit breaker-reactor-resilience4j зависимость:


    org.springframework.cloud
    spring-cloud-starter-circuitbreaker-reactor-resilience4j

Опять же, мы можем протестировать функциональность с помощью curl:

$ curl http://localhost:8080/status/504 
"url": "https://localhost:8080/anything"

4.2. Повторите попытку

Еще одна расширенная функция позволяет клиенту повторить попытку доступа, когда что-то происходит с проксированными службами . Для этого требуется несколько параметров, таких как количество повторных попыток , коды состояния HTTP ( статусы ) и методы , которые должны быть повторены, серии , исключения, и обратные интервалы ожидания после каждой повторной попытки. Давайте посмотрим на конфигурацию YAML:

- id: retry_test
  uri: https://httpbin.org
  predicates:
  - Path=/status/502
  filters:
  - name: Retry
    args:
       retries: 3
       statuses: BAD_GATEWAY
       methods: GET,POST
       backoff:
          firstBackoff: 10ms
          maxBackoff: 50ms
          factor: 2
          basedOnPreviousValue: false

Когда клиент достигает /status/502 (Плохой шлюз), фильтр повторяет попытку три раза, ожидая интервалов отката, настроенных после каждого выполнения. Давайте посмотрим, как это работает:

$ curl http://localhost:8080/status/502

В то же время нам нужно проверить журналы шлюза на сервере:

Mapping [Exchange: GET http://localhost:8080/status/502] to Route{id='retry_test', ...}
Handler is being applied: {uri=https://httpbin.org/status/502, method=GET}
Received last HTTP packet
Handler is being applied: {uri=https://httpbin.org/status/502, method=GET}
Received last HTTP packet
Handler is being applied: {uri=https://httpbin.org/status/502, method=GET}
Received last HTTP packet

Фильтр повторяет три раза с этим отступлением для методов GET и POST, когда шлюз получает статус 502.

4.3. Сохранение сеанса и защищенных заголовков

Безопасный заголовок фабрика добавляет заголовки безопасности HTTP в ответ . Аналогично, Save Session имеет особое значение при использовании с Spring Session и Spring Security :

filters: 
- SaveSession

Этот фильтр сохраняет состояние сеанса перед выполнением переадресованного вызова .

4.4. Ограничитель скорости Запросов

И последнее, но не менее важное: RequestRateLimiter factory определяет, может ли запрос быть продолжен . Если нет, он возвращает статус HTTP – кода 429- Слишком много запросов . Он использует различные параметры и разрешает указать ограничитель скорости .

RedisRateLimiter использует хорошо известную базу данных Redis для проверки количества токенов, которые может хранить ведро. Для этого требуется следующая зависимость:


    org.springframework.boot
    spring-boot-starter-data-redis-reactive
 

Следовательно, он также нуждается в конфигурации Spring Redis :

spring:
  redis:
    host: localhost
    port: 6379

Фильтр имеет несколько свойств. Первый аргумент, скорость пополнения, – это количество разрешенных запросов в секунду. Второй аргумент, емкость пакета, – это максимальное количество запросов за одну секунду. Третий параметр, запрошенные токены, – это сколько токенов стоит запрос. Давайте рассмотрим пример реализации:

- id: request_rate_limiter
  uri: https://httpbin.org
  predicates:
  - Path=/redis/get/**
  filters:
  - StripPrefix=1
  - name: RequestRateLimiter
    args:
       redis-rate-limiter.replenishRate: 10
       redis-rate-limiter.burstCapacity: 5

Давайте используем curl для проверки фильтра. Заранее не забудьте запустить экземпляр Redis , например, с помощью Docker :

$ curl "http://localhost:8080/redis/get" -i
HTTP/1.1 200 OK
X-RateLimit-Remaining: 4
X-RateLimit-Requested-Tokens: 1
X-RateLimit-Burst-Capacity: 5
X-RateLimit-Replenish-Rate: 10

Как только оставшийся предел скорости достигает нуля, шлюз вызывает HTTP-код 429. Для тестирования поведения мы можем использовать модульные тесты. Мы запускаем Встроенный сервер Redis и параллельно запускаем Повторные тесты . Как только ведро достигнет предела, начнет отображаться ошибка:

00:57:48.263 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[4]
00:57:48.394 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[3]
00:57:48.530 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[2]
00:57:48.667 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[1]
00:57:48.826 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[0]
00:57:48.851 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->429, reason->Too Many Requests, remaining->[0]
00:57:48.894 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->429, reason->Too Many Requests, remaining->[0]
00:57:49.135 [main] INFO  c.b.s.w.RedisWebFilterFactoriesLiveTest - Received: status->200, reason->OK, remaining->[4]

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

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

Как всегда, код доступен на GitHub .