Merge remote-tracking branch 'origin/jetty-9.4.x' into issue-1640
This commit is contained in:
commit
82fa665d38
|
@ -1,12 +1,12 @@
|
|||
Contributing to Jetty
|
||||
=====================
|
||||
Thanks for your interest in this project.
|
||||
Thank you for your interest in this project!
|
||||
|
||||
Project description
|
||||
--------------------
|
||||
Jetty is a lightweight highly scalable java based web server and servlet engine.
|
||||
Our goal is to support web protocols like HTTP, HTTP/2, and WebSocket in a high
|
||||
volume low latency way that provides maximum performance while retaining the ease
|
||||
volume, low latency way that provides maximum performance while retaining the ease
|
||||
of use and compatibility with years of servlet development.
|
||||
Jetty is a modern fully async web server that has a long history as a component
|
||||
oriented technology easily embedded into applications while still offering a solid
|
||||
|
@ -20,18 +20,17 @@ Information regarding source code management, builds, coding standards, and more
|
|||
|
||||
- [https://www.eclipse.org/jetty/documentation/current/advanced-contributing.html](https://www.eclipse.org/jetty/documentation/current/advanced-contributing.html)
|
||||
|
||||
The canonical Jetty git repository is located at GitHub. Providing you have
|
||||
The canonical Jetty git repository is located at [GitHub.](https://github.com/eclipse/jetty.project) Providing you have
|
||||
completed the contributors agreement mentioned below we will endeavor to pull
|
||||
your commit into Jetty proper.
|
||||
|
||||
Contributor License Agreement
|
||||
Eclipse Contributor Agreement
|
||||
------------------------------
|
||||
Before your contribution can be accepted by the project, you need to create and electronically sign the
|
||||
Eclipse Foundation [Contributor License Agreement](https://www.eclipse.org/legal/CLA.php) (CLA):
|
||||
Before your contribution can be accepted by the project, you need to create and electronically sign a [Eclipse Contributor Agreement (ECA)](http://www.eclipse.org/legal/ecafaq.php):
|
||||
|
||||
1. Log in to the [Eclipse projects forge](https://projects.eclipse.org/user/login/sso). You will need to
|
||||
1. Log in to the [Eclipse foundation website](https://accounts.eclipse.org/user/login/). You will need to
|
||||
create an account with the Eclipse Foundation if you have not already done so.
|
||||
2. Click on "Contributor License Agreement", and complete the form.
|
||||
2. Click on "Eclipse ECA", and complete the form.
|
||||
|
||||
Be sure to use the same email address in your Eclipse account that you intend to use when you commit to Git.
|
||||
|
||||
|
|
|
@ -42,6 +42,7 @@ import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
|||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.component.DumpableCollection;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.Sweeper;
|
||||
|
@ -446,17 +447,11 @@ public abstract class HttpDestination extends ContainerLifeCycle implements Dest
|
|||
exchange.getRequest().abort(cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String dump()
|
||||
{
|
||||
return ContainerLifeCycle.dump(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
ContainerLifeCycle.dumpObject(out, toString());
|
||||
ContainerLifeCycle.dump(out, indent, Collections.singletonList(connectionPool));
|
||||
super.dump(out, indent);
|
||||
ContainerLifeCycle.dump(out, indent, Collections.singleton(new DumpableCollection("exchanges", exchanges)));
|
||||
}
|
||||
|
||||
public String asString()
|
||||
|
|
|
@ -67,7 +67,6 @@ import org.eclipse.jetty.util.Callback;
|
|||
import org.eclipse.jetty.util.FutureCallback;
|
||||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.StacklessLogging;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.hamcrest.Matchers;
|
||||
|
@ -439,7 +438,6 @@ public class StreamResetTest extends AbstractTest
|
|||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
Log.getLogger(StreamResetTest.class).info("SIMON: uri={}", request.getRequestURI());
|
||||
phaser.get().countDown();
|
||||
IO.copy(request.getInputStream(), response.getOutputStream());
|
||||
}
|
||||
|
@ -466,7 +464,6 @@ public class StreamResetTest extends AbstractTest
|
|||
@Override
|
||||
public void onHeaders(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
Log.getLogger(StreamResetTest.class).info("SIMON: response={}/{}", stream.getId(), frame.getMetaData());
|
||||
MetaData.Response response = (MetaData.Response)frame.getMetaData();
|
||||
if (response.getStatus() == HttpStatus.OK_200)
|
||||
latch.get().countDown();
|
||||
|
@ -475,7 +472,6 @@ public class StreamResetTest extends AbstractTest
|
|||
@Override
|
||||
public void onData(Stream stream, DataFrame frame, Callback callback)
|
||||
{
|
||||
Log.getLogger(StreamResetTest.class).info("SIMON: data={}/{}", stream.getId(), frame);
|
||||
callback.succeeded();
|
||||
if (frame.isEndStream())
|
||||
latch.get().countDown();
|
||||
|
@ -544,7 +540,6 @@ public class StreamResetTest extends AbstractTest
|
|||
|
||||
Session client = newClient(new Session.Listener.Adapter());
|
||||
|
||||
Log.getLogger(HttpChannel.class).info("Expecting java.lang.IllegalStateException: explictly_thrown_by_test");
|
||||
MetaData.Request request = newRequest("GET", new HttpFields());
|
||||
HeadersFrame frame = new HeadersFrame(request, null, false);
|
||||
FuturePromise<Stream> promise = new FuturePromise<>();
|
||||
|
|
|
@ -181,6 +181,7 @@ public class HTTP2Connection extends AbstractConnection
|
|||
{
|
||||
private final Callback fillableCallback = new FillableCallback();
|
||||
private ByteBuffer buffer;
|
||||
private boolean shutdown;
|
||||
|
||||
@Override
|
||||
public Runnable produce()
|
||||
|
@ -191,7 +192,7 @@ public class HTTP2Connection extends AbstractConnection
|
|||
if (task != null)
|
||||
return task;
|
||||
|
||||
if (isFillInterested())
|
||||
if (isFillInterested() || shutdown)
|
||||
return null;
|
||||
|
||||
if (buffer == null)
|
||||
|
@ -227,6 +228,7 @@ public class HTTP2Connection extends AbstractConnection
|
|||
else if (filled < 0)
|
||||
{
|
||||
release();
|
||||
shutdown = true;
|
||||
session.onShutdown();
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -435,8 +435,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
{
|
||||
// We received a GO_AWAY, so try to write
|
||||
// what's in the queue and then disconnect.
|
||||
notifyClose(this, frame);
|
||||
control(null, Callback.NOOP, new DisconnectFrame());
|
||||
notifyClose(this, frame, new DisconnectCallback());
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
@ -476,8 +475,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
@Override
|
||||
public void onConnectionFailure(int error, String reason)
|
||||
{
|
||||
notifyFailure(this, new IOException(String.format("%d/%s", error, reason)));
|
||||
close(error, reason, Callback.NOOP);
|
||||
notifyFailure(this, new IOException(String.format("%d/%s", error, reason)), new CloseCallback(error, reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1003,8 +1001,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
|
||||
protected void abort(Throwable failure)
|
||||
{
|
||||
notifyFailure(this, failure);
|
||||
terminate(failure);
|
||||
notifyFailure(this, failure, new TerminateCallback(failure));
|
||||
}
|
||||
|
||||
public boolean isDisconnected()
|
||||
|
@ -1066,11 +1063,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
}
|
||||
}
|
||||
|
||||
protected void notifyClose(Session session, GoAwayFrame frame)
|
||||
protected void notifyClose(Session session, GoAwayFrame frame, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onClose(session, frame);
|
||||
listener.onClose(session, frame, callback);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
@ -1091,11 +1088,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
}
|
||||
}
|
||||
|
||||
protected void notifyFailure(Session session, Throwable failure)
|
||||
protected void notifyFailure(Session session, Throwable failure, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
listener.onFailure(session, failure);
|
||||
listener.onFailure(session, failure, callback);
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
|
@ -1330,4 +1327,99 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
|
|||
promise.failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
private class CloseCallback implements Callback
|
||||
{
|
||||
private final int error;
|
||||
private final String reason;
|
||||
|
||||
private CloseCallback(int error, String reason)
|
||||
{
|
||||
this.error = error;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
|
||||
private void complete()
|
||||
{
|
||||
close(error, reason, Callback.NOOP);
|
||||
}
|
||||
}
|
||||
|
||||
private class DisconnectCallback implements Callback
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
|
||||
private void complete()
|
||||
{
|
||||
control(null, Callback.NOOP, new DisconnectFrame());
|
||||
}
|
||||
}
|
||||
|
||||
private class TerminateCallback implements Callback
|
||||
{
|
||||
private final Throwable failure;
|
||||
|
||||
private TerminateCallback(Throwable failure)
|
||||
{
|
||||
this.failure = failure;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
failure.addSuppressed(x);
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
|
||||
private void complete()
|
||||
{
|
||||
terminate(failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -195,9 +195,23 @@ public interface Session
|
|||
/**
|
||||
* <p>Callback method invoked when a GOAWAY frame has been received.</p>
|
||||
*
|
||||
* @param session the session
|
||||
* @param frame the GOAWAY frame received
|
||||
* @param session the session
|
||||
* @param frame the GOAWAY frame received
|
||||
* @param callback the callback to notify of the GOAWAY processing
|
||||
*/
|
||||
public default void onClose(Session session, GoAwayFrame frame, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
onClose(session, frame);
|
||||
callback.succeeded();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
public void onClose(Session session, GoAwayFrame frame);
|
||||
|
||||
/**
|
||||
|
@ -210,9 +224,23 @@ public interface Session
|
|||
/**
|
||||
* <p>Callback method invoked when a failure has been detected for this session.</p>
|
||||
*
|
||||
* @param session the session
|
||||
* @param failure the failure
|
||||
* @param session the session
|
||||
* @param failure the failure
|
||||
* @param callback the callback to notify of failure processing
|
||||
*/
|
||||
public default void onFailure(Session session, Throwable failure, Callback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
onFailure(session, failure);
|
||||
callback.succeeded();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
|
||||
public void onFailure(Session session, Throwable failure);
|
||||
|
||||
/**
|
||||
|
|
|
@ -23,6 +23,7 @@ import java.io.IOException;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
@ -55,6 +56,7 @@ import org.eclipse.jetty.server.HttpConfiguration;
|
|||
import org.eclipse.jetty.util.B64Code;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.CountingCallback;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.thread.ReservedThreadExecutor;
|
||||
|
||||
|
@ -199,19 +201,27 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
public boolean onStreamTimeout(IStream stream, Throwable failure)
|
||||
{
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
|
||||
boolean result = channel != null && channel.onStreamTimeout(failure);
|
||||
boolean result = channel != null && channel.onStreamTimeout(failure, task -> offerTask(task, true));
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} idle timeout on {}: {}", result ? "Processed" : "Ignored", stream, failure);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void onStreamFailure(IStream stream, Throwable failure)
|
||||
public void onStreamFailure(IStream stream, Throwable failure, Callback callback)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Processing failure on {}: {}", stream, failure);
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
|
||||
if (channel != null)
|
||||
channel.onFailure(failure);
|
||||
{
|
||||
Runnable task = channel.onFailure(failure, callback);
|
||||
if (task != null)
|
||||
offerTask(task, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean onSessionTimeout(Throwable failure)
|
||||
|
@ -222,20 +232,29 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
|
|||
{
|
||||
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
|
||||
if (channel != null)
|
||||
result &= !channel.isRequestExecuting();
|
||||
result &= channel.isRequestIdle();
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} idle timeout on {}: {}", result ? "Processed" : "Ignored", session, failure);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void onSessionFailure(Throwable failure)
|
||||
public void onSessionFailure(Throwable failure, Callback callback)
|
||||
{
|
||||
ISession session = getSession();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Processing failure on {}: {}", session, failure);
|
||||
for (Stream stream : session.getStreams())
|
||||
onStreamFailure((IStream)stream, failure);
|
||||
Collection<Stream> streams = session.getStreams();
|
||||
if (streams.isEmpty())
|
||||
{
|
||||
callback.succeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
CountingCallback counter = new CountingCallback(callback, streams.size());
|
||||
for (Stream stream : streams)
|
||||
onStreamFailure((IStream)stream, failure, counter);
|
||||
}
|
||||
}
|
||||
|
||||
public void push(Connector connector, IStream stream, MetaData.Request request)
|
||||
|
|
|
@ -122,7 +122,7 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onClose(Session session, GoAwayFrame frame)
|
||||
public void onClose(Session session, GoAwayFrame frame, Callback callback)
|
||||
{
|
||||
ErrorCode error = ErrorCode.from(frame.getError());
|
||||
if (error == null)
|
||||
|
@ -130,13 +130,13 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
|
|||
String reason = frame.tryConvertPayload();
|
||||
if (reason != null && !reason.isEmpty())
|
||||
reason = " (" + reason + ")";
|
||||
getConnection().onSessionFailure(new EofException("HTTP/2 " + error + reason));
|
||||
getConnection().onSessionFailure(new EofException("HTTP/2 " + error + reason), callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Session session, Throwable failure)
|
||||
public void onFailure(Session session, Throwable failure, Callback callback)
|
||||
{
|
||||
getConnection().onSessionFailure(failure);
|
||||
getConnection().onSessionFailure(failure, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -168,7 +168,7 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF
|
|||
ErrorCode error = ErrorCode.from(frame.getError());
|
||||
if (error == null)
|
||||
error = ErrorCode.CANCEL_STREAM_ERROR;
|
||||
getConnection().onStreamFailure((IStream)stream, new EofException("HTTP/2 " + error));
|
||||
getConnection().onStreamFailure((IStream)stream, new EofException("HTTP/2 " + error), Callback.NOOP);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.http2.server;
|
|||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
|
@ -300,40 +301,33 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable
|
|||
return handle || wasDelayed ? this : null;
|
||||
}
|
||||
|
||||
public boolean isRequestExecuting()
|
||||
public boolean isRequestIdle()
|
||||
{
|
||||
return !getState().isIdle();
|
||||
return getState().isIdle();
|
||||
}
|
||||
|
||||
public boolean onStreamTimeout(Throwable failure)
|
||||
public boolean onStreamTimeout(Throwable failure, Consumer<Runnable> consumer)
|
||||
{
|
||||
boolean result = false;
|
||||
if (isRequestIdle())
|
||||
{
|
||||
consumeInput();
|
||||
result = true;
|
||||
}
|
||||
|
||||
getHttpTransport().onStreamTimeout(failure);
|
||||
if (getRequest().getHttpInput().onIdleTimeout(failure))
|
||||
handle();
|
||||
consumer.accept(this::handleWithContext);
|
||||
|
||||
if (isRequestExecuting())
|
||||
return false;
|
||||
|
||||
consumeInput();
|
||||
return true;
|
||||
return result;
|
||||
}
|
||||
|
||||
public void onFailure(Throwable failure)
|
||||
public Runnable onFailure(Throwable failure, Callback callback)
|
||||
{
|
||||
getHttpTransport().onStreamFailure(failure);
|
||||
if (getRequest().getHttpInput().failed(failure))
|
||||
{
|
||||
ContextHandler handler = getState().getContextHandler();
|
||||
if (handler != null)
|
||||
handler.handle(getRequest(), this);
|
||||
else
|
||||
handle();
|
||||
}
|
||||
else
|
||||
{
|
||||
getState().asyncError(failure);
|
||||
}
|
||||
boolean handle = getRequest().getHttpInput().failed(failure);
|
||||
consumeInput();
|
||||
return new FailureTask(failure, callback, handle);
|
||||
}
|
||||
|
||||
protected void consumeInput()
|
||||
|
@ -341,6 +335,15 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable
|
|||
getRequest().getHttpInput().consumeAll();
|
||||
}
|
||||
|
||||
private void handleWithContext()
|
||||
{
|
||||
ContextHandler context = getState().getContextHandler();
|
||||
if (context != null)
|
||||
context.handle(getRequest(), this);
|
||||
else
|
||||
handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the associated response has the Expect header set to 100 Continue,
|
||||
* then accessing the input stream indicates that the handler/servlet
|
||||
|
@ -385,4 +388,35 @@ public class HttpChannelOverHTTP2 extends HttpChannel implements Closeable
|
|||
streamId = stream.getId();
|
||||
return String.format("%s#%d", super.toString(), getStream() == null ? -1 : streamId);
|
||||
}
|
||||
|
||||
private class FailureTask implements Runnable
|
||||
{
|
||||
private final Throwable failure;
|
||||
private final Callback callback;
|
||||
private final boolean handle;
|
||||
|
||||
public FailureTask(Throwable failure, Callback callback, boolean handle)
|
||||
{
|
||||
this.failure = failure;
|
||||
this.callback = callback;
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (handle)
|
||||
handleWithContext();
|
||||
else
|
||||
getState().asyncError(failure);
|
||||
callback.succeeded();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
callback.failed(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -330,9 +330,9 @@ public class HttpTransportOverHTTP2 implements HttpTransport
|
|||
if (state == State.WRITING)
|
||||
{
|
||||
this.state = State.FAILED;
|
||||
this.failure = failure;
|
||||
callback = this.callback;
|
||||
this.callback = null;
|
||||
this.failure = failure;
|
||||
}
|
||||
}
|
||||
if (LOG.isDebugEnabled())
|
||||
|
|
|
@ -165,6 +165,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
|||
_contentConsumed = 0;
|
||||
_firstByteTimeStamp = -1;
|
||||
_blockUntil = 0;
|
||||
_waitingForContent = false;
|
||||
if (_interceptor instanceof Destroyable)
|
||||
((Destroyable)_interceptor).destroy();
|
||||
_interceptor = null;
|
||||
|
|
|
@ -36,6 +36,11 @@ public interface Callback extends Invocable
|
|||
*/
|
||||
static Callback NOOP = new Callback()
|
||||
{
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -45,6 +45,8 @@ public class CountingCallback extends Callback.Nested
|
|||
public CountingCallback(Callback callback, int count)
|
||||
{
|
||||
super(callback);
|
||||
if (count < 1)
|
||||
throw new IllegalArgumentException();
|
||||
this.count = new AtomicInteger(count);
|
||||
}
|
||||
|
||||
|
|
|
@ -86,51 +86,47 @@ public abstract class Credential implements Serializable
|
|||
}
|
||||
|
||||
/**
|
||||
* <p>Utility method that replaces String.equals() to avoid timing attacks.</p>
|
||||
* <p>Utility method that replaces String.equals() to avoid timing attacks.
|
||||
* The length of the loop executed will always be the length of the unknown credential</p>
|
||||
*
|
||||
* @param s1 the first string to compare
|
||||
* @param s2 the second string to compare
|
||||
* @param known the first string to compare (should be known string)
|
||||
* @param unknown the second string to compare (should be the unknown string)
|
||||
* @return whether the two strings are equal
|
||||
*/
|
||||
protected static boolean stringEquals(String s1, String s2)
|
||||
protected static boolean stringEquals(String known, String unknown)
|
||||
{
|
||||
if (s1 == s2)
|
||||
if (known == unknown)
|
||||
return true;
|
||||
if (s1 == null || s2 == null)
|
||||
if (known == null || unknown == null)
|
||||
return false;
|
||||
boolean result = true;
|
||||
int l1 = s1.length();
|
||||
int l2 = s2.length();
|
||||
if (l1 != l2)
|
||||
result = false;
|
||||
int l = Math.min(l1, l2);
|
||||
for (int i = 0; i < l; ++i)
|
||||
result &= s1.charAt(i) == s2.charAt(i);
|
||||
return result;
|
||||
int l1 = known.length();
|
||||
int l2 = unknown.length();
|
||||
for (int i = 0; i < l2; ++i)
|
||||
result &= known.charAt(i%l1) == unknown.charAt(i);
|
||||
return result && l1 == l2;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Utility method that replaces Arrays.equals() to avoid timing attacks.</p>
|
||||
* <p>Utility method that replaces Arrays.equals() to avoid timing attacks.
|
||||
* The length of the loop executed will always be the length of the unknown credential</p>
|
||||
*
|
||||
* @param b1 the first byte array to compare
|
||||
* @param b2 the second byte array to compare
|
||||
* @param known the first byte array to compare (should be known value)
|
||||
* @param unknown the second byte array to compare (should be unknown value)
|
||||
* @return whether the two byte arrays are equal
|
||||
*/
|
||||
protected static boolean byteEquals(byte[] b1, byte[] b2)
|
||||
protected static boolean byteEquals(byte[] known, byte[] unknown)
|
||||
{
|
||||
if (b1 == b2)
|
||||
if (known == unknown)
|
||||
return true;
|
||||
if (b1 == null || b2 == null)
|
||||
if (known == null || unknown == null)
|
||||
return false;
|
||||
boolean result = true;
|
||||
int l1 = b1.length;
|
||||
int l2 = b2.length;
|
||||
if (l1 != l2)
|
||||
result = false;
|
||||
int l = Math.min(l1, l2);
|
||||
for (int i = 0; i < l; ++i)
|
||||
result &= b1[i] == b2[i];
|
||||
return result;
|
||||
int l1 = known.length;
|
||||
int l2 = unknown.length;
|
||||
for (int i = 0; i < l2; ++i)
|
||||
result &= known[i%l1] == unknown[i];
|
||||
return result && l1 == l2;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -76,4 +76,22 @@ public class CredentialTest
|
|||
|
||||
assertTrue (p1.equals(p2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringEquals()
|
||||
{
|
||||
assertTrue(Credential.stringEquals("foo","foo"));
|
||||
assertFalse(Credential.stringEquals("foo","fooo"));
|
||||
assertFalse(Credential.stringEquals("foo","fo"));
|
||||
assertFalse(Credential.stringEquals("foo","bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBytesEquals()
|
||||
{
|
||||
assertTrue(Credential.byteEquals("foo".getBytes(),"foo".getBytes()));
|
||||
assertFalse(Credential.byteEquals("foo".getBytes(),"fooo".getBytes()));
|
||||
assertFalse(Credential.byteEquals("foo".getBytes(),"fo".getBytes()));
|
||||
assertFalse(Credential.byteEquals("foo".getBytes(),"bar".getBytes()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,17 +19,13 @@
|
|||
package org.eclipse.jetty.websocket.jsr356;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import javax.websocket.ContainerProvider;
|
||||
import javax.websocket.WebSocketContainer;
|
||||
|
||||
import org.eclipse.jetty.io.ByteBufferPool;
|
||||
import org.eclipse.jetty.io.MappedByteBufferPool;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ShutdownThread;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
|
||||
import org.eclipse.jetty.websocket.common.scopes.SimpleContainerScope;
|
||||
|
@ -47,11 +43,9 @@ public class JettyClientContainerProvider extends ContainerProvider
|
|||
private static final Logger LOG = Log.getLogger(JettyClientContainerProvider.class);
|
||||
|
||||
private static boolean useSingleton = false;
|
||||
private static WebSocketContainer INSTANCE;
|
||||
private static boolean useServerContainer = false;
|
||||
private static Executor commonExecutor;
|
||||
private static ByteBufferPool commonBufferPool;
|
||||
|
||||
private static WebSocketContainer INSTANCE;
|
||||
|
||||
private static Object lock = new Object();
|
||||
|
||||
/**
|
||||
|
@ -185,28 +179,14 @@ public class JettyClientContainerProvider extends ContainerProvider
|
|||
// Still no instance?
|
||||
if (webSocketContainer == null)
|
||||
{
|
||||
if (commonExecutor == null)
|
||||
{
|
||||
QueuedThreadPool threadPool = new QueuedThreadPool();
|
||||
String name = "Jsr356Client@" + hashCode();
|
||||
threadPool.setName(name);
|
||||
threadPool.setDaemon(true);
|
||||
commonExecutor = threadPool;
|
||||
}
|
||||
|
||||
if (commonBufferPool == null)
|
||||
{
|
||||
commonBufferPool = new MappedByteBufferPool();
|
||||
}
|
||||
|
||||
SimpleContainerScope containerScope = new SimpleContainerScope(WebSocketPolicy.newClientPolicy(), commonBufferPool, commonExecutor, null);
|
||||
SimpleContainerScope containerScope = new SimpleContainerScope(WebSocketPolicy.newClientPolicy());
|
||||
ClientContainer clientContainer = new ClientContainer(containerScope);
|
||||
|
||||
if (contextHandler != null && contextHandler instanceof ContainerLifeCycle)
|
||||
{
|
||||
// Add as bean to contextHandler
|
||||
// Allow startup to follow Jetty lifecycle
|
||||
((ContainerLifeCycle) contextHandler).addBean(clientContainer);
|
||||
((ContainerLifeCycle) contextHandler).addManaged(clientContainer);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -247,7 +247,7 @@ public class DelayedStartClientOnServerTest
|
|||
assertThat("Response", response, startsWith("Connected to ws://"));
|
||||
List<String> threadNames = getThreadNames(server);
|
||||
assertNoHttpClientPoolThreads(threadNames);
|
||||
assertThat("Threads", threadNames, hasItem(containsString("Jsr356Client@")));
|
||||
assertThat("Threads", threadNames, hasItem(containsString("WebSocketContainer@")));
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -392,11 +392,7 @@ public class WebSocketClient extends ContainerLifeCycle implements WebSocketCont
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Stopping {}",this);
|
||||
|
||||
|
||||
if (ShutdownThread.isRegistered(this))
|
||||
{
|
||||
ShutdownThread.deregister(this);
|
||||
}
|
||||
ShutdownThread.deregister(this);
|
||||
|
||||
super.doStop();
|
||||
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -936,7 +936,7 @@
|
|||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-test-helper</artifactId>
|
||||
<version>4.1</version>
|
||||
<version>4.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
|
|
|
@ -694,7 +694,7 @@ public class ServerTimeoutsTest extends AbstractTest
|
|||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(2 * idleTimeout);
|
||||
Thread.sleep(idleTimeout + idleTimeout / 2);
|
||||
IO.copy(request.getInputStream(), response.getOutputStream());
|
||||
}
|
||||
catch (InterruptedException x)
|
||||
|
@ -729,7 +729,7 @@ public class ServerTimeoutsTest extends AbstractTest
|
|||
});
|
||||
|
||||
// Wait for the server application to block reading.
|
||||
Thread.sleep(3 * idleTimeout);
|
||||
Thread.sleep(2 * idleTimeout);
|
||||
content.offer(ByteBuffer.wrap(data2));
|
||||
content.close();
|
||||
|
||||
|
|
Loading…
Reference in New Issue