Skip to content

Commit 6518368

Browse files
shaoxtsbordet
authored andcommitted
Issue #12436 - Allow headers size extend to maxRequestHeadersSize in http client.
1 parent 2d72872 commit 6518368

File tree

3 files changed

+248
-3
lines changed

3 files changed

+248
-3
lines changed

jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/HttpClientTransportOverHTTP.java

+16
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ public class HttpClientTransportOverHTTP extends AbstractConnectorHttpClientTran
3939
private int headerCacheSize = 1024;
4040
private boolean headerCacheCaseSensitive;
4141

42+
private int maxRequestHeadersSize = 32 * 1024;
43+
4244
public HttpClientTransportOverHTTP()
4345
{
4446
this(1);
@@ -127,4 +129,18 @@ public void setInitializeConnections(boolean initialize)
127129
{
128130
factory.setInitializeConnections(initialize);
129131
}
132+
133+
/**
134+
* @return The maximum allowed size in bytes for the HTTP request headers
135+
*/
136+
@ManagedAttribute("The maximum allowed size in bytes for the HTTP request headers")
137+
public int getMaxRequestHeadersSize()
138+
{
139+
return maxRequestHeadersSize;
140+
}
141+
142+
public void setMaxRequestHeadersSize(int maxRequestHeadersSize)
143+
{
144+
this.maxRequestHeadersSize = maxRequestHeadersSize;
145+
}
130146
}

jetty-core/jetty-client/src/main/java/org/eclipse/jetty/client/transport/internal/HttpSenderOverHTTP.java

+25-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
import java.nio.ByteBuffer;
1717

1818
import org.eclipse.jetty.client.HttpClient;
19+
import org.eclipse.jetty.client.HttpClientTransport;
1920
import org.eclipse.jetty.client.HttpRequestException;
21+
import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP;
2022
import org.eclipse.jetty.client.transport.HttpExchange;
2123
import org.eclipse.jetty.client.transport.HttpRequest;
2224
import org.eclipse.jetty.client.transport.HttpSender;
@@ -179,9 +181,29 @@ protected Action process() throws Exception
179181
}
180182
case HEADER_OVERFLOW:
181183
{
182-
headerBuffer.release();
183-
headerBuffer = null;
184-
throw new IllegalArgumentException("Request header too large");
184+
int maxRequestHeadersSize = -1;
185+
//For HTTP1.1 only
186+
HttpClientTransport transport = httpClient.getTransport();
187+
if (transport instanceof HttpClientTransportOverHTTP httpTransport)
188+
{
189+
maxRequestHeadersSize = httpTransport.getMaxRequestHeadersSize();
190+
}
191+
if (headerBuffer.capacity() < maxRequestHeadersSize)
192+
{
193+
RetainableByteBuffer newHeaderBuffer = bufferPool.acquire(maxRequestHeadersSize, useDirectByteBuffers);
194+
headerBuffer.getByteBuffer().flip();
195+
newHeaderBuffer.getByteBuffer().put(headerBuffer.getByteBuffer());
196+
RetainableByteBuffer toRelease = headerBuffer;
197+
headerBuffer = newHeaderBuffer;
198+
toRelease.release();
199+
break;
200+
}
201+
else
202+
{
203+
headerBuffer.release();
204+
headerBuffer = null;
205+
throw new IllegalArgumentException("Request header too large");
206+
}
185207
}
186208
case NEED_CHUNK:
187209
{

jetty-core/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java

+207
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.nio.file.Paths;
3030
import java.nio.file.StandardOpenOption;
3131
import java.util.Arrays;
32+
import java.util.HashMap;
3233
import java.util.Iterator;
3334
import java.util.List;
3435
import java.util.Map;
@@ -56,6 +57,7 @@
5657
import org.eclipse.jetty.http.HttpScheme;
5758
import org.eclipse.jetty.http.HttpStatus;
5859
import org.eclipse.jetty.http.HttpVersion;
60+
import org.eclipse.jetty.io.ByteArrayEndPoint;
5961
import org.eclipse.jetty.io.ClientConnector;
6062
import org.eclipse.jetty.io.Content;
6163
import org.eclipse.jetty.io.EndPoint;
@@ -76,6 +78,7 @@
7678
import org.eclipse.jetty.util.component.LifeCycle;
7779
import org.hamcrest.Matchers;
7880
import org.junit.jupiter.api.Assumptions;
81+
import org.junit.jupiter.api.Test;
7982
import org.junit.jupiter.api.extension.ExtendWith;
8083
import org.junit.jupiter.params.ParameterizedTest;
8184
import org.junit.jupiter.params.provider.ArgumentsSource;
@@ -2012,4 +2015,208 @@ public void perform()
20122015
.send(this);
20132016
}
20142017
}
2018+
2019+
private static Random rnd = new Random();
2020+
private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
2021+
2022+
public static final int CHARS_LENGTH = CHARS.length();
2023+
2024+
protected static String getRandomString(int size)
2025+
{
2026+
StringBuilder sb = new StringBuilder(size);
2027+
while (sb.length() < size)
2028+
{ // length of the random string.
2029+
int index = rnd.nextInt(CHARS_LENGTH);
2030+
sb.append(CHARS.charAt(index));
2031+
}
2032+
return sb.toString();
2033+
}
2034+
2035+
@ParameterizedTest
2036+
@ArgumentsSource(ScenarioProvider.class)
2037+
public void testSmallHeadersSize(Scenario scenario) throws Exception
2038+
{
2039+
startClient(scenario);
2040+
ByteArrayEndPoint endPoint = new ByteArrayEndPoint();
2041+
HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
2042+
destination.start();
2043+
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
2044+
Request request = client.newRequest(URI.create("http://localhost/"));
2045+
request.agent(getRandomString(888)); //More than the request buffer size, but less than the default max request headers size
2046+
final CountDownLatch headersLatch = new CountDownLatch(1);
2047+
final CountDownLatch successLatch = new CountDownLatch(1);
2048+
final CountDownLatch failureLatch = new CountDownLatch(1);
2049+
request.listener(new Request.Listener()
2050+
{
2051+
@Override
2052+
public void onHeaders(Request request)
2053+
{
2054+
headersLatch.countDown();
2055+
}
2056+
2057+
@Override
2058+
public void onSuccess(Request request)
2059+
{
2060+
successLatch.countDown();
2061+
}
2062+
2063+
@Override
2064+
public void onFailure(Request request, Throwable failure)
2065+
{
2066+
failureLatch.countDown();
2067+
}
2068+
});
2069+
connection.send(request, null);
2070+
2071+
String requestString = endPoint.takeOutputString();
2072+
assertTrue(requestString.startsWith("GET / HTTP/1.1\r\nAccept-Encoding: gzip\r\n"));
2073+
assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
2074+
assertTrue(successLatch.await(5, TimeUnit.SECONDS));
2075+
}
2076+
2077+
@ParameterizedTest
2078+
@ArgumentsSource(ScenarioProvider.class)
2079+
public void testMaxRequestHeadersSize(Scenario scenario) throws Exception
2080+
{
2081+
startClient(scenario);
2082+
byte[] buffer = new byte[32 * 1024];
2083+
ByteArrayEndPoint endPoint = new ByteArrayEndPoint(buffer, buffer.length);
2084+
HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
2085+
destination.start();
2086+
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
2087+
Request request = client.newRequest(URI.create("http://localhost/"));
2088+
//More than the request buffer size, but less than the default max request headers size
2089+
2090+
int desiredHeadersSize = 20 * 1024;
2091+
int currentHeadersSize = 0;
2092+
int i = 0;
2093+
while (currentHeadersSize < desiredHeadersSize)
2094+
{
2095+
final int index = i++;
2096+
final String headerValue = getRandomString(800);
2097+
final int headerSize = headerValue.length();
2098+
currentHeadersSize += headerSize;
2099+
request.cookie(new HttpCookie()
2100+
{
2101+
@Override
2102+
public String getName()
2103+
{
2104+
return "large" + index;
2105+
}
2106+
2107+
@Override
2108+
public String getValue()
2109+
{
2110+
return headerValue;
2111+
}
2112+
2113+
@Override
2114+
public int getVersion()
2115+
{
2116+
return 0;
2117+
}
2118+
2119+
@Override
2120+
public Map<String, String> getAttributes()
2121+
{
2122+
return new HashMap<>();
2123+
}
2124+
});
2125+
}
2126+
2127+
final CountDownLatch headersLatch = new CountDownLatch(1);
2128+
final CountDownLatch successLatch = new CountDownLatch(1);
2129+
request.listener(new Request.Listener()
2130+
{
2131+
@Override
2132+
public void onHeaders(Request request)
2133+
{
2134+
headersLatch.countDown();
2135+
}
2136+
2137+
@Override
2138+
public void onSuccess(Request request)
2139+
{
2140+
successLatch.countDown();
2141+
}
2142+
});
2143+
connection.send(request, null);
2144+
2145+
String requestString = endPoint.takeOutputString();
2146+
assertTrue(requestString.startsWith("GET / HTTP/1.1\r\nAccept-Encoding: gzip\r\n"));
2147+
assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
2148+
assertTrue(successLatch.await(5, TimeUnit.SECONDS));
2149+
}
2150+
2151+
@ParameterizedTest
2152+
@ArgumentsSource(ScenarioProvider.class)
2153+
public void testMaxRequestHeadersSizeOverflow(Scenario scenario) throws Exception
2154+
{
2155+
startClient(scenario);
2156+
byte[] buffer = new byte[32 * 1024];
2157+
ByteArrayEndPoint endPoint = new ByteArrayEndPoint(buffer, buffer.length);
2158+
HttpDestination destination = new HttpDestination(client, new Origin("http", "localhost", 8080));
2159+
destination.start();
2160+
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter<Connection>());
2161+
Request request = client.newRequest(URI.create("http://localhost/"));
2162+
//More than the request buffer size, but less than the default max request headers size
2163+
2164+
int desiredHeadersSize = 35 * 1024;
2165+
int currentHeadersSize = 0;
2166+
int i = 0;
2167+
while (currentHeadersSize < desiredHeadersSize)
2168+
{
2169+
final int index = i++;
2170+
final String headerValue = getRandomString(800);
2171+
final int headerSize = headerValue.length();
2172+
currentHeadersSize += headerSize;
2173+
request.cookie(new HttpCookie()
2174+
{
2175+
@Override
2176+
public String getName()
2177+
{
2178+
return "large" + index;
2179+
}
2180+
2181+
@Override
2182+
public String getValue()
2183+
{
2184+
return headerValue;
2185+
}
2186+
2187+
@Override
2188+
public int getVersion()
2189+
{
2190+
return 0;
2191+
}
2192+
2193+
@Override
2194+
public Map<String, String> getAttributes()
2195+
{
2196+
return new HashMap<>();
2197+
}
2198+
});
2199+
}
2200+
2201+
final CountDownLatch headersLatch = new CountDownLatch(1);
2202+
final CountDownLatch failureLatch = new CountDownLatch(1);
2203+
request.listener(new Request.Listener()
2204+
{
2205+
@Override
2206+
public void onHeaders(Request request)
2207+
{
2208+
headersLatch.countDown();
2209+
}
2210+
2211+
@Override
2212+
public void onFailure(Request request, Throwable failure)
2213+
{
2214+
failureLatch.countDown();
2215+
}
2216+
});
2217+
connection.send(request, null);
2218+
2219+
assertTrue(headersLatch.await(5, TimeUnit.SECONDS));
2220+
assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
2221+
}
20152222
}

0 commit comments

Comments
 (0)