Merge branch 'jetty-9.4.x' into bugfix/javadoc_fixes_2056
This commit is contained in:
commit
88cf4603c5
10
VERSION.txt
10
VERSION.txt
|
@ -171,6 +171,16 @@ jetty-9.4.7.v20170914 - 14 September 2017
|
||||||
+ 475546 ClosedChannelException when connection to HTTPS over HTTP proxy with
|
+ 475546 ClosedChannelException when connection to HTTPS over HTTP proxy with
|
||||||
CONNECT
|
CONNECT
|
||||||
|
|
||||||
|
jetty-9.2.23.v20171218 - 18 December 2017
|
||||||
|
+ 1556 Remove a timing channel in Password matching
|
||||||
|
+ 1685 Update ALPN support for Java 8u141
|
||||||
|
+ 1702 Update ALPN support for Java 8u144
|
||||||
|
+ 1914 HttpClient fails to parse Content-Type response header with RFC 2045
|
||||||
|
charset="utf-8" syntax
|
||||||
|
+ 2065 Backport #347 to Jetty 9.2.x
|
||||||
|
+ 475546 ClosedChannelException when connecting to HTTPS over HTTP proxy with
|
||||||
|
CONNECT
|
||||||
|
|
||||||
jetty-9.3.22.v20171030 - 30 October 2017
|
jetty-9.3.22.v20171030 - 30 October 2017
|
||||||
+ 1213 Upgrade to ASM Version 6.0_ALPHA for JDK9
|
+ 1213 Upgrade to ASM Version 6.0_ALPHA for JDK9
|
||||||
+ 1692 Annotation scanning should ignore `module-info.class` files
|
+ 1692 Annotation scanning should ignore `module-info.class` files
|
||||||
|
|
|
@ -156,11 +156,6 @@
|
||||||
<artifactId>cdi-core</artifactId>
|
<artifactId>cdi-core</artifactId>
|
||||||
<version>9.4.9-SNAPSHOT</version>
|
<version>9.4.9-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.eclipse.jetty.cdi</groupId>
|
|
||||||
<artifactId>cdi-full-servlet</artifactId>
|
|
||||||
<version>9.4.9-SNAPSHOT</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.cdi</groupId>
|
<groupId>org.eclipse.jetty.cdi</groupId>
|
||||||
<artifactId>cdi-servlet</artifactId>
|
<artifactId>cdi-servlet</artifactId>
|
||||||
|
@ -206,6 +201,13 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-home</artifactId>
|
<artifactId>jetty-home</artifactId>
|
||||||
<version>9.4.9-SNAPSHOT</version>
|
<version>9.4.9-SNAPSHOT</version>
|
||||||
|
<type>zip</type>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-home</artifactId>
|
||||||
|
<version>9.4.9-SNAPSHOT</version>
|
||||||
|
<type>tar.gz</type>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
|
|
@ -27,6 +27,7 @@ import java.util.regex.Pattern;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Authentication;
|
import org.eclipse.jetty.client.api.Authentication;
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
|
import org.eclipse.jetty.client.api.ContentProvider;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
@ -86,7 +87,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
||||||
{
|
{
|
||||||
HttpRequest request = (HttpRequest)result.getRequest();
|
HttpRequest request = (HttpRequest)result.getRequest();
|
||||||
ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
|
ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
|
||||||
if (result.isFailed())
|
if (result.getResponseFailure() != null)
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Authentication challenge failed {}", result.getFailure());
|
LOG.debug("Authentication challenge failed {}", result.getFailure());
|
||||||
|
@ -98,7 +99,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
||||||
HttpConversation conversation = request.getConversation();
|
HttpConversation conversation = request.getConversation();
|
||||||
if (conversation.getAttribute(authenticationAttribute) != null)
|
if (conversation.getAttribute(authenticationAttribute) != null)
|
||||||
{
|
{
|
||||||
// We have already tried to authenticate, but we failed again
|
// We have already tried to authenticate, but we failed again.
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Bad credentials for {}", request);
|
LOG.debug("Bad credentials for {}", request);
|
||||||
forwardSuccessComplete(request, response);
|
forwardSuccessComplete(request, response);
|
||||||
|
@ -111,7 +112,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Authentication challenge without {} header", header);
|
LOG.debug("Authentication challenge without {} header", header);
|
||||||
forwardFailureComplete(request, null, response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response));
|
forwardFailureComplete(request, result.getRequestFailure(), response, new HttpResponseException("HTTP protocol violation: Authentication challenge without " + header + " header", response));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,9 +139,18 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentProvider requestContent = request.getContent();
|
||||||
|
if (requestContent != null && !requestContent.isReproducible())
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Request content not reproducible for {}", request);
|
||||||
|
forwardSuccessComplete(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
|
Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Authentication result {}", authnResult);
|
LOG.debug("Authentication result {}", authnResult);
|
||||||
if (authnResult == null)
|
if (authnResult == null)
|
||||||
|
|
|
@ -95,6 +95,11 @@ public abstract class HttpReceiver
|
||||||
return channel.getHttpDestination();
|
return channel.getHttpDestination();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFailed()
|
||||||
|
{
|
||||||
|
return responseState.get() == ResponseState.FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method to be invoked when the response status code is available.
|
* Method to be invoked when the response status code is available.
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -86,6 +86,11 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
|
||||||
return channel.getHttpExchange();
|
return channel.getHttpExchange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFailed()
|
||||||
|
{
|
||||||
|
return requestState.get() == RequestState.FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContent()
|
public void onContent()
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.io.Closeable;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
|
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
|
||||||
import org.eclipse.jetty.client.util.PathContentProvider;
|
import org.eclipse.jetty.client.util.PathContentProvider;
|
||||||
|
|
||||||
|
@ -48,6 +49,22 @@ public interface ContentProvider extends Iterable<ByteBuffer>
|
||||||
*/
|
*/
|
||||||
long getLength();
|
long getLength();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Whether this ContentProvider can produce exactly the same content more
|
||||||
|
* than once.</p>
|
||||||
|
* <p>Implementations should return {@code true} only if the content can be
|
||||||
|
* produced more than once, which means that invocations to {@link #iterator()}
|
||||||
|
* must return a new, independent, iterator instance over the content.</p>
|
||||||
|
* <p>The {@link HttpClient} implementation may use this method in particular
|
||||||
|
* cases where it detects that it is safe to retry a request that failed.</p>
|
||||||
|
*
|
||||||
|
* @return whether the content can be produced more than once
|
||||||
|
*/
|
||||||
|
default boolean isReproducible()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An extension of {@link ContentProvider} that provides a content type string
|
* An extension of {@link ContentProvider} that provides a content type string
|
||||||
* to be used as a {@code Content-Type} HTTP header in requests.
|
* to be used as a {@code Content-Type} HTTP header in requests.
|
||||||
|
|
|
@ -57,6 +57,12 @@ public class ByteBufferContentProvider extends AbstractTypedContentProvider
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReproducible()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<ByteBuffer> iterator()
|
public Iterator<ByteBuffer> iterator()
|
||||||
{
|
{
|
||||||
|
@ -85,12 +91,6 @@ public class ByteBufferContentProvider extends AbstractTypedContentProvider
|
||||||
throw new NoSuchElementException();
|
throw new NoSuchElementException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove()
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,12 @@ public class BytesContentProvider extends AbstractTypedContentProvider
|
||||||
return length;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReproducible()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<ByteBuffer> iterator()
|
public Iterator<ByteBuffer> iterator()
|
||||||
{
|
{
|
||||||
|
@ -78,12 +84,6 @@ public class BytesContentProvider extends AbstractTypedContentProvider
|
||||||
throw new NoSuchElementException();
|
throw new NoSuchElementException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove()
|
|
||||||
{
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,12 @@ public class PathContentProvider extends AbstractTypedContentProvider
|
||||||
return fileSize;
|
return fileSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReproducible()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public ByteBufferPool getByteBufferPool()
|
public ByteBufferPool getByteBufferPool()
|
||||||
{
|
{
|
||||||
return bufferPool;
|
return bufferPool;
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
import org.eclipse.jetty.toolchain.test.TestTracker;
|
import org.eclipse.jetty.toolchain.test.TestTracker;
|
||||||
|
import org.eclipse.jetty.util.SocketAddressResolver;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
@ -96,6 +97,7 @@ public abstract class AbstractHttpClientServerTest
|
||||||
clientThreads.setName("client");
|
clientThreads.setName("client");
|
||||||
client = new HttpClient(transport, sslContextFactory);
|
client = new HttpClient(transport, sslContextFactory);
|
||||||
client.setExecutor(clientThreads);
|
client.setExecutor(clientThreads);
|
||||||
|
client.setSocketAddressResolver(new SocketAddressResolver.Sync());
|
||||||
client.start();
|
client.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,14 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -34,6 +37,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.Authentication;
|
import org.eclipse.jetty.client.api.Authentication;
|
||||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||||
|
import org.eclipse.jetty.client.api.ContentProvider;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Request;
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
|
@ -41,6 +45,7 @@ import org.eclipse.jetty.client.api.Result;
|
||||||
import org.eclipse.jetty.client.util.BasicAuthentication;
|
import org.eclipse.jetty.client.util.BasicAuthentication;
|
||||||
import org.eclipse.jetty.client.util.DeferredContentProvider;
|
import org.eclipse.jetty.client.util.DeferredContentProvider;
|
||||||
import org.eclipse.jetty.client.util.DigestAuthentication;
|
import org.eclipse.jetty.client.util.DigestAuthentication;
|
||||||
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
import org.eclipse.jetty.security.Authenticator;
|
import org.eclipse.jetty.security.Authenticator;
|
||||||
import org.eclipse.jetty.security.ConstraintMapping;
|
import org.eclipse.jetty.security.ConstraintMapping;
|
||||||
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
import org.eclipse.jetty.security.ConstraintSecurityHandler;
|
||||||
|
@ -424,6 +429,40 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
|
||||||
Assert.assertEquals(1, requests.get());
|
Assert.assertEquals(1, requests.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test_NonReproducibleContent() throws Exception
|
||||||
|
{
|
||||||
|
startBasic(new EmptyServerHandler());
|
||||||
|
|
||||||
|
AuthenticationStore authenticationStore = client.getAuthenticationStore();
|
||||||
|
URI uri = URI.create(scheme + "://localhost:" + connector.getLocalPort());
|
||||||
|
BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "basic");
|
||||||
|
authenticationStore.addAuthentication(authentication);
|
||||||
|
|
||||||
|
CountDownLatch resultLatch = new CountDownLatch(1);
|
||||||
|
byte[] data = new byte[]{'h', 'e', 'l', 'l', 'o'};
|
||||||
|
DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(data))
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public boolean isReproducible()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Request request = client.newRequest(uri)
|
||||||
|
.path("/secure")
|
||||||
|
.content(content);
|
||||||
|
request.send(result ->
|
||||||
|
{
|
||||||
|
if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.UNAUTHORIZED_401)
|
||||||
|
resultLatch.countDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
content.close();
|
||||||
|
|
||||||
|
Assert.assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void test_RequestFailsAfterResponse() throws Exception
|
public void test_RequestFailsAfterResponse() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -434,32 +473,111 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
|
||||||
BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "basic");
|
BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "basic");
|
||||||
authenticationStore.addAuthentication(authentication);
|
authenticationStore.addAuthentication(authentication);
|
||||||
|
|
||||||
CountDownLatch successLatch = new CountDownLatch(1);
|
AtomicBoolean fail = new AtomicBoolean(true);
|
||||||
|
GeneratingContentProvider content = new GeneratingContentProvider(index ->
|
||||||
|
{
|
||||||
|
switch (index)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
return ByteBuffer.wrap(new byte[]{'h', 'e', 'l', 'l', 'o'});
|
||||||
|
case 1:
|
||||||
|
return ByteBuffer.wrap(new byte[]{'w', 'o', 'r', 'l', 'd'});
|
||||||
|
case 2:
|
||||||
|
if (fail.compareAndSet(true, false))
|
||||||
|
{
|
||||||
|
// Wait for the 401 response to arrive
|
||||||
|
// to the authentication protocol handler.
|
||||||
|
sleep(1000);
|
||||||
|
// Trigger request failure.
|
||||||
|
throw new RuntimeException();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
});
|
||||||
CountDownLatch resultLatch = new CountDownLatch(1);
|
CountDownLatch resultLatch = new CountDownLatch(1);
|
||||||
DeferredContentProvider content = new DeferredContentProvider();
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
Request request = client.newRequest("localhost", connector.getLocalPort())
|
|
||||||
.scheme(scheme)
|
.scheme(scheme)
|
||||||
.path("/secure")
|
.path("/secure")
|
||||||
.content(content)
|
.content(content)
|
||||||
.onResponseSuccess(response -> successLatch.countDown());
|
.send(result ->
|
||||||
request.send(result ->
|
|
||||||
{
|
{
|
||||||
if (result.isFailed() && result.getResponseFailure() == null)
|
if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.OK_200)
|
||||||
resultLatch.countDown();
|
resultLatch.countDown();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send some content to make sure the request is dispatched on the server.
|
|
||||||
content.offer(ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8)));
|
|
||||||
|
|
||||||
// Wait for the response to arrive to
|
|
||||||
// the authentication protocol handler.
|
|
||||||
Thread.sleep(1000);
|
|
||||||
|
|
||||||
// Trigger request failure.
|
|
||||||
request.abort(new Exception());
|
|
||||||
|
|
||||||
// Verify that the response was successful, it's the request that failed.
|
|
||||||
Assert.assertTrue(successLatch.await(5, TimeUnit.SECONDS));
|
|
||||||
Assert.assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sleep(long time)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(time);
|
||||||
|
}
|
||||||
|
catch (InterruptedException x)
|
||||||
|
{
|
||||||
|
throw new RuntimeException(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class GeneratingContentProvider implements ContentProvider
|
||||||
|
{
|
||||||
|
private static final ByteBuffer DONE = ByteBuffer.allocate(0);
|
||||||
|
|
||||||
|
private final IntFunction<ByteBuffer> generator;
|
||||||
|
|
||||||
|
private GeneratingContentProvider(IntFunction<ByteBuffer> generator)
|
||||||
|
{
|
||||||
|
this.generator = generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getLength()
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isReproducible()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<ByteBuffer> iterator()
|
||||||
|
{
|
||||||
|
return new Iterator<ByteBuffer>()
|
||||||
|
{
|
||||||
|
private int index;
|
||||||
|
public ByteBuffer current;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext()
|
||||||
|
{
|
||||||
|
if (current == null)
|
||||||
|
{
|
||||||
|
current = generator.apply(index++);
|
||||||
|
if (current == null)
|
||||||
|
current = DONE;
|
||||||
|
}
|
||||||
|
return current != DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuffer next()
|
||||||
|
{
|
||||||
|
ByteBuffer result = current;
|
||||||
|
current = null;
|
||||||
|
if (result == null)
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -263,10 +263,10 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
|
||||||
return new SslConnection(byteBufferPool, executor, endPoint, engine)
|
return new SslConnection(byteBufferPool, executor, endPoint, engine)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected boolean onReadTimeout()
|
protected boolean onReadTimeout(Throwable timeout)
|
||||||
{
|
{
|
||||||
sslIdle.set(true);
|
sslIdle.set(true);
|
||||||
return super.onReadTimeout();
|
return super.onReadTimeout(timeout);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,12 +126,12 @@ public class SslBytesServerTest extends SslBytesTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean onReadTimeout()
|
protected boolean onReadTimeout(Throwable timeout)
|
||||||
{
|
{
|
||||||
final Runnable idleHook = SslBytesServerTest.this.idleHook;
|
final Runnable idleHook = SslBytesServerTest.this.idleHook;
|
||||||
if (idleHook != null)
|
if (idleHook != null)
|
||||||
idleHook.run();
|
idleHook.run();
|
||||||
return super.onReadTimeout();
|
return super.onReadTimeout(timeout);
|
||||||
}
|
}
|
||||||
}, connector, endPoint);
|
}, connector, endPoint);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ What was in the Upgrade Request and Response.
|
||||||
UpgradeRequest req = session.getUpgradeRequest();
|
UpgradeRequest req = session.getUpgradeRequest();
|
||||||
String channelName = req.getParameterMap().get("channelName");
|
String channelName = req.getParameterMap().get("channelName");
|
||||||
|
|
||||||
UpgradeRespons resp = session.getUpgradeResponse();
|
UpgradeResponse resp = session.getUpgradeResponse();
|
||||||
String subprotocol = resp.getAcceptedSubProtocol();
|
String subprotocol = resp.getAcceptedSubProtocol();
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
|
@ -38,18 +38,17 @@ public class HttpChannelOverFCGI extends HttpChannel
|
||||||
{
|
{
|
||||||
private final HttpConnectionOverFCGI connection;
|
private final HttpConnectionOverFCGI connection;
|
||||||
private final Flusher flusher;
|
private final Flusher flusher;
|
||||||
private final int request;
|
|
||||||
private final HttpSenderOverFCGI sender;
|
private final HttpSenderOverFCGI sender;
|
||||||
private final HttpReceiverOverFCGI receiver;
|
private final HttpReceiverOverFCGI receiver;
|
||||||
private final FCGIIdleTimeout idle;
|
private final FCGIIdleTimeout idle;
|
||||||
|
private int request;
|
||||||
private HttpVersion version;
|
private HttpVersion version;
|
||||||
|
|
||||||
public HttpChannelOverFCGI(final HttpConnectionOverFCGI connection, Flusher flusher, int request, long idleTimeout)
|
public HttpChannelOverFCGI(final HttpConnectionOverFCGI connection, Flusher flusher, long idleTimeout)
|
||||||
{
|
{
|
||||||
super(connection.getHttpDestination());
|
super(connection.getHttpDestination());
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.flusher = flusher;
|
this.flusher = flusher;
|
||||||
this.request = request;
|
|
||||||
this.sender = new HttpSenderOverFCGI(this);
|
this.sender = new HttpSenderOverFCGI(this);
|
||||||
this.receiver = new HttpReceiverOverFCGI(this);
|
this.receiver = new HttpReceiverOverFCGI(this);
|
||||||
this.idle = new FCGIIdleTimeout(connection, idleTimeout);
|
this.idle = new FCGIIdleTimeout(connection, idleTimeout);
|
||||||
|
@ -60,6 +59,11 @@ public class HttpChannelOverFCGI extends HttpChannel
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setRequest(int request)
|
||||||
|
{
|
||||||
|
this.request = request;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected HttpSender getHttpSender()
|
protected HttpSender getHttpSender()
|
||||||
{
|
{
|
||||||
|
@ -72,6 +76,11 @@ public class HttpChannelOverFCGI extends HttpChannel
|
||||||
return receiver;
|
return receiver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFailed()
|
||||||
|
{
|
||||||
|
return sender.isFailed() || receiver.isFailed();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send()
|
public void send()
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,7 +23,9 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.AsynchronousCloseException;
|
import java.nio.channels.AsynchronousCloseException;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
@ -56,7 +58,8 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
private static final Logger LOG = Log.getLogger(HttpConnectionOverFCGI.class);
|
private static final Logger LOG = Log.getLogger(HttpConnectionOverFCGI.class);
|
||||||
|
|
||||||
private final LinkedList<Integer> requests = new LinkedList<>();
|
private final LinkedList<Integer> requests = new LinkedList<>();
|
||||||
private final Map<Integer, HttpChannelOverFCGI> channels = new ConcurrentHashMap<>();
|
private final Map<Integer, HttpChannelOverFCGI> activeChannels = new ConcurrentHashMap<>();
|
||||||
|
private final Queue<HttpChannelOverFCGI> idleChannels = new ConcurrentLinkedQueue<>();
|
||||||
private final AtomicBoolean closed = new AtomicBoolean();
|
private final AtomicBoolean closed = new AtomicBoolean();
|
||||||
private final HttpDestination destination;
|
private final HttpDestination destination;
|
||||||
private final Promise<Connection> promise;
|
private final Promise<Connection> promise;
|
||||||
|
@ -184,7 +187,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
{
|
{
|
||||||
// Close explicitly only if we are idle, since the request may still
|
// Close explicitly only if we are idle, since the request may still
|
||||||
// be in progress, otherwise close only if we can fail the responses.
|
// be in progress, otherwise close only if we can fail the responses.
|
||||||
if (channels.isEmpty())
|
if (activeChannels.isEmpty())
|
||||||
close();
|
close();
|
||||||
else
|
else
|
||||||
failAndClose(new EOFException(String.valueOf(getEndPoint())));
|
failAndClose(new EOFException(String.valueOf(getEndPoint())));
|
||||||
|
@ -204,9 +207,15 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
|
|
||||||
protected void release(HttpChannelOverFCGI channel)
|
protected void release(HttpChannelOverFCGI channel)
|
||||||
{
|
{
|
||||||
channels.remove(channel.getRequest());
|
if (activeChannels.remove(channel.getRequest()) != null)
|
||||||
|
{
|
||||||
|
channel.setRequest(0);
|
||||||
|
// Recycle only non-failed channels.
|
||||||
|
if (!channel.isFailed())
|
||||||
|
idleChannels.offer(channel);
|
||||||
destination.release(this);
|
destination.release(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close()
|
public void close()
|
||||||
|
@ -249,19 +258,20 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
|
|
||||||
protected void abort(Throwable failure)
|
protected void abort(Throwable failure)
|
||||||
{
|
{
|
||||||
for (HttpChannelOverFCGI channel : channels.values())
|
for (HttpChannelOverFCGI channel : activeChannels.values())
|
||||||
{
|
{
|
||||||
HttpExchange exchange = channel.getHttpExchange();
|
HttpExchange exchange = channel.getHttpExchange();
|
||||||
if (exchange != null)
|
if (exchange != null)
|
||||||
exchange.getRequest().abort(failure);
|
exchange.getRequest().abort(failure);
|
||||||
}
|
}
|
||||||
channels.clear();
|
activeChannels.clear();
|
||||||
|
idleChannels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void failAndClose(Throwable failure)
|
private void failAndClose(Throwable failure)
|
||||||
{
|
{
|
||||||
boolean result = false;
|
boolean result = false;
|
||||||
for (HttpChannelOverFCGI channel : channels.values())
|
for (HttpChannelOverFCGI channel : activeChannels.values())
|
||||||
result |= channel.responseFailure(failure);
|
result |= channel.responseFailure(failure);
|
||||||
if (result)
|
if (result)
|
||||||
close(failure);
|
close(failure);
|
||||||
|
@ -286,9 +296,18 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpChannelOverFCGI newHttpChannel(int id, Request request)
|
protected HttpChannelOverFCGI provideHttpChannel(int id, Request request)
|
||||||
{
|
{
|
||||||
return new HttpChannelOverFCGI(this, getFlusher(), id, request.getIdleTimeout());
|
HttpChannelOverFCGI channel = idleChannels.poll();
|
||||||
|
if (channel == null)
|
||||||
|
channel = newHttpChannel(request);
|
||||||
|
channel.setRequest(id);
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected HttpChannelOverFCGI newHttpChannel(Request request)
|
||||||
|
{
|
||||||
|
return new HttpChannelOverFCGI(this, getFlusher(), request.getIdleTimeout());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -314,10 +333,10 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
Request request = exchange.getRequest();
|
Request request = exchange.getRequest();
|
||||||
normalizeRequest(request);
|
normalizeRequest(request);
|
||||||
|
|
||||||
// FCGI may be multiplexed, so create one channel for each request.
|
// FCGI may be multiplexed, so one channel for each exchange.
|
||||||
int id = acquireRequest();
|
int id = acquireRequest();
|
||||||
HttpChannelOverFCGI channel = newHttpChannel(id, request);
|
HttpChannelOverFCGI channel = provideHttpChannel(id, request);
|
||||||
channels.put(id, channel);
|
activeChannels.put(id, channel);
|
||||||
|
|
||||||
return send(channel, exchange);
|
return send(channel, exchange);
|
||||||
}
|
}
|
||||||
|
@ -351,7 +370,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
@Override
|
@Override
|
||||||
public void onBegin(int request, int code, String reason)
|
public void onBegin(int request, int code, String reason)
|
||||||
{
|
{
|
||||||
HttpChannelOverFCGI channel = channels.get(request);
|
HttpChannelOverFCGI channel = activeChannels.get(request);
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
channel.responseBegin(code, reason);
|
channel.responseBegin(code, reason);
|
||||||
else
|
else
|
||||||
|
@ -361,7 +380,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
@Override
|
@Override
|
||||||
public void onHeader(int request, HttpField field)
|
public void onHeader(int request, HttpField field)
|
||||||
{
|
{
|
||||||
HttpChannelOverFCGI channel = channels.get(request);
|
HttpChannelOverFCGI channel = activeChannels.get(request);
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
channel.responseHeader(field);
|
channel.responseHeader(field);
|
||||||
else
|
else
|
||||||
|
@ -371,7 +390,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
@Override
|
@Override
|
||||||
public void onHeaders(int request)
|
public void onHeaders(int request)
|
||||||
{
|
{
|
||||||
HttpChannelOverFCGI channel = channels.get(request);
|
HttpChannelOverFCGI channel = activeChannels.get(request);
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
channel.responseHeaders();
|
channel.responseHeaders();
|
||||||
else
|
else
|
||||||
|
@ -385,7 +404,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
{
|
{
|
||||||
case STD_OUT:
|
case STD_OUT:
|
||||||
{
|
{
|
||||||
HttpChannelOverFCGI channel = channels.get(request);
|
HttpChannelOverFCGI channel = activeChannels.get(request);
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
{
|
{
|
||||||
CompletableCallback callback = new CompletableCallback()
|
CompletableCallback callback = new CompletableCallback()
|
||||||
|
@ -431,7 +450,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
@Override
|
@Override
|
||||||
public void onEnd(int request)
|
public void onEnd(int request)
|
||||||
{
|
{
|
||||||
HttpChannelOverFCGI channel = channels.get(request);
|
HttpChannelOverFCGI channel = activeChannels.get(request);
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
{
|
{
|
||||||
if (channel.responseSuccess())
|
if (channel.responseSuccess())
|
||||||
|
@ -446,7 +465,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(int request, Throwable failure)
|
public void onFailure(int request, Throwable failure)
|
||||||
{
|
{
|
||||||
HttpChannelOverFCGI channel = channels.get(request);
|
HttpChannelOverFCGI channel = activeChannels.get(request);
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
{
|
{
|
||||||
if (channel.responseFailure(failure))
|
if (channel.responseFailure(failure))
|
||||||
|
|
|
@ -121,6 +121,11 @@ public class HazelcastSessionDataStoreFactory
|
||||||
return onlyClient;
|
return onlyClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param onlyClient if <code>true</code> the session manager will only connect to an external Hazelcast instance
|
||||||
|
* and not use this JVM to start an Hazelcast instance
|
||||||
|
*/
|
||||||
public void setOnlyClient( boolean onlyClient )
|
public void setOnlyClient( boolean onlyClient )
|
||||||
{
|
{
|
||||||
this.onlyClient = onlyClient;
|
this.onlyClient = onlyClient;
|
||||||
|
|
|
@ -84,6 +84,7 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-start</artifactId>
|
<artifactId>jetty-start</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
|
<classifier>shaded</classifier>
|
||||||
<type>jar</type>
|
<type>jar</type>
|
||||||
<overWrite>true</overWrite>
|
<overWrite>true</overWrite>
|
||||||
<includes>**</includes>
|
<includes>**</includes>
|
||||||
|
@ -403,6 +404,7 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-start</artifactId>
|
<artifactId>jetty-start</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
|
<classifier>shaded</classifier>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
|
|
@ -678,6 +678,9 @@ public class StreamResetTest extends AbstractTest
|
||||||
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
|
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
|
||||||
Assert.assertTrue(writeLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(writeLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// Give time to the server to process the reset and drain the flusher queue.
|
||||||
|
Thread.sleep(500);
|
||||||
|
|
||||||
HTTP2Session session = connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class).getBean(HTTP2Session.class);
|
HTTP2Session session = connector.getConnectionFactory(AbstractHTTP2ServerConnectionFactory.class).getBean(HTTP2Session.class);
|
||||||
HTTP2Flusher flusher = session.getBean(HTTP2Flusher.class);
|
HTTP2Flusher flusher = session.getBean(HTTP2Flusher.class);
|
||||||
Assert.assertEquals(0, flusher.getFrameQueueSize());
|
Assert.assertEquals(0, flusher.getFrameQueueSize());
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.eclipse.jetty.http2.parser.Parser;
|
||||||
import org.eclipse.jetty.io.AbstractConnection;
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.io.WriteFlusher;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.component.LifeCycle;
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
|
@ -37,7 +38,7 @@ import org.eclipse.jetty.util.thread.ExecutionStrategy;
|
||||||
import org.eclipse.jetty.util.thread.ReservedThreadExecutor;
|
import org.eclipse.jetty.util.thread.ReservedThreadExecutor;
|
||||||
import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill;
|
import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill;
|
||||||
|
|
||||||
public class HTTP2Connection extends AbstractConnection
|
public class HTTP2Connection extends AbstractConnection implements WriteFlusher.Listener
|
||||||
{
|
{
|
||||||
protected static final Logger LOG = Log.getLogger(HTTP2Connection.class);
|
protected static final Logger LOG = Log.getLogger(HTTP2Connection.class);
|
||||||
|
|
||||||
|
@ -176,6 +177,13 @@ public class HTTP2Connection extends AbstractConnection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFlushed(long bytes) throws IOException
|
||||||
|
{
|
||||||
|
// TODO: add method to ISession ?
|
||||||
|
((HTTP2Session)session).onFlushed(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
protected class HTTP2Producer implements ExecutionStrategy.Producer
|
protected class HTTP2Producer implements ExecutionStrategy.Producer
|
||||||
{
|
{
|
||||||
private final Callback fillableCallback = new FillableCallback();
|
private final Callback fillableCallback = new FillableCallback();
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.eclipse.jetty.http2.frames.Frame;
|
||||||
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
|
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.EofException;
|
import org.eclipse.jetty.io.EofException;
|
||||||
|
import org.eclipse.jetty.io.WriteFlusher;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.IteratingCallback;
|
import org.eclipse.jetty.util.IteratingCallback;
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
|
@ -175,7 +176,7 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
||||||
{
|
{
|
||||||
if (entry.generate(lease))
|
if (entry.generate(lease))
|
||||||
{
|
{
|
||||||
if (entry.dataRemaining() > 0)
|
if (entry.getDataBytesRemaining() > 0)
|
||||||
entries.offer(entry);
|
entries.offer(entry);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -207,6 +208,31 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
||||||
return Action.SCHEDULED;
|
return Action.SCHEDULED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onFlushed(long bytes) throws IOException
|
||||||
|
{
|
||||||
|
// For the given flushed bytes, we want to only
|
||||||
|
// forward those that belong to data frame content.
|
||||||
|
for (Entry entry : actives)
|
||||||
|
{
|
||||||
|
int frameBytesLeft = entry.getFrameBytesRemaining();
|
||||||
|
if (frameBytesLeft > 0)
|
||||||
|
{
|
||||||
|
int update = (int)Math.min(bytes, frameBytesLeft);
|
||||||
|
entry.onFrameBytesFlushed(update);
|
||||||
|
bytes -= update;
|
||||||
|
IStream stream = entry.stream;
|
||||||
|
if (stream != null && !entry.isControl())
|
||||||
|
{
|
||||||
|
Object channel = stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
|
||||||
|
if (channel instanceof WriteFlusher.Listener)
|
||||||
|
((WriteFlusher.Listener)channel).onFlushed(update - Frame.HEADER_LENGTH);
|
||||||
|
}
|
||||||
|
if (bytes == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void succeeded()
|
public void succeeded()
|
||||||
{
|
{
|
||||||
|
@ -234,13 +260,13 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
||||||
for (int i = index; i < actives.size(); ++i)
|
for (int i = index; i < actives.size(); ++i)
|
||||||
{
|
{
|
||||||
Entry entry = actives.get(i);
|
Entry entry = actives.get(i);
|
||||||
if (entry.dataRemaining() > 0)
|
if (entry.getDataBytesRemaining() > 0)
|
||||||
append(entry);
|
append(entry);
|
||||||
}
|
}
|
||||||
for (int i = 0; i < index; ++i)
|
for (int i = 0; i < index; ++i)
|
||||||
{
|
{
|
||||||
Entry entry = actives.get(i);
|
Entry entry = actives.get(i);
|
||||||
if (entry.dataRemaining() > 0)
|
if (entry.getDataBytesRemaining() > 0)
|
||||||
append(entry);
|
append(entry);
|
||||||
}
|
}
|
||||||
stalled = null;
|
stalled = null;
|
||||||
|
@ -333,7 +359,11 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int dataRemaining()
|
public abstract int getFrameBytesRemaining();
|
||||||
|
|
||||||
|
public abstract void onFrameBytesFlushed(int bytesFlushed);
|
||||||
|
|
||||||
|
public int getDataBytesRemaining()
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -387,6 +417,17 @@ public class HTTP2Flusher extends IteratingCallback implements Dumpable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isControl()
|
||||||
|
{
|
||||||
|
switch (frame.getType())
|
||||||
|
{
|
||||||
|
case DATA:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
|
|
|
@ -955,6 +955,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onFlushed(long bytes) throws IOException
|
||||||
|
{
|
||||||
|
flusher.onFlushed(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
public void disconnect()
|
public void disconnect()
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
|
@ -1132,15 +1137,28 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
||||||
private class ControlEntry extends HTTP2Flusher.Entry
|
private class ControlEntry extends HTTP2Flusher.Entry
|
||||||
{
|
{
|
||||||
private int bytes;
|
private int bytes;
|
||||||
|
private int frameBytes;
|
||||||
|
|
||||||
private ControlEntry(Frame frame, IStream stream, Callback callback)
|
private ControlEntry(Frame frame, IStream stream, Callback callback)
|
||||||
{
|
{
|
||||||
super(frame, stream, callback);
|
super(frame, stream, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getFrameBytesRemaining()
|
||||||
|
{
|
||||||
|
return frameBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFrameBytesFlushed(int bytesFlushed)
|
||||||
|
{
|
||||||
|
frameBytes -= bytesFlushed;
|
||||||
|
}
|
||||||
|
|
||||||
protected boolean generate(ByteBufferPool.Lease lease)
|
protected boolean generate(ByteBufferPool.Lease lease)
|
||||||
{
|
{
|
||||||
bytes = generator.control(lease, frame);
|
bytes = frameBytes = generator.control(lease, frame);
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Generated {}", frame);
|
LOG.debug("Generated {}", frame);
|
||||||
prepare();
|
prepare();
|
||||||
|
@ -1238,7 +1256,8 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
||||||
private class DataEntry extends HTTP2Flusher.Entry
|
private class DataEntry extends HTTP2Flusher.Entry
|
||||||
{
|
{
|
||||||
private int bytes;
|
private int bytes;
|
||||||
private int dataRemaining;
|
private int frameBytes;
|
||||||
|
private int dataBytes;
|
||||||
private int dataWritten;
|
private int dataWritten;
|
||||||
|
|
||||||
private DataEntry(DataFrame frame, IStream stream, Callback callback)
|
private DataEntry(DataFrame frame, IStream stream, Callback callback)
|
||||||
|
@ -1249,35 +1268,47 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
||||||
// of data frames that cannot be completely written due to
|
// of data frames that cannot be completely written due to
|
||||||
// the flow control window exhausting, since in that case
|
// the flow control window exhausting, since in that case
|
||||||
// we would have to count the padding only once.
|
// we would have to count the padding only once.
|
||||||
dataRemaining = frame.remaining();
|
dataBytes = frame.remaining();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int dataRemaining()
|
public int getFrameBytesRemaining()
|
||||||
{
|
{
|
||||||
return dataRemaining;
|
return frameBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFrameBytesFlushed(int bytesFlushed)
|
||||||
|
{
|
||||||
|
frameBytes -= bytesFlushed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDataBytesRemaining()
|
||||||
|
{
|
||||||
|
return dataBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean generate(ByteBufferPool.Lease lease)
|
protected boolean generate(ByteBufferPool.Lease lease)
|
||||||
{
|
{
|
||||||
int dataRemaining = dataRemaining();
|
int dataBytes = getDataBytesRemaining();
|
||||||
|
|
||||||
int sessionSendWindow = getSendWindow();
|
int sessionSendWindow = getSendWindow();
|
||||||
int streamSendWindow = stream.updateSendWindow(0);
|
int streamSendWindow = stream.updateSendWindow(0);
|
||||||
int window = Math.min(streamSendWindow, sessionSendWindow);
|
int window = Math.min(streamSendWindow, sessionSendWindow);
|
||||||
if (window <= 0 && dataRemaining > 0)
|
if (window <= 0 && dataBytes > 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
int length = Math.min(dataRemaining, window);
|
int length = Math.min(dataBytes, window);
|
||||||
|
|
||||||
// Only one DATA frame is generated.
|
// Only one DATA frame is generated.
|
||||||
bytes = generator.data(lease, (DataFrame)frame, length);
|
bytes = frameBytes = generator.data(lease, (DataFrame)frame, length);
|
||||||
int written = bytes - Frame.HEADER_LENGTH;
|
int written = bytes - Frame.HEADER_LENGTH;
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Generated {}, length/window/data={}/{}/{}", frame, written, window, dataRemaining);
|
LOG.debug("Generated {}, length/window/data={}/{}/{}", frame, written, window, dataBytes);
|
||||||
|
|
||||||
this.dataWritten = written;
|
this.dataWritten = written;
|
||||||
this.dataRemaining -= written;
|
this.dataBytes -= written;
|
||||||
|
|
||||||
flowControl.onDataSending(stream, written);
|
flowControl.onDataSending(stream, written);
|
||||||
|
|
||||||
|
@ -1292,7 +1323,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
||||||
|
|
||||||
// Do we have more to send ?
|
// Do we have more to send ?
|
||||||
DataFrame dataFrame = (DataFrame)frame;
|
DataFrame dataFrame = (DataFrame)frame;
|
||||||
if (dataRemaining() == 0)
|
if (getDataBytesRemaining() == 0)
|
||||||
{
|
{
|
||||||
// Only now we can update the close state
|
// Only now we can update the close state
|
||||||
// and eventually remove the stream.
|
// and eventually remove the stream.
|
||||||
|
|
|
@ -34,17 +34,15 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
||||||
{
|
{
|
||||||
private final HttpConnectionOverHTTP2 connection;
|
private final HttpConnectionOverHTTP2 connection;
|
||||||
private final Session session;
|
private final Session session;
|
||||||
private final boolean push;
|
|
||||||
private final HttpSenderOverHTTP2 sender;
|
private final HttpSenderOverHTTP2 sender;
|
||||||
private final HttpReceiverOverHTTP2 receiver;
|
private final HttpReceiverOverHTTP2 receiver;
|
||||||
private Stream stream;
|
private Stream stream;
|
||||||
|
|
||||||
public HttpChannelOverHTTP2(HttpDestination destination, HttpConnectionOverHTTP2 connection, Session session, boolean push)
|
public HttpChannelOverHTTP2(HttpDestination destination, HttpConnectionOverHTTP2 connection, Session session)
|
||||||
{
|
{
|
||||||
super(destination);
|
super(destination);
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.push = push;
|
|
||||||
this.sender = new HttpSenderOverHTTP2(this);
|
this.sender = new HttpSenderOverHTTP2(this);
|
||||||
this.receiver = new HttpReceiverOverHTTP2(this);
|
this.receiver = new HttpReceiverOverHTTP2(this);
|
||||||
}
|
}
|
||||||
|
@ -86,6 +84,11 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isFailed()
|
||||||
|
{
|
||||||
|
return sender.isFailed() || receiver.isFailed();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send()
|
public void send()
|
||||||
{
|
{
|
||||||
|
@ -103,10 +106,10 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
||||||
@Override
|
@Override
|
||||||
public boolean abort(HttpExchange exchange, Throwable requestFailure, Throwable responseFailure)
|
public boolean abort(HttpExchange exchange, Throwable requestFailure, Throwable responseFailure)
|
||||||
{
|
{
|
||||||
|
Stream stream = getStream();
|
||||||
boolean aborted = super.abort(exchange, requestFailure, responseFailure);
|
boolean aborted = super.abort(exchange, requestFailure, responseFailure);
|
||||||
if (aborted)
|
if (aborted)
|
||||||
{
|
{
|
||||||
Stream stream = getStream();
|
|
||||||
if (stream != null)
|
if (stream != null)
|
||||||
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
|
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
|
||||||
}
|
}
|
||||||
|
@ -117,7 +120,6 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
||||||
public void exchangeTerminated(HttpExchange exchange, Result result)
|
public void exchangeTerminated(HttpExchange exchange, Result result)
|
||||||
{
|
{
|
||||||
super.exchangeTerminated(exchange, result);
|
super.exchangeTerminated(exchange, result);
|
||||||
if (!push)
|
|
||||||
release();
|
release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,11 @@ public class HttpClientTransportOverHTTP2 extends AbstractHttpClientTransport
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HTTP2Client getHTTP2Client()
|
||||||
|
{
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
@ManagedAttribute(value = "The number of selectors", readonly = true)
|
@ManagedAttribute(value = "The number of selectors", readonly = true)
|
||||||
public int getSelectors()
|
public int getSelectors()
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,8 +19,10 @@
|
||||||
package org.eclipse.jetty.http2.client.http;
|
package org.eclipse.jetty.http2.client.http;
|
||||||
|
|
||||||
import java.nio.channels.AsynchronousCloseException;
|
import java.nio.channels.AsynchronousCloseException;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
@ -29,6 +31,7 @@ import org.eclipse.jetty.client.HttpChannel;
|
||||||
import org.eclipse.jetty.client.HttpConnection;
|
import org.eclipse.jetty.client.HttpConnection;
|
||||||
import org.eclipse.jetty.client.HttpDestination;
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
import org.eclipse.jetty.client.HttpExchange;
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
|
import org.eclipse.jetty.client.HttpRequest;
|
||||||
import org.eclipse.jetty.client.SendFailure;
|
import org.eclipse.jetty.client.SendFailure;
|
||||||
import org.eclipse.jetty.http.HttpVersion;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.http2.ErrorCode;
|
import org.eclipse.jetty.http2.ErrorCode;
|
||||||
|
@ -38,7 +41,8 @@ import org.eclipse.jetty.util.thread.Sweeper;
|
||||||
|
|
||||||
public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.Sweepable
|
public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.Sweepable
|
||||||
{
|
{
|
||||||
private final Set<HttpChannel> channels = ConcurrentHashMap.newKeySet();
|
private final Set<HttpChannel> activeChannels = ConcurrentHashMap.newKeySet();
|
||||||
|
private final Queue<HttpChannelOverHTTP2> idleChannels = new ConcurrentLinkedQueue<>();
|
||||||
private final AtomicBoolean closed = new AtomicBoolean();
|
private final AtomicBoolean closed = new AtomicBoolean();
|
||||||
private final AtomicInteger sweeps = new AtomicInteger();
|
private final AtomicInteger sweeps = new AtomicInteger();
|
||||||
private final Session session;
|
private final Session session;
|
||||||
|
@ -57,26 +61,42 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S
|
||||||
@Override
|
@Override
|
||||||
protected SendFailure send(HttpExchange exchange)
|
protected SendFailure send(HttpExchange exchange)
|
||||||
{
|
{
|
||||||
exchange.getRequest().version(HttpVersion.HTTP_2);
|
HttpRequest request = exchange.getRequest();
|
||||||
normalizeRequest(exchange.getRequest());
|
request.version(HttpVersion.HTTP_2);
|
||||||
|
normalizeRequest(request);
|
||||||
|
|
||||||
// One connection maps to N channels, so for each exchange we create a new channel.
|
// One connection maps to N channels, so one channel for each exchange.
|
||||||
HttpChannel channel = newHttpChannel(false);
|
HttpChannelOverHTTP2 channel = provideHttpChannel();
|
||||||
channels.add(channel);
|
activeChannels.add(channel);
|
||||||
|
|
||||||
return send(channel, exchange);
|
return send(channel, exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpChannelOverHTTP2 newHttpChannel(boolean push)
|
protected HttpChannelOverHTTP2 provideHttpChannel()
|
||||||
{
|
{
|
||||||
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession(), push);
|
HttpChannelOverHTTP2 channel = idleChannels.poll();
|
||||||
|
if (channel == null)
|
||||||
|
channel = newHttpChannel();
|
||||||
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void release(HttpChannel channel)
|
protected HttpChannelOverHTTP2 newHttpChannel()
|
||||||
{
|
{
|
||||||
channels.remove(channel);
|
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void release(HttpChannelOverHTTP2 channel)
|
||||||
|
{
|
||||||
|
// Only non-push channels are released.
|
||||||
|
if (activeChannels.remove(channel))
|
||||||
|
{
|
||||||
|
channel.setStream(null);
|
||||||
|
// Recycle only non-failed channels.
|
||||||
|
if (!channel.isFailed())
|
||||||
|
idleChannels.offer(channel);
|
||||||
getHttpDestination().release(this);
|
getHttpDestination().release(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onIdleTimeout(long idleTimeout)
|
public boolean onIdleTimeout(long idleTimeout)
|
||||||
|
@ -113,13 +133,14 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S
|
||||||
|
|
||||||
private void abort(Throwable failure)
|
private void abort(Throwable failure)
|
||||||
{
|
{
|
||||||
for (HttpChannel channel : channels)
|
for (HttpChannel channel : activeChannels)
|
||||||
{
|
{
|
||||||
HttpExchange exchange = channel.getHttpExchange();
|
HttpExchange exchange = channel.getHttpExchange();
|
||||||
if (exchange != null)
|
if (exchange != null)
|
||||||
exchange.getRequest().abort(failure);
|
exchange.getRequest().abort(failure);
|
||||||
}
|
}
|
||||||
channels.clear();
|
activeChannels.clear();
|
||||||
|
idleChannels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -64,6 +64,13 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
||||||
return (HttpChannelOverHTTP2)super.getHttpChannel();
|
return (HttpChannelOverHTTP2)super.getHttpChannel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void reset()
|
||||||
|
{
|
||||||
|
super.reset();
|
||||||
|
contentNotifier.reset();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onHeaders(Stream stream, HeadersFrame frame)
|
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||||
{
|
{
|
||||||
|
@ -114,6 +121,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
||||||
HttpRequest request = exchange.getRequest();
|
HttpRequest request = exchange.getRequest();
|
||||||
MetaData.Request metaData = (MetaData.Request)frame.getMetaData();
|
MetaData.Request metaData = (MetaData.Request)frame.getMetaData();
|
||||||
HttpRequest pushRequest = (HttpRequest)getHttpDestination().getHttpClient().newRequest(metaData.getURIString());
|
HttpRequest pushRequest = (HttpRequest)getHttpDestination().getHttpClient().newRequest(metaData.getURIString());
|
||||||
|
// TODO: copy PUSH_PROMISE headers into pushRequest.
|
||||||
|
|
||||||
BiFunction<Request, Request, Response.CompleteListener> pushListener = request.getPushListener();
|
BiFunction<Request, Request, Response.CompleteListener> pushListener = request.getPushListener();
|
||||||
if (pushListener != null)
|
if (pushListener != null)
|
||||||
|
@ -121,7 +129,7 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
||||||
Response.CompleteListener listener = pushListener.apply(request, pushRequest);
|
Response.CompleteListener listener = pushListener.apply(request, pushRequest);
|
||||||
if (listener != null)
|
if (listener != null)
|
||||||
{
|
{
|
||||||
HttpChannelOverHTTP2 pushChannel = getHttpChannel().getHttpConnection().newHttpChannel(true);
|
HttpChannelOverHTTP2 pushChannel = getHttpChannel().getHttpConnection().provideHttpChannel();
|
||||||
List<Response.ResponseListener> listeners = Collections.singletonList(listener);
|
List<Response.ResponseListener> listeners = Collections.singletonList(listener);
|
||||||
HttpExchange pushExchange = new HttpExchange(getHttpDestination(), pushRequest, listeners);
|
HttpExchange pushExchange = new HttpExchange(getHttpDestination(), pushRequest, listeners);
|
||||||
pushChannel.associate(pushExchange);
|
pushChannel.associate(pushExchange);
|
||||||
|
@ -187,16 +195,16 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
||||||
private final Queue<DataInfo> queue = new ArrayDeque<>();
|
private final Queue<DataInfo> queue = new ArrayDeque<>();
|
||||||
private DataInfo dataInfo;
|
private DataInfo dataInfo;
|
||||||
|
|
||||||
private boolean offer(DataInfo dataInfo)
|
private void offer(DataInfo dataInfo)
|
||||||
{
|
{
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
{
|
{
|
||||||
return queue.offer(dataInfo);
|
queue.offer(dataInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Action process() throws Exception
|
protected Action process()
|
||||||
{
|
{
|
||||||
DataInfo dataInfo;
|
DataInfo dataInfo;
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
|
|
|
@ -247,17 +247,20 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
||||||
return new HttpConnectionOverHTTP2(destination, session)
|
return new HttpConnectionOverHTTP2(destination, session)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected HttpChannelOverHTTP2 newHttpChannel(boolean push)
|
protected HttpChannelOverHTTP2 newHttpChannel()
|
||||||
{
|
{
|
||||||
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession(), push)
|
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession())
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void setStream(Stream stream)
|
public void setStream(Stream stream)
|
||||||
{
|
{
|
||||||
super.setStream(stream);
|
super.setStream(stream);
|
||||||
|
if (stream != null)
|
||||||
|
{
|
||||||
streamRef.set(stream);
|
streamRef.set(stream);
|
||||||
streamLatch.countDown();
|
streamLatch.countDown();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.eclipse.jetty.http2.frames.DataFrame;
|
||||||
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
|
import org.eclipse.jetty.io.WriteFlusher;
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.HttpChannel;
|
import org.eclipse.jetty.server.HttpChannel;
|
||||||
import org.eclipse.jetty.server.HttpConfiguration;
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
|
@ -48,7 +49,7 @@ import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable
|
public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable, WriteFlusher.Listener
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(HttpChannelOverHTTP2.class);
|
private static final Logger LOG = Log.getLogger(HttpChannelOverHTTP2.class);
|
||||||
private static final HttpField SERVER_VERSION = new PreEncodedHttpField(HttpHeader.SERVER, HttpConfiguration.SERVER_VERSION);
|
private static final HttpField SERVER_VERSION = new PreEncodedHttpField(HttpHeader.SERVER, HttpConfiguration.SERVER_VERSION);
|
||||||
|
@ -85,6 +86,12 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable
|
||||||
return getStream().getIdleTimeout();
|
return getStream().getIdleTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFlushed(long bytes) throws IOException
|
||||||
|
{
|
||||||
|
getResponse().getHttpOutput().onFlushed(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
public Runnable onRequest(HeadersFrame frame)
|
public Runnable onRequest(HeadersFrame frame)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -308,16 +315,19 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable
|
||||||
|
|
||||||
public boolean onStreamTimeout(Throwable failure, Consumer<Runnable> consumer)
|
public boolean onStreamTimeout(Throwable failure, Consumer<Runnable> consumer)
|
||||||
{
|
{
|
||||||
boolean result = false;
|
boolean delayed = _delayedUntilContent;
|
||||||
if (isRequestIdle())
|
_delayedUntilContent = false;
|
||||||
{
|
|
||||||
|
boolean result = isRequestIdle();
|
||||||
|
if (result)
|
||||||
consumeInput();
|
consumeInput();
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
getHttpTransport().onStreamTimeout(failure);
|
getHttpTransport().onStreamTimeout(failure);
|
||||||
if (getRequest().getHttpInput().onIdleTimeout(failure))
|
if (getRequest().getHttpInput().onIdleTimeout(failure) || delayed)
|
||||||
|
{
|
||||||
consumer.accept(this::handleWithContext);
|
consumer.accept(this::handleWithContext);
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,7 +167,7 @@ public abstract class AbstractConnection implements Connection
|
||||||
{
|
{
|
||||||
boolean close = true;
|
boolean close = true;
|
||||||
if (cause instanceof TimeoutException)
|
if (cause instanceof TimeoutException)
|
||||||
close = onReadTimeout();
|
close = onReadTimeout(cause);
|
||||||
if (close)
|
if (close)
|
||||||
{
|
{
|
||||||
if (_endPoint.isOutputShutdown())
|
if (_endPoint.isOutputShutdown())
|
||||||
|
@ -183,9 +183,11 @@ public abstract class AbstractConnection implements Connection
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Callback method invoked when the endpoint failed to be ready to be read after a timeout</p>
|
* <p>Callback method invoked when the endpoint failed to be ready to be read after a timeout</p>
|
||||||
|
*
|
||||||
|
* @param timeout the cause of the read timeout
|
||||||
* @return true to signal that the endpoint must be closed, false to keep the endpoint open
|
* @return true to signal that the endpoint must be closed, false to keep the endpoint open
|
||||||
*/
|
*/
|
||||||
protected boolean onReadTimeout()
|
protected boolean onReadTimeout(Throwable timeout)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,13 +26,15 @@ import java.nio.channels.CancelledKeyException;
|
||||||
import java.nio.channels.SelectableChannel;
|
import java.nio.channels.SelectableChannel;
|
||||||
import java.nio.channels.SelectionKey;
|
import java.nio.channels.SelectionKey;
|
||||||
import java.nio.channels.Selector;
|
import java.nio.channels.Selector;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Deque;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
@ -64,7 +66,7 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
||||||
|
|
||||||
private final Locker _locker = new Locker();
|
private final Locker _locker = new Locker();
|
||||||
private boolean _selecting = false;
|
private boolean _selecting = false;
|
||||||
private final Queue<Runnable> _actions = new ArrayDeque<>();
|
private final Deque<Runnable> _actions = new ArrayDeque<>();
|
||||||
private final SelectorManager _selectorManager;
|
private final SelectorManager _selectorManager;
|
||||||
private final int _id;
|
private final int _id;
|
||||||
private final ExecutionStrategy _strategy;
|
private final ExecutionStrategy _strategy;
|
||||||
|
@ -244,20 +246,31 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
||||||
@Override
|
@Override
|
||||||
public void dump(Appendable out, String indent) throws IOException
|
public void dump(Appendable out, String indent) throws IOException
|
||||||
{
|
{
|
||||||
super.dump(out, indent);
|
|
||||||
Selector selector = _selector;
|
Selector selector = _selector;
|
||||||
|
List<String> keys = null;
|
||||||
|
List<Runnable> actions = null;
|
||||||
if (selector != null && selector.isOpen())
|
if (selector != null && selector.isOpen())
|
||||||
{
|
{
|
||||||
List<Runnable> actions;
|
DumpKeys dump = new DumpKeys();
|
||||||
|
String actionsAt;
|
||||||
try (Locker.Lock lock = _locker.lock())
|
try (Locker.Lock lock = _locker.lock())
|
||||||
{
|
{
|
||||||
|
actionsAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now());
|
||||||
actions = new ArrayList<>(_actions);
|
actions = new ArrayList<>(_actions);
|
||||||
|
_actions.addFirst(dump);
|
||||||
|
_selecting = false;
|
||||||
}
|
}
|
||||||
List<Object> keys = new ArrayList<>(selector.keys().size());
|
selector.wakeup();
|
||||||
DumpKeys dumpKeys = new DumpKeys(keys);
|
keys = dump.get(5, TimeUnit.SECONDS);
|
||||||
submit(dumpKeys);
|
String keysAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now());
|
||||||
dumpKeys.await(5, TimeUnit.SECONDS);
|
if (keys==null)
|
||||||
dump(out, indent, Arrays.asList(new DumpableCollection("keys", keys), new DumpableCollection("actions", actions)));
|
keys = Collections.singletonList("No dump keys retrieved");
|
||||||
|
dumpBeans(out, indent, Arrays.asList(new DumpableCollection("actions @ "+actionsAt, actions),
|
||||||
|
new DumpableCollection("keys @ "+keysAt, keys)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dumpBeans(out, indent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -495,13 +508,8 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
||||||
|
|
||||||
private class DumpKeys extends Invocable.NonBlocking
|
private class DumpKeys extends Invocable.NonBlocking
|
||||||
{
|
{
|
||||||
private final CountDownLatch latch = new CountDownLatch(1);
|
private CountDownLatch latch = new CountDownLatch(1);
|
||||||
private final List<Object> _dumps;
|
private List<String> keys;
|
||||||
|
|
||||||
private DumpKeys(List<Object> dumps)
|
|
||||||
{
|
|
||||||
this._dumps = dumps;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
|
@ -509,33 +517,37 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
|
||||||
Selector selector = _selector;
|
Selector selector = _selector;
|
||||||
if (selector != null && selector.isOpen())
|
if (selector != null && selector.isOpen())
|
||||||
{
|
{
|
||||||
Set<SelectionKey> keys = selector.keys();
|
Set<SelectionKey> selector_keys = selector.keys();
|
||||||
_dumps.add(selector + " keys=" + keys.size());
|
List<String> list = new ArrayList<>(selector_keys.size()+1);
|
||||||
for (SelectionKey key : keys)
|
list.add(selector + " keys=" + selector_keys.size());
|
||||||
|
for (SelectionKey key : selector_keys)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_dumps.add(String.format("SelectionKey@%x{i=%d}->%s", key.hashCode(), key.interestOps(), key.attachment()));
|
list.add(String.format("SelectionKey@%x{i=%d}->%s", key.hashCode(), key.interestOps(), key.attachment()));
|
||||||
}
|
}
|
||||||
catch (Throwable x)
|
catch (Throwable x)
|
||||||
{
|
{
|
||||||
_dumps.add(String.format("SelectionKey@%x[%s]->%s", key.hashCode(), x, key.attachment()));
|
list.add(String.format("SelectionKey@%x[%s]->%s", key.hashCode(), x, key.attachment()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
keys = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean await(long timeout, TimeUnit unit)
|
public List<String> get(long timeout, TimeUnit unit)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return latch.await(timeout, unit);
|
latch.await(timeout, unit);
|
||||||
}
|
}
|
||||||
catch (InterruptedException x)
|
catch (InterruptedException x)
|
||||||
{
|
{
|
||||||
return false;
|
LOG.ignore(x);
|
||||||
}
|
}
|
||||||
|
return keys;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,6 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.thread.Invocable;
|
import org.eclipse.jetty.util.thread.Invocable;
|
||||||
import org.eclipse.jetty.util.thread.Invocable.InvocationType;
|
import org.eclipse.jetty.util.thread.Invocable.InvocationType;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Utility class to help implement {@link EndPoint#write(Callback, ByteBuffer...)} by calling
|
* A Utility class to help implement {@link EndPoint#write(Callback, ByteBuffer...)} by calling
|
||||||
* {@link EndPoint#flush(ByteBuffer...)} until all content is written.
|
* {@link EndPoint#flush(ByteBuffer...)} until all content is written.
|
||||||
|
@ -104,6 +103,7 @@ abstract public class WriteFlusher
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to update the current state to the given new state.
|
* Tries to update the current state to the given new state.
|
||||||
|
*
|
||||||
* @param previous the expected current state
|
* @param previous the expected current state
|
||||||
* @param next the desired new state
|
* @param next the desired new state
|
||||||
* @return the previous state or null if the state transition failed
|
* @return the previous state or null if the state transition failed
|
||||||
|
@ -209,6 +209,7 @@ abstract public class WriteFlusher
|
||||||
private static class FailedState extends State
|
private static class FailedState extends State
|
||||||
{
|
{
|
||||||
private final Throwable _cause;
|
private final Throwable _cause;
|
||||||
|
|
||||||
private FailedState(Throwable cause)
|
private FailedState(Throwable cause)
|
||||||
{
|
{
|
||||||
super(StateType.FAILED);
|
super(StateType.FAILED);
|
||||||
|
@ -300,7 +301,7 @@ abstract public class WriteFlusher
|
||||||
* Tries to switch state to WRITING. If successful it writes the given buffers to the EndPoint. If state transition
|
* Tries to switch state to WRITING. If successful it writes the given buffers to the EndPoint. If state transition
|
||||||
* fails it'll fail the callback.
|
* fails it'll fail the callback.
|
||||||
*
|
*
|
||||||
* If not all buffers can be written in one go it creates a new <code>PendingState</code> object to preserve the state
|
* If not all buffers can be written in one go it creates a new {@code PendingState} object to preserve the state
|
||||||
* and then calls {@link #onIncompleteFlush()}. The remaining buffers will be written in {@link #completeWrite()}.
|
* and then calls {@link #onIncompleteFlush()}. The remaining buffers will be written in {@link #completeWrite()}.
|
||||||
*
|
*
|
||||||
* If all buffers have been written it calls callback.complete().
|
* If all buffers have been written it calls callback.complete().
|
||||||
|
@ -354,7 +355,6 @@ abstract public class WriteFlusher
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete a write that has not completed and that called {@link #onIncompleteFlush()} to request a call to this
|
* Complete a write that has not completed and that called {@link #onIncompleteFlush()} to request a call to this
|
||||||
* method when a call to {@link EndPoint#flush(ByteBuffer...)} is likely to be able to progress.
|
* method when a call to {@link EndPoint#flush(ByteBuffer...)} is likely to be able to progress.
|
||||||
|
@ -425,33 +425,47 @@ abstract public class WriteFlusher
|
||||||
boolean progress = true;
|
boolean progress = true;
|
||||||
while (progress && buffers != null)
|
while (progress && buffers != null)
|
||||||
{
|
{
|
||||||
int before=buffers.length==0?0:buffers[0].remaining();
|
long before = remaining(buffers);
|
||||||
boolean flushed = _endPoint.flush(buffers);
|
boolean flushed = _endPoint.flush(buffers);
|
||||||
int r=buffers.length==0?0:buffers[0].remaining();
|
long after = remaining(buffers);
|
||||||
|
long written = before - after;
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("Flushed={} {}/{}+{} {}",flushed,before-r,before,buffers.length-1,this);
|
LOG.debug("Flushed={} written={} remaining={} {}", flushed, written, after, this);
|
||||||
|
|
||||||
|
if (written > 0)
|
||||||
|
{
|
||||||
|
Connection connection = _endPoint.getConnection();
|
||||||
|
if (connection instanceof Listener)
|
||||||
|
((Listener)connection).onFlushed(written);
|
||||||
|
}
|
||||||
|
|
||||||
if (flushed)
|
if (flushed)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
progress=before!=r;
|
progress = written > 0;
|
||||||
|
|
||||||
int not_empty=0;
|
int index = 0;
|
||||||
while(r==0)
|
while (true)
|
||||||
{
|
{
|
||||||
if (++not_empty==buffers.length)
|
if (index == buffers.length)
|
||||||
{
|
{
|
||||||
|
// All buffers consumed.
|
||||||
buffers = null;
|
buffers = null;
|
||||||
not_empty=0;
|
index = 0;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int remaining = buffers[index].remaining();
|
||||||
|
if (remaining > 0)
|
||||||
|
break;
|
||||||
|
++index;
|
||||||
progress = true;
|
progress = true;
|
||||||
r=buffers[not_empty].remaining();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (not_empty>0)
|
if (index > 0)
|
||||||
buffers=Arrays.copyOfRange(buffers,not_empty,buffers.length);
|
buffers = Arrays.copyOfRange(buffers, index, buffers.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
|
@ -463,8 +477,19 @@ abstract public class WriteFlusher
|
||||||
return buffers == null ? EMPTY_BUFFERS : buffers;
|
return buffers == null ? EMPTY_BUFFERS : buffers;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
private long remaining(ByteBuffer[] buffers)
|
||||||
/** Notify the flusher of a failure
|
{
|
||||||
|
if (buffers == null)
|
||||||
|
return 0;
|
||||||
|
long result = 0;
|
||||||
|
for (ByteBuffer buffer : buffers)
|
||||||
|
result += buffer.remaining();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notify the flusher of a failure
|
||||||
|
*
|
||||||
* @param cause The cause of the failure
|
* @param cause The cause of the failure
|
||||||
* @return true if the flusher passed the failure to a {@link Callback} instance
|
* @return true if the flusher passed the failure to a {@link Callback} instance
|
||||||
*/
|
*/
|
||||||
|
@ -512,26 +537,6 @@ abstract public class WriteFlusher
|
||||||
return _state.get().getType() == StateType.IDLE;
|
return _state.get().getType() == StateType.IDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isInProgress()
|
|
||||||
{
|
|
||||||
switch(_state.get().getType())
|
|
||||||
{
|
|
||||||
case WRITING:
|
|
||||||
case PENDING:
|
|
||||||
case COMPLETING:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
State s = _state.get();
|
|
||||||
return String.format("WriteFlusher@%x{%s}->%s", hashCode(), s,s instanceof PendingState?((PendingState)s).getCallback():null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toStateString()
|
public String toStateString()
|
||||||
{
|
{
|
||||||
switch (_state.get().getType())
|
switch (_state.get().getType())
|
||||||
|
@ -550,4 +555,33 @@ abstract public class WriteFlusher
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
State s = _state.get();
|
||||||
|
return String.format("WriteFlusher@%x{%s}->%s", hashCode(), s, s instanceof PendingState ? ((PendingState)s).getCallback() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>A listener of {@link WriteFlusher} events.</p>
|
||||||
|
*/
|
||||||
|
public interface Listener
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* <p>Invoked when a {@link WriteFlusher} flushed bytes in a non-blocking way,
|
||||||
|
* as part of a - possibly larger - write.</p>
|
||||||
|
* <p>This method may be invoked multiple times, for example when writing a large
|
||||||
|
* buffer: a first flush of bytes, then the connection became TCP congested, and
|
||||||
|
* a subsequent flush of bytes when the connection became writable again.</p>
|
||||||
|
* <p>This method is never invoked concurrently, but may be invoked by different
|
||||||
|
* threads, so implementations may not rely on thread-local variables.</p>
|
||||||
|
* <p>Implementations may throw an {@link IOException} to signal that the write
|
||||||
|
* should fail, for example if the implementation enforces a minimum data rate.</p>
|
||||||
|
*
|
||||||
|
* @param bytes the number of bytes flushed
|
||||||
|
* @throws IOException if the write should fail
|
||||||
|
*/
|
||||||
|
void onFlushed(long bytes) throws IOException;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -399,6 +399,17 @@ public class HttpChannelOverHttp extends HttpChannel implements HttpParser.Reque
|
||||||
return !_delayedForContent;
|
return !_delayedForContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean onIdleTimeout(Throwable timeout)
|
||||||
|
{
|
||||||
|
if (_delayedForContent)
|
||||||
|
{
|
||||||
|
_delayedForContent = false;
|
||||||
|
getRequest().getHttpInput().onIdleTimeout(timeout);
|
||||||
|
execute(this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Attempts to perform a HTTP/1.1 upgrade.</p>
|
* <p>Attempts to perform a HTTP/1.1 upgrade.</p>
|
||||||
|
|
|
@ -66,6 +66,7 @@ public class HttpConfiguration
|
||||||
private boolean _persistentConnectionsEnabled = true;
|
private boolean _persistentConnectionsEnabled = true;
|
||||||
private int _maxErrorDispatches = 10;
|
private int _maxErrorDispatches = 10;
|
||||||
private long _minRequestDataRate;
|
private long _minRequestDataRate;
|
||||||
|
private long _minResponseDataRate;
|
||||||
private CookieCompliance _cookieCompliance = CookieCompliance.RFC6265;
|
private CookieCompliance _cookieCompliance = CookieCompliance.RFC6265;
|
||||||
private boolean _notifyRemoteAsyncErrors = true;
|
private boolean _notifyRemoteAsyncErrors = true;
|
||||||
|
|
||||||
|
@ -127,6 +128,7 @@ public class HttpConfiguration
|
||||||
_persistentConnectionsEnabled=config._persistentConnectionsEnabled;
|
_persistentConnectionsEnabled=config._persistentConnectionsEnabled;
|
||||||
_maxErrorDispatches=config._maxErrorDispatches;
|
_maxErrorDispatches=config._maxErrorDispatches;
|
||||||
_minRequestDataRate=config._minRequestDataRate;
|
_minRequestDataRate=config._minRequestDataRate;
|
||||||
|
_minResponseDataRate=config._minResponseDataRate;
|
||||||
_cookieCompliance=config._cookieCompliance;
|
_cookieCompliance=config._cookieCompliance;
|
||||||
_notifyRemoteAsyncErrors=config._notifyRemoteAsyncErrors;
|
_notifyRemoteAsyncErrors=config._notifyRemoteAsyncErrors;
|
||||||
}
|
}
|
||||||
|
@ -497,6 +499,28 @@ public class HttpConfiguration
|
||||||
_minRequestDataRate=bytesPerSecond;
|
_minRequestDataRate=bytesPerSecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The minimum response data rate in bytes per second; or <=0 for no limit
|
||||||
|
*/
|
||||||
|
@ManagedAttribute("The minimum response content data rate in bytes per second")
|
||||||
|
public long getMinResponseDataRate()
|
||||||
|
{
|
||||||
|
return _minResponseDataRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Sets an minimum response content data rate.</p>
|
||||||
|
* <p>The value is enforced only approximately - not precisely - due to the fact that
|
||||||
|
* for efficiency reasons buffer writes may be comprised of both response headers and
|
||||||
|
* response content.</p>
|
||||||
|
*
|
||||||
|
* @param bytesPerSecond The minimum response data rate in bytes per second; or <=0 for no limit
|
||||||
|
*/
|
||||||
|
public void setMinResponseDataRate(long bytesPerSecond)
|
||||||
|
{
|
||||||
|
_minResponseDataRate = bytesPerSecond;
|
||||||
|
}
|
||||||
|
|
||||||
public CookieCompliance getCookieCompliance()
|
public CookieCompliance getCookieCompliance()
|
||||||
{
|
{
|
||||||
return _cookieCompliance;
|
return _cookieCompliance;
|
||||||
|
|
|
@ -40,6 +40,7 @@ import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.io.Connection;
|
import org.eclipse.jetty.io.Connection;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.EofException;
|
import org.eclipse.jetty.io.EofException;
|
||||||
|
import org.eclipse.jetty.io.WriteFlusher;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.IteratingCallback;
|
import org.eclipse.jetty.util.IteratingCallback;
|
||||||
|
@ -49,7 +50,7 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
/**
|
/**
|
||||||
* <p>A {@link Connection} that handles the HTTP protocol.</p>
|
* <p>A {@link Connection} that handles the HTTP protocol.</p>
|
||||||
*/
|
*/
|
||||||
public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport, Connection.UpgradeFrom
|
public class HttpConnection extends AbstractConnection implements Runnable, HttpTransport, Connection.UpgradeFrom, WriteFlusher.Listener
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(HttpConnection.class);
|
private static final Logger LOG = Log.getLogger(HttpConnection.class);
|
||||||
public static final HttpField CONNECTION_CLOSE = new PreEncodedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE.asString());
|
public static final HttpField CONNECTION_CLOSE = new PreEncodedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE.asString());
|
||||||
|
@ -195,6 +196,14 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFlushed(long bytes) throws IOException
|
||||||
|
{
|
||||||
|
// Unfortunately cannot distinguish between header and content
|
||||||
|
// bytes, and for content bytes whether they are chunked or not.
|
||||||
|
_channel.getResponse().getHttpOutput().onFlushed(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
void releaseRequestBuffer()
|
void releaseRequestBuffer()
|
||||||
{
|
{
|
||||||
if (_requestBuffer != null && !_requestBuffer.hasRemaining())
|
if (_requestBuffer != null && !_requestBuffer.hasRemaining())
|
||||||
|
@ -477,6 +486,12 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean onReadTimeout(Throwable timeout)
|
||||||
|
{
|
||||||
|
return _channel.onIdleTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onFillInterestedFailed(Throwable cause)
|
protected void onFillInterestedFailed(Throwable cause)
|
||||||
{
|
{
|
||||||
|
|
|
@ -286,7 +286,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
||||||
{
|
{
|
||||||
long minimum_data = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1);
|
long minimum_data = minRequestDataRate * TimeUnit.NANOSECONDS.toMillis(period) / TimeUnit.SECONDS.toMillis(1);
|
||||||
if (_contentArrived < minimum_data)
|
if (_contentArrived < minimum_data)
|
||||||
throw new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408,String.format("Request data rate < %d B/s",minRequestDataRate));
|
throw new BadMessageException(HttpStatus.REQUEST_TIMEOUT_408,String.format("Request content data rate < %d B/s",minRequestDataRate));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -787,7 +787,8 @@ public class HttpInput extends ServletInputStream implements Runnable
|
||||||
{
|
{
|
||||||
synchronized (_inputQ)
|
synchronized (_inputQ)
|
||||||
{
|
{
|
||||||
if (_waitingForContent && !isError())
|
boolean neverDispatched = getHttpChannelState().isIdle();
|
||||||
|
if ((_waitingForContent || neverDispatched) && !isError())
|
||||||
{
|
{
|
||||||
x.addSuppressed(new Throwable("HttpInput idle timeout"));
|
x.addSuppressed(new Throwable("HttpInput idle timeout"));
|
||||||
_state = new ErrorState(x);
|
_state = new ErrorState(x);
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.io.InputStream;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
import java.nio.channels.WritePendingException;
|
import java.nio.channels.WritePendingException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import javax.servlet.RequestDispatcher;
|
import javax.servlet.RequestDispatcher;
|
||||||
|
@ -122,12 +123,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
private final HttpChannel _channel;
|
private final HttpChannel _channel;
|
||||||
private final SharedBlockingCallback _writeBlocker;
|
private final SharedBlockingCallback _writeBlocker;
|
||||||
private Interceptor _interceptor;
|
private Interceptor _interceptor;
|
||||||
|
|
||||||
/**
|
|
||||||
* Bytes written via the write API (excludes bytes written via sendContent). Used to autocommit once content length is written.
|
|
||||||
*/
|
|
||||||
private long _written;
|
private long _written;
|
||||||
|
private long _flushed;
|
||||||
|
private long _firstByteTimeStamp = -1;
|
||||||
private ByteBuffer _aggregate;
|
private ByteBuffer _aggregate;
|
||||||
private int _bufferSize;
|
private int _bufferSize;
|
||||||
private int _commitSize;
|
private int _commitSize;
|
||||||
|
@ -231,6 +229,14 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
|
|
||||||
protected void write(ByteBuffer content, boolean complete, Callback callback)
|
protected void write(ByteBuffer content, boolean complete, Callback callback)
|
||||||
{
|
{
|
||||||
|
if (_firstByteTimeStamp == -1)
|
||||||
|
{
|
||||||
|
long minDataRate = getHttpChannel().getHttpConfiguration().getMinResponseDataRate();
|
||||||
|
if (minDataRate > 0)
|
||||||
|
_firstByteTimeStamp = System.nanoTime();
|
||||||
|
else
|
||||||
|
_firstByteTimeStamp = Long.MAX_VALUE;
|
||||||
|
}
|
||||||
_interceptor.write(content, complete, callback);
|
_interceptor.write(content, complete, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -908,6 +914,30 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
_commitSize = size;
|
_commitSize = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Invoked when bytes have been flushed to the network.</p>
|
||||||
|
* <p>The number of flushed bytes may be different from the bytes written
|
||||||
|
* by the application if an {@link Interceptor} changed them, for example
|
||||||
|
* by compressing them.</p>
|
||||||
|
*
|
||||||
|
* @param bytes the number of bytes flushed
|
||||||
|
* @throws IOException if the minimum data rate, when set, is not respected
|
||||||
|
* @see org.eclipse.jetty.io.WriteFlusher.Listener
|
||||||
|
*/
|
||||||
|
public void onFlushed(long bytes) throws IOException
|
||||||
|
{
|
||||||
|
if (_firstByteTimeStamp == -1 || _firstByteTimeStamp == Long.MAX_VALUE)
|
||||||
|
return;
|
||||||
|
long minDataRate = getHttpChannel().getHttpConfiguration().getMinResponseDataRate();
|
||||||
|
_flushed += bytes;
|
||||||
|
long elapsed = System.nanoTime() - _firstByteTimeStamp;
|
||||||
|
long minFlushed = minDataRate * TimeUnit.NANOSECONDS.toMillis(elapsed) / TimeUnit.SECONDS.toMillis(1);
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Flushed bytes min/actual {}/{}", minFlushed, _flushed);
|
||||||
|
if (_flushed < minFlushed)
|
||||||
|
throw new IOException(String.format("Response content data rate < %d B/s", minDataRate));
|
||||||
|
}
|
||||||
|
|
||||||
public void recycle()
|
public void recycle()
|
||||||
{
|
{
|
||||||
_interceptor = _channel;
|
_interceptor = _channel;
|
||||||
|
@ -920,6 +950,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
||||||
_written = 0;
|
_written = 0;
|
||||||
_writeListener = null;
|
_writeListener = null;
|
||||||
_onError = null;
|
_onError = null;
|
||||||
|
_firstByteTimeStamp = -1;
|
||||||
|
_flushed = 0;
|
||||||
reopen();
|
reopen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ import org.eclipse.jetty.server.handler.HandlerWrapper;
|
||||||
import org.eclipse.jetty.server.handler.StatisticsHandler;
|
import org.eclipse.jetty.server.handler.StatisticsHandler;
|
||||||
import org.eclipse.jetty.util.Attributes;
|
import org.eclipse.jetty.util.Attributes;
|
||||||
import org.eclipse.jetty.util.AttributesMap;
|
import org.eclipse.jetty.util.AttributesMap;
|
||||||
|
import org.eclipse.jetty.util.JavaVersion;
|
||||||
import org.eclipse.jetty.util.Jetty;
|
import org.eclipse.jetty.util.Jetty;
|
||||||
import org.eclipse.jetty.util.MultiException;
|
import org.eclipse.jetty.util.MultiException;
|
||||||
import org.eclipse.jetty.util.URIUtil;
|
import org.eclipse.jetty.util.URIUtil;
|
||||||
|
@ -370,7 +371,7 @@ public class Server extends HandlerWrapper implements Attributes
|
||||||
String gitHash = Jetty.GIT_HASH;
|
String gitHash = Jetty.GIT_HASH;
|
||||||
String timestamp = Jetty.BUILD_TIMESTAMP;
|
String timestamp = Jetty.BUILD_TIMESTAMP;
|
||||||
|
|
||||||
LOG.info("jetty-{}, build timestamp: {}, git hash: {}", getVersion(), timestamp, gitHash);
|
LOG.info("jetty-{}; built: {}; git: {}; jvm {}", getVersion(), timestamp, gitHash, JavaVersion.VERSION);
|
||||||
if (!Jetty.STABLE)
|
if (!Jetty.STABLE)
|
||||||
{
|
{
|
||||||
LOG.warn("THIS IS NOT A STABLE RELEASE! DO NOT USE IN PRODUCTION!");
|
LOG.warn("THIS IS NOT A STABLE RELEASE! DO NOT USE IN PRODUCTION!");
|
||||||
|
|
|
@ -601,7 +601,8 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IO.toString(is);
|
String response = IO.toString(is);
|
||||||
|
Assert.assertThat(response,Matchers.is(""));
|
||||||
Assert.assertEquals(-1, is.read());
|
Assert.assertEquals(-1, is.read());
|
||||||
}
|
}
|
||||||
catch(SSLException e)
|
catch(SSLException e)
|
||||||
|
@ -629,12 +630,13 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
IO.toString(is);
|
String response = IO.toString(is);
|
||||||
|
Assert.assertThat(response,Matchers.is(""));
|
||||||
Assert.assertEquals(-1, is.read());
|
Assert.assertEquals(-1, is.read());
|
||||||
}
|
}
|
||||||
catch(SSLException e)
|
catch(SSLException e)
|
||||||
{
|
{
|
||||||
// e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
catch(Exception e)
|
catch(Exception e)
|
||||||
{
|
{
|
||||||
|
@ -644,6 +646,88 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(timeout=60000)
|
||||||
|
public void testMaxIdleDelayedDispatch() throws Exception
|
||||||
|
{
|
||||||
|
configureServer(new EchoHandler());
|
||||||
|
Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort());
|
||||||
|
client.setSoTimeout(10000);
|
||||||
|
InputStream is=client.getInputStream();
|
||||||
|
Assert.assertFalse(client.isClosed());
|
||||||
|
|
||||||
|
OutputStream os=client.getOutputStream();
|
||||||
|
os.write((
|
||||||
|
"GET / HTTP/1.1\r\n"+
|
||||||
|
"host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+
|
||||||
|
"connection: keep-alive\r\n"+
|
||||||
|
"Content-Length: 20\r\n"+
|
||||||
|
"Content-Type: text/plain\r\n"+
|
||||||
|
"Connection: close\r\n"+
|
||||||
|
"\r\n").getBytes("utf-8"));
|
||||||
|
os.flush();
|
||||||
|
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String response = IO.toString(is);
|
||||||
|
Assert.assertThat(response,Matchers.containsString("500"));
|
||||||
|
Assert.assertEquals(-1, is.read());
|
||||||
|
}
|
||||||
|
catch(SSLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
int duration = (int)(System.currentTimeMillis() - start);
|
||||||
|
Assert.assertThat(duration,Matchers.greaterThanOrEqualTo(MAX_IDLE_TIME));
|
||||||
|
Assert.assertThat(duration,Matchers.lessThan(maximumTestRuntime));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout=60000)
|
||||||
|
public void testMaxIdleDispatch() throws Exception
|
||||||
|
{
|
||||||
|
configureServer(new EchoHandler());
|
||||||
|
Socket client=newSocket(_serverURI.getHost(),_serverURI.getPort());
|
||||||
|
client.setSoTimeout(10000);
|
||||||
|
InputStream is=client.getInputStream();
|
||||||
|
Assert.assertFalse(client.isClosed());
|
||||||
|
|
||||||
|
OutputStream os=client.getOutputStream();
|
||||||
|
os.write((
|
||||||
|
"GET / HTTP/1.1\r\n"+
|
||||||
|
"host: "+_serverURI.getHost()+":"+_serverURI.getPort()+"\r\n"+
|
||||||
|
"connection: keep-alive\r\n"+
|
||||||
|
"Content-Length: 20\r\n"+
|
||||||
|
"Content-Type: text/plain\r\n"+
|
||||||
|
"Connection: close\r\n"+
|
||||||
|
"\r\n"+
|
||||||
|
"1234567890").getBytes("utf-8"));
|
||||||
|
os.flush();
|
||||||
|
|
||||||
|
long start = System.currentTimeMillis();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
String response = IO.toString(is);
|
||||||
|
Assert.assertThat(response,Matchers.containsString("500"));
|
||||||
|
Assert.assertEquals(-1, is.read());
|
||||||
|
}
|
||||||
|
catch(SSLException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
int duration = (int)(System.currentTimeMillis() - start);
|
||||||
|
Assert.assertThat(duration,Matchers.greaterThanOrEqualTo(MAX_IDLE_TIME));
|
||||||
|
Assert.assertThat(duration,Matchers.lessThan(maximumTestRuntime));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test(timeout=60000)
|
@Test(timeout=60000)
|
||||||
public void testMaxIdleWithSlowRequest() throws Exception
|
public void testMaxIdleWithSlowRequest() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
<url>http://www.eclipse.org/jetty</url>
|
<url>http://www.eclipse.org/jetty</url>
|
||||||
<properties>
|
<properties>
|
||||||
<bundle-symbolic-name>${project.groupId}.start</bundle-symbolic-name>
|
<bundle-symbolic-name>${project.groupId}.start</bundle-symbolic-name>
|
||||||
<start-jar-file-name>start.jar</start-jar-file-name>
|
|
||||||
</properties>
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
@ -32,9 +31,47 @@
|
||||||
<onlyAnalyze>org.eclipse.jetty.start.*</onlyAnalyze>
|
<onlyAnalyze>org.eclipse.jetty.start.*</onlyAnalyze>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||||
|
<createSourcesJar>true</createSourcesJar>
|
||||||
|
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||||
|
<shadedClassifierName>shaded</shadedClassifierName>
|
||||||
|
<filters>
|
||||||
|
<filter>
|
||||||
|
<artifact>org.eclipse.jetty:jetty-util</artifact>
|
||||||
|
<includes>
|
||||||
|
<include>org/eclipse/jetty/util/JavaVersion*</include>
|
||||||
|
<include>org/eclipse/jetty/util/TopologicalSort*</include>
|
||||||
|
</includes>
|
||||||
|
</filter>
|
||||||
|
</filters>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>org.eclipse.jetty.util</pattern>
|
||||||
|
<shadedPattern>org.eclipse.jetty.start.shaded.util</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-util</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
<artifactId>jetty-test-helper</artifactId>
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
|
|
|
@ -1,159 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 1995-2017 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.start;
|
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Java Version Utility class.
|
|
||||||
* <p>Parses java versions to extract a consistent set of version parts</p>
|
|
||||||
*/
|
|
||||||
public class JavaVersion
|
|
||||||
{
|
|
||||||
// Copy of code in jetty-util
|
|
||||||
|
|
||||||
private static final Pattern PRE_JDK9 = Pattern.compile("1\\.(\\d)(\\.(\\d+)(_(\\d+))?)?(-.+)?");
|
|
||||||
// Regexp from JEP 223 (http://openjdk.java.net/jeps/223).
|
|
||||||
private static final Pattern JDK9 = Pattern.compile("(\\d+)(\\.(\\d+))?(\\.(\\d+))?((-.+)?(\\+(\\d+)?(-.+)?)?)");
|
|
||||||
|
|
||||||
public static JavaVersion parse(String version)
|
|
||||||
{
|
|
||||||
if (version.startsWith("1."))
|
|
||||||
return parsePreJDK9(version);
|
|
||||||
return parseJDK9(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JavaVersion parsePreJDK9(String version)
|
|
||||||
{
|
|
||||||
Matcher matcher = PRE_JDK9.matcher(version);
|
|
||||||
if (!matcher.matches())
|
|
||||||
throw new IllegalArgumentException("Invalid Java version " + version);
|
|
||||||
int major = 1;
|
|
||||||
int minor = Integer.parseInt(matcher.group(1));
|
|
||||||
String microGroup = matcher.group(3);
|
|
||||||
int micro = microGroup == null || microGroup.isEmpty() ? 0 : Integer.parseInt(microGroup);
|
|
||||||
String updateGroup = matcher.group(5);
|
|
||||||
int update = updateGroup == null || updateGroup.isEmpty() ? 0 : Integer.parseInt(updateGroup);
|
|
||||||
String suffix = matcher.group(6);
|
|
||||||
return new JavaVersion(version, minor, major, minor, micro, update, suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JavaVersion parseJDK9(String version)
|
|
||||||
{
|
|
||||||
Matcher matcher = JDK9.matcher(version);
|
|
||||||
if (!matcher.matches())
|
|
||||||
throw new IllegalArgumentException("Invalid Java version " + version);
|
|
||||||
int major = Integer.parseInt(matcher.group(1));
|
|
||||||
String minorGroup = matcher.group(3);
|
|
||||||
int minor = minorGroup == null || minorGroup.isEmpty() ? 0 : Integer.parseInt(minorGroup);
|
|
||||||
String microGroup = matcher.group(5);
|
|
||||||
int micro = microGroup == null || microGroup.isEmpty() ? 0 : Integer.parseInt(microGroup);
|
|
||||||
String suffix = matcher.group(6);
|
|
||||||
return new JavaVersion(version, major, major, minor, micro, 0, suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String version;
|
|
||||||
private final int platform;
|
|
||||||
private final int major;
|
|
||||||
private final int minor;
|
|
||||||
private final int micro;
|
|
||||||
private final int update;
|
|
||||||
private final String suffix;
|
|
||||||
|
|
||||||
private JavaVersion(String version, int platform, int major, int minor, int micro, int update, String suffix)
|
|
||||||
{
|
|
||||||
this.version = version;
|
|
||||||
this.platform = platform;
|
|
||||||
this.major = major;
|
|
||||||
this.minor = minor;
|
|
||||||
this.micro = micro;
|
|
||||||
this.update = update;
|
|
||||||
this.suffix = suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the string from which this JavaVersion was created
|
|
||||||
*/
|
|
||||||
public String getVersion()
|
|
||||||
{
|
|
||||||
return version;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Returns the Java Platform version, such as {@code 8} for JDK 1.8.0_92 and {@code 9} for JDK 9.2.4.</p>
|
|
||||||
*
|
|
||||||
* @return the Java Platform version
|
|
||||||
*/
|
|
||||||
public int getPlatform()
|
|
||||||
{
|
|
||||||
return platform;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Returns the major number version, such as {@code 1} for JDK 1.8.0_92 and {@code 9} for JDK 9.2.4.</p>
|
|
||||||
*
|
|
||||||
* @return the major number version
|
|
||||||
*/
|
|
||||||
public int getMajor()
|
|
||||||
{
|
|
||||||
return major;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Returns the minor number version, such as {@code 8} for JDK 1.8.0_92 and {@code 2} for JDK 9.2.4.</p>
|
|
||||||
*
|
|
||||||
* @return the minor number version
|
|
||||||
*/
|
|
||||||
public int getMinor()
|
|
||||||
{
|
|
||||||
return minor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Returns the micro number version, such as {@code 0} for JDK 1.8.0_92 and {@code 4} for JDK 9.2.4.</p>
|
|
||||||
*
|
|
||||||
* @return the micro number version
|
|
||||||
*/
|
|
||||||
public int getMicro()
|
|
||||||
{
|
|
||||||
return micro;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Returns the update number version, such as {@code 92} for JDK 1.8.0_92 and {@code 0} for JDK 9.2.4.</p>
|
|
||||||
*
|
|
||||||
* @return the update number version
|
|
||||||
*/
|
|
||||||
public int getUpdate()
|
|
||||||
{
|
|
||||||
return update;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Returns the remaining string after the version numbers, such as {@code -internal} for
|
|
||||||
* JDK 1.8.0_92-internal and {@code -ea} for JDK 9-ea, or {@code +13} for JDK 9.2.4+13.</p>
|
|
||||||
*
|
|
||||||
* @return the remaining string after the version numbers
|
|
||||||
*/
|
|
||||||
public String getSuffix()
|
|
||||||
{
|
|
||||||
return suffix;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -37,6 +37,8 @@ import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.TopologicalSort;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Access for all modules declared, as well as what is enabled.
|
* Access for all modules declared, as well as what is enabled.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -41,6 +41,7 @@ import org.eclipse.jetty.start.Props.Prop;
|
||||||
import org.eclipse.jetty.start.config.ConfigSource;
|
import org.eclipse.jetty.start.config.ConfigSource;
|
||||||
import org.eclipse.jetty.start.config.ConfigSources;
|
import org.eclipse.jetty.start.config.ConfigSources;
|
||||||
import org.eclipse.jetty.start.config.DirConfigSource;
|
import org.eclipse.jetty.start.config.DirConfigSource;
|
||||||
|
import org.eclipse.jetty.util.JavaVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Arguments required to start Jetty.
|
* The Arguments required to start Jetty.
|
||||||
|
|
|
@ -1,204 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 1995-2017 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.start;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.SortedSet;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Topological sort a list or array.
|
|
||||||
* <p>A Topological sort is used when you have a partial ordering expressed as
|
|
||||||
* dependencies between elements (also often represented as edges in a directed
|
|
||||||
* acyclic graph). A Topological sort should not be used when you have a total
|
|
||||||
* ordering expressed as a {@link Comparator} over the items. The algorithm has
|
|
||||||
* the additional characteristic that dependency sets are sorted by the original
|
|
||||||
* list order so that order is preserved when possible.</p>
|
|
||||||
* <p>
|
|
||||||
* The sort algorithm works by recursively visiting every item, once and
|
|
||||||
* only once. On each visit, the items dependencies are first visited and then the
|
|
||||||
* item is added to the sorted list. Thus the algorithm ensures that dependency
|
|
||||||
* items are always added before dependent items.</p>
|
|
||||||
*
|
|
||||||
* @param <T> The type to be sorted. It must be able to be added to a {@link HashSet}
|
|
||||||
*/
|
|
||||||
public class TopologicalSort<T>
|
|
||||||
{
|
|
||||||
private final Map<T,Set<T>> _dependencies = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a dependency to be considered in the sort.
|
|
||||||
* @param dependent The dependent item will be sorted after all its dependencies
|
|
||||||
* @param dependency The dependency item, will be sorted before its dependent item
|
|
||||||
*/
|
|
||||||
public void addDependency(T dependent, T dependency)
|
|
||||||
{
|
|
||||||
Set<T> set = _dependencies.get(dependent);
|
|
||||||
if (set==null)
|
|
||||||
{
|
|
||||||
set=new HashSet<>();
|
|
||||||
_dependencies.put(dependent,set);
|
|
||||||
}
|
|
||||||
set.add(dependency);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sort the passed array according to dependencies previously set with
|
|
||||||
* {@link #addDependency(Object, Object)}. Where possible, ordering will be
|
|
||||||
* preserved if no dependency
|
|
||||||
* @param array The array to be sorted.
|
|
||||||
*/
|
|
||||||
public void sort(T[] array)
|
|
||||||
{
|
|
||||||
List<T> sorted = new ArrayList<>();
|
|
||||||
Set<T> visited = new HashSet<>();
|
|
||||||
Comparator<T> comparator = new InitialOrderComparator<>(array);
|
|
||||||
|
|
||||||
// Visit all items in the array
|
|
||||||
for (T t : array)
|
|
||||||
visit(t,visited,sorted,comparator);
|
|
||||||
|
|
||||||
sorted.toArray(array);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sort the passed list according to dependencies previously set with
|
|
||||||
* {@link #addDependency(Object, Object)}. Where possible, ordering will be
|
|
||||||
* preserved if no dependency
|
|
||||||
* @param list The list to be sorted.
|
|
||||||
*/
|
|
||||||
public void sort(Collection<T> list)
|
|
||||||
{
|
|
||||||
List<T> sorted = new ArrayList<>();
|
|
||||||
Set<T> visited = new HashSet<>();
|
|
||||||
Comparator<T> comparator = new InitialOrderComparator<>(list);
|
|
||||||
|
|
||||||
// Visit all items in the list
|
|
||||||
for (T t : list)
|
|
||||||
visit(t,visited,sorted,comparator);
|
|
||||||
|
|
||||||
list.clear();
|
|
||||||
list.addAll(sorted);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Visit an item to be sorted.
|
|
||||||
* @param item The item to be visited
|
|
||||||
* @param visited The Set of items already visited
|
|
||||||
* @param sorted The list to sort items into
|
|
||||||
* @param comparator A comparator used to sort dependencies.
|
|
||||||
*/
|
|
||||||
private void visit(T item, Set<T> visited, List<T> sorted,Comparator<T> comparator)
|
|
||||||
{
|
|
||||||
// If the item has not been visited
|
|
||||||
if(!visited.contains(item))
|
|
||||||
{
|
|
||||||
// We are visiting it now, so add it to the visited set
|
|
||||||
visited.add(item);
|
|
||||||
|
|
||||||
// Lookup the items dependencies
|
|
||||||
Set<T> dependencies = _dependencies.get(item);
|
|
||||||
if (dependencies!=null)
|
|
||||||
{
|
|
||||||
// Sort the dependencies
|
|
||||||
SortedSet<T> ordered_deps = new TreeSet<>(comparator);
|
|
||||||
ordered_deps.addAll(dependencies);
|
|
||||||
|
|
||||||
// recursively visit each dependency
|
|
||||||
try
|
|
||||||
{
|
|
||||||
for (T d:ordered_deps)
|
|
||||||
visit(d,visited,sorted,comparator);
|
|
||||||
}
|
|
||||||
catch (CyclicException e)
|
|
||||||
{
|
|
||||||
throw new CyclicException(item,e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we have visited all our dependencies, they and their
|
|
||||||
// dependencies will have been added to the sorted list. So we can
|
|
||||||
// now add the current item and it will be after its dependencies
|
|
||||||
sorted.add(item);
|
|
||||||
}
|
|
||||||
else if (!sorted.contains(item))
|
|
||||||
// If we have already visited an item, but it has not yet been put in the
|
|
||||||
// sorted list, then we must be in a cycle!
|
|
||||||
throw new CyclicException(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/** A comparator that is used to sort dependencies in the order they
|
|
||||||
* were in the original list. This ensures that dependencies are visited
|
|
||||||
* in the original order and no needless reordering takes place.
|
|
||||||
* @param <T>
|
|
||||||
*/
|
|
||||||
private static class InitialOrderComparator<T> implements Comparator<T>
|
|
||||||
{
|
|
||||||
private final Map<T,Integer> _indexes = new HashMap<>();
|
|
||||||
InitialOrderComparator(T[] initial)
|
|
||||||
{
|
|
||||||
int i=0;
|
|
||||||
for (T t : initial)
|
|
||||||
_indexes.put(t,i++);
|
|
||||||
}
|
|
||||||
|
|
||||||
InitialOrderComparator(Collection<T> initial)
|
|
||||||
{
|
|
||||||
int i=0;
|
|
||||||
for (T t : initial)
|
|
||||||
_indexes.put(t,i++);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compare(T o1, T o2)
|
|
||||||
{
|
|
||||||
Integer i1=_indexes.get(o1);
|
|
||||||
Integer i2=_indexes.get(o2);
|
|
||||||
if (i1==null || i2==null || i1.equals(o2))
|
|
||||||
return 0;
|
|
||||||
if (i1<i2)
|
|
||||||
return -1;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString()
|
|
||||||
{
|
|
||||||
return "TopologicalSort "+_dependencies;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CyclicException extends IllegalStateException
|
|
||||||
{
|
|
||||||
CyclicException(Object item)
|
|
||||||
{
|
|
||||||
super("cyclic at "+item);
|
|
||||||
}
|
|
||||||
|
|
||||||
CyclicException(Object item,CyclicException e)
|
|
||||||
{
|
|
||||||
super("cyclic at "+item,e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -27,55 +27,103 @@ import java.util.regex.Pattern;
|
||||||
*/
|
*/
|
||||||
public class JavaVersion
|
public class JavaVersion
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context attribute that can be set to target a different version of the jvm than the current runtime.
|
* Context attribute that can be set to target a different version of the jvm than the current runtime.
|
||||||
* Acceptable values should correspond to those returned by JavaVersion.getPlatform().
|
* Acceptable values should correspond to those returned by JavaVersion.getPlatform().
|
||||||
*/
|
*/
|
||||||
public static final String JAVA_TARGET_PLATFORM = "org.eclipse.jetty.javaTargetPlatform";
|
public static final String JAVA_TARGET_PLATFORM = "org.eclipse.jetty.javaTargetPlatform";
|
||||||
|
|
||||||
// Copy of version in jetty-start
|
/** Regex for Java version numbers */
|
||||||
|
private static final String VNUM = "(?<VNUM>[1-9][0-9]*(?:(?:\\.0)*\\.[0-9]+)*)";
|
||||||
|
private static final String UPDATE = "(?:(?<UNDERSCORE>_)(?<UPDATE>[0-9]+))?";
|
||||||
|
private static final String PRE = "(?:-(?<PRE>[a-zA-Z0-9]+))?";
|
||||||
|
private static final String BUILD = "(?:(?<PLUS>\\+)(?<BUILD>[0-9]+))?";
|
||||||
|
private static final String OPT = "(?:-(?<OPT>[-a-zA-Z0-9.]+))?";
|
||||||
|
|
||||||
private static final Pattern PRE_JDK9 = Pattern.compile("1\\.(\\d)(\\.(\\d+)(_(\\d+))?)?(-.+)?");
|
private static final String VSTR_FORMAT = VNUM + UPDATE + PRE + BUILD + OPT;
|
||||||
// Regexp from JEP 223 (http://openjdk.java.net/jeps/223).
|
|
||||||
private static final Pattern JDK9 = Pattern.compile("(\\d+)(\\.(\\d+))?(\\.(\\d+))?((-.+)?(\\+(\\d+)?(-.+)?)?)");
|
|
||||||
|
|
||||||
public static final JavaVersion VERSION = parse(System.getProperty("java.version"));
|
static final Pattern VSTR_PATTERN = Pattern.compile(VSTR_FORMAT);
|
||||||
|
|
||||||
public static JavaVersion parse(String version)
|
public static final JavaVersion VERSION = parse(System.getProperty("java.runtime.version",System.getProperty("java.version")));
|
||||||
|
|
||||||
|
public static JavaVersion parse(String v)
|
||||||
{
|
{
|
||||||
if (version.startsWith("1."))
|
Matcher m = VSTR_PATTERN.matcher(v);
|
||||||
return parsePreJDK9(version);
|
if (!m.matches())
|
||||||
return parseJDK9(version);
|
throw new IllegalArgumentException("Invalid version string: '" + v + "'");
|
||||||
|
|
||||||
|
// $VNUM is a dot-separated list of integers of arbitrary length
|
||||||
|
String[] split = m.group("VNUM").split("\\.");
|
||||||
|
int[] version = new int[split.length];
|
||||||
|
for (int i = 0; i < split.length; i++)
|
||||||
|
version[i] = Integer.parseInt(split[i]);
|
||||||
|
|
||||||
|
if (m.group("UNDERSCORE")!=null)
|
||||||
|
{
|
||||||
|
return new JavaVersion(
|
||||||
|
v,
|
||||||
|
(version[0]>=9 || version.length==1)?version[0]:version[1],
|
||||||
|
version[0],
|
||||||
|
version.length>1?version[1]:0,
|
||||||
|
version.length>2?version[2]:0,
|
||||||
|
Integer.parseInt(m.group("UPDATE")),
|
||||||
|
suffix(version,m.group("PRE"),m.group("OPT"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JavaVersion parsePreJDK9(String version)
|
if (m.group("PLUS")!=null)
|
||||||
{
|
{
|
||||||
Matcher matcher = PRE_JDK9.matcher(version);
|
return new JavaVersion(
|
||||||
if (!matcher.matches())
|
v,
|
||||||
throw new IllegalArgumentException("Invalid Java version " + version);
|
(version[0]>=9 || version.length==1)?version[0]:version[1],
|
||||||
int major = 1;
|
version[0],
|
||||||
int minor = Integer.parseInt(matcher.group(1));
|
version.length>1?version[1]:0,
|
||||||
String microGroup = matcher.group(3);
|
version.length>2?version[2]:0,
|
||||||
int micro = microGroup == null || microGroup.isEmpty() ? 0 : Integer.parseInt(microGroup);
|
Integer.parseInt(m.group("BUILD")),
|
||||||
String updateGroup = matcher.group(5);
|
suffix(version,m.group("PRE"),m.group("OPT"))
|
||||||
int update = updateGroup == null || updateGroup.isEmpty() ? 0 : Integer.parseInt(updateGroup);
|
);
|
||||||
String suffix = matcher.group(6);
|
|
||||||
return new JavaVersion(version, minor, major, minor, micro, update, suffix);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JavaVersion parseJDK9(String version)
|
return new JavaVersion(
|
||||||
|
v,
|
||||||
|
(version[0]>=9 || version.length==1)?version[0]:version[1],
|
||||||
|
version[0],
|
||||||
|
version.length>1?version[1]:0,
|
||||||
|
version.length>2?version[2]:0,
|
||||||
|
0,
|
||||||
|
suffix(version,m.group("PRE"),m.group("OPT"))
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String suffix(int[] version, String pre, String opt)
|
||||||
{
|
{
|
||||||
Matcher matcher = JDK9.matcher(version);
|
StringBuilder buf = new StringBuilder();
|
||||||
if (!matcher.matches())
|
for (int i=3;i<version.length;i++)
|
||||||
throw new IllegalArgumentException("Invalid Java version " + version);
|
{
|
||||||
int major = Integer.parseInt(matcher.group(1));
|
if (i>3)
|
||||||
String minorGroup = matcher.group(3);
|
buf.append(".");
|
||||||
int minor = minorGroup == null || minorGroup.isEmpty() ? 0 : Integer.parseInt(minorGroup);
|
buf.append(version[i]);
|
||||||
String microGroup = matcher.group(5);
|
}
|
||||||
int micro = microGroup == null || microGroup.isEmpty() ? 0 : Integer.parseInt(microGroup);
|
|
||||||
String suffix = matcher.group(6);
|
if (pre!=null)
|
||||||
return new JavaVersion(version, major, major, minor, micro, 0, suffix);
|
{
|
||||||
|
if (buf.length()>0)
|
||||||
|
buf.append('-');
|
||||||
|
buf.append(pre);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opt!=null)
|
||||||
|
{
|
||||||
|
if (buf.length()>0)
|
||||||
|
buf.append('-');
|
||||||
|
buf.append(opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf.length()==0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String version;
|
private final String version;
|
||||||
|
@ -136,7 +184,7 @@ public class JavaVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Returns the micro number version, such as {@code 0} for JDK 1.8.0_92 and {@code 4} for JDK 9.2.4.</p>
|
* <p>Returns the micro number version (aka security number), such as {@code 0} for JDK 1.8.0_92 and {@code 4} for JDK 9.2.4.</p>
|
||||||
*
|
*
|
||||||
* @return the micro number version
|
* @return the micro number version
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -56,7 +56,10 @@ public class Jetty
|
||||||
LOG.ignore( e );
|
LOG.ignore( e );
|
||||||
}
|
}
|
||||||
|
|
||||||
GIT_HASH = __buildProperties.getProperty( "buildNumber", "unknown" );
|
String git_hash = __buildProperties.getProperty( "buildNumber", "unknown" );
|
||||||
|
if (git_hash.startsWith("${"))
|
||||||
|
git_hash = "unknown";
|
||||||
|
GIT_HASH = git_hash;
|
||||||
System.setProperty( "jetty.git.hash" , GIT_HASH );
|
System.setProperty( "jetty.git.hash" , GIT_HASH );
|
||||||
BUILD_TIMESTAMP = formatTimestamp( __buildProperties.getProperty( "timestamp", "unknown" ));
|
BUILD_TIMESTAMP = formatTimestamp( __buildProperties.getProperty( "timestamp", "unknown" ));
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
package org.eclipse.jetty.util;
|
package org.eclipse.jetty.util;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
@ -29,9 +28,6 @@ import java.util.Set;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
|
||||||
import org.eclipse.jetty.util.component.Dumpable;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Topological sort a list or array.
|
* Topological sort a list or array.
|
||||||
|
@ -49,7 +45,7 @@ import org.eclipse.jetty.util.component.Dumpable;
|
||||||
*
|
*
|
||||||
* @param <T> The type to be sorted. It must be able to be added to a {@link HashSet}
|
* @param <T> The type to be sorted. It must be able to be added to a {@link HashSet}
|
||||||
*/
|
*/
|
||||||
public class TopologicalSort<T> implements Dumpable
|
public class TopologicalSort<T>
|
||||||
{
|
{
|
||||||
private final Map<T,Set<T>> _dependencies = new HashMap<>();
|
private final Map<T,Set<T>> _dependencies = new HashMap<>();
|
||||||
|
|
||||||
|
@ -185,7 +181,6 @@ public class TopologicalSort<T> implements Dumpable
|
||||||
return -1;
|
return -1;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -194,19 +189,6 @@ public class TopologicalSort<T> implements Dumpable
|
||||||
return "TopologicalSort "+_dependencies;
|
return "TopologicalSort "+_dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String dump()
|
|
||||||
{
|
|
||||||
return ContainerLifeCycle.dump(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void dump(Appendable out, String indent) throws IOException
|
|
||||||
{
|
|
||||||
out.append(String.format("TopologicalSort@%x%n",hashCode()));
|
|
||||||
ContainerLifeCycle.dump(out, indent,_dependencies.entrySet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CyclicException extends IllegalStateException
|
private static class CyclicException extends IllegalStateException
|
||||||
{
|
{
|
||||||
CyclicException(Object item)
|
CyclicException(Object item)
|
||||||
|
|
|
@ -21,9 +21,6 @@ package org.eclipse.jetty.util.component;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
|
||||||
import org.eclipse.jetty.util.component.Dumpable;
|
|
||||||
|
|
||||||
public class DumpableCollection implements Dumpable
|
public class DumpableCollection implements Dumpable
|
||||||
{
|
{
|
||||||
private final String _name;
|
private final String _name;
|
||||||
|
@ -44,7 +41,7 @@ public class DumpableCollection implements Dumpable
|
||||||
@Override
|
@Override
|
||||||
public void dump(Appendable out, String indent) throws IOException
|
public void dump(Appendable out, String indent) throws IOException
|
||||||
{
|
{
|
||||||
out.append(_name).append("\n");
|
out.append(_name).append(System.lineSeparator());
|
||||||
if (_collection!=null)
|
if (_collection!=null)
|
||||||
ContainerLifeCycle.dump(out,indent,_collection);
|
ContainerLifeCycle.dump(out,indent,_collection);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,151 @@
|
||||||
|
//
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 1995-2017 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.util;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for LazyList utility class.
|
||||||
|
*/
|
||||||
|
public class JavaVersionTest
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void test9()
|
||||||
|
{
|
||||||
|
JavaVersion version = JavaVersion.parse("9.0.1");
|
||||||
|
assertThat(version.getPlatform(),is(9));
|
||||||
|
assertThat(version.getMajor(),is(9));
|
||||||
|
assertThat(version.getMinor(),is(0));
|
||||||
|
assertThat(version.getMicro(),is(1));
|
||||||
|
assertThat(version.getUpdate(),is(0));
|
||||||
|
assertThat(version.getSuffix(),nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test9nano()
|
||||||
|
{
|
||||||
|
JavaVersion version = JavaVersion.parse("9.0.1.3");
|
||||||
|
assertThat(version.getPlatform(),is(9));
|
||||||
|
assertThat(version.getMajor(),is(9));
|
||||||
|
assertThat(version.getMinor(),is(0));
|
||||||
|
assertThat(version.getMicro(),is(1));
|
||||||
|
assertThat(version.getUpdate(),is(0));
|
||||||
|
assertThat(version.getSuffix(),is("3"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test9build()
|
||||||
|
{
|
||||||
|
JavaVersion version = JavaVersion.parse("9.0.1+11");
|
||||||
|
assertThat(version.getPlatform(),is(9));
|
||||||
|
assertThat(version.getMajor(),is(9));
|
||||||
|
assertThat(version.getMinor(),is(0));
|
||||||
|
assertThat(version.getMicro(),is(1));
|
||||||
|
assertThat(version.getUpdate(),is(11));
|
||||||
|
assertThat(version.getSuffix(),nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test9all()
|
||||||
|
{
|
||||||
|
JavaVersion version = JavaVersion.parse("9.0.1-ea+11-b01");
|
||||||
|
assertThat(version.getPlatform(),is(9));
|
||||||
|
assertThat(version.getMajor(),is(9));
|
||||||
|
assertThat(version.getMinor(),is(0));
|
||||||
|
assertThat(version.getMicro(),is(1));
|
||||||
|
assertThat(version.getUpdate(),is(11));
|
||||||
|
assertThat(version.getSuffix(),is("ea-b01"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test9yuck()
|
||||||
|
{
|
||||||
|
JavaVersion version = JavaVersion.parse("9.0.1.2.3-ea+11-b01");
|
||||||
|
assertThat(version.getPlatform(),is(9));
|
||||||
|
assertThat(version.getMajor(),is(9));
|
||||||
|
assertThat(version.getMinor(),is(0));
|
||||||
|
assertThat(version.getMicro(),is(1));
|
||||||
|
assertThat(version.getUpdate(),is(11));
|
||||||
|
assertThat(version.getSuffix(),is("2.3-ea-b01"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test10ea()
|
||||||
|
{
|
||||||
|
JavaVersion version = JavaVersion.parse("10-ea");
|
||||||
|
assertThat(version.getPlatform(),is(10));
|
||||||
|
assertThat(version.getMajor(),is(10));
|
||||||
|
assertThat(version.getMinor(),is(0));
|
||||||
|
assertThat(version.getMicro(),is(0));
|
||||||
|
assertThat(version.getUpdate(),is(0));
|
||||||
|
assertThat(version.getSuffix(),is("ea"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test8()
|
||||||
|
{
|
||||||
|
JavaVersion version = JavaVersion.parse("1.8.0_152");
|
||||||
|
assertThat(version.getPlatform(),is(8));
|
||||||
|
assertThat(version.getMajor(),is(1));
|
||||||
|
assertThat(version.getMinor(),is(8));
|
||||||
|
assertThat(version.getMicro(),is(0));
|
||||||
|
assertThat(version.getUpdate(),is(152));
|
||||||
|
assertThat(version.getSuffix(),nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test8ea()
|
||||||
|
{
|
||||||
|
JavaVersion version = JavaVersion.parse("1.8.1_03-ea");
|
||||||
|
assertThat(version.getPlatform(),is(8));
|
||||||
|
assertThat(version.getMajor(),is(1));
|
||||||
|
assertThat(version.getMinor(),is(8));
|
||||||
|
assertThat(version.getMicro(),is(1));
|
||||||
|
assertThat(version.getUpdate(),is(3));
|
||||||
|
assertThat(version.getSuffix(),is("ea"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test3eaBuild()
|
||||||
|
{
|
||||||
|
JavaVersion version = JavaVersion.parse("1.3.1_05-ea-b01");
|
||||||
|
assertThat(version.getPlatform(),is(3));
|
||||||
|
assertThat(version.getMajor(),is(1));
|
||||||
|
assertThat(version.getMinor(),is(3));
|
||||||
|
assertThat(version.getMicro(),is(1));
|
||||||
|
assertThat(version.getUpdate(),is(5));
|
||||||
|
assertThat(version.getSuffix(),is("ea-b01"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUbuntu()
|
||||||
|
{
|
||||||
|
JavaVersion version = JavaVersion.parse("9-Ubuntu+0-9b181-4");
|
||||||
|
assertThat(version.getPlatform(),is(9));
|
||||||
|
assertThat(version.getMajor(),is(9));
|
||||||
|
assertThat(version.getMinor(),is(0));
|
||||||
|
assertThat(version.getMicro(),is(0));
|
||||||
|
assertThat(version.getUpdate(),is(0));
|
||||||
|
assertThat(version.getSuffix(),is("Ubuntu-9b181-4"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,14 +18,6 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.websocket.client;
|
package org.eclipse.jetty.websocket.client;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.allOf;
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
|
||||||
import static org.hamcrest.Matchers.is;
|
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
@ -78,6 +70,14 @@ import org.junit.Ignore;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.allOf;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
public class ClientCloseTest
|
public class ClientCloseTest
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(ClientCloseTest.class);
|
private static final Logger LOG = Log.getLogger(ClientCloseTest.class);
|
||||||
|
@ -147,7 +147,7 @@ public class ClientCloseTest
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketError(Throwable cause)
|
public void onWebSocketError(Throwable cause)
|
||||||
{
|
{
|
||||||
LOG.warn("onWebSocketError",cause);
|
LOG.debug("onWebSocketError",cause);
|
||||||
assertThat("Unique Error Event", error.compareAndSet(null, cause), is(true));
|
assertThat("Unique Error Event", error.compareAndSet(null, cause), is(true));
|
||||||
errorLatch.countDown();
|
errorLatch.countDown();
|
||||||
}
|
}
|
||||||
|
@ -526,8 +526,7 @@ public class ClientCloseTest
|
||||||
|
|
||||||
// client idle timeout triggers close event on client ws-endpoint
|
// client idle timeout triggers close event on client ws-endpoint
|
||||||
assertThat("OnError Latch", clientSocket.errorLatch.await(2, TimeUnit.SECONDS), is(true));
|
assertThat("OnError Latch", clientSocket.errorLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||||
assertThat("OnError", clientSocket.error.get(), instanceOf(SocketTimeoutException.class));
|
assertThat("OnError", clientSocket.error.get(), instanceOf(TimeoutException.class));
|
||||||
assertThat("OnError", clientSocket.error.get().getMessage(), containsString("Timeout on Read"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout = 5000L)
|
@Test(timeout = 5000L)
|
||||||
|
|
|
@ -21,7 +21,6 @@ package org.eclipse.jetty.websocket.common.io;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketTimeoutException;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -453,7 +452,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
* @return true to signal that the endpoint must be closed, false to keep the endpoint open
|
* @return true to signal that the endpoint must be closed, false to keep the endpoint open
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected boolean onReadTimeout()
|
protected boolean onReadTimeout(Throwable timeout)
|
||||||
{
|
{
|
||||||
IOState state = getIOState();
|
IOState state = getIOState();
|
||||||
ConnectionState cstate = state.getConnectionState();
|
ConnectionState cstate = state.getConnectionState();
|
||||||
|
@ -471,7 +470,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
notifyError(new SocketTimeoutException("Timeout on Read"));
|
notifyError(timeout);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,11 +27,16 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
|
|
||||||
public class EmptyServerHandler extends AbstractHandler
|
public class EmptyServerHandler extends AbstractHandler.ErrorDispatchHandler
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
jettyRequest.setHandled(true);
|
||||||
|
service(target, jettyRequest, request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
{
|
{
|
||||||
baseRequest.setHandled(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.eclipse.jetty.client.HttpClientTransport;
|
||||||
import org.eclipse.jetty.client.HttpDestination;
|
import org.eclipse.jetty.client.HttpDestination;
|
||||||
import org.eclipse.jetty.client.HttpExchange;
|
import org.eclipse.jetty.client.HttpExchange;
|
||||||
import org.eclipse.jetty.client.api.Connection;
|
import org.eclipse.jetty.client.api.Connection;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.client.http.HttpChannelOverHTTP;
|
import org.eclipse.jetty.client.http.HttpChannelOverHTTP;
|
||||||
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
|
||||||
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
|
||||||
|
@ -146,9 +147,9 @@ public class HttpChannelAssociationTest extends AbstractTest
|
||||||
return new HttpConnectionOverHTTP2(destination, session)
|
return new HttpConnectionOverHTTP2(destination, session)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected HttpChannelOverHTTP2 newHttpChannel(boolean push)
|
protected HttpChannelOverHTTP2 newHttpChannel()
|
||||||
{
|
{
|
||||||
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession(), push)
|
return new HttpChannelOverHTTP2(getHttpDestination(), this, getSession())
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public boolean associate(HttpExchange exchange)
|
public boolean associate(HttpExchange exchange)
|
||||||
|
@ -171,9 +172,9 @@ public class HttpChannelAssociationTest extends AbstractTest
|
||||||
return new HttpConnectionOverFCGI(endPoint, destination, promise, isMultiplexed())
|
return new HttpConnectionOverFCGI(endPoint, destination, promise, isMultiplexed())
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected HttpChannelOverFCGI newHttpChannel(int id, org.eclipse.jetty.client.api.Request request)
|
protected HttpChannelOverFCGI newHttpChannel(Request request)
|
||||||
{
|
{
|
||||||
return new HttpChannelOverFCGI(this, getFlusher(), id, request.getIdleTimeout())
|
return new HttpChannelOverFCGI(this, getFlusher(), request.getIdleTimeout())
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public boolean associate(HttpExchange exchange)
|
public boolean associate(HttpExchange exchange)
|
||||||
|
|
|
@ -37,7 +37,6 @@ import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.client.api.Response;
|
import org.eclipse.jetty.client.api.Response;
|
||||||
import org.eclipse.jetty.client.util.BytesContentProvider;
|
import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||||
|
@ -497,9 +496,8 @@ public class HttpClientTest extends AbstractTest
|
||||||
start(new EmptyServerHandler()
|
start(new EmptyServerHandler()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
{
|
{
|
||||||
super.handle(target, baseRequest, request, response);
|
|
||||||
response.getWriter().write("Jetty");
|
response.getWriter().write("Jetty");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -43,14 +43,19 @@ import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||||
import org.eclipse.jetty.client.util.DeferredContentProvider;
|
import org.eclipse.jetty.client.util.DeferredContentProvider;
|
||||||
import org.eclipse.jetty.http.BadMessageException;
|
import org.eclipse.jetty.http.BadMessageException;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http2.FlowControlStrategy;
|
||||||
|
import org.eclipse.jetty.http2.client.http.HttpClientTransportOverHTTP2;
|
||||||
import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory;
|
import org.eclipse.jetty.http2.server.AbstractHTTP2ServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.HttpChannel;
|
import org.eclipse.jetty.server.HttpChannel;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.util.Callback;
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.eclipse.jetty.util.log.StacklessLogging;
|
import org.eclipse.jetty.util.log.StacklessLogging;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Assume;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class ServerTimeoutsTest extends AbstractTest
|
public class ServerTimeoutsTest extends AbstractTest
|
||||||
|
@ -71,34 +76,114 @@ public class ServerTimeoutsTest extends AbstractTest
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDelayedDispatchRequestWithDelayedFirstContentIdleTimeoutFires() throws Exception
|
public void testBlockingReadWithDelayedFirstContentWithUndelayedDispatchIdleTimeoutFires() throws Exception
|
||||||
{
|
{
|
||||||
httpConfig.setDelayDispatchUntilContent(true);
|
testBlockingReadWithDelayedFirstContentIdleTimeoutFires(false);
|
||||||
CountDownLatch handlerLatch = new CountDownLatch(1);
|
}
|
||||||
start(new AbstractHandler.ErrorDispatchHandler()
|
|
||||||
|
@Test
|
||||||
|
public void testBlockingReadWithDelayedFirstContentWithDelayedDispatchIdleTimeoutFires() throws Exception
|
||||||
|
{
|
||||||
|
testBlockingReadWithDelayedFirstContentIdleTimeoutFires(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAsyncReadWithDelayedFirstContentWithUndelayedDispatchIdleTimeoutFires() throws Exception
|
||||||
|
{
|
||||||
|
testAsyncReadWithDelayedFirstContentIdleTimeoutFires(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAsyncReadWithDelayedFirstContentWithDelayedDispatchIdleTimeoutFires() throws Exception
|
||||||
|
{
|
||||||
|
testAsyncReadWithDelayedFirstContentIdleTimeoutFires(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testBlockingReadWithDelayedFirstContentIdleTimeoutFires(boolean delayDispatch) throws Exception
|
||||||
|
{
|
||||||
|
testReadWithDelayedFirstContentIdleTimeoutFires(new EmptyServerHandler()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void doNonErrorHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
{
|
{
|
||||||
baseRequest.setHandled(true);
|
// The client did not send the content,
|
||||||
handlerLatch.countDown();
|
// idle timeout should result in IOException.
|
||||||
|
request.getInputStream().read();
|
||||||
|
}
|
||||||
|
}, delayDispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testAsyncReadWithDelayedFirstContentIdleTimeoutFires(boolean delayDispatch) throws Exception
|
||||||
|
{
|
||||||
|
testReadWithDelayedFirstContentIdleTimeoutFires(new EmptyServerHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
AsyncContext asyncContext = request.startAsync();
|
||||||
|
asyncContext.setTimeout(0);
|
||||||
|
request.getInputStream().setReadListener(new ReadListener()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onDataAvailable()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAllDataRead()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable t)
|
||||||
|
{
|
||||||
|
if (t instanceof TimeoutException)
|
||||||
|
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
|
||||||
|
asyncContext.complete();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
long idleTimeout = 2500;
|
|
||||||
|
}
|
||||||
|
}, delayDispatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testReadWithDelayedFirstContentIdleTimeoutFires(Handler handler, boolean delayDispatch) throws Exception
|
||||||
|
{
|
||||||
|
httpConfig.setDelayDispatchUntilContent(delayDispatch);
|
||||||
|
CountDownLatch handlerLatch = new CountDownLatch(1);
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
handler.handle(target, jettyRequest, request, response);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
handlerLatch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
long idleTimeout = 1000;
|
||||||
setServerIdleTimeout(idleTimeout);
|
setServerIdleTimeout(idleTimeout);
|
||||||
|
|
||||||
CountDownLatch resultLatch = new CountDownLatch(1);
|
CountDownLatch resultLatch = new CountDownLatch(2);
|
||||||
|
DeferredContentProvider content = new DeferredContentProvider();
|
||||||
client.POST(newURI())
|
client.POST(newURI())
|
||||||
.content(new DeferredContentProvider())
|
.content(content)
|
||||||
.send(result ->
|
.onResponseSuccess(response ->
|
||||||
{
|
{
|
||||||
if (result.isFailed())
|
if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR_500)
|
||||||
resultLatch.countDown();
|
resultLatch.countDown();
|
||||||
});
|
content.close();
|
||||||
|
})
|
||||||
|
.send(result -> resultLatch.countDown());
|
||||||
|
|
||||||
// We did not send the content, the request was not
|
// The client did not send the content, the request was
|
||||||
// dispatched, the server should have idle timed out.
|
// dispatched, the server should have idle timed it out.
|
||||||
Assert.assertFalse(handlerLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
|
Assert.assertTrue(handlerLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
|
||||||
Assert.assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -736,6 +821,76 @@ public class ServerTimeoutsTest extends AbstractTest
|
||||||
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBlockingWriteWithMinimumDataRateBelowLimit() throws Exception
|
||||||
|
{
|
||||||
|
// This test needs a large write to stall the server, and a slow reading client.
|
||||||
|
// In HTTP/1.1, when using the loopback interface, the buffers are so large that
|
||||||
|
// it would require a very large write (32 MiB) and a lot of time for this test
|
||||||
|
// to pass. On the first writes, the server fills in the large buffers with a lot
|
||||||
|
// of bytes (about 4 MiB), and so it would take a lot of time for the client to
|
||||||
|
// read those bytes and eventually produce a write rate that will make the server
|
||||||
|
// fail; and the write should be large enough to _not_ complete before the rate
|
||||||
|
// is below the minimum.
|
||||||
|
// In HTTP/2, we force the flow control window to be small, so that the server
|
||||||
|
// stalls almost immediately without having written many bytes, so that the test
|
||||||
|
// completes quickly.
|
||||||
|
Assume.assumeThat(transport, Matchers.isOneOf(Transport.H2, Transport.H2C));
|
||||||
|
|
||||||
|
int bytesPerSecond = 16 * 1024;
|
||||||
|
httpConfig.setMinResponseDataRate(bytesPerSecond);
|
||||||
|
CountDownLatch serverLatch = new CountDownLatch(1);
|
||||||
|
start(new EmptyServerHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void service(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ServletOutputStream output = response.getOutputStream();
|
||||||
|
output.write(new byte[8 * 1024 * 1024]);
|
||||||
|
}
|
||||||
|
catch (IOException x)
|
||||||
|
{
|
||||||
|
serverLatch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
((HttpClientTransportOverHTTP2)client.getTransport()).getHTTP2Client().setInitialStreamRecvWindow(FlowControlStrategy.DEFAULT_WINDOW_SIZE);
|
||||||
|
|
||||||
|
// Setup the client to read slower than the min data rate.
|
||||||
|
BlockingQueue<Object> objects = new LinkedBlockingQueue<>();
|
||||||
|
CountDownLatch clientLatch = new CountDownLatch(1);
|
||||||
|
client.newRequest(newURI())
|
||||||
|
.onResponseContentAsync((response, content, callback) ->
|
||||||
|
{
|
||||||
|
objects.offer(content.remaining());
|
||||||
|
objects.offer(callback);
|
||||||
|
})
|
||||||
|
.send(result ->
|
||||||
|
{
|
||||||
|
objects.offer(-1);
|
||||||
|
objects.offer(Callback.NOOP);
|
||||||
|
if (result.isFailed())
|
||||||
|
clientLatch.countDown();
|
||||||
|
});
|
||||||
|
|
||||||
|
long readRate = bytesPerSecond / 2;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int bytes = (Integer)objects.poll(5, TimeUnit.SECONDS);
|
||||||
|
if (bytes < 0)
|
||||||
|
break;
|
||||||
|
long ms = bytes * 1000L / readRate;
|
||||||
|
Thread.sleep(ms);
|
||||||
|
Callback callback = (Callback)objects.poll();
|
||||||
|
callback.succeeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.assertTrue(serverLatch.await(15, TimeUnit.SECONDS));
|
||||||
|
Assert.assertTrue(clientLatch.await(15, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
private static class BlockingReadHandler extends AbstractHandler.ErrorDispatchHandler
|
private static class BlockingReadHandler extends AbstractHandler.ErrorDispatchHandler
|
||||||
{
|
{
|
||||||
private final CountDownLatch handlerLatch;
|
private final CountDownLatch handlerLatch;
|
||||||
|
|
|
@ -49,6 +49,15 @@
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<java.util.logging.config.file>${project.build.testOutputDirectory}/logging.properties</java.util.logging.config.file>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -88,7 +97,6 @@
|
||||||
<artifactId>jetty-test-helper</artifactId>
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -20,19 +20,29 @@ package org.eclipse.jetty.hazelcast.session;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.session.AbstractClusteredLastAccessTimeTest;
|
import org.eclipse.jetty.server.session.AbstractClusteredLastAccessTimeTest;
|
||||||
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
||||||
|
import org.junit.After;
|
||||||
|
|
||||||
public class ClusteredLastAccessTimeTest
|
public class ClusteredLastAccessTimeTest
|
||||||
extends AbstractClusteredLastAccessTimeTest
|
extends AbstractClusteredLastAccessTimeTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
HazelcastSessionDataStoreFactory factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SessionDataStoreFactory createSessionDataStoreFactory()
|
public SessionDataStoreFactory createSessionDataStoreFactory()
|
||||||
{
|
{
|
||||||
HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
|
factory = new HazelcastSessionDataStoreFactory();
|
||||||
|
factory.setMapName( Long.toString( System.currentTimeMillis() ) );
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
factory.getHazelcastInstance().getMap( factory.getMapName() ).clear();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.session.AbstractClusteredOrphanedSessionTest;
|
import org.eclipse.jetty.server.session.AbstractClusteredOrphanedSessionTest;
|
||||||
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
||||||
|
import org.junit.After;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ClusteredOrphanedSessionTest
|
* ClusteredOrphanedSessionTest
|
||||||
|
@ -29,6 +30,7 @@ public class ClusteredOrphanedSessionTest
|
||||||
extends AbstractClusteredOrphanedSessionTest
|
extends AbstractClusteredOrphanedSessionTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
HazelcastSessionDataStoreFactory factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
||||||
|
@ -36,9 +38,14 @@ public class ClusteredOrphanedSessionTest
|
||||||
@Override
|
@Override
|
||||||
public SessionDataStoreFactory createSessionDataStoreFactory()
|
public SessionDataStoreFactory createSessionDataStoreFactory()
|
||||||
{
|
{
|
||||||
HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
|
factory = new HazelcastSessionDataStoreFactory();
|
||||||
|
factory.setMapName( Long.toString( System.currentTimeMillis() ) );
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
factory.getHazelcastInstance().getMap( factory.getMapName() ).clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.session.AbstractClusteredSessionMigrationTest;
|
import org.eclipse.jetty.server.session.AbstractClusteredSessionMigrationTest;
|
||||||
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
||||||
|
import org.junit.After;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ClusteredSessionMigrationTest
|
* ClusteredSessionMigrationTest
|
||||||
|
@ -28,6 +29,7 @@ import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
||||||
public class ClusteredSessionMigrationTest
|
public class ClusteredSessionMigrationTest
|
||||||
extends AbstractClusteredSessionMigrationTest
|
extends AbstractClusteredSessionMigrationTest
|
||||||
{
|
{
|
||||||
|
HazelcastSessionDataStoreFactory factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
||||||
|
@ -35,7 +37,14 @@ public class ClusteredSessionMigrationTest
|
||||||
@Override
|
@Override
|
||||||
public SessionDataStoreFactory createSessionDataStoreFactory()
|
public SessionDataStoreFactory createSessionDataStoreFactory()
|
||||||
{
|
{
|
||||||
HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
|
factory = new HazelcastSessionDataStoreFactory();
|
||||||
|
factory.setMapName( Long.toString( System.currentTimeMillis() ) );
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
factory.getHazelcastInstance().getMap( factory.getMapName() ).clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.session.AbstractClusteredSessionScavengingTest;
|
import org.eclipse.jetty.server.session.AbstractClusteredSessionScavengingTest;
|
||||||
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
||||||
|
import org.junit.After;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ClusteredSessionScavengingTest
|
* ClusteredSessionScavengingTest
|
||||||
|
@ -29,13 +30,22 @@ public class ClusteredSessionScavengingTest
|
||||||
extends AbstractClusteredSessionScavengingTest
|
extends AbstractClusteredSessionScavengingTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
HazelcastSessionDataStoreFactory factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SessionDataStoreFactory createSessionDataStoreFactory()
|
public SessionDataStoreFactory createSessionDataStoreFactory()
|
||||||
{
|
{
|
||||||
HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
|
factory = new HazelcastSessionDataStoreFactory();
|
||||||
|
factory.setMapName( Long.toString( System.currentTimeMillis() ) );
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
factory.getHazelcastInstance().getMap( factory.getMapName() ).clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.session.AbstractModifyMaxInactiveIntervalTest;
|
import org.eclipse.jetty.server.session.AbstractModifyMaxInactiveIntervalTest;
|
||||||
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
||||||
|
import org.junit.After;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ModifyMaxInactiveIntervalTest
|
* ModifyMaxInactiveIntervalTest
|
||||||
|
@ -29,14 +30,23 @@ public class ModifyMaxInactiveIntervalTest
|
||||||
extends AbstractModifyMaxInactiveIntervalTest
|
extends AbstractModifyMaxInactiveIntervalTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
HazelcastSessionDataStoreFactory factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SessionDataStoreFactory createSessionDataStoreFactory()
|
public SessionDataStoreFactory createSessionDataStoreFactory()
|
||||||
{
|
{
|
||||||
HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
|
factory = new HazelcastSessionDataStoreFactory();
|
||||||
|
factory.setMapName( Long.toString( System.currentTimeMillis() ) );
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
factory.getHazelcastInstance().getMap( factory.getMapName() ).clear();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.session.AbstractNonClusteredSessionScavengingTest;
|
import org.eclipse.jetty.server.session.AbstractNonClusteredSessionScavengingTest;
|
||||||
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
||||||
|
import org.junit.After;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
@ -31,6 +32,8 @@ public class NonClusteredSessionScavengingTest
|
||||||
extends AbstractNonClusteredSessionScavengingTest
|
extends AbstractNonClusteredSessionScavengingTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
HazelcastSessionDataStoreFactory factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.server.session.AbstractNonClusteredSessionScavengingTest#assertSession(java.lang.String, boolean)
|
* @see org.eclipse.jetty.server.session.AbstractNonClusteredSessionScavengingTest#assertSession(java.lang.String, boolean)
|
||||||
*/
|
*/
|
||||||
|
@ -63,7 +66,14 @@ public class NonClusteredSessionScavengingTest
|
||||||
@Override
|
@Override
|
||||||
public SessionDataStoreFactory createSessionDataStoreFactory()
|
public SessionDataStoreFactory createSessionDataStoreFactory()
|
||||||
{
|
{
|
||||||
HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
|
factory = new HazelcastSessionDataStoreFactory();
|
||||||
|
factory.setMapName( Long.toString( System.currentTimeMillis() ) );
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
factory.getHazelcastInstance().getMap( factory.getMapName() ).clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,20 +21,30 @@ package org.eclipse.jetty.hazelcast.session;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.session.AbstractSessionExpiryTest;
|
import org.eclipse.jetty.server.session.AbstractSessionExpiryTest;
|
||||||
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class SessionExpiryTest
|
public class SessionExpiryTest
|
||||||
extends AbstractSessionExpiryTest
|
extends AbstractSessionExpiryTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
HazelcastSessionDataStoreFactory factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SessionDataStoreFactory createSessionDataStoreFactory()
|
public SessionDataStoreFactory createSessionDataStoreFactory()
|
||||||
{
|
{
|
||||||
HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
|
factory = new HazelcastSessionDataStoreFactory();
|
||||||
|
factory.setMapName( Long.toString( System.currentTimeMillis() ) );
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
factory.getHazelcastInstance().getMap( factory.getMapName() ).clear();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.hazelcast.session;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.session.AbstractSessionInvalidateCreateScavengeTest;
|
import org.eclipse.jetty.server.session.AbstractSessionInvalidateCreateScavengeTest;
|
||||||
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
||||||
|
import org.junit.After;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SessionInvalidateCreateScavengeTest
|
* SessionInvalidateCreateScavengeTest
|
||||||
|
@ -29,13 +30,22 @@ public class SessionInvalidateCreateScavengeTest
|
||||||
extends AbstractSessionInvalidateCreateScavengeTest
|
extends AbstractSessionInvalidateCreateScavengeTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
HazelcastSessionDataStoreFactory factory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public SessionDataStoreFactory createSessionDataStoreFactory()
|
public SessionDataStoreFactory createSessionDataStoreFactory()
|
||||||
{
|
{
|
||||||
HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
|
factory = new HazelcastSessionDataStoreFactory();
|
||||||
|
factory.setMapName( Long.toString( System.currentTimeMillis() ) );
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
factory.getHazelcastInstance().getMap( factory.getMapName() ).clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ public class ClientLastAccessTimeTest
|
||||||
extends AbstractClusteredLastAccessTimeTest
|
extends AbstractClusteredLastAccessTimeTest
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String MAP_NAME = "jetty_foo_session";
|
private static final String MAP_NAME = Long.toString( System.currentTimeMillis() );
|
||||||
|
|
||||||
private HazelcastInstance hazelcastInstance;
|
private HazelcastInstance hazelcastInstance;
|
||||||
|
|
||||||
|
|
|
@ -19,9 +19,15 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.hazelcast.session.client;
|
package org.eclipse.jetty.hazelcast.session.client;
|
||||||
|
|
||||||
|
import com.hazelcast.config.Config;
|
||||||
|
import com.hazelcast.config.MapConfig;
|
||||||
|
import com.hazelcast.core.Hazelcast;
|
||||||
|
import com.hazelcast.core.HazelcastInstance;
|
||||||
import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory;
|
import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory;
|
||||||
import org.eclipse.jetty.server.session.AbstractModifyMaxInactiveIntervalTest;
|
import org.eclipse.jetty.server.session.AbstractModifyMaxInactiveIntervalTest;
|
||||||
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
import org.eclipse.jetty.server.session.SessionDataStoreFactory;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ModifyMaxInactiveIntervalTest
|
* ModifyMaxInactiveIntervalTest
|
||||||
|
@ -30,6 +36,27 @@ public class ClientModifyMaxInactiveIntervalTest
|
||||||
extends AbstractModifyMaxInactiveIntervalTest
|
extends AbstractModifyMaxInactiveIntervalTest
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private static final String MAP_NAME = Long.toString( System.currentTimeMillis() );
|
||||||
|
|
||||||
|
private HazelcastInstance hazelcastInstance;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void startHazelcast()
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
Config config = new Config().addMapConfig( new MapConfig().setName( MAP_NAME ) ) //
|
||||||
|
.setInstanceName( "beer" );
|
||||||
|
// start Hazelcast instance
|
||||||
|
hazelcastInstance = Hazelcast.getOrCreateHazelcastInstance( config );
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void stopHazelcast()
|
||||||
|
throws Exception
|
||||||
|
{
|
||||||
|
hazelcastInstance.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
* @see org.eclipse.jetty.server.session.AbstractTestBase#createSessionDataStoreFactory()
|
||||||
*/
|
*/
|
||||||
|
@ -37,6 +64,8 @@ public class ClientModifyMaxInactiveIntervalTest
|
||||||
public SessionDataStoreFactory createSessionDataStoreFactory()
|
public SessionDataStoreFactory createSessionDataStoreFactory()
|
||||||
{
|
{
|
||||||
HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
|
HazelcastSessionDataStoreFactory factory = new HazelcastSessionDataStoreFactory();
|
||||||
|
factory.setOnlyClient( true );
|
||||||
|
factory.setMapName( MAP_NAME );
|
||||||
return factory;
|
return factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ public class ClientNonClusteredSessionScavengingTest
|
||||||
fail( e.getMessage() );
|
fail( e.getMessage() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static final String MAP_NAME = "jetty_foo_session";
|
private static final String MAP_NAME = Long.toString( System.currentTimeMillis() );
|
||||||
|
|
||||||
private HazelcastInstance hazelcastInstance;
|
private HazelcastInstance hazelcastInstance;
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ public class ClientOrphanedSessionTest
|
||||||
extends AbstractClusteredOrphanedSessionTest
|
extends AbstractClusteredOrphanedSessionTest
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String MAP_NAME = "jetty_foo_session";
|
private static final String MAP_NAME = Long.toString( System.currentTimeMillis() );
|
||||||
|
|
||||||
private HazelcastInstance hazelcastInstance;
|
private HazelcastInstance hazelcastInstance;
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ public class ClientSessionExpiryTest
|
||||||
extends AbstractSessionExpiryTest
|
extends AbstractSessionExpiryTest
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String MAP_NAME = "jetty_foo_session";
|
private static final String MAP_NAME = Long.toString( System.currentTimeMillis() );
|
||||||
|
|
||||||
private HazelcastInstance hazelcastInstance;
|
private HazelcastInstance hazelcastInstance;
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.junit.Before;
|
||||||
public class ClientSessionInvalidateCreateScavengeTest
|
public class ClientSessionInvalidateCreateScavengeTest
|
||||||
extends AbstractSessionInvalidateCreateScavengeTest
|
extends AbstractSessionInvalidateCreateScavengeTest
|
||||||
{
|
{
|
||||||
private static final String MAP_NAME = "jetty_foo_session";
|
private static final String MAP_NAME = Long.toString( System.currentTimeMillis() );
|
||||||
|
|
||||||
private HazelcastInstance hazelcastInstance;
|
private HazelcastInstance hazelcastInstance;
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ import org.junit.Before;
|
||||||
public class ClientSessionMigrationTest
|
public class ClientSessionMigrationTest
|
||||||
extends AbstractClusteredSessionMigrationTest
|
extends AbstractClusteredSessionMigrationTest
|
||||||
{
|
{
|
||||||
private static final String MAP_NAME = "jetty_foo_session";
|
private static final String MAP_NAME = Long.toString( System.currentTimeMillis() );
|
||||||
|
|
||||||
private HazelcastInstance hazelcastInstance;
|
private HazelcastInstance hazelcastInstance;
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ public class ClientSessionScavengingTest
|
||||||
extends AbstractClusteredSessionScavengingTest
|
extends AbstractClusteredSessionScavengingTest
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String MAP_NAME = "jetty_foo_session";
|
private static final String MAP_NAME = Long.toString( System.currentTimeMillis() );
|
||||||
|
|
||||||
private HazelcastInstance hazelcastInstance;
|
private HazelcastInstance hazelcastInstance;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
|
|
@ -0,0 +1,3 @@
|
||||||
|
handlers=java.util.logging.ConsoleHandler
|
||||||
|
.level=INFO
|
||||||
|
com.hazelcast.level=SEVERE
|
|
@ -41,6 +41,7 @@ import org.eclipse.jetty.client.api.Request;
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -182,8 +183,10 @@ public abstract class AbstractSessionExpiryTest extends AbstractTestBase
|
||||||
server1.addContext(contextPath).addServlet(holder, servletMapping);
|
server1.addContext(contextPath).addServlet(holder, servletMapping);
|
||||||
|
|
||||||
HttpClient client = new HttpClient();
|
HttpClient client = new HttpClient();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Log.getLogger(org.eclipse.jetty.util.thread.QueuedThreadPool.class).setDebugEnabled(true);
|
||||||
server1.start();
|
server1.start();
|
||||||
int port1 = server1.getPort();
|
int port1 = server1.getPort();
|
||||||
|
|
||||||
|
@ -213,6 +216,7 @@ public abstract class AbstractSessionExpiryTest extends AbstractTestBase
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
Log.getLogger(org.eclipse.jetty.util.thread.QueuedThreadPool.class).setDebugEnabled(false);
|
||||||
client.stop();
|
client.stop();
|
||||||
server1.stop();
|
server1.stop();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue