Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,11 @@ static void fromJson(Iterable<java.util.Map.Entry<String, Object>> json, HttpCli
obj.setMaxRedirects(((Number)member.getValue()).intValue());
}
break;
case "maxRedirectBufferSize":
if (member.getValue() instanceof Number) {
obj.setMaxRedirectBufferSize(((Number)member.getValue()).intValue());
}
break;
case "forceSni":
if (member.getValue() instanceof Boolean) {
obj.setForceSni((Boolean)member.getValue());
Expand Down Expand Up @@ -193,6 +198,7 @@ static void toJson(HttpClientOptions obj, java.util.Map<String, Object> json) {
json.put("http2ClearTextUpgrade", obj.isHttp2ClearTextUpgrade());
json.put("http2ClearTextUpgradeWithPreflightRequest", obj.isHttp2ClearTextUpgradeWithPreflightRequest());
json.put("maxRedirects", obj.getMaxRedirects());
json.put("maxRedirectBufferSize", obj.getMaxRedirectBufferSize());
json.put("forceSni", obj.isForceSni());
json.put("decoderInitialBufferSize", obj.getDecoderInitialBufferSize());
if (obj.getTracingPolicy() != null) {
Expand Down
22 changes: 22 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/http/HttpClientConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ private static QuicClientConfig defaultQuicConfig() {
private String defaultHost;
private int defaultPort;
private int maxRedirects;
private int maxRedirectBufferSize;
private ObservabilityConfig observabilityConfig;
private boolean shared;
private String name;
Expand All @@ -97,6 +98,7 @@ public HttpClientConfig() {
this.defaultHost = HttpClientOptions.DEFAULT_DEFAULT_HOST;
this.defaultPort = HttpClientOptions.DEFAULT_DEFAULT_PORT;
this.maxRedirects = HttpClientOptions.DEFAULT_MAX_REDIRECTS;
this.maxRedirectBufferSize = HttpClientOptions.DEFAULT_MAX_REDIRECT_BUFFER_SIZE;
this.observabilityConfig = null;
this.shared = HttpClientOptions.DEFAULT_SHARED;
this.name = HttpClientOptions.DEFAULT_NAME;
Expand All @@ -116,6 +118,7 @@ public HttpClientConfig(HttpClientConfig other) {
this.defaultHost = other.defaultHost;
this.defaultPort = other.defaultPort;
this.maxRedirects = other.maxRedirects;
this.maxRedirectBufferSize = other.maxRedirectBufferSize;
this.observabilityConfig = other.observabilityConfig != null ? new ObservabilityConfig(other.observabilityConfig) : null;
this.shared = other.shared;
this.name = other.name;
Expand Down Expand Up @@ -143,6 +146,7 @@ public HttpClientConfig(HttpClientOptions options) {
this.defaultHost = options.getDefaultHost();
this.defaultPort = options.getDefaultPort();
this.maxRedirects = options.getMaxRedirects();
this.maxRedirectBufferSize = options.getMaxRedirectBufferSize();
this.observabilityConfig = observabilityConfig;
this.shared = options.isShared();
this.name = options.getName();
Expand Down Expand Up @@ -451,6 +455,24 @@ public HttpClientConfig setMaxRedirects(int maxRedirects) {
return this;
}

/**
* @return the maximum size of the redirect buffer when redirecting QUERY requests
*/
public int getMaxRedirectBufferSize() {
return maxRedirectBufferSize;
}

/**
* Set the maximum size of the redirect buffer in bytes when redirecting QUERY requests.
*
* @param maxRedirectBufferSize the maximum buffer size
* @return a reference to this, so the API can be used fluently
*/
public HttpClientConfig setMaxRedirectBufferSize(int maxRedirectBufferSize) {
this.maxRedirectBufferSize = maxRedirectBufferSize;
return this;
}

/**
* @return the client observability config.
*/
Expand Down
26 changes: 26 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/http/HttpClientOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ public class HttpClientOptions extends ClientOptionsBase {
*/
public static final int DEFAULT_MAX_REDIRECTS = 16;

/**
* Default max redirect buffer size = 1024 * 1024 bytes (1MB)
*/
public static final int DEFAULT_MAX_REDIRECT_BUFFER_SIZE = 1024 * 1024;

/*
* Default force SNI = {@code false}
*/
Expand Down Expand Up @@ -171,6 +176,7 @@ public class HttpClientOptions extends ClientOptionsBase {
private int defaultPort;
private HttpVersion protocolVersion;
private int maxRedirects;
private int maxRedirectBufferSize;
private boolean forceSni;

private TracingPolicy tracingPolicy;
Expand Down Expand Up @@ -211,6 +217,7 @@ public HttpClientOptions(HttpClientOptions other) {
this.defaultPort = other.defaultPort;
this.protocolVersion = other.protocolVersion;
this.maxRedirects = other.maxRedirects;
this.maxRedirectBufferSize = other.maxRedirectBufferSize;
this.forceSni = other.forceSni;
this.tracingPolicy = other.tracingPolicy;
this.shared = other.shared;
Expand Down Expand Up @@ -248,6 +255,7 @@ private void init() {
defaultPort = DEFAULT_DEFAULT_PORT;
protocolVersion = DEFAULT_PROTOCOL_VERSION;
maxRedirects = DEFAULT_MAX_REDIRECTS;
maxRedirectBufferSize = DEFAULT_MAX_REDIRECT_BUFFER_SIZE;
forceSni = DEFAULT_FORCE_SNI;
tracingPolicy = DEFAULT_TRACING_POLICY;
shared = DEFAULT_SHARED;
Expand Down Expand Up @@ -896,6 +904,24 @@ public HttpClientOptions setMaxRedirects(int maxRedirects) {
return this;
}

/**
* @return the maximum size of the redirect buffer when redirecting QUERY requests
*/
public int getMaxRedirectBufferSize() {
return maxRedirectBufferSize;
}

/**
* Set the maximum size of the redirect buffer in bytes when redirecting QUERY requests.
*
* @param maxRedirectBufferSize the maximum buffer size
* @return a reference to this, so the API can be used fluently
*/
public HttpClientOptions setMaxRedirectBufferSize(int maxRedirectBufferSize) {
this.maxRedirectBufferSize = maxRedirectBufferSize;
return this;
}

/**
* @return whether the client should always use SNI on TLS/SSL connections
*/
Expand Down
14 changes: 14 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/http/HttpClientRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,20 @@ public interface HttpClientRequest extends WriteStream<Buffer> {
*/
int getMaxRedirects();

/**
* @return the maximum size of the redirect buffer in bytes when redirecting QUERY requests
*/
int getMaxRedirectBufferSize();

/**
* Set the maximum size of the redirect buffer in bytes when redirecting QUERY requests.
*
* @param maxRedirectBufferSize the maximum buffer size
* @return a reference to this, so the API can be used fluently
*/
@Fluent
HttpClientRequest setMaxRedirectBufferSize(int maxRedirectBufferSize);

/**
* @return the number of followed redirections for the current HTTP request
*/
Expand Down
8 changes: 8 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/http/HttpHeaders.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ public interface HttpHeaders {
@GenIgnore(GenIgnore.PERMITTED_TYPE)
CharSequence ACCEPT_PATCH = HttpHeaderNames.ACCEPT_PATCH;

/**
* Accept-Query header name
Comment thread
desiderantes marked this conversation as resolved.
* <p>
* TODO: Use {@code HttpHeaderNames.ACCEPT_QUERY} when bumping to the next Netty release
*/
@GenIgnore(GenIgnore.PERMITTED_TYPE)
CharSequence ACCEPT_QUERY = createOptimized("accept-query");

/**
* Access-Control-Allow-Credentials header name
*/
Expand Down
14 changes: 13 additions & 1 deletion vertx-core/src/main/java/io/vertx/core/http/HttpMethod.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public class HttpMethod {
*/
public static final HttpMethod PATCH;

/**
* The RFC 10008 {@code QUERY} method, this instance is interned and uniquely used.
*/
public static final HttpMethod QUERY;

/**
* The RFC 2518/4918 {@code PROPFIND} method, this instance is interned and uniquely used.
*/
Expand Down Expand Up @@ -198,6 +203,8 @@ public class HttpMethod {
TRACE = new HttpMethod(io.netty.handler.codec.http.HttpMethod.TRACE);
CONNECT = new HttpMethod(io.netty.handler.codec.http.HttpMethod.CONNECT);
PATCH = new HttpMethod(io.netty.handler.codec.http.HttpMethod.PATCH);
// TODO: Use Netty HttpMethod.QUERY in the next Netty bump
QUERY = new HttpMethod(io.netty.handler.codec.http.HttpMethod.valueOf("QUERY"));
Comment thread
desiderantes marked this conversation as resolved.
PROPFIND = new HttpMethod(io.netty.handler.codec.http.HttpMethod.valueOf("PROPFIND"));
PROPPATCH = new HttpMethod(io.netty.handler.codec.http.HttpMethod.valueOf("PROPPATCH"));
MKCOL = new HttpMethod(io.netty.handler.codec.http.HttpMethod.valueOf("MKCOL"));
Expand Down Expand Up @@ -252,7 +259,8 @@ public class HttpMethod {
HttpMethod.MKACTIVITY,
HttpMethod.ORDERPATCH,
HttpMethod.ACL,
HttpMethod.SEARCH
HttpMethod.SEARCH,
HttpMethod.QUERY
));
}

Expand Down Expand Up @@ -303,6 +311,8 @@ private static HttpMethod _fromNetty(io.netty.handler.codec.http.HttpMethod sMet
return CONNECT;
case "PATCH":
return PATCH;
case "QUERY":
return QUERY;
case "PROPFIND":
return PROPFIND;
case "PROPPATCH":
Expand Down Expand Up @@ -386,6 +396,8 @@ public static HttpMethod valueOf(String value) {
return CONNECT;
case "PATCH":
return PATCH;
case "QUERY":
return QUERY;
case "PROPFIND":
return PROPFIND;
case "PROPPATCH":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public Future<RequestOptions> apply(HttpClientResponse resp) {
HttpMethod m = resp.request().getMethod();
if (statusCode == 303) {
m = HttpMethod.GET;
} else if (m != HttpMethod.GET && m != HttpMethod.HEAD) {
} else if (m != HttpMethod.GET && m != HttpMethod.HEAD && m != HttpMethod.QUERY) {
return null;
}
URI uri = HttpUtils.resolveURIReference(resp.request().absoluteURI(), location);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ private HttpClientImpl createHttpClientImpl(HttpClientConfig config,
config.getDefaultHost(),
config.getDefaultPort(),
config.getMaxRedirects(),
config.getMaxRedirectBufferSize(),
versions,
sslOptions,
connectHandler,
Expand Down Expand Up @@ -210,14 +211,15 @@ public LegacyHttpClient(
String defaultHost,
int defaultPort,
int maxRedirects,
int maxRedirectBufferSize,
List<HttpVersion> versions,
ClientSSLOptions sslOptions,
Handler<HttpConnection> connectHandler,
HttpClientTransport tcpTransport,
HttpClientTransport quicTransport,
HttpClientConfig config,
HttpClientOptions options) {
super(vertx, resolver, redirectHandler, httpMetrics, poolOptions, defaultProxyOptions, nonProxyHosts, loadBalancer, followAlternativeServices, resolverIdeTimeout, verifyHost, defaultSsl, defaultHost, defaultPort, maxRedirects, versions, sslOptions, connectHandler, tcpTransport, quicTransport);
super(vertx, resolver, redirectHandler, httpMetrics, poolOptions, defaultProxyOptions, nonProxyHosts, loadBalancer, followAlternativeServices, resolverIdeTimeout, verifyHost, defaultSsl, defaultHost, defaultPort, maxRedirects, maxRedirectBufferSize, versions, sslOptions, connectHandler, tcpTransport, quicTransport);
this.config = config;
this.options = options;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public class HttpClientImpl extends HttpClientBase implements HttpClientInternal
private final String defaultHost;
private final int defaultPort;
private final int maxRedirects;
private final int maxRedirectBufferSize;
private final List<HttpVersion> versions;
private final Handler<HttpConnection> connectHandler;
private volatile Handler<Throwable> exceptionHandler;
Expand All @@ -89,6 +90,7 @@ public class HttpClientImpl extends HttpClientBase implements HttpClientInternal
String defaultHost,
int defaultPort,
int maxRedirects,
int maxRedirectBufferSize,
List<HttpVersion> versions,
ClientSSLOptions sslOptions,
Handler<HttpConnection> connectHandler,
Expand Down Expand Up @@ -117,6 +119,7 @@ public class HttpClientImpl extends HttpClientBase implements HttpClientInternal
this.defaultHost = defaultHost;
this.defaultPort = defaultPort;
this.maxRedirects = maxRedirects;
this.maxRedirectBufferSize = maxRedirectBufferSize;
this.versions = versions;
this.sslOptions = sslOptions;
this.connectHandler = connectHandler;
Expand Down Expand Up @@ -791,6 +794,7 @@ public ConnectionObtainedResult(HttpClientStream stream, Lease<HttpClientConnect
HttpClientRequestImpl createRequest(HttpConnection connection, HttpClientStream stream, RequestOptions options) {
HttpClientRequestImpl request = new HttpClientRequestImpl(connection, stream);
request.init(options);
request.setMaxRedirectBufferSize(maxRedirectBufferSize);
Function<HttpClientResponse, Future<RequestOptions>> rHandler = redirectHandler;
if (rHandler != null) {
request.setMaxRedirects(maxRedirects);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
import io.vertx.core.net.ProxyOptions;
import io.vertx.core.net.ProxyType;

import java.util.ArrayList;
import java.util.List;
import io.netty.buffer.Unpooled;
import io.netty.buffer.CompositeByteBuf;
import java.util.Base64;
import java.util.Map;
import java.util.Objects;
Expand All @@ -41,6 +45,7 @@ public class HttpClientRequestImpl extends HttpClientRequestBase implements Http

static final Logger log = LoggerFactory.getLogger(HttpClientRequestImpl.class);

private static final int MAX_REDIRECT_BUFFER_SIZE = 1024 * 1024;
Comment thread
desiderantes marked this conversation as resolved.
private final Promise<Void> endPromise;
private final Future<Void> endFuture;
private boolean chunked;
Expand All @@ -58,6 +63,8 @@ public class HttpClientRequestImpl extends HttpClientRequestBase implements Http
private StreamPriority priority;
private boolean isConnect;
private String traceOperation;
private int maxRedirectBufferSize = HttpClientOptions.DEFAULT_MAX_REDIRECT_BUFFER_SIZE;
private List<Buffer> bodyBuffer;

public HttpClientRequestImpl(HttpConnection connection, HttpClientStream stream) {
super(connection, stream, stream.context().promise(), HttpMethod.GET, "/");
Expand Down Expand Up @@ -160,6 +167,18 @@ public synchronized int getMaxRedirects() {
return maxRedirects;
}

@Override
public synchronized HttpClientRequest setMaxRedirectBufferSize(int maxRedirectBufferSize) {
Arguments.require(maxRedirectBufferSize >= 0, "Max redirect buffer size must be >= 0");
this.maxRedirectBufferSize = maxRedirectBufferSize;
return this;
}

@Override
public synchronized int getMaxRedirectBufferSize() {
return maxRedirectBufferSize;
}

@Override
public int numberOfRedirections() {
return numberOfRedirections;
Expand Down Expand Up @@ -384,13 +403,29 @@ private void handleNextRequest(HttpClientRequest next, Promise<HttpClientRespons
next.pushHandler(pushHandler());
next.setFollowRedirects(true);
next.setMaxRedirects(maxRedirects);
((HttpClientRequestImpl)next).numberOfRedirections = numberOfRedirections + 1;
HttpClientRequestImpl nextImpl = (HttpClientRequestImpl) next;
nextImpl.numberOfRedirections = numberOfRedirections + 1;
nextImpl.bodyBuffer = bodyBuffer;
endFuture.onComplete(ar -> {
if (ar.succeeded()) {
if (timeoutMs > 0) {
next.idleTimeout(timeoutMs);
}
next.end();
if (next.getMethod() == HttpMethod.QUERY && bodyBuffer != null && !bodyBuffer.isEmpty()) {
Buffer redirectBody;
if (bodyBuffer.size() == 1) {
redirectBody = bodyBuffer.get(0);
} else {
CompositeByteBuf composite = Unpooled.compositeBuffer();
for (Buffer b : bodyBuffer) {
composite.addComponent(true, ((BufferInternal) b).getByteBuf());
}
redirectBody = BufferInternal.buffer(composite);
}
next.end(redirectBody);
} else {
next.end();
}
} else {
next.reset(0);
}
Expand Down Expand Up @@ -505,6 +540,25 @@ private Future<Void> doWrite(Buffer buff, boolean end, boolean connect) {
if (trailersSent) {
return context.failedFuture(new IllegalStateException("Request already complete"));
}
if (followRedirects && getMethod() == HttpMethod.QUERY) {
if (buff != null) {
int currentLen = 0;
if (bodyBuffer != null) {
for (Buffer b : bodyBuffer) {
currentLen += b.length();
}
}
if (currentLen + buff.length() > maxRedirectBufferSize) {
followRedirects = false;
bodyBuffer = null;
} else {
if (bodyBuffer == null) {
bodyBuffer = new ArrayList<>();
}
bodyBuffer.add(buff);
}
}
}
if (!headersSent) {
if (!connect) {
boolean requiresContentLength = !this.chunked && !headers.contains(CONTENT_LENGTH);
Expand Down
Loading