org.eclipse.jetty
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
index 3fe68cc340d..5531f289740 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
@@ -27,6 +27,7 @@ import java.util.regex.Pattern;
import org.eclipse.jetty.client.api.Authentication;
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.Request;
import org.eclipse.jetty.client.api.Response;
@@ -86,7 +87,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
{
HttpRequest request = (HttpRequest)result.getRequest();
ContentResponse response = new HttpContentResponse(result.getResponse(), getContent(), getMediaType(), getEncoding());
- if (result.isFailed())
+ if (result.getResponseFailure() != null)
{
if (LOG.isDebugEnabled())
LOG.debug("Authentication challenge failed {}", result.getFailure());
@@ -98,7 +99,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
HttpConversation conversation = request.getConversation();
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())
LOG.debug("Bad credentials for {}", request);
forwardSuccessComplete(request, response);
@@ -111,7 +112,7 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
{
if (LOG.isDebugEnabled())
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;
}
@@ -138,9 +139,18 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
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
{
- final Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
+ Authentication.Result authnResult = authentication.authenticate(request, response, headerInfo, conversation);
if (LOG.isDebugEnabled())
LOG.debug("Authentication result {}", authnResult);
if (authnResult == null)
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
index a2d978ef0d7..40ce6c768bd 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
@@ -95,6 +95,11 @@ public abstract class HttpReceiver
return channel.getHttpDestination();
}
+ public boolean isFailed()
+ {
+ return responseState.get() == ResponseState.FAILURE;
+ }
+
/**
* Method to be invoked when the response status code is available.
*
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
index bdae1570819..22a50d88ee8 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
@@ -86,6 +86,11 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
return channel.getHttpExchange();
}
+ public boolean isFailed()
+ {
+ return requestState.get() == RequestState.FAILURE;
+ }
+
@Override
public void onContent()
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java
index 03754f72c9b..6419b11e8f4 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java
@@ -22,6 +22,7 @@ import java.io.Closeable;
import java.nio.ByteBuffer;
import java.util.Iterator;
+import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
import org.eclipse.jetty.client.util.PathContentProvider;
@@ -48,6 +49,22 @@ public interface ContentProvider extends Iterable
*/
long getLength();
+ /**
+ * Whether this ContentProvider can produce exactly the same content more
+ * than once.
+ * 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.
+ * The {@link HttpClient} implementation may use this method in particular
+ * cases where it detects that it is safe to retry a request that failed.
+ *
+ * @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
* to be used as a {@code Content-Type} HTTP header in requests.
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java
index 68386758da9..2bc5233f8be 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java
@@ -57,6 +57,12 @@ public class ByteBufferContentProvider extends AbstractTypedContentProvider
return length;
}
+ @Override
+ public boolean isReproducible()
+ {
+ return true;
+ }
+
@Override
public Iterator iterator()
{
@@ -85,12 +91,6 @@ public class ByteBufferContentProvider extends AbstractTypedContentProvider
throw new NoSuchElementException();
}
}
-
- @Override
- public void remove()
- {
- throw new UnsupportedOperationException();
- }
};
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java
index d2c080aa700..39f5fd5ad51 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java
@@ -53,6 +53,12 @@ public class BytesContentProvider extends AbstractTypedContentProvider
return length;
}
+ @Override
+ public boolean isReproducible()
+ {
+ return true;
+ }
+
@Override
public Iterator iterator()
{
@@ -78,12 +84,6 @@ public class BytesContentProvider extends AbstractTypedContentProvider
throw new NoSuchElementException();
}
}
-
- @Override
- public void remove()
- {
- throw new UnsupportedOperationException();
- }
};
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java
index d1051d94136..00147a556df 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java
@@ -85,6 +85,12 @@ public class PathContentProvider extends AbstractTypedContentProvider
return fileSize;
}
+ @Override
+ public boolean isReproducible()
+ {
+ return true;
+ }
+
public ByteBufferPool getByteBufferPool()
{
return bufferPool;
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java
index b591cd7481f..4eea728f923 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpClientServerTest.java
@@ -27,6 +27,7 @@ import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
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.thread.QueuedThreadPool;
import org.junit.After;
@@ -96,6 +97,7 @@ public abstract class AbstractHttpClientServerTest
clientThreads.setName("client");
client = new HttpClient(transport, sslContextFactory);
client.setExecutor(clientThreads);
+ client.setSocketAddressResolver(new SocketAddressResolver.Sync());
client.start();
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
index 6e1a5ca074d..14d23267228 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
@@ -22,11 +22,14 @@ import java.io.File;
import java.io.IOException;
import java.net.URI;
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.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.IntFunction;
import javax.servlet.ServletException;
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.AuthenticationStore;
+import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
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.DeferredContentProvider;
import org.eclipse.jetty.client.util.DigestAuthentication;
+import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.security.Authenticator;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
@@ -220,7 +225,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
{
baseRequest.setHandled(true);
if (requests.incrementAndGet() == 1)
- response.sendRedirect(URIUtil.newURI(scheme,request.getServerName(),request.getServerPort(),request.getRequestURI(),null));
+ response.sendRedirect(URIUtil.newURI(scheme, request.getServerName(), request.getServerPort(), request.getRequestURI(), null));
}
});
@@ -259,7 +264,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
{
baseRequest.setHandled(true);
if (request.getRequestURI().endsWith("/redirect"))
- response.sendRedirect(URIUtil.newURI(scheme,request.getServerName(),request.getServerPort(),"/secure",null));
+ response.sendRedirect(URIUtil.newURI(scheme, request.getServerName(), request.getServerPort(), "/secure", null));
}
});
@@ -424,6 +429,40 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
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
public void test_RequestFailsAfterResponse() throws Exception
{
@@ -434,32 +473,111 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
BasicAuthentication authentication = new BasicAuthentication(uri, realm, "basic", "basic");
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);
- DeferredContentProvider content = new DeferredContentProvider();
- Request request = client.newRequest("localhost", connector.getLocalPort())
+ client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.path("/secure")
.content(content)
- .onResponseSuccess(response -> successLatch.countDown());
- request.send(result ->
- {
- if (result.isFailed() && result.getResponseFailure() == null)
- resultLatch.countDown();
- });
+ .send(result ->
+ {
+ if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.OK_200)
+ 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));
}
+
+ 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 generator;
+
+ private GeneratingContentProvider(IntFunction generator)
+ {
+ this.generator = generator;
+ }
+
+ @Override
+ public long getLength()
+ {
+ return -1;
+ }
+
+ @Override
+ public boolean isReproducible()
+ {
+ return true;
+ }
+
+ @Override
+ public Iterator iterator()
+ {
+ return new Iterator()
+ {
+ 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;
+ }
+ };
+ }
+ }
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
index 639d9af7bac..851118c3c95 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTimeoutTest.java
@@ -263,10 +263,10 @@ public class HttpClientTimeoutTest extends AbstractHttpClientServerTest
return new SslConnection(byteBufferPool, executor, endPoint, engine)
{
@Override
- protected boolean onReadTimeout()
+ protected boolean onReadTimeout(Throwable timeout)
{
sslIdle.set(true);
- return super.onReadTimeout();
+ return super.onReadTimeout(timeout);
}
};
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
index 735d08b4299..db32212ee55 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ssl/SslBytesServerTest.java
@@ -126,12 +126,12 @@ public class SslBytesServerTest extends SslBytesTest
}
@Override
- protected boolean onReadTimeout()
+ protected boolean onReadTimeout(Throwable timeout)
{
final Runnable idleHook = SslBytesServerTest.this.idleHook;
if (idleHook != null)
idleHook.run();
- return super.onReadTimeout();
+ return super.onReadTimeout(timeout);
}
}, connector, endPoint);
}
diff --git a/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-send-message.adoc b/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-send-message.adoc
index d89a77bf391..49afbe47204 100644
--- a/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-send-message.adoc
+++ b/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-send-message.adoc
@@ -17,7 +17,7 @@
[[jetty-websocket-api-send-message]]
=== Send Messages to Remote Endpoint
-The most important feature of the Session is access to the link:{JDURL}/org/eclipse/jetty/websocket/api/RemoteEndpoint.html[`org.eclipse.jetty.websocket.api.RemoteEndpoint`]needed to send messages.
+The most important feature of the Session is access to the link:{JDURL}/org/eclipse/jetty/websocket/api/RemoteEndpoint.html[`org.eclipse.jetty.websocket.api.RemoteEndpoint`] needed to send messages.
With RemoteEndpoint you can choose to send TEXT or BINARY WebSocket messages, or the WebSocket PING and PONG control frames.
diff --git a/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-session.adoc b/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-session.adoc
index abcee12d5f5..754e0044c0e 100644
--- a/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-session.adoc
+++ b/jetty-documentation/src/main/asciidoc/development/websockets/jetty/jetty-websocket-api-session.adoc
@@ -44,7 +44,7 @@ What was in the Upgrade Request and Response.
UpgradeRequest req = session.getUpgradeRequest();
String channelName = req.getParameterMap().get("channelName");
-UpgradeRespons resp = session.getUpgradeResponse();
+UpgradeResponse resp = session.getUpgradeResponse();
String subprotocol = resp.getAcceptedSubProtocol();
----
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java
index 267bf249e4d..551d6bcee6e 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java
@@ -38,18 +38,17 @@ public class HttpChannelOverFCGI extends HttpChannel
{
private final HttpConnectionOverFCGI connection;
private final Flusher flusher;
- private final int request;
private final HttpSenderOverFCGI sender;
private final HttpReceiverOverFCGI receiver;
private final FCGIIdleTimeout idle;
+ private int request;
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());
this.connection = connection;
this.flusher = flusher;
- this.request = request;
this.sender = new HttpSenderOverFCGI(this);
this.receiver = new HttpReceiverOverFCGI(this);
this.idle = new FCGIIdleTimeout(connection, idleTimeout);
@@ -60,6 +59,11 @@ public class HttpChannelOverFCGI extends HttpChannel
return request;
}
+ void setRequest(int request)
+ {
+ this.request = request;
+ }
+
@Override
protected HttpSender getHttpSender()
{
@@ -72,6 +76,11 @@ public class HttpChannelOverFCGI extends HttpChannel
return receiver;
}
+ public boolean isFailed()
+ {
+ return sender.isFailed() || receiver.isFailed();
+ }
+
@Override
public void send()
{
diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java
index 28358d37736..82a8de881b0 100644
--- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java
+++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java
@@ -23,7 +23,9 @@ import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.util.LinkedList;
import java.util.Map;
+import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeoutException;
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 final LinkedList requests = new LinkedList<>();
- private final Map channels = new ConcurrentHashMap<>();
+ private final Map activeChannels = new ConcurrentHashMap<>();
+ private final Queue idleChannels = new ConcurrentLinkedQueue<>();
private final AtomicBoolean closed = new AtomicBoolean();
private final HttpDestination destination;
private final Promise promise;
@@ -184,7 +187,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
{
// Close explicitly only if we are idle, since the request may still
// be in progress, otherwise close only if we can fail the responses.
- if (channels.isEmpty())
+ if (activeChannels.isEmpty())
close();
else
failAndClose(new EOFException(String.valueOf(getEndPoint())));
@@ -204,8 +207,14 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
protected void release(HttpChannelOverFCGI channel)
{
- channels.remove(channel.getRequest());
- destination.release(this);
+ if (activeChannels.remove(channel.getRequest()) != null)
+ {
+ channel.setRequest(0);
+ // Recycle only non-failed channels.
+ if (!channel.isFailed())
+ idleChannels.offer(channel);
+ destination.release(this);
+ }
}
@Override
@@ -249,19 +258,20 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
protected void abort(Throwable failure)
{
- for (HttpChannelOverFCGI channel : channels.values())
+ for (HttpChannelOverFCGI channel : activeChannels.values())
{
HttpExchange exchange = channel.getHttpExchange();
if (exchange != null)
exchange.getRequest().abort(failure);
}
- channels.clear();
+ activeChannels.clear();
+ idleChannels.clear();
}
private void failAndClose(Throwable failure)
{
boolean result = false;
- for (HttpChannelOverFCGI channel : channels.values())
+ for (HttpChannelOverFCGI channel : activeChannels.values())
result |= channel.responseFailure(failure);
if (result)
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
@@ -314,10 +333,10 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
Request request = exchange.getRequest();
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();
- HttpChannelOverFCGI channel = newHttpChannel(id, request);
- channels.put(id, channel);
+ HttpChannelOverFCGI channel = provideHttpChannel(id, request);
+ activeChannels.put(id, channel);
return send(channel, exchange);
}
@@ -351,7 +370,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
@Override
public void onBegin(int request, int code, String reason)
{
- HttpChannelOverFCGI channel = channels.get(request);
+ HttpChannelOverFCGI channel = activeChannels.get(request);
if (channel != null)
channel.responseBegin(code, reason);
else
@@ -361,7 +380,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
@Override
public void onHeader(int request, HttpField field)
{
- HttpChannelOverFCGI channel = channels.get(request);
+ HttpChannelOverFCGI channel = activeChannels.get(request);
if (channel != null)
channel.responseHeader(field);
else
@@ -371,7 +390,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
@Override
public void onHeaders(int request)
{
- HttpChannelOverFCGI channel = channels.get(request);
+ HttpChannelOverFCGI channel = activeChannels.get(request);
if (channel != null)
channel.responseHeaders();
else
@@ -385,7 +404,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
{
case STD_OUT:
{
- HttpChannelOverFCGI channel = channels.get(request);
+ HttpChannelOverFCGI channel = activeChannels.get(request);
if (channel != null)
{
CompletableCallback callback = new CompletableCallback()
@@ -431,7 +450,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
@Override
public void onEnd(int request)
{
- HttpChannelOverFCGI channel = channels.get(request);
+ HttpChannelOverFCGI channel = activeChannels.get(request);
if (channel != null)
{
if (channel.responseSuccess())
@@ -446,7 +465,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec
@Override
public void onFailure(int request, Throwable failure)
{
- HttpChannelOverFCGI channel = channels.get(request);
+ HttpChannelOverFCGI channel = activeChannels.get(request);
if (channel != null)
{
if (channel.responseFailure(failure))
diff --git a/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStoreFactory.java b/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStoreFactory.java
index 7959f8a5072..766920ed9c3 100644
--- a/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStoreFactory.java
+++ b/jetty-hazelcast/src/main/java/org/eclipse/jetty/hazelcast/session/HazelcastSessionDataStoreFactory.java
@@ -121,6 +121,11 @@ public class HazelcastSessionDataStoreFactory
return onlyClient;
}
+ /**
+ *
+ * @param onlyClient if true
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 )
{
this.onlyClient = onlyClient;
diff --git a/jetty-home/pom.xml b/jetty-home/pom.xml
index 7959d55f049..df9c3afc80d 100644
--- a/jetty-home/pom.xml
+++ b/jetty-home/pom.xml
@@ -84,6 +84,7 @@
org.eclipse.jetty
jetty-start
${project.version}
+ shaded
jar
true
**
@@ -403,6 +404,7 @@
org.eclipse.jetty
jetty-start
${project.version}
+ shaded