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
import io.micronaut.core.async.publisher.Publishers;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Filter;
import io.micronaut.http.client.ProxyHttpClient;
import io.micronaut.http.filter.HttpServerFilter;
import io.micronaut.http.filter.OncePerRequestHttpServerFilter;
import io.micronaut.http.filter.ServerFilterChain;
import io.micronaut.runtime.server.EmbeddedServer;
import org.reactivestreams.Publisher;
@Filter("/proxy/**")
public class ProxyFilter implements HttpServerFilter { (1)
private final ProxyHttpClient client;
private final EmbeddedServer embeddedServer;
public ProxyFilter(ProxyHttpClient client,
EmbeddedServer embeddedServer) { (2)
this.client = client;
this.embeddedServer = embeddedServer;
}
@Override
public Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request,
ServerFilterChain chain) {
return Publishers.map(client.proxy( (3)
request.mutate() (4)
.uri(b -> b (5)
.scheme("http")
.host(embeddedServer.getHost())
.port(embeddedServer.getPort())
.replacePath(StringUtils.prependUri(
"/real",
request.getPath().substring("/proxy".length())
))
)
.header("X-My-Request-Header", "XXX") (6)
), response -> response.header("X-My-Response-Header", "YYY"));
}
}
Customizing the Netty pipeline for Logbook
import io.micronaut.core.async.publisher.Publishers
import io.micronaut.core.util.StringUtils
import io.micronaut.http.HttpRequest
import io.micronaut.http.MutableHttpResponse
import io.micronaut.http.annotation.Filter
import io.micronaut.http.client.ProxyHttpClient
import io.micronaut.http.filter.HttpServerFilter
import io.micronaut.http.filter.OncePerRequestHttpServerFilter
import io.micronaut.http.filter.ServerFilterChain
import io.micronaut.http.uri.UriBuilder
import io.micronaut.runtime.server.EmbeddedServer
import org.reactivestreams.Publisher
@Filter("/proxy/**")
class ProxyFilter implements HttpServerFilter { (1)
private final ProxyHttpClient client
private final EmbeddedServer embeddedServer
ProxyFilter(ProxyHttpClient client,
EmbeddedServer embeddedServer) { (2)
this.client = client
this.embeddedServer = embeddedServer
}
@Override
Publisher<MutableHttpResponse<?>> doFilter(HttpRequest<?> request,
ServerFilterChain chain) {
Publishers.map(client.proxy( (3)
request.mutate() (4)
.uri { UriBuilder b -> (5)
b.with {
scheme("http")
host(embeddedServer.host)
port(embeddedServer.port)
replacePath(StringUtils.prependUri(
"/real",
request.path.substring("/proxy".length())
))
}
}
.header("X-My-Request-Header", "XXX") (6)
), { it.header("X-My-Response-Header", "YYY") })
}
}
Customizing the Netty pipeline for Logbook
import io.micronaut.core.async.publisher.Publishers
import io.micronaut.core.util.StringUtils
import io.micronaut.http.HttpRequest
import io.micronaut.http.MutableHttpResponse
import io.micronaut.http.annotation.Filter
import io.micronaut.http.client.ProxyHttpClient
import io.micronaut.http.filter.HttpServerFilter
import io.micronaut.http.filter.OncePerRequestHttpServerFilter
import io.micronaut.http.filter.ServerFilterChain
import io.micronaut.http.uri.UriBuilder
import io.micronaut.runtime.server.EmbeddedServer
import org.reactivestreams.Publisher
@Filter("/proxy/**")
class ProxyFilter(
private val client: ProxyHttpClient, (2)
private val embeddedServer: EmbeddedServer
) : HttpServerFilter { (1)
override fun doFilter(request: HttpRequest<*>,
chain: ServerFilterChain): Publisher<MutableHttpResponse<*>> {
return Publishers.map(client.proxy( (3)
request.mutate() (4)
.uri { b: UriBuilder -> (5)
b.apply {
scheme("http")
host(embeddedServer.host)
port(embeddedServer.port)
replacePath(StringUtils.prependUri(
"/real",
request.path.substring("/proxy".length))
)
}
}
.header("X-My-Request-Header", "XXX") (6)
), { response: MutableHttpResponse<*> -> response.header("X-My-Response-Header", "YYY") })
}
}
1 | The filter extends HttpServerFilter |
2 | The ProxyHttpClient is injected into the constructor. |
3 | The proxy method proxies the request |
4 | The request is mutated to modify the URI and include an additional header |
5 | The UriBuilder API rewrites the URI |
6 | Additional 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. |