はじめに
アプリケーションレイヤーでオリジナルなルールでルーティングするプロキシが欲しい際にSpring Cloud Gatewayは非常に便利です。
プロキシを検討する際に、例えばAWSではCloudFrontやALBが候補として上がりますし、コンテナでプロキシを経由させるのであればEnvoyやNginxが候補として上がりますが、いずれもオリジナルなルールを作成するには限界があったり、少し骨が折れます。(と調べて理解しました)
Spring Cloud Gatewayを利用すれば、(Springを知っている人なら)簡単にオリジナルなルールを作成でき、非常に便利です。
ただし、Spring WebFlux上に構築されているため、通常のSpring MVCとは様相が違います。特にServerWebExchangeに頭を悩ませることでしょう。
中でもリクエストボディについては沼る部分(私は沼りました)なので、リクエストボディの中身を確認してルーティングするルール(predicate)を作成するサンプルを作成しました。
サンプルコード
githubへサンプルを上げてますので、参考にいただければ幸いです。
サンプルコードではリクエストボディが”target-request-body”とう文字列であれば”http://localhost:50080″へルーティングし、それ以外は”http://localhost:60080″へルーティングします。
ちなみに、以下の記事がdocker-composeで50080と60080でFlaskを上げるサンプルを紹介しているので、併せて参考にいただけると幸いです。
何すれば良い
predicateを作成する
オリジナルのpredicateを作成する方法についてはbaeldungの記事を参照してください。わかりやすいです。
リクエストボディを取り回したい場合はServerWebExchangeから”cachedRequestBodyObject”の属性値を取得することでリクエストボデイを取得できます。
サンプルでは以下の部分です。
1 2 3 4 5 6 7 8 |
@Override public Predicate<ServerWebExchange> apply(Config config) { return (ServerWebExchange exchange) -> { String requestBody = exchange.getAttribute("cachedRequestBodyObject"); System.out.println("requestBody: " + requestBody); return config.returnInverse ? !isTarget(requestBody) : isTarget(requestBody); }; } |
routesの定義をapplication.ymlからJavaへ変更してReadBodyPredicateFactoryを含める
requestBodyを利用したpredicateを作成するにはReadBodyPredicateFactoryを利用する必要がありますが、application.ymlでは利用できません。
application.ymlでルーティングを定義している場合はjavaに移行した上で、対象とするroutesの定義にReadBodyPredicateFactoryのpredicateを追加する必要があります。
ちなみに、GETリクエストの場合はリクエストボディが含まれないためReadBodyPredicateFactoryでFalseになってしまいます。可読性向上と意図しない動作を防ぐためにもmethodのpredicateを入れておくことを強くお勧めします。
サンプルでは以下のようにjavaの@Configurationで定義してます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * Route Config */ @Configuration public class Config { /** * Definition of path pattern. All request is routing target. */ final private String ALL_MATCH_URI_PATTERN = "/**"; final private String targetRouteUri = "http://localhost:50080"; final private String otherRouteUri = "http://localhost:60080"; @Bean public RouteLocator routes(RouteLocatorBuilder builder, RequestBodyPredicateFactory requestBodyFiilterFactory) { return builder.routes() .route("target", r -> r.path(ALL_MATCH_URI_PATTERN) .and().method("POST") .and().readBody(String.class, requestBody -> true) .and().predicate(requestBodyFiilterFactory.apply(new RequestBodyPredicateFactory.Config())) .uri(targetRouteUri) ) .route("other", r -> r.path(ALL_MATCH_URI_PATTERN) .uri(otherRouteUri) ) .build(); } } |
参考:
https://github.com/spring-cloud/spring-cloud-gateway/issues/690
https://github.com/spring-cloud/spring-cloud-gateway/issues/152