diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index 4944bc5..67b2ee8 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -48,16 +48,16 @@ All user-agent requests are forwarded to the *origin server* conveniently. === Origin server routing -You can create a proxy that forwards all the traffic to a single server like seen before +You can create a proxy that forwards all the traffic to a single server like seen before. -You can set an origin selector to route the traffic to a given server +You can set an origin selector to route the traffic to a given server: [source,java] ---- {@link examples.HttpProxyExamples#originSelector} ---- -You can set a function to create the client request to the origin server for ultimate flexibility +You can set a function to create the client request to the origin server for ultimate flexibility: [source,java] ---- diff --git a/src/main/java/examples/HttpProxyExamples.java b/src/main/java/examples/HttpProxyExamples.java index 184ec62..0fb1bfd 100644 --- a/src/main/java/examples/HttpProxyExamples.java +++ b/src/main/java/examples/HttpProxyExamples.java @@ -6,23 +6,17 @@ import io.vertx.core.buffer.Buffer; import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpServer; -import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.RequestOptions; import io.vertx.core.net.HostAndPort; import io.vertx.core.net.SocketAddress; -import io.vertx.httpproxy.Body; -import io.vertx.httpproxy.HttpProxy; -import io.vertx.httpproxy.ProxyContext; -import io.vertx.httpproxy.ProxyInterceptor; -import io.vertx.httpproxy.ProxyOptions; -import io.vertx.httpproxy.ProxyRequest; -import io.vertx.httpproxy.ProxyResponse; +import io.vertx.httpproxy.*; import io.vertx.httpproxy.cache.CacheOptions; /** * @author Emad Alblueshi */ +@SuppressWarnings("unused") public class HttpProxyExamples { public void origin(Vertx vertx) { @@ -46,20 +40,20 @@ public void proxy(Vertx vertx) { proxyServer.requestHandler(proxy).listen(8080); } - private SocketAddress resolveOriginAddress(HttpServerRequest request) { + private Future resolveOriginAddress(ProxyContext proxyContext) { return null; } public void originSelector(HttpProxy proxy) { - proxy.originSelector(request -> Future.succeededFuture(resolveOriginAddress(request))); + proxy.origin(OriginRequestProvider.selector(proxyContext -> resolveOriginAddress(proxyContext))); } - private RequestOptions resolveOriginOptions(HttpServerRequest request) { + private RequestOptions resolveOriginOptions(ProxyContext request) { return null; } public void originRequestProvider(HttpProxy proxy) { - proxy.originRequestProvider((request, client) -> client.request(resolveOriginOptions(request))); + proxy.origin((proxyContext) -> proxyContext.client().request(resolveOriginOptions(proxyContext))); } public void inboundInterceptor(HttpProxy proxy) { @@ -137,34 +131,6 @@ private Body filter(Body body) { return body; } - public void more(Vertx vertx, HttpClient proxyClient) { - HttpProxy proxy = HttpProxy.reverseProxy(proxyClient).originSelector( - address -> Future.succeededFuture(SocketAddress.inetSocketAddress(7070, "origin")) - ); - } - - public void lowLevel(Vertx vertx, HttpServer proxyServer, HttpClient proxyClient) { - - proxyServer.requestHandler(request -> { - ProxyRequest proxyRequest = ProxyRequest.reverseProxy(request); - - proxyClient.request(proxyRequest.getMethod(), 8080, "origin", proxyRequest.getURI()) - .compose(proxyRequest::send) - .onSuccess(proxyResponse -> { - // Send the proxy response - proxyResponse.send(); - }) - .onFailure(err -> { - // Release the request - proxyRequest.release(); - - // Send error - request.response().setStatusCode(500) - .send(); - }); - }); - } - public void overrideAuthority(HttpProxy proxy) { proxy.addInterceptor(new ProxyInterceptor() { @Override diff --git a/src/main/java/io/vertx/httpproxy/HttpProxy.java b/src/main/java/io/vertx/httpproxy/HttpProxy.java index 9137dda..d06f164 100644 --- a/src/main/java/io/vertx/httpproxy/HttpProxy.java +++ b/src/main/java/io/vertx/httpproxy/HttpProxy.java @@ -18,7 +18,6 @@ import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientRequest; import io.vertx.core.http.HttpServerRequest; -import io.vertx.core.http.RequestOptions; import io.vertx.core.net.SocketAddress; import io.vertx.httpproxy.impl.ReverseProxy; @@ -61,7 +60,7 @@ static HttpProxy reverseProxy(ProxyOptions options, HttpClient client) { */ @Fluent default HttpProxy origin(SocketAddress address) { - return originSelector(req -> Future.succeededFuture(address)); + return origin(OriginRequestProvider.fixedAddress(address)); } /** @@ -73,7 +72,7 @@ default HttpProxy origin(SocketAddress address) { */ @Fluent default HttpProxy origin(int port, String host) { - return origin(SocketAddress.inetSocketAddress(port, host)); + return origin(OriginRequestProvider.fixedAddress(port, host)); } /** @@ -81,12 +80,12 @@ default HttpProxy origin(int port, String host) { * * @param selector the selector * @return a reference to this, so the API can be used fluently + * @deprecated use {@link #origin(OriginRequestProvider)} instead */ + @Deprecated @Fluent default HttpProxy originSelector(Function> selector) { - return originRequestProvider((req, client) -> selector - .apply(req) - .flatMap(server -> client.request(new RequestOptions().setServer(server)))); + return origin(OriginRequestProvider.selector(proxyContext -> selector.apply(proxyContext.request().proxiedRequest()))); } /** @@ -95,10 +94,23 @@ default HttpProxy originSelector(Function> provider); + default HttpProxy originRequestProvider(BiFunction> provider) { + return origin(proxyContext -> provider.apply(proxyContext.request().proxiedRequest(), proxyContext.client())); + } + + /** + * Set a provider that creates the request to the origin server based on {@link ProxyContext}. + * + * @param provider the provider + * @return a reference to this, so the API can be used fluently + */ + @Fluent + HttpProxy origin(OriginRequestProvider provider); /** * Add an interceptor to the interceptor chain. diff --git a/src/main/java/io/vertx/httpproxy/OriginRequestProvider.java b/src/main/java/io/vertx/httpproxy/OriginRequestProvider.java new file mode 100644 index 0000000..afc3300 --- /dev/null +++ b/src/main/java/io/vertx/httpproxy/OriginRequestProvider.java @@ -0,0 +1,56 @@ +package io.vertx.httpproxy; + +import io.vertx.codegen.annotations.VertxGen; +import io.vertx.core.Future; +import io.vertx.core.http.HttpClientRequest; +import io.vertx.core.http.RequestOptions; +import io.vertx.core.net.SocketAddress; + +import java.util.function.Function; + +/** + * A provider that creates the request to the origin server based on {@link ProxyContext}. + */ +@VertxGen +@FunctionalInterface +public interface OriginRequestProvider { + + /** + * Creates a simple provider for a fixed {@code port} and {@code host}. + */ + static OriginRequestProvider fixedAddress(int port, String host) { + return fixedAddress(SocketAddress.inetSocketAddress(port, host)); + } + + /** + * Creates a simple provider for a fixed {@link SocketAddress}. + */ + static OriginRequestProvider fixedAddress(SocketAddress address) { + return new OriginRequestProvider() { + @Override + public Future create(ProxyContext proxyContext) { + return proxyContext.client().request(new RequestOptions().setServer(address)); + } + }; + } + + /** + * Creates a provider that selects the origin server based on {@link ProxyContext}. + */ + static OriginRequestProvider selector(Function> selector) { + return new OriginRequestProvider() { + @Override + public Future create(ProxyContext proxyContext) { + return selector.apply(proxyContext).flatMap(server -> proxyContext.client().request(new RequestOptions().setServer(server))); + } + }; + } + + /** + * Create the {@link HttpClientRequest} to the origin server for a given {@link ProxyContext}. + * + * @param proxyContext the context of the proxied request and response + * @return a future, completed with the {@link HttpClientRequest} or failed + */ + Future create(ProxyContext proxyContext); +} diff --git a/src/main/java/io/vertx/httpproxy/ProxyContext.java b/src/main/java/io/vertx/httpproxy/ProxyContext.java index 8853325..acbfaad 100644 --- a/src/main/java/io/vertx/httpproxy/ProxyContext.java +++ b/src/main/java/io/vertx/httpproxy/ProxyContext.java @@ -2,6 +2,7 @@ import io.vertx.codegen.annotations.VertxGen; import io.vertx.core.Future; +import io.vertx.core.http.HttpClient; /** * A controller for proxy interception. @@ -45,4 +46,9 @@ public interface ProxyContext { * @return the attached payload */ T get(String name, Class type); + + /** + * @return the {@link HttpClient} use to interact with the origin server + */ + HttpClient client(); } diff --git a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java index dc63531..f54fc7b 100644 --- a/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java +++ b/src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java @@ -20,7 +20,6 @@ import io.vertx.httpproxy.spi.cache.Cache; import java.util.*; -import java.util.function.BiFunction; import static io.vertx.core.http.HttpHeaders.CONNECTION; import static io.vertx.core.http.HttpHeaders.UPGRADE; @@ -30,7 +29,7 @@ public class ReverseProxy implements HttpProxy { private final static Logger log = LoggerFactory.getLogger(ReverseProxy.class); private final HttpClient client; private final boolean supportWebSocket; - private BiFunction> selector = (req, client) -> Future.failedFuture("No origin available"); + private OriginRequestProvider originRequestProvider = (pc) -> Future.failedFuture("No origin available"); private final List interceptors = new ArrayList<>(); public ReverseProxy(ProxyOptions options, HttpClient client) { @@ -44,8 +43,8 @@ public ReverseProxy(ProxyOptions options, HttpClient client) { } @Override - public HttpProxy originRequestProvider(BiFunction> provider) { - selector = provider; + public HttpProxy origin(OriginRequestProvider provider) { + originRequestProvider = Objects.requireNonNull(provider); return this; } @@ -67,13 +66,14 @@ public void handle(HttpServerRequest request) { return; } + Proxy proxy = new Proxy(proxyRequest); + // WebSocket upgrade tunneling if (supportWebSocket && io.vertx.core.http.impl.HttpUtils.canUpgradeToWebSocket(request)) { - handleWebSocketUpgrade(proxyRequest); + handleWebSocketUpgrade(proxy); return; } - Proxy proxy = new Proxy(proxyRequest); proxy.filters = interceptors.listIterator(); proxy.sendRequest() .recover(throwable -> { @@ -87,9 +87,10 @@ public void handle(HttpServerRequest request) { }); } - private void handleWebSocketUpgrade(ProxyRequest proxyRequest) { + private void handleWebSocketUpgrade(ProxyContext proxyContext) { + ProxyRequest proxyRequest = proxyContext.request(); HttpServerRequest proxiedRequest = proxyRequest.proxiedRequest(); - resolveOrigin(proxiedRequest).onComplete(ar -> { + resolveOrigin(proxyContext).onComplete(ar -> { if (ar.succeeded()) { HttpClientRequest request = ar.result(); request.setMethod(HttpMethod.GET); @@ -148,8 +149,8 @@ private void end(ProxyRequest proxyRequest, int sc) { .send(); } - private Future resolveOrigin(HttpServerRequest proxiedRequest) { - return selector.apply(proxiedRequest, client); + private Future resolveOrigin(ProxyContext proxyContext) { + return originRequestProvider.create(proxyContext); } private class Proxy implements ProxyContext { @@ -174,6 +175,11 @@ public T get(String name, Class type) { return type.isInstance(o) ? type.cast(o) : null; } + @Override + public HttpClient client() { + return client; + } + @Override public ProxyRequest request() { return request; @@ -205,7 +211,7 @@ public Future sendResponse() { } private Future sendProxyRequest(ProxyRequest proxyRequest) { - return resolveOrigin(proxyRequest.proxiedRequest()).compose(proxyRequest::send); + return resolveOrigin(this).compose(proxyRequest::send); } private Future sendProxyResponse(ProxyResponse response) { diff --git a/src/test/java/io/vertx/httpproxy/ProxyTest.java b/src/test/java/io/vertx/httpproxy/ProxyTest.java index c6c9ad7..887a560 100644 --- a/src/test/java/io/vertx/httpproxy/ProxyTest.java +++ b/src/test/java/io/vertx/httpproxy/ProxyTest.java @@ -14,6 +14,7 @@ import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClientResponse; import io.vertx.core.http.HttpMethod; +import io.vertx.core.http.RequestOptions; import io.vertx.core.net.SocketAddress; import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; @@ -49,7 +50,7 @@ public void testRoundRobinSelector(TestContext ctx) { backends[i] = startHttpBackend(ctx, 8081 + value, req -> req.response().end("" + value)); } AtomicInteger count = new AtomicInteger(); - startProxy(proxy -> proxy.originSelector(req -> Future.succeededFuture(backends[count.getAndIncrement() % backends.length]))); + startProxy(proxy -> proxy.origin(OriginRequestProvider.selector(proxyContext -> Future.succeededFuture(backends[count.getAndIncrement() % backends.length])))); HttpClient client = vertx.createHttpClient(); Map result = Collections.synchronizedMap(new HashMap<>()); Async latch = ctx.async(); @@ -196,4 +197,29 @@ public Future handleProxyResponse(ProxyContext context) { })) .onComplete(ctx.asyncAssertSuccess(body -> async.complete())); } + + @Test + public void testVariableFromInterceptor(TestContext ctx) { + SocketAddress backend = startHttpBackend(ctx, 8081, req -> req.response().end("HOLA")); + ProxyInterceptor interceptor = new ProxyInterceptor() { + @Override + public Future handleProxyRequest(ProxyContext context) { + context.set("foo", "bar"); + return context.sendRequest(); + } + }; + OriginRequestProvider provider = (proxyContext) -> { + ctx.assertEquals("bar", proxyContext.get("foo", String.class)); + return proxyContext.client().request(new RequestOptions().setServer(backend)); + }; + startProxy(proxy -> proxy.origin(provider).addInterceptor(interceptor)); + HttpClient client = vertx.createHttpClient(); + client + .request(HttpMethod.GET, 8080, "localhost", "/") + .compose(req -> req + .send() + .compose(HttpClientResponse::body) + ) + .onComplete(ctx.asyncAssertSuccess(buffer -> ctx.assertEquals("HOLA", buffer.toString()))); + } }