Skip to content

Commit

Permalink
The proxy uses and expose the authority as a host header, performing …
Browse files Browse the repository at this point in the history
…limited host header parsing (IPV6 host are not parsed).

Instead of relying on the HttpServerRequest#host() method, use HttpServerRequest#authority() which provides the same value parsed as an HostAndPort instance.
  • Loading branch information
vietj committed Jan 28, 2024
1 parent 83475be commit e5a411f
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 38 deletions.
3 changes: 2 additions & 1 deletion src/main/java/examples/HttpProxyExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand Down Expand Up @@ -169,7 +170,7 @@ public void overrideAuthority(HttpProxy proxy) {
@Override
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
ProxyRequest proxyRequest = context.request();
proxyRequest.setAuthority("example.com:80");
proxyRequest.setAuthority(HostAndPort.create("example.com", 80));
return ProxyInterceptor.super.handleProxyRequest(context);
}
});
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/io/vertx/httpproxy/ProxyRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.net.HostAndPort;
import io.vertx.httpproxy.impl.ProxiedRequest;

/**
Expand Down Expand Up @@ -113,12 +114,12 @@ static ProxyRequest reverseProxy(HttpServerRequest proxiedRequest) {
* @return a reference to this, so the API can be used fluently
*/
@Fluent
ProxyRequest setAuthority(String authority);
ProxyRequest setAuthority(HostAndPort authority);

/**
* @return the request authority, for HTTP2 the {@literal :authority} pseudo header otherwise the {@literal Host} header
*/
String getAuthority();
HostAndPort getAuthority();

/**
* @return the headers that will be sent to the origin server, the returned headers can be modified. The headers
Expand Down
49 changes: 19 additions & 30 deletions src/main/java/io/vertx/httpproxy/impl/ProxiedRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import io.vertx.core.http.HttpVersion;
import io.vertx.core.http.impl.HttpServerRequestInternal;
import io.vertx.core.impl.ContextInternal;
import io.vertx.core.net.HostAndPort;
import io.vertx.core.streams.Pipe;
import io.vertx.httpproxy.Body;
import io.vertx.httpproxy.ProxyRequest;
Expand Down Expand Up @@ -51,7 +52,7 @@ public class ProxiedRequest implements ProxyRequest {
private String uri;
private final String absoluteURI;
private Body body;
private String authority;
private HostAndPort authority;
private final MultiMap headers;
HttpClientRequest request;
private final HttpServerRequest proxiedRequest;
Expand All @@ -77,7 +78,7 @@ public ProxiedRequest(HttpServerRequest proxiedRequest) {
this.absoluteURI = proxiedRequest.absoluteURI();
this.proxiedRequest = proxiedRequest;
this.context = (ContextInternal) ((HttpServerRequestInternal) proxiedRequest).context();
this.authority = proxiedRequest.host();
this.authority = proxiedRequest.authority();
}

@Override
Expand Down Expand Up @@ -108,14 +109,14 @@ public ProxyRequest setBody(Body body) {
}

@Override
public ProxyRequest setAuthority(String authority) {
public ProxyRequest setAuthority(HostAndPort authority) {
Objects.requireNonNull(authority);
this.authority= authority;
return this;
}

@Override
public String getAuthority() {
public HostAndPort getAuthority() {
return authority;
}

Expand Down Expand Up @@ -174,32 +175,13 @@ void sendRequest(Handler<AsyncResult<ProxyResponse>> responseHandler) {
}

//
String proxiedAuthority = proxiedRequest.host();
int idx = proxiedAuthority.indexOf(':');
String proxiedHost;
int proxiedPort;
if (idx == -1) {
proxiedHost = proxiedAuthority;
proxiedPort = -1;
} else {
proxiedHost = proxiedAuthority.substring(0, idx);
proxiedPort = Integer.parseInt(proxiedAuthority.substring(idx + 1));
}

String host;
int port;
idx = authority.indexOf(':');
if (idx == -1) {
host = authority;
port = -1;
} else {
host = authority.substring(0, idx);
port = Integer.parseInt(authority.substring(idx + 1));
}
request.setHost(host);
request.setPort(port == -1 ? (request.absoluteURI().startsWith("https://") ? 443 : 80) : port);
if (!proxiedHost.equals(host) || proxiedPort != port) {
request.putHeader(X_FORWARDED_HOST, proxiedAuthority);
if (authority != null) {
request.authority(authority);
HostAndPort proxiedAuthority = proxiedRequest.authority();
if (!equals(authority, proxiedAuthority)) {
// Should cope with existing forwarded host headers
request.putHeader(X_FORWARDED_HOST, proxiedAuthority.toString());
}
}

long len = body.length();
Expand All @@ -220,6 +202,13 @@ void sendRequest(Handler<AsyncResult<ProxyResponse>> responseHandler) {
});
}

private static boolean equals(HostAndPort hp1, HostAndPort hp2) {
if (hp1 == null || hp2 == null) {
return false;
}
return hp1.host().equals(hp2.host()) && hp1.port() == hp2.port();
}

@Override
public ProxyRequest putHeader(CharSequence name, CharSequence value) {
headers.set(name, value);
Expand Down
53 changes: 48 additions & 5 deletions src/test/java/io/vertx/httpproxy/ProxyClientKeepAliveTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@
import io.vertx.core.Promise;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.*;
import io.vertx.core.net.HostAndPort;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.SocketAddress;
import io.vertx.core.streams.WriteStream;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import org.junit.Ignore;
import org.junit.Test;

import java.io.Closeable;
Expand Down Expand Up @@ -729,22 +731,62 @@ public void testPropagateHeaders(TestContext ctx) {
}));
}

@Test
public void testIPV6Authority(TestContext ctx) {
testAuthority(ctx, HostAndPort.authority("[7a03:908:671:b520:ba27:bbff:ffff:fed2]", 1234));
}

@Test
public void testIPV4Authority(TestContext ctx) {
testAuthority(ctx, HostAndPort.authority("192.168.0.1", 1234));
}

@Test
public void testMissingPortAuthority(TestContext ctx) {
testAuthority(ctx, HostAndPort.authority("localhost", -1));
}

private void testAuthority(TestContext ctx, HostAndPort requestAuthority) {
SocketAddress backend = startHttpBackend(ctx, 8081, req -> {
ctx.assertEquals("/somepath", req.uri());
ctx.assertEquals(requestAuthority.host(), req.authority().host());
ctx.assertEquals(requestAuthority.port(), req.authority().port());
ctx.assertEquals(null, req.getHeader("x-forwarded-host"));
req.response().end("Hello World");
});
startProxy(proxy -> {
proxy.origin(backend);
});
HttpClient client = vertx.createHttpClient();
client.request(GET, 8080, "localhost", "/somepath")
.compose(req -> req
.authority(requestAuthority)
.send()
.compose(resp -> {
ctx.assertEquals(200, resp.statusCode());
return resp.body();
}))
.onComplete(ctx.asyncAssertSuccess(body -> {
ctx.assertEquals("Hello World", body.toString());
}));
}

@Test
public void testAuthorityOverride1(TestContext ctx) {
testAuthorityOverride(ctx, "foo:8080", "foo:8080", "localhost:8080");
testAuthorityOverride(ctx, HostAndPort.authority("foo", 8080), "foo:8080", "localhost:8080");
}

@Test
public void testAuthorityOverride2(TestContext ctx) {
testAuthorityOverride(ctx, "foo", "foo", "localhost:8080");
testAuthorityOverride(ctx, HostAndPort.authority("foo"), "foo", "localhost:8080");
}

@Test
public void testAuthorityOverride3(TestContext ctx) {
testAuthorityOverride(ctx, "localhost:8080", "localhost:8080", null);
testAuthorityOverride(ctx, HostAndPort.authority("localhost", 8080), "localhost:8080", null);
}

private void testAuthorityOverride(TestContext ctx, String authority, String expectedAuthority, String expectedForwardedHost) {
private void testAuthorityOverride(TestContext ctx, HostAndPort authority, String expectedAuthority, String expectedForwardedHost) {
SocketAddress backend = startHttpBackend(ctx, 8081, req -> {
ctx.assertEquals("/somepath", req.uri());
ctx.assertEquals(expectedAuthority, req.host());
Expand All @@ -757,7 +799,8 @@ private void testAuthorityOverride(TestContext ctx, String authority, String exp
@Override
public Future<ProxyResponse> handleProxyRequest(ProxyContext context) {
ProxyRequest request = context.request();
ctx.assertEquals("localhost:8080", request.getAuthority());
ctx.assertEquals("localhost", request.getAuthority().host());
ctx.assertEquals(8080, request.getAuthority().port());
request.setAuthority(authority);
return ProxyInterceptor.super.handleProxyRequest(context);
}
Expand Down

0 comments on commit e5a411f

Please sign in to comment.