Merged branch 'jetty-9.4.x' into 'jetty-10.0.x'.
This commit is contained in:
commit
0892ed8983
30
VERSION.txt
30
VERSION.txt
|
@ -1,5 +1,31 @@
|
|||
jetty-10.0.0-SNAPSHOT
|
||||
|
||||
jetty-9.4.12.RC0 - 11 July 2018
|
||||
+ 901 Overriding SSL context KeyStoreType requires explicit override of
|
||||
TrustStoreType
|
||||
+ 2075 Deprecating MultiException
|
||||
+ 2342 File Descriptor Leak: Conscrypt: "Too many open files"
|
||||
+ 2349 HTTP/2 max streams enforcement
|
||||
+ 2398 MultiPartFormInputStream parsing should default to UTF-8, but allowed
|
||||
to be overridden by Request.setCharacterEncoding()
|
||||
+ 2468 EWYK concurrent produce can fail SSL connections
|
||||
+ 2501 Include accepting connections in connection limit
|
||||
+ 2530 Client waits forever for cancelled large uploads
|
||||
+ 2560 Review PathResource exception handling
|
||||
+ 2565 HashLoginService silently ignores file:/ config paths from 9.3.x
|
||||
+ 2631 IllegalArgumentException: Buffering capacity exceeded, from HttpClient
|
||||
HEAD Requests to resources referencing large body contents
|
||||
+ 2648 LdapLoginModule fails with forceBinding=true under Java 9
|
||||
+ 2655 WebSocketClient not removing closed WebSocket Session's from managed
|
||||
beans
|
||||
+ 2662 Remove unnecessary boxing conversions
|
||||
+ 2663 Guard Throwable.addSuppressed() calls
|
||||
+ 2675 Demo rewrite rules prevent URL Session tracking
|
||||
+ 2677 Decode URI before matching against "/favicon.ico"
|
||||
+ 2683 NPE in FrameFlusher toString()
|
||||
+ 2684 MimeTypes.getAssumedEncodings() does not work
|
||||
+ 2696 GcloudDataStore dependency generation broken
|
||||
|
||||
jetty-9.4.11.v20180605 - 05 June 2018
|
||||
+ 1785 Support for vhost@connectorname syntax of virtual hosts
|
||||
+ 2346 Revert stack trace logging for HTTPChannel.onException
|
||||
|
@ -27,8 +53,8 @@ jetty-9.4.11.v20180605 - 05 June 2018
|
|||
+ 2571 Jetty Client 9.4.x incorrectly handles too large fields from nginx 1.14
|
||||
server
|
||||
+ 2574 Clarify max request queued exception message
|
||||
+ 2575 Work around broken OSGi implementations Bundle.getEntry() behavior returning
|
||||
with unescaped URLs
|
||||
+ 2575 Work around broken OSGi implementations Bundle.getEntry() behavior
|
||||
returning with unescaped URLs
|
||||
+ 2580 Stop creating unnecessary exceptions with MultiException
|
||||
+ 2586 Update to asm 6.2
|
||||
+ 2603 WebSocket ByteAccumulator initialized with wrong maximum
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
DO NOT EDIT - See: https://www.eclipse.org/jetty/documentation/current/startup-modules.html
|
||||
|
||||
[files]
|
||||
maven://org.mortbay.jetty.alpn/alpn-boot/8.1.12.v20180117|lib/alpn/alpn-boot-8.1.12.v20180117.jar
|
||||
|
||||
[exec]
|
||||
-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.12.v20180117.jar
|
|
@ -218,7 +218,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
|
|||
public int getHeaderCacheSize()
|
||||
{
|
||||
// TODO get from configuration
|
||||
return 256;
|
||||
return 4096;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
||||
public class EmptyServerHandler extends AbstractHandler.ErrorDispatchHandler
|
||||
{
|
||||
|
@ -38,5 +39,6 @@ public class EmptyServerHandler extends AbstractHandler.ErrorDispatchHandler
|
|||
|
||||
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
Log.getRootLogger().info("EMPTY service {}",target);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -510,7 +510,26 @@ public class HttpClientTest extends AbstractHttpClientServerTest
|
|||
@Test
|
||||
public void test_ExchangeIsComplete_OnlyWhenBothRequestAndResponseAreComplete() throws Exception
|
||||
{
|
||||
start(new RespondThenConsumeHandler());
|
||||
start(new AbstractHandler.ErrorDispatchHandler()
|
||||
{
|
||||
@Override
|
||||
protected void doNonErrorHandle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.setContentLength(0);
|
||||
response.setStatus(200);
|
||||
response.flushBuffer();
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
InputStream in = request.getInputStream();
|
||||
while(true)
|
||||
{
|
||||
int read = in.read(buffer);
|
||||
if (read < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Prepare a big file to upload
|
||||
Path targetTestsDir = testdir.getEmptyPathDir();
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2018 Mort Bay Consulting Pty. Ltd.
|
||||
// ------------------------------------------------------------------------
|
||||
// All rights reserved. This program and the accompanying materials
|
||||
// are made available under the terms of the Eclipse Public License v1.0
|
||||
// and Apache License v2.0 which accompanies this distribution.
|
||||
//
|
||||
// The Eclipse Public License is available at
|
||||
// http://www.eclipse.org/legal/epl-v10.html
|
||||
//
|
||||
// The Apache License v2.0 is available at
|
||||
// http://www.opensource.org/licenses/apache2.0.php
|
||||
//
|
||||
// You may elect to redistribute this code under either of these licenses.
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.client;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||
|
||||
class RespondThenConsumeHandler extends AbstractHandler
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException, ServletException
|
||||
{
|
||||
baseRequest.setHandled(true);
|
||||
response.setContentLength(0);
|
||||
response.setStatus(200);
|
||||
response.flushBuffer();
|
||||
|
||||
InputStream in = request.getInputStream();
|
||||
while(in.read()>=0);
|
||||
}
|
||||
|
||||
}
|
|
@ -43,7 +43,6 @@ import java.util.regex.Pattern;
|
|||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -75,7 +74,6 @@ import org.junit.After;
|
|||
import org.junit.Assert;
|
||||
import org.junit.Assume;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SslBytesServerTest extends SslBytesTest
|
||||
|
@ -99,6 +97,11 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
threadPool = Executors.newCachedThreadPool();
|
||||
server = new Server();
|
||||
|
||||
sslFills.set(0);
|
||||
sslFlushes.set(0);
|
||||
httpParses.set(0);
|
||||
serverEndPoint.set(null);
|
||||
|
||||
File keyStore = MavenTestingUtils.getTestResourceFile("keystore.jks");
|
||||
sslContextFactory = new SslContextFactory();
|
||||
sslContextFactory.setKeyStorePath(keyStore.getAbsolutePath());
|
||||
|
@ -185,7 +188,7 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
server.setHandler(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
|
||||
public void handle(String target, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -1319,10 +1322,8 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
|
||||
closeClient(client);
|
||||
}
|
||||
|
||||
// TODO work out why this test frequently fails
|
||||
@Ignore
|
||||
@Test(timeout=10000)
|
||||
|
||||
@Test(timeout=60000)
|
||||
public void testRequestWithContentWithRenegotiationInMiddleOfContentWhenRenegotiationIsForbidden() throws Exception
|
||||
{
|
||||
assumeJavaVersionSupportsTLSRenegotiations();
|
||||
|
@ -1367,37 +1368,28 @@ public class SslBytesServerTest extends SslBytesTest
|
|||
Assert.assertEquals(TLSRecord.Type.HANDSHAKE, record.getType());
|
||||
proxy.flushToServer(record);
|
||||
|
||||
// Renegotiation now allowed, server has closed
|
||||
record = proxy.readFromServer();
|
||||
// Renegotiation not allowed, server has closed
|
||||
loop: while(true)
|
||||
{
|
||||
record = proxy.readFromServer();
|
||||
if (record==null)
|
||||
break;
|
||||
switch(record.getType())
|
||||
{
|
||||
case APPLICATION:
|
||||
Assert.fail("application data not allows after renegotiate");
|
||||
case ALERT:
|
||||
break loop;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
|
||||
proxy.flushToClient(record);
|
||||
|
||||
record = proxy.readFromServer();
|
||||
Assert.assertNull(record);
|
||||
|
||||
// Write the rest of the request
|
||||
threadPool.submit(() ->
|
||||
{
|
||||
clientOutput.write(content2.getBytes(StandardCharsets.UTF_8));
|
||||
clientOutput.flush();
|
||||
return null;
|
||||
});
|
||||
|
||||
// Trying to write more application data results in an exception since the server closed
|
||||
record = proxy.readFromClient();
|
||||
proxy.flushToServer(record);
|
||||
try
|
||||
{
|
||||
record = proxy.readFromClient();
|
||||
Assert.assertNotNull(record);
|
||||
proxy.flushToServer(record);
|
||||
Assert.fail();
|
||||
}
|
||||
catch (IOException expected)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Check that we did not spin
|
||||
TimeUnit.MILLISECONDS.sleep(500);
|
||||
Assert.assertThat(sslFills.get(), Matchers.lessThan(50));
|
||||
|
|
|
@ -2,4 +2,5 @@ class=org.eclipse.jetty.util.log.StdErrLog
|
|||
#org.eclipse.jetty.LEVEL=INFO
|
||||
#org.eclipse.jetty.client.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.http.LEVEL=DEBUG
|
||||
|
|
|
@ -84,7 +84,7 @@
|
|||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="sendServerVersion">true</Set>
|
||||
<Set name="sendDateHeader">false</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
|
||||
<!-- Uncomment to enable handling of X-Forwarded- style headers
|
||||
<Call name="addCustomizer">
|
||||
|
|
|
@ -346,6 +346,7 @@ The ALPN implementation, relying on modifications of OpenJDK classes, updates ev
|
|||
|1.8.0u162 |8.1.12.v20180117
|
||||
|1.8.0u171 |8.1.12.v20180117
|
||||
|1.8.0u172 |8.1.12.v20180117
|
||||
|1.8.0u181 |8.1.12.v20180117
|
||||
|=============================
|
||||
|
||||
[[alpn-build]]
|
||||
|
|
|
@ -213,7 +213,7 @@ Below is the relevant section taken from link:{GITBROWSEURL}/jetty-server/src/ma
|
|||
<Set name="responseHeaderSize"><Property name="jetty.httpConfig.responseHeaderSize" deprecated="jetty.response.header.size" default="8192" /></Set>
|
||||
<Set name="sendServerVersion"><Property name="jetty.httpConfig.sendServerVersion" deprecated="jetty.send.server.version" default="true" /></Set>
|
||||
<Set name="sendDateHeader"><Property name="jetty.httpConfig.sendDateHeader" deprecated="jetty.send.date.header" default="false" /></Set>
|
||||
<Set name="headerCacheSize"><Property name="jetty.httpConfig.headerCacheSize" default="512" /></Set>
|
||||
<Set name="headerCacheSize"><Property name="jetty.httpConfig.headerCacheSize" default="4096" /></Set>
|
||||
<Set name="delayDispatchUntilContent"><Property name="jetty.httpConfig.delayDispatchUntilContent" deprecated="jetty.delayDispatchUntilContent" default="true"/></Set>
|
||||
<Set name="maxErrorDispatches"><Property name="jetty.httpConfig.maxErrorDispatches" default="10"/></Set>
|
||||
<Set name="blockingTimeout"><Property name="jetty.httpConfig.blockingTimeout" default="-1"/></Set>
|
||||
|
|
|
@ -154,7 +154,7 @@ public class ResponseContentParser extends StreamContentParser
|
|||
public int getHeaderCacheSize()
|
||||
{
|
||||
// TODO: configure this
|
||||
return 0;
|
||||
return 4096;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
<appendOutput>false</appendOutput>
|
||||
<outputFile>${project.build.directory}/deps.txt</outputFile>
|
||||
<sort>true</sort>
|
||||
<excludeGroupIds>org.eclipse.jetty</excludeGroupIds>
|
||||
<excludeGroupIds>org.eclipse.jetty,javax.servlet</excludeGroupIds>
|
||||
<prependGroupId>true</prependGroupId>
|
||||
<includeScope>runtime</includeScope>
|
||||
</configuration>
|
||||
|
@ -118,7 +118,7 @@
|
|||
<configuration>
|
||||
<tasks>
|
||||
<replaceregexp file="${project.build.directory}/deps.txt"
|
||||
match=" *(.*):(.*):jar:(.*):[a-z]*"
|
||||
match=" *(.*):(.*):jar:(.*):.*$"
|
||||
replace="maven://\1/\2/\3|lib/gcloud/\2-\3.jar"
|
||||
byline="true"
|
||||
/>
|
||||
|
|
|
@ -226,12 +226,6 @@ public class HttpParser
|
|||
for (HttpHeader h:HttpHeader.values())
|
||||
if (!CACHE.put(new HttpField(h,(String)null)))
|
||||
throw new IllegalStateException("CACHE FULL");
|
||||
// Add some more common headers
|
||||
CACHE.put(new HttpField(HttpHeader.REFERER,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null));
|
||||
CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null));
|
||||
}
|
||||
|
||||
private static HttpCompliance compliance()
|
||||
|
|
|
@ -268,7 +268,7 @@ public class HttpGeneratorServerHTTPTest
|
|||
@Override
|
||||
public int getHeaderCacheSize()
|
||||
{
|
||||
return 256;
|
||||
return 4096;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,16 +18,11 @@
|
|||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpComplianceSection.NO_FIELD_FOLDING;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jetty.http.HttpParser.State;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
|
@ -37,6 +32,9 @@ import org.junit.Assert;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.eclipse.jetty.http.HttpComplianceSection.NO_FIELD_FOLDING;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
|
||||
public class HttpParserTest
|
||||
{
|
||||
static
|
||||
|
@ -2360,7 +2358,7 @@ public class HttpParserTest
|
|||
@Override
|
||||
public int getHeaderCacheSize()
|
||||
{
|
||||
return 512;
|
||||
return 4096;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,13 +18,16 @@
|
|||
|
||||
package org.eclipse.jetty.http;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class QuotedQualityCSVTest
|
||||
{
|
||||
|
||||
|
@ -309,5 +312,46 @@ public class QuotedQualityCSVTest
|
|||
values.addValue("one,two;,three;x=y");
|
||||
Assert.assertThat(values.getValues(),Matchers.contains("one","two","three;x=y"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void testQuality()
|
||||
{
|
||||
List<String> results = new ArrayList<>();
|
||||
|
||||
QuotedQualityCSV values = new QuotedQualityCSV()
|
||||
{
|
||||
@Override
|
||||
protected void parsedValue(StringBuffer buffer)
|
||||
{
|
||||
results.add("parsedValue: " + buffer.toString());
|
||||
|
||||
super.parsedValue(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue)
|
||||
{
|
||||
String param = buffer.substring(paramName, buffer.length());
|
||||
results.add("parsedParam: " + param);
|
||||
|
||||
super.parsedParam(buffer, valueLength, paramName, paramValue);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// The provided string is not legal according to some RFCs ( not a token because of = and not a parameter because not preceded by ; )
|
||||
// The string is legal according to RFC7239 which allows for just parameters (called forwarded-pairs)
|
||||
values.addValue("p=0.5,q=0.5");
|
||||
|
||||
|
||||
// The QuotedCSV implementation is lenient and adopts the later interpretation and thus sees q=0.5 and p=0.5 both as parameters
|
||||
assertThat(results,contains("parsedValue: ", "parsedParam: p=0.5",
|
||||
"parsedValue: ", "parsedParam: q=0.5"));
|
||||
|
||||
|
||||
// However the QuotedQualityCSV only handles the q parameter and that is consumed from the parameter string.
|
||||
assertThat(values,contains("p=0.5", ""));
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
|
|||
import org.eclipse.jetty.http2.BufferingFlowControlStrategy;
|
||||
import org.eclipse.jetty.http2.FlowControlStrategy;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.ClientConnectionFactory;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
|
@ -129,6 +130,7 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
private List<String> protocols = Arrays.asList("h2", "h2-17", "h2-16", "h2-15", "h2-14");
|
||||
private int initialSessionRecvWindow = 16 * 1024 * 1024;
|
||||
private int initialStreamRecvWindow = 8 * 1024 * 1024;
|
||||
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
|
||||
private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
|
||||
|
||||
@Override
|
||||
|
@ -334,6 +336,17 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
this.initialStreamRecvWindow = initialStreamRecvWindow;
|
||||
}
|
||||
|
||||
@ManagedAttribute("The max number of keys in all SETTINGS frames")
|
||||
public int getMaxSettingsKeys()
|
||||
{
|
||||
return maxSettingsKeys;
|
||||
}
|
||||
|
||||
public void setMaxSettingsKeys(int maxSettingsKeys)
|
||||
{
|
||||
this.maxSettingsKeys = maxSettingsKeys;
|
||||
}
|
||||
|
||||
public void connect(InetSocketAddress address, Session.Listener listener, Promise<Session> promise)
|
||||
{
|
||||
connect(null, address, listener, promise);
|
||||
|
|
|
@ -66,7 +66,9 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
|
|||
Generator generator = new Generator(byteBufferPool);
|
||||
FlowControlStrategy flowControl = client.getFlowControlStrategyFactory().newFlowControlStrategy();
|
||||
HTTP2ClientSession session = new HTTP2ClientSession(scheduler, endPoint, generator, listener, flowControl);
|
||||
|
||||
Parser parser = new Parser(byteBufferPool, session, 4096, 8192);
|
||||
parser.setMaxSettingsKeys(client.getMaxSettingsKeys());
|
||||
|
||||
HTTP2ClientConnection connection = new HTTP2ClientConnection(client, byteBufferPool, executor, endPoint,
|
||||
parser, session, client.getInputBufferSize(), promise, listener);
|
||||
|
|
|
@ -1324,7 +1324,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
int length = Math.min(dataRemaining, window);
|
||||
|
||||
// Only one DATA frame is generated.
|
||||
int frameBytes = generator.data(lease, (DataFrame)frame, length);
|
||||
DataFrame dataFrame = (DataFrame)frame;
|
||||
int frameBytes = generator.data(lease, dataFrame, length);
|
||||
this.frameBytes += frameBytes;
|
||||
this.frameRemaining += frameBytes;
|
||||
|
||||
|
@ -1332,10 +1333,12 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
this.dataBytes += dataBytes;
|
||||
this.dataRemaining -= dataBytes;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Generated {}, length/window/data={}/{}/{}", frame, dataBytes, window, dataRemaining);
|
||||
LOG.debug("Generated {}, length/window/data={}/{}/{}", dataFrame, dataBytes, window, dataRemaining);
|
||||
|
||||
flowControl.onDataSending(stream, dataBytes);
|
||||
|
||||
stream.updateClose(dataFrame.isEndStream(), CloseState.Event.BEFORE_SEND);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ import java.util.Map;
|
|||
|
||||
public class SettingsFrame extends Frame
|
||||
{
|
||||
public static final int DEFAULT_MAX_KEYS = 64;
|
||||
|
||||
public static final int HEADER_TABLE_SIZE = 1;
|
||||
public static final int ENABLE_PUSH = 2;
|
||||
public static final int MAX_CONCURRENT_STREAMS = 3;
|
||||
|
|
|
@ -52,6 +52,7 @@ public class Parser
|
|||
private final HeaderParser headerParser;
|
||||
private final HeaderBlockParser headerBlockParser;
|
||||
private final BodyParser[] bodyParsers;
|
||||
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
|
||||
private boolean continuation;
|
||||
private State state = State.HEADER;
|
||||
|
||||
|
@ -71,7 +72,7 @@ public class Parser
|
|||
bodyParsers[FrameType.HEADERS.getType()] = new HeadersBodyParser(headerParser, listener, headerBlockParser, headerBlockFragments);
|
||||
bodyParsers[FrameType.PRIORITY.getType()] = new PriorityBodyParser(headerParser, listener);
|
||||
bodyParsers[FrameType.RST_STREAM.getType()] = new ResetBodyParser(headerParser, listener);
|
||||
bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener);
|
||||
bodyParsers[FrameType.SETTINGS.getType()] = new SettingsBodyParser(headerParser, listener, getMaxSettingsKeys());
|
||||
bodyParsers[FrameType.PUSH_PROMISE.getType()] = new PushPromiseBodyParser(headerParser, listener, headerBlockParser);
|
||||
bodyParsers[FrameType.PING.getType()] = new PingBodyParser(headerParser, listener);
|
||||
bodyParsers[FrameType.GO_AWAY.getType()] = new GoAwayBodyParser(headerParser, listener);
|
||||
|
@ -203,6 +204,16 @@ public class Parser
|
|||
return headerParser.hasFlag(bit);
|
||||
}
|
||||
|
||||
public int getMaxSettingsKeys()
|
||||
{
|
||||
return maxSettingsKeys;
|
||||
}
|
||||
|
||||
public void setMaxSettingsKeys(int maxSettingsKeys)
|
||||
{
|
||||
this.maxSettingsKeys = maxSettingsKeys;
|
||||
}
|
||||
|
||||
protected void notifyConnectionFailure(int error, String reason)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -32,16 +32,25 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
public class SettingsBodyParser extends BodyParser
|
||||
{
|
||||
private static final Logger LOG = Log.getLogger(SettingsBodyParser.class);
|
||||
|
||||
private final int maxKeys;
|
||||
private State state = State.PREPARE;
|
||||
private int cursor;
|
||||
private int length;
|
||||
private int settingId;
|
||||
private int settingValue;
|
||||
private int keys;
|
||||
private Map<Integer, Integer> settings;
|
||||
|
||||
public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener)
|
||||
{
|
||||
this(headerParser, listener, SettingsFrame.DEFAULT_MAX_KEYS);
|
||||
}
|
||||
|
||||
public SettingsBodyParser(HeaderParser headerParser, Parser.Listener listener, int maxKeys)
|
||||
{
|
||||
super(headerParser, listener);
|
||||
this.maxKeys = maxKeys;
|
||||
}
|
||||
|
||||
protected void reset()
|
||||
|
@ -54,6 +63,11 @@ public class SettingsBodyParser extends BodyParser
|
|||
settings = null;
|
||||
}
|
||||
|
||||
public int getMaxKeys()
|
||||
{
|
||||
return maxKeys;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void emptyBody(ByteBuffer buffer)
|
||||
{
|
||||
|
@ -116,7 +130,8 @@ public class SettingsBodyParser extends BodyParser
|
|||
settingValue = buffer.getInt();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("setting %d=%d",settingId, settingValue));
|
||||
settings.put(settingId, settingValue);
|
||||
if (!onSetting(buffer, settings, settingId, settingValue))
|
||||
return false;
|
||||
state = State.SETTING_ID;
|
||||
length -= 4;
|
||||
if (length == 0)
|
||||
|
@ -142,7 +157,8 @@ public class SettingsBodyParser extends BodyParser
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug(String.format("setting %d=%d",settingId, settingValue));
|
||||
settings.put(settingId, settingValue);
|
||||
if (!onSetting(buffer, settings, settingId, settingValue))
|
||||
return false;
|
||||
state = State.SETTING_ID;
|
||||
if (length == 0)
|
||||
return onSettings(settings);
|
||||
|
@ -158,6 +174,15 @@ public class SettingsBodyParser extends BodyParser
|
|||
return false;
|
||||
}
|
||||
|
||||
protected boolean onSetting(ByteBuffer buffer, Map<Integer, Integer> settings, int key, int value)
|
||||
{
|
||||
++keys;
|
||||
if (keys > getMaxKeys())
|
||||
return connectionFailure(buffer, ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, "invalid_settings_frame");
|
||||
settings.put(key, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected boolean onSettings(Map<Integer, Integer> settings)
|
||||
{
|
||||
SettingsFrame frame = new SettingsFrame(settings, hasFlag(Flags.ACK));
|
||||
|
|
|
@ -41,9 +41,9 @@ public class SettingsGenerateParseTest
|
|||
private final ByteBufferPool byteBufferPool = new MappedByteBufferPool();
|
||||
|
||||
@Test
|
||||
public void testGenerateParseNoSettings() throws Exception
|
||||
public void testGenerateParseNoSettings()
|
||||
{
|
||||
List<SettingsFrame> frames = testGenerateParse(Collections.<Integer, Integer>emptyMap());
|
||||
List<SettingsFrame> frames = testGenerateParse(Collections.emptyMap());
|
||||
Assert.assertEquals(1, frames.size());
|
||||
SettingsFrame frame = frames.get(0);
|
||||
Assert.assertEquals(0, frame.getSettings().size());
|
||||
|
@ -51,7 +51,7 @@ public class SettingsGenerateParseTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseSettings() throws Exception
|
||||
public void testGenerateParseSettings()
|
||||
{
|
||||
Map<Integer, Integer> settings1 = new HashMap<>();
|
||||
int key1 = 13;
|
||||
|
@ -73,7 +73,7 @@ public class SettingsGenerateParseTest
|
|||
{
|
||||
SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator());
|
||||
|
||||
final List<SettingsFrame> frames = new ArrayList<>();
|
||||
List<SettingsFrame> frames = new ArrayList<>();
|
||||
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
|
@ -104,11 +104,11 @@ public class SettingsGenerateParseTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseInvalidSettings() throws Exception
|
||||
public void testGenerateParseInvalidSettings()
|
||||
{
|
||||
SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator());
|
||||
|
||||
final AtomicInteger errorRef = new AtomicInteger();
|
||||
AtomicInteger errorRef = new AtomicInteger();
|
||||
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
|
@ -139,11 +139,11 @@ public class SettingsGenerateParseTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseOneByteAtATime() throws Exception
|
||||
public void testGenerateParseOneByteAtATime()
|
||||
{
|
||||
SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator());
|
||||
|
||||
final List<SettingsFrame> frames = new ArrayList<>();
|
||||
List<SettingsFrame> frames = new ArrayList<>();
|
||||
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
|
@ -182,4 +182,72 @@ public class SettingsGenerateParseTest
|
|||
Assert.assertTrue(frame.isReply());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseTooManySettingsInOneFrame()
|
||||
{
|
||||
SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator());
|
||||
|
||||
AtomicInteger errorRef = new AtomicInteger();
|
||||
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onConnectionFailure(int error, String reason)
|
||||
{
|
||||
errorRef.set(error);
|
||||
}
|
||||
}, 4096, 8192);
|
||||
int maxSettingsKeys = 32;
|
||||
parser.setMaxSettingsKeys(maxSettingsKeys);
|
||||
parser.init(UnaryOperator.identity());
|
||||
|
||||
Map<Integer, Integer> settings = new HashMap<>();
|
||||
for (int i = 0; i < maxSettingsKeys + 1; ++i)
|
||||
settings.put(i + 10, i);
|
||||
|
||||
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
|
||||
generator.generateSettings(lease, settings, false);
|
||||
|
||||
for (ByteBuffer buffer : lease.getByteBuffers())
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
parser.parse(buffer);
|
||||
}
|
||||
|
||||
Assert.assertEquals(ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, errorRef.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenerateParseTooManySettingsInMultipleFrames()
|
||||
{
|
||||
SettingsGenerator generator = new SettingsGenerator(new HeaderGenerator());
|
||||
|
||||
AtomicInteger errorRef = new AtomicInteger();
|
||||
Parser parser = new Parser(byteBufferPool, new Parser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onConnectionFailure(int error, String reason)
|
||||
{
|
||||
errorRef.set(error);
|
||||
}
|
||||
}, 4096, 8192);
|
||||
int maxSettingsKeys = 32;
|
||||
parser.setMaxSettingsKeys(maxSettingsKeys);
|
||||
parser.init(UnaryOperator.identity());
|
||||
|
||||
Map<Integer, Integer> settings = new HashMap<>();
|
||||
settings.put(13, 17);
|
||||
|
||||
ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool);
|
||||
for (int i = 0; i < maxSettingsKeys + 1; ++i)
|
||||
generator.generateSettings(lease, settings, false);
|
||||
|
||||
for (ByteBuffer buffer : lease.getByteBuffers())
|
||||
{
|
||||
while (buffer.hasRemaining())
|
||||
parser.parse(buffer);
|
||||
}
|
||||
|
||||
Assert.assertEquals(ErrorCode.ENHANCE_YOUR_CALM_ERROR.code, errorRef.get());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,10 @@ package org.eclipse.jetty.http2.client.http;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicMarkableReference;
|
||||
|
||||
import org.eclipse.jetty.alpn.client.ALPNClientConnectionFactory;
|
||||
import org.eclipse.jetty.client.AbstractHttpClientTransport;
|
||||
|
@ -135,7 +138,12 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
|||
if (HttpScheme.HTTPS.is(destination.getScheme()))
|
||||
sslContextFactory = httpClient.getSslContextFactory();
|
||||
|
||||
client.connect(sslContextFactory, address, listenerPromise, listenerPromise, context);
|
||||
connect(sslContextFactory, address, listenerPromise, listenerPromise, context);
|
||||
}
|
||||
|
||||
protected void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
|
||||
{
|
||||
getHTTP2Client().connect(sslContextFactory, address, listener, promise, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -164,8 +172,8 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
|||
|
||||
private class SessionListenerPromise extends Session.Listener.Adapter implements Promise<Session>
|
||||
{
|
||||
private final AtomicMarkableReference<HttpConnectionOverHTTP2> connection = new AtomicMarkableReference<>(null, false);
|
||||
private final Map<String, Object> context;
|
||||
private HttpConnectionOverHTTP2 connection;
|
||||
|
||||
private SessionListenerPromise(Map<String, Object> context)
|
||||
{
|
||||
|
@ -175,14 +183,15 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
|||
@Override
|
||||
public void succeeded(Session session)
|
||||
{
|
||||
connection = newHttpConnection(destination(), session);
|
||||
promise().succeeded(connection);
|
||||
// This method is invoked when the client preface
|
||||
// is sent, but we want to succeed the nested
|
||||
// promise when the server preface is received.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable failure)
|
||||
{
|
||||
promise().failed(failure);
|
||||
failConnectionPromise(failure);
|
||||
}
|
||||
|
||||
private HttpDestinationOverHTTP2 destination()
|
||||
|
@ -191,7 +200,7 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
|||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Promise<Connection> promise()
|
||||
private Promise<Connection> connectionPromise()
|
||||
{
|
||||
return (Promise<Connection>)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
|
||||
}
|
||||
|
@ -202,26 +211,55 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
|||
Map<Integer, Integer> settings = frame.getSettings();
|
||||
if (settings.containsKey(SettingsFrame.MAX_CONCURRENT_STREAMS))
|
||||
destination().setMaxRequestsPerConnection(settings.get(SettingsFrame.MAX_CONCURRENT_STREAMS));
|
||||
if (!connection.isMarked())
|
||||
onServerPreface(session);
|
||||
}
|
||||
|
||||
private void onServerPreface(Session session)
|
||||
{
|
||||
HttpConnectionOverHTTP2 connection = newHttpConnection(destination(), session);
|
||||
if (this.connection.compareAndSet(null, connection, false, true))
|
||||
connectionPromise().succeeded(connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Session session, GoAwayFrame frame)
|
||||
{
|
||||
HttpClientTransportOverHTTP2.this.onClose(connection, frame);
|
||||
if (failConnectionPromise(new ClosedChannelException()))
|
||||
return;
|
||||
HttpConnectionOverHTTP2 connection = this.connection.getReference();
|
||||
if (connection != null)
|
||||
HttpClientTransportOverHTTP2.this.onClose(connection, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onIdleTimeout(Session session)
|
||||
{
|
||||
return connection.onIdleTimeout(((HTTP2Session)session).getEndPoint().getIdleTimeout());
|
||||
long idleTimeout = ((HTTP2Session)session).getEndPoint().getIdleTimeout();
|
||||
if (failConnectionPromise(new TimeoutException("Idle timeout expired: " + idleTimeout + " ms")))
|
||||
return true;
|
||||
HttpConnectionOverHTTP2 connection = this.connection.getReference();
|
||||
if (connection != null)
|
||||
return connection.onIdleTimeout(idleTimeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Session session, Throwable failure)
|
||||
{
|
||||
HttpConnectionOverHTTP2 c = connection;
|
||||
if (c != null)
|
||||
c.close(failure);
|
||||
if (failConnectionPromise(failure))
|
||||
return;
|
||||
HttpConnectionOverHTTP2 connection = this.connection.getReference();
|
||||
if (connection != null)
|
||||
connection.close(failure);
|
||||
}
|
||||
|
||||
private boolean failConnectionPromise(Throwable failure)
|
||||
{
|
||||
boolean result = connection.compareAndSet(null, null, false, true);
|
||||
if (result)
|
||||
connectionPromise().failed(failure);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -477,22 +477,33 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
|||
ServerParser parser = new ServerParser(byteBufferPool, new ServerParser.Listener.Adapter()
|
||||
{
|
||||
@Override
|
||||
public void onHeaders(HeadersFrame request)
|
||||
public void onPreface()
|
||||
{
|
||||
// Server's preface.
|
||||
generator.control(lease, new SettingsFrame(new HashMap<>(), false));
|
||||
// Reply to client's SETTINGS.
|
||||
generator.control(lease, new SettingsFrame(new HashMap<>(), true));
|
||||
writeFrames();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeaders(HeadersFrame request)
|
||||
{
|
||||
// Response.
|
||||
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields());
|
||||
HeadersFrame response = new HeadersFrame(request.getStreamId(), metaData, null, true);
|
||||
generator.control(lease, response);
|
||||
writeFrames();
|
||||
}
|
||||
|
||||
private void writeFrames()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Write the frames.
|
||||
for (ByteBuffer buffer : lease.getByteBuffers())
|
||||
output.write(BufferUtil.toArray(buffer));
|
||||
lease.recycle();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
|
|
@ -18,33 +18,64 @@
|
|||
|
||||
package org.eclipse.jetty.http2.client.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.client.AbstractConnectionPool;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.HttpDestination;
|
||||
import org.eclipse.jetty.client.HttpResponseException;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http2.api.Session;
|
||||
import org.eclipse.jetty.http2.api.Stream;
|
||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||
import org.eclipse.jetty.http2.frames.GoAwayFrame;
|
||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||
import org.eclipse.jetty.http2.frames.PingFrame;
|
||||
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||
import org.eclipse.jetty.http2.frames.SettingsFrame;
|
||||
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class MaxConcurrentStreamsTest extends AbstractTest
|
||||
{
|
||||
private void start(int maxConcurrentStreams, Handler handler) throws Exception
|
||||
{
|
||||
startServer(maxConcurrentStreams, handler);
|
||||
prepareClient();
|
||||
client.start();
|
||||
}
|
||||
|
||||
private void startServer(int maxConcurrentStreams, Handler handler) throws Exception
|
||||
{
|
||||
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(new HttpConfiguration());
|
||||
http2.setMaxConcurrentStreams(maxConcurrentStreams);
|
||||
prepareServer(http2);
|
||||
server.setHandler(handler);
|
||||
server.start();
|
||||
prepareClient();
|
||||
client.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -114,6 +145,85 @@ public class MaxConcurrentStreamsTest extends AbstractTest
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSmallMaxConcurrentStreamsExceededOnClient() throws Exception
|
||||
{
|
||||
int maxConcurrentStreams = 1;
|
||||
startServer(maxConcurrentStreams, new EmptyServerHandler()
|
||||
{
|
||||
@Override
|
||||
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
sleep(1000);
|
||||
}
|
||||
});
|
||||
|
||||
String scheme = "http";
|
||||
String host = "localhost";
|
||||
int port = connector.getLocalPort();
|
||||
|
||||
AtomicInteger connections = new AtomicInteger();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
List<Throwable> failures = new ArrayList<>();
|
||||
client = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client())
|
||||
{
|
||||
@Override
|
||||
protected void connect(SslContextFactory sslContextFactory, InetSocketAddress address, Session.Listener listener, Promise<Session> promise, Map<String, Object> context)
|
||||
{
|
||||
super.connect(sslContextFactory, address, new Wrapper(listener)
|
||||
{
|
||||
@Override
|
||||
public void onSettings(Session session, SettingsFrame frame)
|
||||
{
|
||||
// Send another request to simulate a request being
|
||||
// sent concurrently with connection establishment.
|
||||
// Sending this request will trigger the creation of
|
||||
// another connection since maxConcurrentStream=1.
|
||||
if (connections.incrementAndGet() == 1)
|
||||
{
|
||||
client.newRequest(host, port)
|
||||
.path("/2")
|
||||
.send(result ->
|
||||
{
|
||||
if (result.isSucceeded())
|
||||
{
|
||||
Response response2 = result.getResponse();
|
||||
if (response2.getStatus() == HttpStatus.OK_200)
|
||||
latch.countDown();
|
||||
else
|
||||
failures.add(new HttpResponseException("", response2));
|
||||
}
|
||||
else
|
||||
{
|
||||
failures.add(result.getFailure());
|
||||
}
|
||||
});
|
||||
}
|
||||
super.onSettings(session, frame);
|
||||
}
|
||||
}, promise, context);
|
||||
}
|
||||
}, null);
|
||||
QueuedThreadPool clientExecutor = new QueuedThreadPool();
|
||||
clientExecutor.setName("client");
|
||||
client.setExecutor(clientExecutor);
|
||||
client.start();
|
||||
|
||||
// This request will be queued and establish the connection,
|
||||
// which will trigger the send of the second request.
|
||||
ContentResponse response1 = client.newRequest(host, port)
|
||||
.path("/1")
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
Assert.assertEquals(HttpStatus.OK_200, response1.getStatus());
|
||||
Assert.assertTrue(failures.toString(), latch.await(5, TimeUnit.SECONDS));
|
||||
Assert.assertEquals(2, connections.get());
|
||||
HttpDestination destination = (HttpDestination)client.getDestination(scheme, host, port);
|
||||
AbstractConnectionPool connectionPool = (AbstractConnectionPool)destination.getConnectionPool();
|
||||
Assert.assertEquals(2, connectionPool.getConnectionCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoConcurrentStreamsThirdWaits() throws Exception
|
||||
{
|
||||
|
@ -214,6 +324,49 @@ public class MaxConcurrentStreamsTest extends AbstractTest
|
|||
Assert.assertTrue(latch.await(maxConcurrent * sleep / 2, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testManyConcurrentRequestsWithSmallConcurrentStreams() throws Exception
|
||||
{
|
||||
byte[] data = new byte[64 * 1024];
|
||||
start(1, new EmptyServerHandler()
|
||||
{
|
||||
@Override
|
||||
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
response.getOutputStream().write(data);
|
||||
}
|
||||
});
|
||||
|
||||
client.setMaxConnectionsPerDestination(32768);
|
||||
client.setMaxRequestsQueuedPerDestination(1024 * 1024);
|
||||
|
||||
int parallelism = 16;
|
||||
int runs = 1;
|
||||
int iterations = 256;
|
||||
int total = parallelism * runs * iterations;
|
||||
CountDownLatch latch = new CountDownLatch(total);
|
||||
Queue<Result> failures = new ConcurrentLinkedQueue<>();
|
||||
ForkJoinPool pool = new ForkJoinPool(parallelism);
|
||||
pool.submit(() -> IntStream.range(0, parallelism).parallel().forEach(i ->
|
||||
IntStream.range(0, runs).forEach(j ->
|
||||
{
|
||||
for (int k = 0; k < iterations; ++k)
|
||||
{
|
||||
client.newRequest("localhost", connector.getLocalPort())
|
||||
.path("/" + i + "_" + j + "_" + k)
|
||||
.send(result ->
|
||||
{
|
||||
if (result.isFailed())
|
||||
failures.offer(result);
|
||||
latch.countDown();
|
||||
});
|
||||
}
|
||||
})));
|
||||
|
||||
Assert.assertTrue(latch.await(total * 10, TimeUnit.MILLISECONDS));
|
||||
Assert.assertTrue(failures.toString(), failures.isEmpty());
|
||||
}
|
||||
|
||||
private void primeConnection() throws Exception
|
||||
{
|
||||
// Prime the connection so that the maxConcurrentStream setting arrives to the client.
|
||||
|
@ -233,4 +386,62 @@ public class MaxConcurrentStreamsTest extends AbstractTest
|
|||
throw new RuntimeException(x);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Wrapper implements Session.Listener
|
||||
{
|
||||
private final Session.Listener listener;
|
||||
|
||||
private Wrapper(Session.Listener listener)
|
||||
{
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, Integer> onPreface(Session session)
|
||||
{
|
||||
return listener.onPreface(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
return listener.onNewStream(stream, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSettings(Session session, SettingsFrame frame)
|
||||
{
|
||||
listener.onSettings(session, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPing(Session session, PingFrame frame)
|
||||
{
|
||||
listener.onPing(session, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReset(Session session, ResetFrame frame)
|
||||
{
|
||||
listener.onReset(session, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Session session, GoAwayFrame frame)
|
||||
{
|
||||
listener.onClose(session, frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onIdleTimeout(Session session)
|
||||
{
|
||||
return listener.onIdleTimeout(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Session session, Throwable failure)
|
||||
{
|
||||
listener.onFailure(session, failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
|
|||
private int initialStreamRecvWindow = 512 * 1024;
|
||||
private int maxConcurrentStreams = 128;
|
||||
private int maxHeaderBlockFragment = 0;
|
||||
private int maxSettingsKeys = SettingsFrame.DEFAULT_MAX_KEYS;
|
||||
private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
|
||||
private long streamIdleTimeout;
|
||||
|
||||
|
@ -145,6 +146,17 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
|
|||
this.streamIdleTimeout = streamIdleTimeout;
|
||||
}
|
||||
|
||||
@ManagedAttribute("The max number of keys in all SETTINGS frames")
|
||||
public int getMaxSettingsKeys()
|
||||
{
|
||||
return maxSettingsKeys;
|
||||
}
|
||||
|
||||
public void setMaxSettingsKeys(int maxSettingsKeys)
|
||||
{
|
||||
this.maxSettingsKeys = maxSettingsKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return -1
|
||||
* @deprecated feature removed, no replacement
|
||||
|
@ -205,6 +217,8 @@ public abstract class AbstractHTTP2ServerConnectionFactory extends AbstractConne
|
|||
session.setWriteThreshold(getHttpConfiguration().getOutputBufferSize());
|
||||
|
||||
ServerParser parser = newServerParser(connector, session);
|
||||
parser.setMaxSettingsKeys(getMaxSettingsKeys());
|
||||
|
||||
HTTP2Connection connection = new HTTP2ServerConnection(connector.getByteBufferPool(), connector.getExecutor(),
|
||||
endPoint, httpConfiguration, parser, session, getInputBufferSize(), listener);
|
||||
connection.addListener(connectionListener);
|
||||
|
|
|
@ -263,7 +263,7 @@ public abstract class AbstractConnection implements Connection
|
|||
@Override
|
||||
public final String toString()
|
||||
{
|
||||
return String.format("%s<-%s",toConnectionString(),getEndPoint());
|
||||
return String.format("%s@%h::%s",getClass().getSimpleName(),this,getEndPoint());
|
||||
}
|
||||
|
||||
public String toConnectionString()
|
||||
|
|
|
@ -101,7 +101,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
_selectorManager.execute(_strategy::produce);
|
||||
|
||||
// Set started only if we really are started
|
||||
submit(s->_started.set(true));
|
||||
submit(s->_started.set(true));
|
||||
}
|
||||
|
||||
public int size()
|
||||
|
@ -130,7 +130,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
stop_selector._stopped.await();
|
||||
}
|
||||
|
||||
super.doStop();
|
||||
super.doStop();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -158,11 +158,30 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
if (selector != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("wakeup on submit {}", this);
|
||||
LOG.debug("Wakeup on submit {}", this);
|
||||
selector.wakeup();
|
||||
}
|
||||
}
|
||||
|
||||
private void wakeup()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Wakeup {}", this);
|
||||
|
||||
Selector selector = null;
|
||||
synchronized (ManagedSelector.this)
|
||||
{
|
||||
if (_selecting)
|
||||
{
|
||||
selector = _selector;
|
||||
_selecting = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (selector != null)
|
||||
selector.wakeup();
|
||||
}
|
||||
|
||||
private void execute(Runnable task)
|
||||
{
|
||||
try
|
||||
|
@ -236,6 +255,10 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
|||
|
||||
public void destroyEndPoint(final EndPoint endPoint)
|
||||
{
|
||||
// Waking up the selector is necessary to clean the
|
||||
// cancelled-key set and tell the TCP stack that the
|
||||
// socket is closed (so that senders receive RST).
|
||||
wakeup();
|
||||
execute(new DestroyEndPoint(endPoint));
|
||||
}
|
||||
|
||||
|
|
|
@ -43,6 +43,8 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
|||
private final ByteBufferPool byteBufferPool;
|
||||
private final Executor executor;
|
||||
private final ClientConnectionFactory connectionFactory;
|
||||
private boolean _directBuffersForEncryption = true;
|
||||
private boolean _directBuffersForDecryption = true;
|
||||
private boolean allowMissingCloseMessage = true;
|
||||
|
||||
public SslClientConnectionFactory(SslContextFactory sslContextFactory, ByteBufferPool byteBufferPool, Executor executor, ClientConnectionFactory connectionFactory)
|
||||
|
@ -53,6 +55,26 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
|||
this.connectionFactory = connectionFactory;
|
||||
}
|
||||
|
||||
public void setDirectBuffersForEncryption(boolean useDirectBuffers)
|
||||
{
|
||||
this._directBuffersForEncryption = useDirectBuffers;
|
||||
}
|
||||
|
||||
public void setDirectBuffersForDecryption(boolean useDirectBuffers)
|
||||
{
|
||||
this._directBuffersForDecryption = useDirectBuffers;
|
||||
}
|
||||
|
||||
public boolean isDirectBuffersForDecryption()
|
||||
{
|
||||
return _directBuffersForDecryption;
|
||||
}
|
||||
|
||||
public boolean isDirectBuffersForEncryption()
|
||||
{
|
||||
return _directBuffersForEncryption;
|
||||
}
|
||||
|
||||
public boolean isAllowMissingCloseMessage()
|
||||
{
|
||||
return allowMissingCloseMessage;
|
||||
|
@ -85,7 +107,7 @@ public class SslClientConnectionFactory implements ClientConnectionFactory
|
|||
|
||||
protected SslConnection newSslConnection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, SSLEngine engine)
|
||||
{
|
||||
return new SslConnection(byteBufferPool, executor, endPoint, engine);
|
||||
return new SslConnection(byteBufferPool, executor, endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -47,6 +47,7 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
|||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
import org.eclipse.jetty.util.thread.TimerScheduler;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
|
@ -55,6 +56,7 @@ import org.junit.Test;
|
|||
|
||||
public class SslConnectionTest
|
||||
{
|
||||
private final static int TIMEOUT = 1000000;
|
||||
private static SslContextFactory __sslCtxFactory=new SslContextFactory();
|
||||
private static ByteBufferPool __byteBufferPool = new LeakTrackingByteBufferPool(new MappedByteBufferPool.Tagged());
|
||||
|
||||
|
@ -94,7 +96,7 @@ public class SslConnectionTest
|
|||
protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey)
|
||||
{
|
||||
SocketChannelEndPoint endp = new TestEP(channel, selector, selectionKey, getScheduler());
|
||||
endp.setIdleTimeout(60000);
|
||||
endp.setIdleTimeout(TIMEOUT);
|
||||
_lastEndp=endp;
|
||||
return endp;
|
||||
}
|
||||
|
@ -141,6 +143,12 @@ public class SslConnectionTest
|
|||
__sslCtxFactory.start();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void stopSsl() throws Exception
|
||||
{
|
||||
__sslCtxFactory.stop();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void startManager() throws Exception
|
||||
{
|
||||
|
@ -257,12 +265,12 @@ public class SslConnectionTest
|
|||
{
|
||||
try (Socket client = newClient())
|
||||
{
|
||||
client.setSoTimeout(60000);
|
||||
client.setSoTimeout(TIMEOUT);
|
||||
try (SocketChannel server = _connector.accept())
|
||||
{
|
||||
server.configureBlocking(false);
|
||||
_manager.accept(server);
|
||||
|
||||
|
||||
client.getOutputStream().write("Hello".getBytes(StandardCharsets.UTF_8));
|
||||
byte[] buffer = new byte[1024];
|
||||
int len = client.getInputStream().read(buffer);
|
||||
|
@ -283,7 +291,7 @@ public class SslConnectionTest
|
|||
{
|
||||
try (SSLSocket client = newClient())
|
||||
{
|
||||
client.setSoTimeout(60000);
|
||||
client.setSoTimeout(TIMEOUT);
|
||||
try (SocketChannel server = _connector.accept())
|
||||
{
|
||||
server.configureBlocking(false);
|
||||
|
@ -312,7 +320,7 @@ public class SslConnectionTest
|
|||
|
||||
try (SSLSocket client = newClient())
|
||||
{
|
||||
client.setSoTimeout(60000);
|
||||
client.setSoTimeout(TIMEOUT);
|
||||
try (SocketChannel server = _connector.accept())
|
||||
{
|
||||
server.configureBlocking(false);
|
||||
|
@ -348,7 +356,7 @@ public class SslConnectionTest
|
|||
|
||||
try (SSLSocket client = newClient())
|
||||
{
|
||||
client.setSoTimeout(60000);
|
||||
client.setSoTimeout(TIMEOUT);
|
||||
try (SocketChannel server = _connector.accept())
|
||||
{
|
||||
server.configureBlocking(false);
|
||||
|
@ -396,18 +404,23 @@ public class SslConnectionTest
|
|||
_testFill=false;
|
||||
_writeCallback = new FutureCallback();
|
||||
|
||||
try (Socket client = newClient())
|
||||
try (SSLSocket client = newClient())
|
||||
{
|
||||
client.setSoTimeout(10000);
|
||||
client.setSoTimeout(TIMEOUT);
|
||||
try (SocketChannel server = _connector.accept())
|
||||
{
|
||||
server.configureBlocking(false);
|
||||
_manager.accept(server);
|
||||
|
||||
// The server side will write something, and in order
|
||||
// to proceed with the initial TLS handshake we need
|
||||
// to start reading before waiting for the callback.
|
||||
|
||||
byte[] buffer = new byte[1024];
|
||||
int len = client.getInputStream().read(buffer);
|
||||
Assert.assertEquals("Hello Client", new String(buffer, 0, len, StandardCharsets.UTF_8));
|
||||
Assert.assertNull(_writeCallback.get(100, TimeUnit.MILLISECONDS));
|
||||
|
||||
Assert.assertNull(_writeCallback.get(1, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<Set name="outputBufferSize">32768</Set>
|
||||
<Set name="requestHeaderSize">8192</Set>
|
||||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
</New>
|
||||
|
||||
<Call name="addConnector">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<Set name="outputBufferSize">32768</Set>
|
||||
<Set name="requestHeaderSize">8192</Set>
|
||||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
</New>
|
||||
|
||||
<Call name="addConnector">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<Set name="outputBufferSize">32768</Set>
|
||||
<Set name="requestHeaderSize">8192</Set>
|
||||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
</New>
|
||||
|
||||
<Call name="addConnector">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<Set name="outputBufferSize">32768</Set>
|
||||
<Set name="requestHeaderSize">8192</Set>
|
||||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
</New>
|
||||
|
||||
<Call name="addConnector">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<Set name="outputBufferSize">32768</Set>
|
||||
<Set name="requestHeaderSize">8192</Set>
|
||||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
</New>
|
||||
|
||||
<Call name="addConnector">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<Set name="outputBufferSize">32768</Set>
|
||||
<Set name="requestHeaderSize">8192</Set>
|
||||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
</New>
|
||||
|
||||
<Call name="addConnector">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<Set name="outputBufferSize">32768</Set>
|
||||
<Set name="requestHeaderSize">8192</Set>
|
||||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
</New>
|
||||
|
||||
<Call name="addConnector">
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<Set name="outputBufferSize">32768</Set>
|
||||
<Set name="requestHeaderSize">8192</Set>
|
||||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
</New>
|
||||
|
||||
<Call name="addConnector">
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="sendServerVersion">true</Set>
|
||||
<Set name="sendDateHeader">false</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
</New>
|
||||
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="sendServerVersion">true</Set>
|
||||
<Set name="sendDateHeader">false</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
</New>
|
||||
|
||||
<!-- =========================================================== -->
|
||||
|
|
|
@ -59,7 +59,7 @@
|
|||
<Set name="responseHeaderSize"><Property name="jetty.httpConfig.responseHeaderSize" deprecated="jetty.response.header.size" default="8192" /></Set>
|
||||
<Set name="sendServerVersion"><Property name="jetty.httpConfig.sendServerVersion" deprecated="jetty.send.server.version" default="true" /></Set>
|
||||
<Set name="sendDateHeader"><Property name="jetty.httpConfig.sendDateHeader" deprecated="jetty.send.date.header" default="false" /></Set>
|
||||
<Set name="headerCacheSize"><Property name="jetty.httpConfig.headerCacheSize" default="512" /></Set>
|
||||
<Set name="headerCacheSize"><Property name="jetty.httpConfig.headerCacheSize" default="4096" /></Set>
|
||||
<Set name="delayDispatchUntilContent"><Property name="jetty.httpConfig.delayDispatchUntilContent" deprecated="jetty.delayDispatchUntilContent" default="true"/></Set>
|
||||
<Set name="maxErrorDispatches"><Property name="jetty.httpConfig.maxErrorDispatches" default="10"/></Set>
|
||||
<Set name="blockingTimeout"><Property deprecated="jetty.httpConfig.blockingTimeout" name="jetty.httpConfig.blockingTimeout.DEPRECATED" default="-1"/></Set>
|
||||
|
|
|
@ -51,7 +51,7 @@ etc/jetty.xml
|
|||
# jetty.httpConfig.sendDateHeader=false
|
||||
|
||||
## Max per-connection header cache size (in nodes)
|
||||
# jetty.httpConfig.headerCacheSize=512
|
||||
# jetty.httpConfig.headerCacheSize=4096
|
||||
|
||||
## Whether, for requests with content, delay dispatch until some content has arrived
|
||||
# jetty.httpConfig.delayDispatchUntilContent=true
|
||||
|
|
|
@ -57,7 +57,7 @@ public class HttpConfiguration
|
|||
private int _outputAggregationSize=_outputBufferSize/4;
|
||||
private int _requestHeaderSize=8*1024;
|
||||
private int _responseHeaderSize=8*1024;
|
||||
private int _headerCacheSize=512;
|
||||
private int _headerCacheSize=4*1024;
|
||||
private int _securePort;
|
||||
private long _idleTimeout=-1;
|
||||
private long _blockingTimeout=-1;
|
||||
|
|
|
@ -37,6 +37,8 @@ public class SslConnectionFactory extends AbstractConnectionFactory
|
|||
{
|
||||
private final SslContextFactory _sslContextFactory;
|
||||
private final String _nextProtocol;
|
||||
private boolean _directBuffersForEncryption = false;
|
||||
private boolean _directBuffersForDecryption = false;
|
||||
|
||||
public SslConnectionFactory()
|
||||
{
|
||||
|
@ -61,6 +63,26 @@ public class SslConnectionFactory extends AbstractConnectionFactory
|
|||
return _sslContextFactory;
|
||||
}
|
||||
|
||||
public void setDirectBuffersForEncryption(boolean useDirectBuffers)
|
||||
{
|
||||
this._directBuffersForEncryption = useDirectBuffers;
|
||||
}
|
||||
|
||||
public void setDirectBuffersForDecryption(boolean useDirectBuffers)
|
||||
{
|
||||
this._directBuffersForDecryption = useDirectBuffers;
|
||||
}
|
||||
|
||||
public boolean isDirectBuffersForDecryption()
|
||||
{
|
||||
return _directBuffersForDecryption;
|
||||
}
|
||||
|
||||
public boolean isDirectBuffersForEncryption()
|
||||
{
|
||||
return _directBuffersForEncryption;
|
||||
}
|
||||
|
||||
public String getNextProtocol()
|
||||
{
|
||||
return _nextProtocol;
|
||||
|
@ -100,7 +122,7 @@ public class SslConnectionFactory extends AbstractConnectionFactory
|
|||
|
||||
protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine)
|
||||
{
|
||||
return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine);
|
||||
return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -584,7 +584,7 @@ public class StatisticsHandler extends HandlerWrapper implements Graceful
|
|||
FutureCallback shutdown=new FutureCallback(false);
|
||||
_shutdown.compareAndSet(null,shutdown);
|
||||
shutdown=_shutdown.get();
|
||||
if (_dispatchedStats.getCurrent()==0)
|
||||
if (_requestStats.getCurrent()==0)
|
||||
shutdown.succeeded();
|
||||
return shutdown;
|
||||
}
|
||||
|
|
|
@ -1654,7 +1654,7 @@ public class SessionHandler extends ScopedHandler
|
|||
}
|
||||
}
|
||||
|
||||
if (requested_session_id == null || session == null)
|
||||
if (isUsingURLs() && (requested_session_id == null || session == null))
|
||||
{
|
||||
String uri = request.getRequestURI();
|
||||
|
||||
|
|
|
@ -1170,7 +1170,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
InputStream is = client.getInputStream();
|
||||
|
||||
os.write((
|
||||
"POST /echo?charset=utf-8 HTTP/1.1\r\n" +
|
||||
"POST /echo/0?charset=utf-8 HTTP/1.1\r\n" +
|
||||
"host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
|
||||
"content-type: text/plain; charset=utf-8\r\n" +
|
||||
"content-length: 10\r\n" +
|
||||
|
@ -1181,7 +1181,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
).getBytes("utf-8"));
|
||||
|
||||
os.write((
|
||||
"POST /echo?charset=utf-8 HTTP/1.1\r\n" +
|
||||
"POST /echo/1?charset=utf-8 HTTP/1.1\r\n" +
|
||||
"host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
|
||||
"content-type: text/plain; charset=utf-8\r\n" +
|
||||
"content-length: 10\r\n" +
|
||||
|
@ -1195,7 +1195,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
String content = "Wibble";
|
||||
byte[] contentB = content.getBytes(StandardCharsets.UTF_16);
|
||||
os.write((
|
||||
"POST /echo?charset=utf-8 HTTP/1.1\r\n" +
|
||||
"POST /echo/2?charset=utf-8 HTTP/1.1\r\n" +
|
||||
"host: " + _serverURI.getHost() + ":" + _serverURI.getPort() + "\r\n" +
|
||||
"content-type: text/plain; charset=utf-16\r\n" +
|
||||
"content-length: " + contentB.length + "\r\n" +
|
||||
|
|
|
@ -35,6 +35,7 @@ import org.eclipse.jetty.server.handler.AbstractHandler;
|
|||
import org.eclipse.jetty.server.handler.HotSwapHandler;
|
||||
import org.eclipse.jetty.toolchain.test.PropertyFlag;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
@ -114,6 +115,7 @@ public class HttpServerTestFixture
|
|||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||
{
|
||||
Log.getRootLogger().info("handle "+target);
|
||||
baseRequest.setHandled(true);
|
||||
|
||||
if (request.getContentType()!=null)
|
||||
|
@ -154,6 +156,8 @@ public class HttpServerTestFixture
|
|||
|
||||
if (reader.read()>=0)
|
||||
throw new IllegalStateException("Not closed");
|
||||
|
||||
Log.getRootLogger().info("handled "+target);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.server.handler;
|
|||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
|
@ -31,6 +32,7 @@ import javax.servlet.ServletException;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.io.ConnectionStatistics;
|
||||
import org.eclipse.jetty.server.LocalConnector;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
|
@ -41,6 +43,7 @@ import org.junit.Test;
|
|||
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
@ -85,7 +88,7 @@ public class StatisticsHandlerTest
|
|||
_statsHandler.setHandler(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
|
||||
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException
|
||||
{
|
||||
request.setHandled(true);
|
||||
try
|
||||
|
@ -97,7 +100,7 @@ public class StatisticsHandlerTest
|
|||
catch (Exception x)
|
||||
{
|
||||
Thread.currentThread().interrupt();
|
||||
throw (IOException)new IOException().initCause(x);
|
||||
throw new IOException(x);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -182,7 +185,7 @@ public class StatisticsHandlerTest
|
|||
_statsHandler.setHandler(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
|
||||
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException
|
||||
{
|
||||
request.setHandled(true);
|
||||
try
|
||||
|
@ -193,7 +196,7 @@ public class StatisticsHandlerTest
|
|||
catch (Exception x)
|
||||
{
|
||||
Thread.currentThread().interrupt();
|
||||
throw (IOException)new IOException().initCause(x);
|
||||
throw new IOException(x);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -245,7 +248,7 @@ public class StatisticsHandlerTest
|
|||
_statsHandler.setHandler(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
|
||||
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException
|
||||
{
|
||||
request.setHandled(true);
|
||||
try
|
||||
|
@ -306,22 +309,22 @@ public class StatisticsHandlerTest
|
|||
asyncHolder.get().addListener(new AsyncListener()
|
||||
{
|
||||
@Override
|
||||
public void onTimeout(AsyncEvent event) throws IOException
|
||||
public void onTimeout(AsyncEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartAsync(AsyncEvent event) throws IOException
|
||||
public void onStartAsync(AsyncEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(AsyncEvent event) throws IOException
|
||||
public void onError(AsyncEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(AsyncEvent event) throws IOException
|
||||
public void onComplete(AsyncEvent event)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -375,7 +378,7 @@ public class StatisticsHandlerTest
|
|||
_statsHandler.setHandler(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
|
||||
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException
|
||||
{
|
||||
request.setHandled(true);
|
||||
try
|
||||
|
@ -428,23 +431,23 @@ public class StatisticsHandlerTest
|
|||
asyncHolder.get().addListener(new AsyncListener()
|
||||
{
|
||||
@Override
|
||||
public void onTimeout(AsyncEvent event) throws IOException
|
||||
public void onTimeout(AsyncEvent event)
|
||||
{
|
||||
event.getAsyncContext().complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartAsync(AsyncEvent event) throws IOException
|
||||
public void onStartAsync(AsyncEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(AsyncEvent event) throws IOException
|
||||
public void onError(AsyncEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(AsyncEvent event) throws IOException
|
||||
public void onComplete(AsyncEvent event)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -491,7 +494,7 @@ public class StatisticsHandlerTest
|
|||
_statsHandler.setHandler(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException
|
||||
public void handle(String path, Request request, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException
|
||||
{
|
||||
request.setHandled(true);
|
||||
try
|
||||
|
@ -550,22 +553,22 @@ public class StatisticsHandlerTest
|
|||
asyncHolder.get().addListener(new AsyncListener()
|
||||
{
|
||||
@Override
|
||||
public void onTimeout(AsyncEvent event) throws IOException
|
||||
public void onTimeout(AsyncEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartAsync(AsyncEvent event) throws IOException
|
||||
public void onStartAsync(AsyncEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(AsyncEvent event) throws IOException
|
||||
public void onError(AsyncEvent event)
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(AsyncEvent event) throws IOException
|
||||
public void onComplete(AsyncEvent event)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -601,6 +604,53 @@ public class StatisticsHandlerTest
|
|||
assertEquals(_statsHandler.getDispatchedTimeTotal(), _statsHandler.getDispatchedTimeMean(), 0.01);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncRequestWithShutdown() throws Exception
|
||||
{
|
||||
long delay = 500;
|
||||
CountDownLatch serverLatch = new CountDownLatch(1);
|
||||
_statsHandler.setHandler(new AbstractHandler()
|
||||
{
|
||||
@Override
|
||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
|
||||
{
|
||||
AsyncContext asyncContext = request.startAsync();
|
||||
asyncContext.setTimeout(0);
|
||||
new Thread(() ->
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(delay);
|
||||
asyncContext.complete();
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
|
||||
asyncContext.complete();
|
||||
}
|
||||
}).start();
|
||||
serverLatch.countDown();
|
||||
}
|
||||
});
|
||||
_server.start();
|
||||
|
||||
String request = "GET / HTTP/1.1\r\n" +
|
||||
"Host: localhost\r\n" +
|
||||
"\r\n";
|
||||
_connector.executeRequest(request);
|
||||
|
||||
assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
Future<Void> shutdown = _statsHandler.shutdown();
|
||||
assertFalse(shutdown.isDone());
|
||||
|
||||
Thread.sleep(delay / 2);
|
||||
assertFalse(shutdown.isDone());
|
||||
|
||||
Thread.sleep(delay);
|
||||
assertTrue(shutdown.isDone());
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler is external to the statistics handler and it is used to ensure that statistics handler's
|
||||
* handle() is fully executed before asserting its values in the tests, to avoid race conditions with the
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
||||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.server.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.io.ssl.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.server.ConnectionLimit.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.server.AcceptRateLimit.LEVEL=DEBUG
|
|
@ -0,0 +1,5 @@
|
|||
[files]
|
||||
maven://org.mortbay.jetty.alpn/alpn-boot/8.1.12.v20180117|lib/alpn/alpn-boot-8.1.12.v20180117.jar
|
||||
|
||||
[exec]
|
||||
-Xbootclasspath/p:lib/alpn/alpn-boot-8.1.12.v20180117.jar
|
|
@ -23,7 +23,7 @@
|
|||
<instructions>
|
||||
<Bundle-Description>Websocket Servlet Interface</Bundle-Description>
|
||||
<Bundle-Classpath />
|
||||
<DynamicImport-Package>org.eclipse.jetty.websocket.server.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}",org.eclipse.jetty.websocket.server.pathmap.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}"</DynamicImport-Package>
|
||||
<DynamicImport-Package>org.eclipse.jetty.websocket.server;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}",org.eclipse.jetty.websocket.server.pathmap.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}"</DynamicImport-Package>
|
||||
</instructions>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
|
12
pom.xml
12
pom.xml
|
@ -1704,6 +1704,18 @@
|
|||
<alpn.version>8.1.12.v20180117</alpn.version>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>8u181</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>java.version</name>
|
||||
<value>1.8.0_181</value>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<alpn.version>8.1.12.v20180117</alpn.version>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>jdk9</id>
|
||||
<activation>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="sendServerVersion">true</Set>
|
||||
<Set name="sendDateHeader">false</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
|
||||
<!-- Uncomment to enable handling of X-Forwarded- style headers
|
||||
<Call name="addCustomizer">
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<Set name="responseHeaderSize">8192</Set>
|
||||
<Set name="sendServerVersion">true</Set>
|
||||
<Set name="sendDateHeader">false</Set>
|
||||
<Set name="headerCacheSize">512</Set>
|
||||
<Set name="headerCacheSize">4096</Set>
|
||||
|
||||
<!-- Uncomment to enable handling of X-Forwarded- style headers
|
||||
<Call name="addCustomizer">
|
||||
|
|
Loading…
Reference in New Issue