1. введение
Spring Cloud Gateway – это интеллектуальная прокси-служба, часто используемая в микросервисах. Он прозрачно централизует запросы в единой точке входа и направляет их в соответствующую службу. Одной из его наиболее интересных особенностей является концепция filters ( Web Filter или Gateway Filter ).
Веб-фильтр, вместе с Фабриками предикатов , включает в себя полный механизм маршрутизации. Spring Cloud Gateway предоставляет множество встроенных Веб-фильтров фабрик, которые позволяют взаимодействовать с HTTP-запросами до достижения проксируемой службы и HTTP-ответами до доставки результата клиенту . Также возможно реализовать пользовательские фильтры .
В этом уроке мы сосредоточимся на встроенных веб-фильтрах фабриках, включенных в проект, и на том, как их использовать в расширенных случаях использования.
2. Фабрики веб-фильтров
Фабрики Web Filter (или Gateway Filter ) позволяют изменять входящие HTTP-запросы и исходящие HTTP-ответы. В этом смысле он предлагает набор интересных функций для применения до и после взаимодействия с нижестоящими службами.
Сопоставление обработчиков управляет запросом клиента. Он проверяет, соответствует ли он какому-либо настроенному маршруту. Затем он отправляет запрос веб-обработчику для выполнения определенной цепочки фильтров для этого маршрута. Пунктирная линия разделяет логику между логикой до и после фильтрации. Фильтры доходов запускаются перед запросом прокси-сервера. Выходные фильтры вступают в действие, когда они получают ответ прокси-сервера. Фильтры предоставляют механизмы для изменения процесса между ними.
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 .