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