Skip to content

Commit e28fa33

Browse files
authored
Introduce OriginRequestProvider (#123)
Closes #35, closes #44 OriginRequestProvider is a Vert.x API type that unifies all the origin selection methods currently present as overloaded methods in HttpProxy. OriginRequestProvider being a FunctionalInterface, users can provide one in the form of a lambda. OriginRequestProvider is a little different from the existing contract. Instead of the proxied request and HTTP client as parameters, it has the ProxyContext (that exposes all the proxy data and the client). This allows implementations to make a decision on the modifications interceptors can make (including when the request is an upgrade to WebSocket). See #44 It also allows interceptor and provider to exchange data via the ProxyContext attachments. See #35 Signed-off-by: Thomas Segismont <[email protected]>
1 parent 651a003 commit e28fa33

File tree

7 files changed

+135
-63
lines changed

7 files changed

+135
-63
lines changed

src/main/asciidoc/index.adoc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,16 @@ All user-agent requests are forwarded to the *origin server* conveniently.
4848

4949
=== Origin server routing
5050

51-
You can create a proxy that forwards all the traffic to a single server like seen before
51+
You can create a proxy that forwards all the traffic to a single server like seen before.
5252

53-
You can set an origin selector to route the traffic to a given server
53+
You can set an origin selector to route the traffic to a given server:
5454

5555
[source,java]
5656
----
5757
{@link examples.HttpProxyExamples#originSelector}
5858
----
5959

60-
You can set a function to create the client request to the origin server for ultimate flexibility
60+
You can set a function to create the client request to the origin server for ultimate flexibility:
6161

6262
[source,java]
6363
----

src/main/java/examples/HttpProxyExamples.java

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,17 @@
66
import io.vertx.core.buffer.Buffer;
77
import io.vertx.core.http.HttpClient;
88
import io.vertx.core.http.HttpServer;
9-
import io.vertx.core.http.HttpServerRequest;
109
import io.vertx.core.http.RequestOptions;
1110
import io.vertx.core.net.HostAndPort;
1211
import io.vertx.core.net.SocketAddress;
13-
import io.vertx.httpproxy.Body;
14-
import io.vertx.httpproxy.HttpProxy;
15-
import io.vertx.httpproxy.ProxyContext;
16-
import io.vertx.httpproxy.ProxyInterceptor;
17-
import io.vertx.httpproxy.ProxyOptions;
18-
import io.vertx.httpproxy.ProxyRequest;
19-
import io.vertx.httpproxy.ProxyResponse;
12+
import io.vertx.httpproxy.*;
2013
import io.vertx.httpproxy.cache.CacheOptions;
2114

2215
/**
2316
* @author <a href="mailto:[email protected]">Emad Alblueshi</a>
2417
*/
2518

19+
@SuppressWarnings("unused")
2620
public class HttpProxyExamples {
2721

2822
public void origin(Vertx vertx) {
@@ -46,20 +40,20 @@ public void proxy(Vertx vertx) {
4640
proxyServer.requestHandler(proxy).listen(8080);
4741
}
4842

49-
private SocketAddress resolveOriginAddress(HttpServerRequest request) {
43+
private Future<SocketAddress> resolveOriginAddress(ProxyContext proxyContext) {
5044
return null;
5145
}
5246

5347
public void originSelector(HttpProxy proxy) {
54-
proxy.originSelector(request -> Future.succeededFuture(resolveOriginAddress(request)));
48+
proxy.origin(OriginRequestProvider.selector(proxyContext -> resolveOriginAddress(proxyContext)));
5549
}
5650

57-
private RequestOptions resolveOriginOptions(HttpServerRequest request) {
51+
private RequestOptions resolveOriginOptions(ProxyContext request) {
5852
return null;
5953
}
6054

6155
public void originRequestProvider(HttpProxy proxy) {
62-
proxy.originRequestProvider((request, client) -> client.request(resolveOriginOptions(request)));
56+
proxy.origin((proxyContext) -> proxyContext.client().request(resolveOriginOptions(proxyContext)));
6357
}
6458

6559
public void inboundInterceptor(HttpProxy proxy) {
@@ -137,34 +131,6 @@ private Body filter(Body body) {
137131
return body;
138132
}
139133

140-
public void more(Vertx vertx, HttpClient proxyClient) {
141-
HttpProxy proxy = HttpProxy.reverseProxy(proxyClient).originSelector(
142-
address -> Future.succeededFuture(SocketAddress.inetSocketAddress(7070, "origin"))
143-
);
144-
}
145-
146-
public void lowLevel(Vertx vertx, HttpServer proxyServer, HttpClient proxyClient) {
147-
148-
proxyServer.requestHandler(request -> {
149-
ProxyRequest proxyRequest = ProxyRequest.reverseProxy(request);
150-
151-
proxyClient.request(proxyRequest.getMethod(), 8080, "origin", proxyRequest.getURI())
152-
.compose(proxyRequest::send)
153-
.onSuccess(proxyResponse -> {
154-
// Send the proxy response
155-
proxyResponse.send();
156-
})
157-
.onFailure(err -> {
158-
// Release the request
159-
proxyRequest.release();
160-
161-
// Send error
162-
request.response().setStatusCode(500)
163-
.send();
164-
});
165-
});
166-
}
167-
168134
public void overrideAuthority(HttpProxy proxy) {
169135
proxy.addInterceptor(new ProxyInterceptor() {
170136
@Override

src/main/java/io/vertx/httpproxy/HttpProxy.java

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import io.vertx.core.http.HttpClient;
1919
import io.vertx.core.http.HttpClientRequest;
2020
import io.vertx.core.http.HttpServerRequest;
21-
import io.vertx.core.http.RequestOptions;
2221
import io.vertx.core.net.SocketAddress;
2322
import io.vertx.httpproxy.impl.ReverseProxy;
2423

@@ -61,7 +60,7 @@ static HttpProxy reverseProxy(ProxyOptions options, HttpClient client) {
6160
*/
6261
@Fluent
6362
default HttpProxy origin(SocketAddress address) {
64-
return originSelector(req -> Future.succeededFuture(address));
63+
return origin(OriginRequestProvider.fixedAddress(address));
6564
}
6665

6766
/**
@@ -73,20 +72,20 @@ default HttpProxy origin(SocketAddress address) {
7372
*/
7473
@Fluent
7574
default HttpProxy origin(int port, String host) {
76-
return origin(SocketAddress.inetSocketAddress(port, host));
75+
return origin(OriginRequestProvider.fixedAddress(port, host));
7776
}
7877

7978
/**
8079
* Set a selector that resolves the <i><b>origin</b></i> address based on the incoming HTTP request.
8180
*
8281
* @param selector the selector
8382
* @return a reference to this, so the API can be used fluently
83+
* @deprecated use {@link #origin(OriginRequestProvider)} instead
8484
*/
85+
@Deprecated
8586
@Fluent
8687
default HttpProxy originSelector(Function<HttpServerRequest, Future<SocketAddress>> selector) {
87-
return originRequestProvider((req, client) -> selector
88-
.apply(req)
89-
.flatMap(server -> client.request(new RequestOptions().setServer(server))));
88+
return origin(OriginRequestProvider.selector(proxyContext -> selector.apply(proxyContext.request().proxiedRequest())));
9089
}
9190

9291
/**
@@ -95,10 +94,23 @@ default HttpProxy originSelector(Function<HttpServerRequest, Future<SocketAddres
9594
*
9695
* @param provider the provider
9796
* @return a reference to this, so the API can be used fluently
97+
* @deprecated use {@link #origin(OriginRequestProvider)} instead
9898
*/
99-
@GenIgnore()
99+
@Deprecated
100+
@GenIgnore
100101
@Fluent
101-
HttpProxy originRequestProvider(BiFunction<HttpServerRequest, HttpClient, Future<HttpClientRequest>> provider);
102+
default HttpProxy originRequestProvider(BiFunction<HttpServerRequest, HttpClient, Future<HttpClientRequest>> provider) {
103+
return origin(proxyContext -> provider.apply(proxyContext.request().proxiedRequest(), proxyContext.client()));
104+
}
105+
106+
/**
107+
* Set a provider that creates the request to the <i><b>origin</b></i> server based on {@link ProxyContext}.
108+
*
109+
* @param provider the provider
110+
* @return a reference to this, so the API can be used fluently
111+
*/
112+
@Fluent
113+
HttpProxy origin(OriginRequestProvider provider);
102114

103115
/**
104116
* Add an interceptor to the interceptor chain.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.vertx.httpproxy;
2+
3+
import io.vertx.codegen.annotations.VertxGen;
4+
import io.vertx.core.Future;
5+
import io.vertx.core.http.HttpClientRequest;
6+
import io.vertx.core.http.RequestOptions;
7+
import io.vertx.core.net.SocketAddress;
8+
9+
import java.util.function.Function;
10+
11+
/**
12+
* A provider that creates the request to the <i><b>origin</b></i> server based on {@link ProxyContext}.
13+
*/
14+
@VertxGen
15+
@FunctionalInterface
16+
public interface OriginRequestProvider {
17+
18+
/**
19+
* Creates a simple provider for a fixed {@code port} and {@code host}.
20+
*/
21+
static OriginRequestProvider fixedAddress(int port, String host) {
22+
return fixedAddress(SocketAddress.inetSocketAddress(port, host));
23+
}
24+
25+
/**
26+
* Creates a simple provider for a fixed {@link SocketAddress}.
27+
*/
28+
static OriginRequestProvider fixedAddress(SocketAddress address) {
29+
return new OriginRequestProvider() {
30+
@Override
31+
public Future<HttpClientRequest> create(ProxyContext proxyContext) {
32+
return proxyContext.client().request(new RequestOptions().setServer(address));
33+
}
34+
};
35+
}
36+
37+
/**
38+
* Creates a provider that selects the <i><b>origin</b></i> server based on {@link ProxyContext}.
39+
*/
40+
static OriginRequestProvider selector(Function<ProxyContext, Future<SocketAddress>> selector) {
41+
return new OriginRequestProvider() {
42+
@Override
43+
public Future<HttpClientRequest> create(ProxyContext proxyContext) {
44+
return selector.apply(proxyContext).flatMap(server -> proxyContext.client().request(new RequestOptions().setServer(server)));
45+
}
46+
};
47+
}
48+
49+
/**
50+
* Create the {@link HttpClientRequest} to the origin server for a given {@link ProxyContext}.
51+
*
52+
* @param proxyContext the context of the proxied request and response
53+
* @return a future, completed with the {@link HttpClientRequest} or failed
54+
*/
55+
Future<HttpClientRequest> create(ProxyContext proxyContext);
56+
}

src/main/java/io/vertx/httpproxy/ProxyContext.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.vertx.codegen.annotations.VertxGen;
44
import io.vertx.core.Future;
5+
import io.vertx.core.http.HttpClient;
56

67
/**
78
* A controller for proxy interception.
@@ -45,4 +46,9 @@ public interface ProxyContext {
4546
* @return the attached payload
4647
*/
4748
<T> T get(String name, Class<T> type);
49+
50+
/**
51+
* @return the {@link HttpClient} use to interact with the origin server
52+
*/
53+
HttpClient client();
4854
}

src/main/java/io/vertx/httpproxy/impl/ReverseProxy.java

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import io.vertx.httpproxy.spi.cache.Cache;
2121

2222
import java.util.*;
23-
import java.util.function.BiFunction;
2423

2524
import static io.vertx.core.http.HttpHeaders.CONNECTION;
2625
import static io.vertx.core.http.HttpHeaders.UPGRADE;
@@ -30,7 +29,7 @@ public class ReverseProxy implements HttpProxy {
3029
private final static Logger log = LoggerFactory.getLogger(ReverseProxy.class);
3130
private final HttpClient client;
3231
private final boolean supportWebSocket;
33-
private BiFunction<HttpServerRequest, HttpClient, Future<HttpClientRequest>> selector = (req, client) -> Future.failedFuture("No origin available");
32+
private OriginRequestProvider originRequestProvider = (pc) -> Future.failedFuture("No origin available");
3433
private final List<ProxyInterceptor> interceptors = new ArrayList<>();
3534

3635
public ReverseProxy(ProxyOptions options, HttpClient client) {
@@ -44,8 +43,8 @@ public ReverseProxy(ProxyOptions options, HttpClient client) {
4443
}
4544

4645
@Override
47-
public HttpProxy originRequestProvider(BiFunction<HttpServerRequest, HttpClient, Future<HttpClientRequest>> provider) {
48-
selector = provider;
46+
public HttpProxy origin(OriginRequestProvider provider) {
47+
originRequestProvider = Objects.requireNonNull(provider);
4948
return this;
5049
}
5150

@@ -67,13 +66,14 @@ public void handle(HttpServerRequest request) {
6766
return;
6867
}
6968

69+
Proxy proxy = new Proxy(proxyRequest);
70+
7071
// WebSocket upgrade tunneling
7172
if (supportWebSocket && io.vertx.core.http.impl.HttpUtils.canUpgradeToWebSocket(request)) {
72-
handleWebSocketUpgrade(proxyRequest);
73+
handleWebSocketUpgrade(proxy);
7374
return;
7475
}
7576

76-
Proxy proxy = new Proxy(proxyRequest);
7777
proxy.filters = interceptors.listIterator();
7878
proxy.sendRequest()
7979
.recover(throwable -> {
@@ -87,9 +87,10 @@ public void handle(HttpServerRequest request) {
8787
});
8888
}
8989

90-
private void handleWebSocketUpgrade(ProxyRequest proxyRequest) {
90+
private void handleWebSocketUpgrade(ProxyContext proxyContext) {
91+
ProxyRequest proxyRequest = proxyContext.request();
9192
HttpServerRequest proxiedRequest = proxyRequest.proxiedRequest();
92-
resolveOrigin(proxiedRequest).onComplete(ar -> {
93+
resolveOrigin(proxyContext).onComplete(ar -> {
9394
if (ar.succeeded()) {
9495
HttpClientRequest request = ar.result();
9596
request.setMethod(HttpMethod.GET);
@@ -148,8 +149,8 @@ private void end(ProxyRequest proxyRequest, int sc) {
148149
.send();
149150
}
150151

151-
private Future<HttpClientRequest> resolveOrigin(HttpServerRequest proxiedRequest) {
152-
return selector.apply(proxiedRequest, client);
152+
private Future<HttpClientRequest> resolveOrigin(ProxyContext proxyContext) {
153+
return originRequestProvider.create(proxyContext);
153154
}
154155

155156
private class Proxy implements ProxyContext {
@@ -174,6 +175,11 @@ public <T> T get(String name, Class<T> type) {
174175
return type.isInstance(o) ? type.cast(o) : null;
175176
}
176177

178+
@Override
179+
public HttpClient client() {
180+
return client;
181+
}
182+
177183
@Override
178184
public ProxyRequest request() {
179185
return request;
@@ -205,7 +211,7 @@ public Future<Void> sendResponse() {
205211
}
206212

207213
private Future<ProxyResponse> sendProxyRequest(ProxyRequest proxyRequest) {
208-
return resolveOrigin(proxyRequest.proxiedRequest()).compose(proxyRequest::send);
214+
return resolveOrigin(this).compose(proxyRequest::send);
209215
}
210216

211217
private Future<Void> sendProxyResponse(ProxyResponse response) {

src/test/java/io/vertx/httpproxy/ProxyTest.java

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import io.vertx.core.http.HttpClient;
1515
import io.vertx.core.http.HttpClientResponse;
1616
import io.vertx.core.http.HttpMethod;
17+
import io.vertx.core.http.RequestOptions;
1718
import io.vertx.core.net.SocketAddress;
1819
import io.vertx.ext.unit.Async;
1920
import io.vertx.ext.unit.TestContext;
@@ -49,7 +50,7 @@ public void testRoundRobinSelector(TestContext ctx) {
4950
backends[i] = startHttpBackend(ctx, 8081 + value, req -> req.response().end("" + value));
5051
}
5152
AtomicInteger count = new AtomicInteger();
52-
startProxy(proxy -> proxy.originSelector(req -> Future.succeededFuture(backends[count.getAndIncrement() % backends.length])));
53+
startProxy(proxy -> proxy.origin(OriginRequestProvider.selector(proxyContext -> Future.succeededFuture(backends[count.getAndIncrement() % backends.length]))));
5354
HttpClient client = vertx.createHttpClient();
5455
Map<String, AtomicInteger> result = Collections.synchronizedMap(new HashMap<>());
5556
Async latch = ctx.async();
@@ -196,4 +197,29 @@ public Future<Void> handleProxyResponse(ProxyContext context) {
196197
}))
197198
.onComplete(ctx.asyncAssertSuccess(body -> async.complete()));
198199
}
200+
201+
@Test
202+
public void testVariableFromInterceptor(TestContext ctx) {
203+
SocketAddress backend = startHttpBackend(ctx, 8081, req -> req.response().end("HOLA"));
204+
ProxyInterceptor interceptor = new ProxyInterceptor() {
205+
@Override
206+
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
207+
context.set("foo", "bar");
208+
return context.sendRequest();
209+
}
210+
};
211+
OriginRequestProvider provider = (proxyContext) -> {
212+
ctx.assertEquals("bar", proxyContext.get("foo", String.class));
213+
return proxyContext.client().request(new RequestOptions().setServer(backend));
214+
};
215+
startProxy(proxy -> proxy.origin(provider).addInterceptor(interceptor));
216+
HttpClient client = vertx.createHttpClient();
217+
client
218+
.request(HttpMethod.GET, 8080, "localhost", "/")
219+
.compose(req -> req
220+
.send()
221+
.compose(HttpClientResponse::body)
222+
)
223+
.onComplete(ctx.asyncAssertSuccess(buffer -> ctx.assertEquals("HOLA", buffer.toString())));
224+
}
199225
}

0 commit comments

Comments
 (0)