Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow headers size extend to maxRequestHeadersSize in http client #12438

Closed
wants to merge 7 commits into from
Closed
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 @@ -135,6 +135,7 @@ public class HttpClient extends ContainerLifeCycle implements AutoCloseable
private String defaultRequestContentType = "application/octet-stream";
private boolean useInputDirectByteBuffers = true;
private boolean useOutputDirectByteBuffers = true;
private int maxRequestHeadersSize = 32 * 1024;
private int maxResponseHeadersSize = -1;
private Sweeper destinationSweeper;

Expand Down Expand Up @@ -1142,6 +1143,14 @@ public ClientConnectionFactory newSslClientConnectionFactory(SslContextFactory.C
return new SslClientConnectionFactory(sslContextFactory, getByteBufferPool(), getExecutor(), connectionFactory);
}

public int getMaxRequestHeadersSize() {
return maxRequestHeadersSize;
}

public void setMaxRequestHeadersSize(int maxRequestHeadersSize) {
this.maxRequestHeadersSize = maxRequestHeadersSize;
}

@Override
public void close() throws Exception
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ protected Action process() throws Exception
{
HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient();
HttpExchange exchange = getHttpExchange();

ByteBufferPool bufferPool = httpClient.getByteBufferPool();
boolean useDirectByteBuffers = httpClient.isUseOutputDirectByteBuffers();
while (true)
Expand All @@ -178,9 +179,21 @@ protected Action process() throws Exception
}
case HEADER_OVERFLOW:
{
headerBuffer.release();
headerBuffer = null;
throw new IllegalArgumentException("Request header too large");
int maxRequestHeadersSize = httpClient.getMaxRequestHeadersSize();
if (headerBuffer.capacity() < maxRequestHeadersSize) {
RetainableByteBuffer newHeaderBuffer = bufferPool.acquire(maxRequestHeadersSize, useDirectByteBuffers);
headerBuffer.getByteBuffer().flip();
newHeaderBuffer.getByteBuffer().put(headerBuffer.getByteBuffer());
RetainableByteBuffer toRelease = headerBuffer;
headerBuffer = newHeaderBuffer;
toRelease.release();
break;
}
else {
headerBuffer.release();
headerBuffer = null;
throw new IllegalArgumentException("Request header too large");
}
}
case NEED_CHUNK:
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

Expand All @@ -29,6 +32,7 @@
import org.eclipse.jetty.client.Result;
import org.eclipse.jetty.client.transport.HttpDestination;
import org.eclipse.jetty.client.transport.internal.HttpConnectionOverHTTP;
import org.eclipse.jetty.http.HttpCookie;
import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.eclipse.jetty.util.Promise;
import org.hamcrest.Matchers;
Expand All @@ -39,6 +43,7 @@
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.wildfly.common.Assert.assertFalse;

public class HttpSenderOverHTTPTest
{
Expand Down Expand Up @@ -302,4 +307,187 @@ public void onSuccess(Request request)
assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
assertTrue(successLatch.await(5, TimeUnit.SECONDS));
}

private static Random rnd = new Random();
private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";

public static final int CHARS_LENGTH = CHARS.length();

protected static String getRandomString(int size) {
StringBuilder sb = new StringBuilder(size);
while (sb.length() < size) { // length of the random string.
int index = rnd.nextInt(CHARS_LENGTH);
sb.append(CHARS.charAt(index));
}
return sb.toString();
}

@Test
public void testSmallHeadersSize() throws Exception
{
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
Request request = client.newRequest(URI.create("http://localhost/"));
request.agent(getRandomString(888)); //More than the request buffer size, but less than the default max request headers size
final CountDownLatch headersLatch = new CountDownLatch(1);
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
request.listener(new Request.Listener()
{
@Override
public void onHeaders(Request request)
{
headersLatch.countDown();
}

@Override
public void onSuccess(Request request)
{
successLatch.countDown();
}

@Override
public void onFailure(Request request, Throwable failure) {
failureLatch.countDown();
}
});
connection.send(request, null);

String requestString = endPoint.takeOutputString();
assertTrue(requestString.startsWith("GET / HTTP/1.1\r\nAccept-Encoding: gzip\r\n"));
assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
assertTrue(successLatch.await(5, TimeUnit.SECONDS));
}

@Test
public void testMaxRequestHeadersSize() throws Exception
{
byte[] buffer = new byte[32 * 1024];
ByteArrayEndPoint endPoint = new ByteArrayEndPoint(buffer, buffer.length);
HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
Request request = client.newRequest(URI.create("http://localhost/"));
//More than the request buffer size, but less than the default max request headers size

int desiredHeadersSize = 20 * 1024;
int currentHeadersSize = 0;
int i = 0;
while(currentHeadersSize < desiredHeadersSize) {
final int index = i ++;
final String headerValue = getRandomString(800);
final int headerSize = headerValue.length();
currentHeadersSize += headerSize;
request.cookie(new HttpCookie() {
@Override
public String getName() {
return "large" + index;
}

@Override
public String getValue() {
return headerValue;
}

@Override
public int getVersion() {
return 0;
}

@Override
public Map<String, String> getAttributes() {
return new HashMap<>();
}
});
}

final CountDownLatch headersLatch = new CountDownLatch(1);
final CountDownLatch successLatch = new CountDownLatch(1);
request.listener(new Request.Listener()
{
@Override
public void onHeaders(Request request)
{
headersLatch.countDown();
}

@Override
public void onSuccess(Request request)
{
successLatch.countDown();
}
});
connection.send(request, null);

String requestString = endPoint.takeOutputString();
assertTrue(requestString.startsWith("GET / HTTP/1.1\r\nAccept-Encoding: gzip\r\n"));
assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
assertTrue(successLatch.await(5, TimeUnit.SECONDS));
}

@Test
public void testMaxRequestHeadersSizeOverflow() throws Exception
{
byte[] buffer = new byte[32 * 1024];
ByteArrayEndPoint endPoint = new ByteArrayEndPoint(buffer, buffer.length);
HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
destination.start();
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
Request request = client.newRequest(URI.create("http://localhost/"));
//More than the request buffer size, but less than the default max request headers size

int desiredHeadersSize = 35 * 1024;
int currentHeadersSize = 0;
int i = 0;
while(currentHeadersSize < desiredHeadersSize) {
final int index = i ++;
final String headerValue = getRandomString(800);
final int headerSize = headerValue.length();
currentHeadersSize += headerSize;
request.cookie(new HttpCookie() {
@Override
public String getName() {
return "large" + index;
}

@Override
public String getValue() {
return headerValue;
}

@Override
public int getVersion() {
return 0;
}

@Override
public Map<String, String> getAttributes() {
return new HashMap<>();
}
});
}

final CountDownLatch headersLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
request.listener(new Request.Listener()
{
@Override
public void onHeaders(Request request)
{
headersLatch.countDown();
}

@Override
public void onFailure(Request request, Throwable failure)
{
failureLatch.countDown();
}
});
connection.send(request, null);

assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
}
}
Loading