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 exchange of messages and is not really designed for proxying requests. For this use case you should use the 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 (clearly in a real environment you would generally proxy onto 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.*;
  8. import io.micronaut.http.filter.ServerFilterChain;
  9. import io.micronaut.runtime.server.EmbeddedServer;
  10. import org.reactivestreams.Publisher;
  11. @Filter("/proxy/**")
  12. public class ProxyFilter extends OncePerRequestHttpServerFilter { (1)
  13. private final ProxyHttpClient client;
  14. private final EmbeddedServer embeddedServer;
  15. public ProxyFilter(ProxyHttpClient client, EmbeddedServer embeddedServer) { (2)
  16. this.client = client;
  17. this.embeddedServer = embeddedServer;
  18. }
  19. @Override
  20. protected Publisher<MutableHttpResponse<?>> doFilterOnce(HttpRequest<?> request, ServerFilterChain chain) {
  21. return Publishers.map(client.proxy( (3)
  22. request.mutate() (4)
  23. .uri(b -> b (5)
  24. .scheme("http")
  25. .host(embeddedServer.getHost())
  26. .port(embeddedServer.getPort())
  27. .replacePath(StringUtils.prependUri(
  28. "/real",
  29. request.getPath().substring("/proxy".length())
  30. ))
  31. )
  32. .header("X-My-Request-Header", "XXX") (6)
  33. ), response -> response.header("X-My-Response-Header", "YYY"));
  34. }
  35. }

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.*
  8. import io.micronaut.http.filter.ServerFilterChain
  9. import io.micronaut.http.uri.UriBuilder
  10. import io.micronaut.runtime.server.EmbeddedServer
  11. import org.reactivestreams.Publisher
  12. @Filter("/proxy/**")
  13. class ProxyFilter extends OncePerRequestHttpServerFilter { (1)
  14. private final ProxyHttpClient client
  15. private final EmbeddedServer embeddedServer
  16. ProxyFilter(ProxyHttpClient client, EmbeddedServer embeddedServer) { (2)
  17. this.client = client
  18. this.embeddedServer = embeddedServer
  19. }
  20. @Override
  21. protected Publisher<MutableHttpResponse<?>> doFilterOnce(HttpRequest<?> request, ServerFilterChain chain) {
  22. return Publishers.map(client.proxy( (3)
  23. request.mutate() (4)
  24. .uri { UriBuilder b -> (5)
  25. b.with {
  26. scheme("http")
  27. host(embeddedServer.getHost())
  28. port(embeddedServer.getPort())
  29. replacePath(StringUtils.prependUri(
  30. "/real",
  31. request.getPath().substring("/proxy".length())
  32. ))
  33. }
  34. }
  35. .header("X-My-Request-Header", "XXX") (6)
  36. ), { MutableHttpResponse<?> 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.*
  4. import io.micronaut.http.annotation.Filter
  5. import io.micronaut.http.client.ProxyHttpClient
  6. import io.micronaut.http.filter.*
  7. import io.micronaut.http.uri.UriBuilder
  8. import io.micronaut.runtime.server.EmbeddedServer
  9. import org.reactivestreams.Publisher
  10. import java.util.function.Function
  11. @Filter("/proxy/**")
  12. class ProxyFilter(
  13. private val client: ProxyHttpClient, (2)
  14. private val embeddedServer: EmbeddedServer) : OncePerRequestHttpServerFilter() { (1)
  15. override fun doFilterOnce(request: HttpRequest<*>, chain: ServerFilterChain): Publisher<MutableHttpResponse<*>> {
  16. return Publishers.map(client.proxy( (3)
  17. request.mutate() (4)
  18. .uri { b: UriBuilder -> (5)
  19. b.apply {
  20. scheme("http")
  21. host(embeddedServer.host)
  22. port(embeddedServer.port)
  23. replacePath(StringUtils.prependUri(
  24. "/real",
  25. request.path.substring("/proxy".length)
  26. ))
  27. }
  28. }
  29. .header("X-My-Request-Header", "XXX") (6)
  30. ), Function { response: MutableHttpResponse<*> -> response.header("X-My-Response-Header", "YYY") })
  31. }
  32. }
1The filter extends OncePerRequestHttpServerFilter
2The ProxyHttpClient is injected into the constructor.
3The proxy method is used to proxy the request
4The request is mutated to modify the URI and include an additional header
5Here the UriBuilder API is used to rewrite 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.