7.2 Proxying Requests with ProxyHttpClient

A common requirement in Microservice environments is to proxy requests in a Gateway Microservice to other backend Microservices.

The regular HttpClient API is designed around simplifying message exchange and is not designed for proxying requests. For this case, use the ProxyHttpClient, which can be used from a HTTP Server Filter to proxy requests to backend Microservices.

The following example demonstrates rewriting requests under the URI /proxy to the URI /real onto the same server (although in a real environment you generally proxy to another server):

Customizing the Netty pipeline for Logbook

  1. import io.micronaut.core.async.publisher.Publishers;
  2. import io.micronaut.core.util.StringUtils;
  3. import io.micronaut.http.HttpRequest;
  4. import io.micronaut.http.MutableHttpResponse;
  5. import io.micronaut.http.annotation.Filter;
  6. import io.micronaut.http.client.ProxyHttpClient;
  7. import io.micronaut.http.filter.HttpServerFilter;
  8. import io.micronaut.http.filter.OncePerRequestHttpServerFilter;
  9. import io.micronaut.http.filter.ServerFilterChain;
  10. import io.micronaut.runtime.server.EmbeddedServer;
  11. import org.reactivestreams.Publisher;
  12. @Filter("/proxy/**")
  13. public class ProxyFilter implements HttpServerFilter { (1)
  14. private final ProxyHttpClient client;
  15. private final EmbeddedServer embeddedServer;
  16. public ProxyFilter(ProxyHttpClient client,
  17. EmbeddedServer embeddedServer) { (2)
  18. this.client = client;
  19. this.embeddedServer = embeddedServer;
  20. }
  21. @Override
  22. public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request,
  23. ServerFilterChain chain) {
  24. return Publishers.map(client.proxy( (3)
  25. request.mutate() (4)
  26. .uri(b -> b (5)
  27. .scheme("http")
  28. .host(embeddedServer.getHost())
  29. .port(embeddedServer.getPort())
  30. .replacePath(StringUtils.prependUri(
  31. "/real",
  32. request.getPath().substring("/proxy".length())
  33. ))
  34. )
  35. .header("X-My-Request-Header", "XXX") (6)
  36. ), response -> response.header("X-My-Response-Header", "YYY"));
  37. }
  38. }

Customizing the Netty pipeline for Logbook

  1. import io.micronaut.core.async.publisher.Publishers
  2. import io.micronaut.core.util.StringUtils
  3. import io.micronaut.http.HttpRequest
  4. import io.micronaut.http.MutableHttpResponse
  5. import io.micronaut.http.annotation.Filter
  6. import io.micronaut.http.client.ProxyHttpClient
  7. import io.micronaut.http.filter.HttpServerFilter
  8. import io.micronaut.http.filter.OncePerRequestHttpServerFilter
  9. import io.micronaut.http.filter.ServerFilterChain
  10. import io.micronaut.http.uri.UriBuilder
  11. import io.micronaut.runtime.server.EmbeddedServer
  12. import org.reactivestreams.Publisher
  13. @Filter("/proxy/**")
  14. class ProxyFilter implements HttpServerFilter { (1)
  15. private final ProxyHttpClient client
  16. private final EmbeddedServer embeddedServer
  17. ProxyFilter(ProxyHttpClient client,
  18. EmbeddedServer embeddedServer) { (2)
  19. this.client = client
  20. this.embeddedServer = embeddedServer
  21. }
  22. @Override
  23. Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request,
  24. ServerFilterChain chain) {
  25. Publishers.map(client.proxy( (3)
  26. request.mutate() (4)
  27. .uri { UriBuilder b -> (5)
  28. b.with {
  29. scheme("http")
  30. host(embeddedServer.host)
  31. port(embeddedServer.port)
  32. replacePath(StringUtils.prependUri(
  33. "/real",
  34. request.path.substring("/proxy".length())
  35. ))
  36. }
  37. }
  38. .header("X-My-Request-Header", "XXX") (6)
  39. ), { it.header("X-My-Response-Header", "YYY") })
  40. }
  41. }

Customizing the Netty pipeline for Logbook

  1. import io.micronaut.core.async.publisher.Publishers
  2. import io.micronaut.core.util.StringUtils
  3. import io.micronaut.http.HttpRequest
  4. import io.micronaut.http.MutableHttpResponse
  5. import io.micronaut.http.annotation.Filter
  6. import io.micronaut.http.client.ProxyHttpClient
  7. import io.micronaut.http.filter.HttpServerFilter
  8. import io.micronaut.http.filter.OncePerRequestHttpServerFilter
  9. import io.micronaut.http.filter.ServerFilterChain
  10. import io.micronaut.http.uri.UriBuilder
  11. import io.micronaut.runtime.server.EmbeddedServer
  12. import org.reactivestreams.Publisher
  13. @Filter("/proxy/**")
  14. class ProxyFilter(
  15. private val client: ProxyHttpClient, (2)
  16. private val embeddedServer: EmbeddedServer
  17. ) : HttpServerFilter { (1)
  18. override fun doFilter(request: HttpRequest<*>,
  19. chain: ServerFilterChain): Publisher<MutableHttpResponse<*>> {
  20. return Publishers.map(client.proxy( (3)
  21. request.mutate() (4)
  22. .uri { b: UriBuilder -> (5)
  23. b.apply {
  24. scheme("http")
  25. host(embeddedServer.host)
  26. port(embeddedServer.port)
  27. replacePath(StringUtils.prependUri(
  28. "/real",
  29. request.path.substring("/proxy".length))
  30. )
  31. }
  32. }
  33. .header("X-My-Request-Header", "XXX") (6)
  34. ), { response: MutableHttpResponse<*> -> response.header("X-My-Response-Header", "YYY") })
  35. }
  36. }
1The filter extends HttpServerFilter
2The ProxyHttpClient is injected into the constructor.
3The proxy method proxies the request
4The request is mutated to modify the URI and include an additional header
5The UriBuilder API rewrites the URI
6Additional request and response headers are included
The ProxyHttpClient API is a low-level API that can be used to build a higher-level abstraction such as an API Gateway.