Issue #207 - reworking I/O to allow streaming backpressure
This commit is contained in:
parent
be747c44e1
commit
aae4a24726
|
@ -121,13 +121,6 @@ public interface LogicalConnection extends OutgoingFrames, SuspendToken
|
||||||
*/
|
*/
|
||||||
boolean isOpen();
|
boolean isOpen();
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests if the connection is actively reading.
|
|
||||||
*
|
|
||||||
* @return true if connection is actively attempting to read.
|
|
||||||
*/
|
|
||||||
boolean isReading();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the maximum number of milliseconds of idleness before the connection is closed/disconnected, (ie no frames are either sent or received)
|
* Set the maximum number of milliseconds of idleness before the connection is closed/disconnected, (ie no frames are either sent or received)
|
||||||
* <p>
|
* <p>
|
||||||
|
|
|
@ -49,7 +49,13 @@ public class Parser
|
||||||
{
|
{
|
||||||
public interface Handler
|
public interface Handler
|
||||||
{
|
{
|
||||||
void onFrame(Frame frame);
|
/**
|
||||||
|
* Notification of completely parsed frame.
|
||||||
|
*
|
||||||
|
* @param frame the frame
|
||||||
|
* @return true to continue parsing, false to stop parsing
|
||||||
|
*/
|
||||||
|
boolean onFrame(Frame frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum State
|
private enum State
|
||||||
|
@ -182,64 +188,42 @@ public class Parser
|
||||||
{
|
{
|
||||||
return (flagsInUse & 0x10) != 0;
|
return (flagsInUse & 0x10) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void notifyFrame(final Frame f)
|
public boolean parse(ByteBuffer buffer) throws WebSocketException
|
||||||
{
|
{
|
||||||
|
// TODO quick fail, nothing to parse
|
||||||
if (policy.getBehavior() == WebSocketBehavior.SERVER)
|
if (!buffer.hasRemaining())
|
||||||
{
|
{
|
||||||
/* Parsing on server.
|
return true;
|
||||||
*
|
|
||||||
* Then you MUST make sure all incoming frames are masked!
|
|
||||||
*
|
|
||||||
* Technically, this test is in violation of RFC-6455, Section 5.1
|
|
||||||
* http://tools.ietf.org/html/rfc6455#section-5.1
|
|
||||||
*
|
|
||||||
* But we can't trust the client at this point, so Jetty opts to close
|
|
||||||
* the connection as a Protocol error.
|
|
||||||
*/
|
|
||||||
if (!f.isMasked())
|
|
||||||
{
|
|
||||||
throw new ProtocolException("Client MUST mask all frames (RFC-6455: Section 5.1)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(policy.getBehavior() == WebSocketBehavior.CLIENT)
|
|
||||||
{
|
|
||||||
// Required by RFC-6455 / Section 5.1
|
|
||||||
if (f.isMasked())
|
|
||||||
{
|
|
||||||
throw new ProtocolException("Server MUST NOT mask any frames (RFC-6455: Section 5.1)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.parserHandler.onFrame(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void parse(ByteBuffer buffer) throws WebSocketException
|
|
||||||
{
|
|
||||||
if (buffer.remaining() <= 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// parse through all the frames in the buffer
|
// parse through all the frames in the buffer
|
||||||
while (parseFrame(buffer))
|
while (parseFrame(buffer))
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
LOG.debug("{} Parsed Frame: {}",policy.getBehavior(),frame);
|
LOG.debug("{} Parsed Frame: {}", policy.getBehavior(), frame);
|
||||||
notifyFrame(frame);
|
|
||||||
|
assertBehavior();
|
||||||
|
|
||||||
if (frame.isDataFrame())
|
if (frame.isDataFrame())
|
||||||
{
|
{
|
||||||
priorDataFrame = !frame.isFin();
|
priorDataFrame = !frame.isFin();
|
||||||
}
|
}
|
||||||
reset();
|
|
||||||
|
if(!this.parserHandler.onFrame(frame))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// completely consumed buffer
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
catch (Throwable t)
|
catch (Throwable t)
|
||||||
{
|
{
|
||||||
buffer.position(buffer.limit()); // consume remaining
|
buffer.position(buffer.limit()); // consume remaining
|
||||||
reset();
|
|
||||||
|
|
||||||
// let session know
|
// let session know
|
||||||
WebSocketException wse;
|
WebSocketException wse;
|
||||||
|
@ -251,16 +235,42 @@ public class Parser
|
||||||
throw wse;
|
throw wse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reset()
|
private void assertBehavior()
|
||||||
{
|
{
|
||||||
if (frame != null)
|
if (policy.getBehavior() == WebSocketBehavior.SERVER)
|
||||||
frame.reset();
|
{
|
||||||
frame = null;
|
/* Parsing on server.
|
||||||
bufferPool.release(payload);
|
*
|
||||||
payload = null;
|
* Then you MUST make sure all incoming frames are masked!
|
||||||
|
*
|
||||||
|
* Technically, this test is in violation of RFC-6455, Section 5.1
|
||||||
|
* http://tools.ietf.org/html/rfc6455#section-5.1
|
||||||
|
*
|
||||||
|
* But we can't trust the client at this point, so Jetty opts to close
|
||||||
|
* the connection as a Protocol error.
|
||||||
|
*/
|
||||||
|
if (!frame.isMasked())
|
||||||
|
{
|
||||||
|
throw new ProtocolException("Client MUST mask all frames (RFC-6455: Section 5.1)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(policy.getBehavior() == WebSocketBehavior.CLIENT)
|
||||||
|
{
|
||||||
|
// Required by RFC-6455 / Section 5.1
|
||||||
|
if (frame.isMasked())
|
||||||
|
{
|
||||||
|
throw new ProtocolException("Server MUST NOT mask any frames (RFC-6455: Section 5.1)");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void release(Frame frame)
|
||||||
|
{
|
||||||
|
if (frame.hasPayload())
|
||||||
|
bufferPool.release(frame.getPayload());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the base framing protocol buffer.
|
* Parse the base framing protocol buffer.
|
||||||
*
|
*
|
||||||
|
@ -602,17 +612,21 @@ public class Parser
|
||||||
payload = bufferPool.acquire(payloadLength,false);
|
payload = bufferPool.acquire(payloadLength,false);
|
||||||
BufferUtil.clearToFill(payload);
|
BufferUtil.clearToFill(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the payload.
|
// Copy the payload.
|
||||||
payload.put(window);
|
payload.put(window);
|
||||||
|
|
||||||
|
// if the payload is complete
|
||||||
if (payload.position() == payloadLength)
|
if (payload.position() == payloadLength)
|
||||||
{
|
{
|
||||||
BufferUtil.flipToFlush(payload, 0);
|
BufferUtil.flipToFlush(payload, 0);
|
||||||
frame.setPayload(payload);
|
frame.setPayload(payload);
|
||||||
|
// notify that frame is complete
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// frame not (yet) complete
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.io.AbstractConnection;
|
import org.eclipse.jetty.io.AbstractConnection;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
|
@ -63,8 +62,6 @@ import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener;
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, Connection.UpgradeTo, ConnectionStateListener, Dumpable, Parser.Handler
|
public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, Connection.UpgradeTo, ConnectionStateListener, Dumpable, Parser.Handler
|
||||||
{
|
{
|
||||||
private final AtomicBoolean closed = new AtomicBoolean();
|
|
||||||
|
|
||||||
private class Flusher extends FrameFlusher
|
private class Flusher extends FrameFlusher
|
||||||
{
|
{
|
||||||
private Flusher(ByteBufferPool bufferPool, int bufferSize, Generator generator, EndPoint endpoint)
|
private Flusher(ByteBufferPool bufferPool, int bufferSize, Generator generator, EndPoint endpoint)
|
||||||
|
@ -174,35 +171,6 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Stats
|
|
||||||
{
|
|
||||||
private AtomicLong countFillInterestedEvents = new AtomicLong(0);
|
|
||||||
private AtomicLong countOnFillableEvents = new AtomicLong(0);
|
|
||||||
private AtomicLong countFillableErrors = new AtomicLong(0);
|
|
||||||
|
|
||||||
public long getFillableErrorCount()
|
|
||||||
{
|
|
||||||
return countFillableErrors.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getFillInterestedCount()
|
|
||||||
{
|
|
||||||
return countFillInterestedEvents.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getOnFillableCount()
|
|
||||||
{
|
|
||||||
return countOnFillableEvents.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum ReadMode
|
|
||||||
{
|
|
||||||
PARSE,
|
|
||||||
DISCARD,
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Logger LOG = Log.getLogger(AbstractWebSocketConnection.class);
|
private static final Logger LOG = Log.getLogger(AbstractWebSocketConnection.class);
|
||||||
private static final Logger LOG_OPEN = Log.getLogger(AbstractWebSocketConnection.class.getName() + "_OPEN");
|
private static final Logger LOG_OPEN = Log.getLogger(AbstractWebSocketConnection.class.getName() + "_OPEN");
|
||||||
private static final Logger LOG_CLOSE = Log.getLogger(AbstractWebSocketConnection.class.getName() + "_CLOSE");
|
private static final Logger LOG_CLOSE = Log.getLogger(AbstractWebSocketConnection.class.getName() + "_CLOSE");
|
||||||
|
@ -211,7 +179,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
* Minimum size of a buffer is the determined to be what would be the maximum framing header size (not including payload)
|
* Minimum size of a buffer is the determined to be what would be the maximum framing header size (not including payload)
|
||||||
*/
|
*/
|
||||||
private static final int MIN_BUFFER_SIZE = Generator.MAX_HEADER_LENGTH;
|
private static final int MIN_BUFFER_SIZE = Generator.MAX_HEADER_LENGTH;
|
||||||
|
|
||||||
private final ByteBufferPool bufferPool;
|
private final ByteBufferPool bufferPool;
|
||||||
private final Scheduler scheduler;
|
private final Scheduler scheduler;
|
||||||
private final Generator generator;
|
private final Generator generator;
|
||||||
|
@ -219,15 +187,14 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
private final WebSocketPolicy policy;
|
private final WebSocketPolicy policy;
|
||||||
private final WebSocketBehavior behavior;
|
private final WebSocketBehavior behavior;
|
||||||
private final AtomicBoolean suspendToken;
|
private final AtomicBoolean suspendToken;
|
||||||
|
private final AtomicBoolean closed = new AtomicBoolean();
|
||||||
private final FrameFlusher flusher;
|
private final FrameFlusher flusher;
|
||||||
private final String id;
|
private final String id;
|
||||||
private final ExtensionStack extensionStack;
|
private final ExtensionStack extensionStack;
|
||||||
private List<ExtensionConfig> extensions;
|
private List<ExtensionConfig> extensions;
|
||||||
private boolean isFilling;
|
private ByteBuffer networkBuffer;
|
||||||
private ByteBuffer prefillBuffer;
|
private ByteBuffer prefillBuffer;
|
||||||
private ReadMode readMode = ReadMode.PARSE;
|
|
||||||
private IOState ioState;
|
private IOState ioState;
|
||||||
private Stats stats = new Stats();
|
|
||||||
|
|
||||||
public AbstractWebSocketConnection(EndPoint endp, Executor executor, Scheduler scheduler, WebSocketPolicy policy, ByteBufferPool bufferPool, ExtensionStack extensionStack)
|
public AbstractWebSocketConnection(EndPoint endp, Executor executor, Scheduler scheduler, WebSocketPolicy policy, ByteBufferPool bufferPool, ExtensionStack extensionStack)
|
||||||
{
|
{
|
||||||
|
@ -342,13 +309,6 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void fillInterested()
|
|
||||||
{
|
|
||||||
stats.countFillInterestedEvents.incrementAndGet();
|
|
||||||
super.fillInterested();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ByteBufferPool getBufferPool()
|
public ByteBufferPool getBufferPool()
|
||||||
{
|
{
|
||||||
|
@ -417,23 +377,12 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
return scheduler;
|
return scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Stats getStats()
|
|
||||||
{
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOpen()
|
public boolean isOpen()
|
||||||
{
|
{
|
||||||
return !closed.get();
|
return !closed.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReading()
|
|
||||||
{
|
|
||||||
return isFilling;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Physical connection disconnect.
|
* Physical connection disconnect.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -505,69 +454,109 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFrame(Frame frame)
|
public boolean onFrame(Frame frame)
|
||||||
{
|
{
|
||||||
|
AtomicBoolean result = new AtomicBoolean(false);
|
||||||
|
|
||||||
extensionStack.incomingFrame(frame, new FrameCallback()
|
extensionStack.incomingFrame(frame, new FrameCallback()
|
||||||
{
|
{
|
||||||
@Override
|
|
||||||
public void fail(Throwable cause)
|
|
||||||
{
|
|
||||||
// TODO: suspend
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void succeed()
|
public void succeed()
|
||||||
{
|
{
|
||||||
// TODO: resume
|
parser.release(frame);
|
||||||
|
if(!result.compareAndSet(false,true))
|
||||||
|
{
|
||||||
|
// callback has been notified asynchronously
|
||||||
|
fillAndParse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fail(Throwable cause)
|
||||||
|
{
|
||||||
|
parser.release(frame);
|
||||||
|
|
||||||
|
// notify session & endpoint
|
||||||
|
notifyError(cause);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(result.compareAndSet(false, true))
|
||||||
|
{
|
||||||
|
// callback hasn't been notified yet
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFillable()
|
public void onFillable()
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
networkBuffer = bufferPool.acquire(getInputBufferSize(),true);
|
||||||
LOG.debug("{} onFillable()",behavior);
|
|
||||||
stats.countOnFillableEvents.incrementAndGet();
|
fillAndParse();
|
||||||
|
}
|
||||||
ByteBuffer buffer = bufferPool.acquire(getInputBufferSize(),true);
|
|
||||||
|
private void fillAndParse()
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
isFilling = true;
|
while (true)
|
||||||
|
|
||||||
if(readMode == ReadMode.PARSE)
|
|
||||||
{
|
{
|
||||||
readMode = readParse(buffer);
|
if (suspendToken.get())
|
||||||
}
|
{
|
||||||
else
|
return;
|
||||||
{
|
}
|
||||||
readMode = readDiscard(buffer);
|
|
||||||
|
if (networkBuffer.hasRemaining())
|
||||||
|
{
|
||||||
|
if (!parser.parse(networkBuffer)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: flip/fill?
|
||||||
|
|
||||||
|
int filled = getEndPoint().fill(networkBuffer);
|
||||||
|
|
||||||
|
if (filled < 0)
|
||||||
|
{
|
||||||
|
bufferPool.release(networkBuffer);
|
||||||
|
shutdown();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filled == 0)
|
||||||
|
{
|
||||||
|
bufferPool.release(networkBuffer);
|
||||||
|
fillInterested();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parser.parse(networkBuffer)) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
bufferPool.release(buffer);
|
LOG.warn(e);
|
||||||
|
close(StatusCode.PROTOCOL,e.getMessage());
|
||||||
}
|
}
|
||||||
|
catch (CloseException e)
|
||||||
if ((readMode != ReadMode.EOF) && (suspendToken.get() == false))
|
|
||||||
{
|
{
|
||||||
fillInterested();
|
LOG.debug(e);
|
||||||
|
close(e.getStatusCode(),e.getMessage());
|
||||||
}
|
}
|
||||||
else
|
catch (Throwable t)
|
||||||
{
|
{
|
||||||
isFilling = false;
|
LOG.warn(t);
|
||||||
|
close(StatusCode.ABNORMAL,t.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onFillInterestedFailed(Throwable cause)
|
|
||||||
{
|
|
||||||
LOG.ignore(cause);
|
|
||||||
stats.countFillInterestedEvents.incrementAndGet();
|
|
||||||
super.onFillInterestedFailed(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extra bytes from the initial HTTP upgrade that need to
|
* Extra bytes from the initial HTTP upgrade that need to
|
||||||
* be processed by the websocket parser before starting
|
* be processed by the websocket parser before starting
|
||||||
|
@ -583,9 +572,9 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
prefillBuffer = prefilled;
|
prefillBuffer = prefilled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyError(Throwable t)
|
private void notifyError(Throwable cause)
|
||||||
{
|
{
|
||||||
extensionStack.incomingError(t);
|
extensionStack.incomingError(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -643,45 +632,15 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
|
|
||||||
flusher.enqueue(frame,callback,batchMode);
|
flusher.enqueue(frame,callback,batchMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ReadMode readDiscard(ByteBuffer buffer)
|
/**
|
||||||
{
|
* Read from Endpoint and parse bytes.
|
||||||
EndPoint endPoint = getEndPoint();
|
*
|
||||||
try
|
* @param buffer
|
||||||
{
|
* @return
|
||||||
while (true)
|
*/
|
||||||
{
|
@Deprecated
|
||||||
int filled = endPoint.fill(buffer);
|
private int readParse(ByteBuffer buffer)
|
||||||
if (filled == 0)
|
|
||||||
{
|
|
||||||
return ReadMode.DISCARD;
|
|
||||||
}
|
|
||||||
else if (filled < 0)
|
|
||||||
{
|
|
||||||
if (LOG_CLOSE.isDebugEnabled())
|
|
||||||
LOG_CLOSE.debug("read - EOF Reached (remote: {})",getRemoteAddress());
|
|
||||||
return ReadMode.EOF;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (LOG_CLOSE.isDebugEnabled())
|
|
||||||
LOG_CLOSE.debug("Discarded {} bytes - {}",filled,BufferUtil.toDetailString(buffer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
LOG.ignore(e);
|
|
||||||
return ReadMode.EOF;
|
|
||||||
}
|
|
||||||
catch (Throwable t)
|
|
||||||
{
|
|
||||||
LOG.ignore(t);
|
|
||||||
return ReadMode.DISCARD;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReadMode readParse(ByteBuffer buffer)
|
|
||||||
{
|
{
|
||||||
EndPoint endPoint = getEndPoint();
|
EndPoint endPoint = getEndPoint();
|
||||||
try
|
try
|
||||||
|
@ -694,12 +653,12 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
{
|
{
|
||||||
LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress());
|
LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress());
|
||||||
ioState.onReadFailure(new EOFException("Remote Read EOF"));
|
ioState.onReadFailure(new EOFException("Remote Read EOF"));
|
||||||
return ReadMode.EOF;
|
return filled;
|
||||||
}
|
}
|
||||||
else if (filled == 0)
|
else if (filled == 0)
|
||||||
{
|
{
|
||||||
// Done reading, wait for next onFillable
|
// Done reading, wait for next onFillable
|
||||||
return ReadMode.PARSE;
|
return filled;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
|
@ -714,33 +673,27 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp
|
||||||
{
|
{
|
||||||
LOG.warn(e);
|
LOG.warn(e);
|
||||||
close(StatusCode.PROTOCOL,e.getMessage());
|
close(StatusCode.PROTOCOL,e.getMessage());
|
||||||
return ReadMode.DISCARD;
|
return -1;
|
||||||
}
|
}
|
||||||
catch (CloseException e)
|
catch (CloseException e)
|
||||||
{
|
{
|
||||||
LOG.debug(e);
|
LOG.debug(e);
|
||||||
close(e.getStatusCode(),e.getMessage());
|
close(e.getStatusCode(),e.getMessage());
|
||||||
return ReadMode.DISCARD;
|
return -1;
|
||||||
}
|
}
|
||||||
catch (Throwable t)
|
catch (Throwable t)
|
||||||
{
|
{
|
||||||
LOG.warn(t);
|
LOG.warn(t);
|
||||||
close(StatusCode.ABNORMAL,t.getMessage());
|
close(StatusCode.ABNORMAL,t.getMessage());
|
||||||
// TODO: should ws only switch to discard if a non-ws-endpoint error?
|
return -1;
|
||||||
return ReadMode.DISCARD;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resume()
|
public void resume()
|
||||||
{
|
{
|
||||||
if (suspendToken.getAndSet(false))
|
suspendToken.set(false);
|
||||||
{
|
fillAndParse();
|
||||||
if (!isReading())
|
|
||||||
{
|
|
||||||
fillInterested();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -90,6 +90,7 @@ public class InputStreamMessageSink implements MessageSink
|
||||||
LOG.debug("dispatch complete await() - {}", stream);
|
LOG.debug("dispatch complete await() - {}", stream);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// TODO: remove
|
||||||
dispatchCompleted.await();
|
dispatchCompleted.await();
|
||||||
}
|
}
|
||||||
catch (InterruptedException e)
|
catch (InterruptedException e)
|
||||||
|
|
|
@ -20,11 +20,10 @@ package org.eclipse.jetty.websocket.common.message;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.InterruptedIOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.concurrent.BlockingDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.Deque;
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
@ -42,180 +41,144 @@ public class MessageInputStream extends InputStream implements MessageSink
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(MessageInputStream.class);
|
private static final Logger LOG = Log.getLogger(MessageInputStream.class);
|
||||||
private static final FrameCallbackBuffer EOF = new FrameCallbackBuffer(new FrameCallback.Adapter(), ByteBuffer.allocate(0).asReadOnlyBuffer());
|
private static final FrameCallbackBuffer EOF = new FrameCallbackBuffer(new FrameCallback.Adapter(), ByteBuffer.allocate(0).asReadOnlyBuffer());
|
||||||
|
private final Deque<FrameCallbackBuffer> buffers = new ArrayDeque<>(2);
|
||||||
private final BlockingDeque<FrameCallbackBuffer> buffers = new LinkedBlockingDeque<>();
|
|
||||||
|
|
||||||
private final AtomicBoolean closed = new AtomicBoolean(false);
|
private final AtomicBoolean closed = new AtomicBoolean(false);
|
||||||
private final long timeoutMs;
|
|
||||||
private final CountDownLatch closedLatch = new CountDownLatch(1);
|
|
||||||
|
|
||||||
private FrameCallbackBuffer activeBuffer = null;
|
|
||||||
|
|
||||||
public MessageInputStream()
|
|
||||||
{
|
|
||||||
this(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageInputStream(int timeoutMs)
|
|
||||||
{
|
|
||||||
this.timeoutMs = timeoutMs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void accept(Frame frame, FrameCallback callback)
|
public void accept(Frame frame, FrameCallback callback)
|
||||||
{
|
{
|
||||||
if (LOG.isDebugEnabled())
|
if (LOG.isDebugEnabled())
|
||||||
{
|
{
|
||||||
LOG.debug("Appending {}", frame);
|
LOG.debug("accepting {}", frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If closed, we should just toss incoming payloads into the bit bucket.
|
// If closed, we should just toss incoming payloads into the bit bucket.
|
||||||
if (closed.get())
|
if (closed.get())
|
||||||
{
|
{
|
||||||
callback.fail(new IOException("Already Closed"));
|
callback.fail(new IOException("Already Closed"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put the payload into the queue, by copying it.
|
if (!frame.hasPayload() && !frame.isFin())
|
||||||
// Copying is necessary because the payload will
|
{
|
||||||
// be processed after this method returns.
|
callback.succeed();
|
||||||
try
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (buffers)
|
||||||
{
|
{
|
||||||
if (!frame.hasPayload())
|
|
||||||
{
|
|
||||||
// skip if no payload
|
|
||||||
callback.succeed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ByteBuffer payload = frame.getPayload();
|
ByteBuffer payload = frame.getPayload();
|
||||||
|
buffers.offer(new FrameCallbackBuffer(callback, payload));
|
||||||
int capacity = payload.remaining();
|
|
||||||
if (capacity <= 0)
|
|
||||||
{
|
|
||||||
// skip if no payload data to copy
|
|
||||||
callback.succeed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: the copy buffer should be pooled too, but no buffer pool available from here.
|
|
||||||
ByteBuffer copy = payload.isDirect() ? ByteBuffer.allocateDirect(capacity) : ByteBuffer.allocate(capacity);
|
|
||||||
copy.put(payload).flip();
|
|
||||||
buffers.put(new FrameCallbackBuffer(callback,copy));
|
|
||||||
|
|
||||||
// TODO: backpressure
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (frame.isFin())
|
if (frame.isFin())
|
||||||
{
|
{
|
||||||
buffers.offer(EOF);
|
buffers.offer(EOF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// notify other thread
|
||||||
|
buffers.notify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException
|
public void close() throws IOException
|
||||||
{
|
{
|
||||||
if (closed.compareAndSet(false, true))
|
if (closed.compareAndSet(false, true))
|
||||||
{
|
{
|
||||||
buffers.offer(EOF);
|
synchronized (buffers)
|
||||||
super.close();
|
{
|
||||||
closedLatch.countDown();
|
buffers.offer(EOF);
|
||||||
|
buffers.notify();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void shutdown()
|
||||||
|
{
|
||||||
|
closed.set(true);
|
||||||
|
// Removed buffers that may have remained in the queue.
|
||||||
|
buffers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mark(int readlimit)
|
public void mark(int readlimit)
|
||||||
{
|
{
|
||||||
// Not supported.
|
// Not supported.
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean markSupported()
|
public boolean markSupported()
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() throws IOException
|
public int read() throws IOException
|
||||||
{
|
{
|
||||||
try
|
byte buf[] = new byte[1];
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
if (closed.get())
|
int len = read(buf, 0, 1);
|
||||||
{
|
if (len < 0) // EOF
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("Stream closed");
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
if (len > 0) // did read something
|
||||||
|
return buf[0];
|
||||||
// grab a fresh buffer
|
// reading nothing (len == 0) tries again
|
||||||
while (activeBuffer == null || !activeBuffer.buffer.hasRemaining())
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("Waiting {} ms to read", timeoutMs);
|
|
||||||
if (timeoutMs < 0)
|
|
||||||
{
|
|
||||||
// Wait forever until a buffer is available.
|
|
||||||
// TODO: notify connection to resume (if paused)
|
|
||||||
activeBuffer = buffers.take();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Wait at most for the given timeout.
|
|
||||||
activeBuffer = buffers.poll(timeoutMs, TimeUnit.MILLISECONDS);
|
|
||||||
if (activeBuffer == null)
|
|
||||||
{
|
|
||||||
throw new IOException(String.format("Read timeout: %,dms expired", timeoutMs));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeBuffer == EOF)
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("Reached EOF");
|
|
||||||
// Be sure that this stream cannot be reused.
|
|
||||||
closed.set(true);
|
|
||||||
closedLatch.countDown();
|
|
||||||
// Removed buffers that may have remained in the queue.
|
|
||||||
buffers.clear();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return activeBuffer.buffer.get() & 0xFF;
|
|
||||||
}
|
|
||||||
catch (InterruptedException x)
|
|
||||||
{
|
|
||||||
if (LOG.isDebugEnabled())
|
|
||||||
LOG.debug("Interrupted while waiting to read", x);
|
|
||||||
closed.set(true);
|
|
||||||
closedLatch.countDown();
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException
|
||||||
|
{
|
||||||
|
if (closed.get())
|
||||||
|
{
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("Stream closed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sync and poll queue
|
||||||
|
FrameCallbackBuffer result;
|
||||||
|
synchronized (buffers)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while ((result = buffers.poll()) == null)
|
||||||
|
{
|
||||||
|
// TODO: handle read timeout here?
|
||||||
|
buffers.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (InterruptedException e)
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
throw new InterruptedIOException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == EOF)
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have content
|
||||||
|
int fillLen = Math.min(result.buffer.remaining(), len);
|
||||||
|
result.buffer.get(b, off, fillLen);
|
||||||
|
|
||||||
|
if (!result.buffer.hasRemaining())
|
||||||
|
{
|
||||||
|
result.callback.succeed();
|
||||||
|
}
|
||||||
|
|
||||||
|
// return number of bytes actually copied into buffer
|
||||||
|
return fillLen;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() throws IOException
|
public void reset() throws IOException
|
||||||
{
|
{
|
||||||
throw new IOException("reset() not supported");
|
throw new IOException("reset() not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove await!
|
|
||||||
@Deprecated
|
|
||||||
public void awaitClose()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
closedLatch.await();
|
|
||||||
}
|
|
||||||
catch (InterruptedException e)
|
|
||||||
{
|
|
||||||
throw new RuntimeException("Stream Close wait interrupted", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,11 +44,4 @@ public class MessageReader extends InputStreamReader implements MessageSink
|
||||||
{
|
{
|
||||||
this.stream.accept(frame, callback);
|
this.stream.accept(frame, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove await!
|
|
||||||
@Deprecated
|
|
||||||
public void awaitClose()
|
|
||||||
{
|
|
||||||
stream.awaitClose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,11 @@ public class ExtensionTool
|
||||||
Assert.assertThat("extClass", extClass, notNullValue());
|
Assert.assertThat("extClass", extClass, notNullValue());
|
||||||
|
|
||||||
this.capture = new IncomingFramesCapture();
|
this.capture = new IncomingFramesCapture();
|
||||||
this.parser = new UnitParser(policy,frame -> ext.incomingFrame(frame, new FrameCallbackAdapter()));
|
this.parser = new UnitParser(policy, frame ->
|
||||||
|
{
|
||||||
|
ext.incomingFrame(frame, new FrameCallbackAdapter());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRequestedExtParams()
|
public String getRequestedExtParams()
|
||||||
|
|
|
@ -45,7 +45,6 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
|
||||||
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
||||||
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
|
import org.eclipse.jetty.websocket.api.extensions.IncomingFrames;
|
||||||
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
|
import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames;
|
||||||
import org.eclipse.jetty.websocket.common.FrameCallbackAdapter;
|
|
||||||
import org.eclipse.jetty.websocket.common.Generator;
|
import org.eclipse.jetty.websocket.common.Generator;
|
||||||
import org.eclipse.jetty.websocket.common.OpCode;
|
import org.eclipse.jetty.websocket.common.OpCode;
|
||||||
import org.eclipse.jetty.websocket.common.Parser;
|
import org.eclipse.jetty.websocket.common.Parser;
|
||||||
|
@ -87,7 +86,11 @@ public class DeflateFrameExtensionTest extends AbstractExtensionTest
|
||||||
// Wire up stack
|
// Wire up stack
|
||||||
ext.setNextIncomingFrames(capture);
|
ext.setNextIncomingFrames(capture);
|
||||||
|
|
||||||
Parser parser = new UnitParser(policy, frame -> ext.incomingFrame(frame, new FrameCallbackAdapter()));
|
Parser parser = new UnitParser(policy, (frame) ->
|
||||||
|
{
|
||||||
|
ext.incomingFrame(frame, new FrameCallback.Adapter());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
parser.configureFromExtensions(Collections.singletonList(ext));
|
parser.configureFromExtensions(Collections.singletonList(ext));
|
||||||
parser.parse(ByteBuffer.wrap(raw));
|
parser.parse(ByteBuffer.wrap(raw));
|
||||||
|
|
||||||
|
|
|
@ -161,12 +161,6 @@ public class LocalWebSocketConnection implements LogicalConnection, ConnectionSt
|
||||||
return getIOState().isOpen();
|
return getIOState().isOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReading()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionStateChange(ConnectionState state)
|
public void onConnectionStateChange(ConnectionState state)
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,6 +27,7 @@ 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.AtomicBoolean;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.eclipse.jetty.websocket.api.FrameCallback;
|
import org.eclipse.jetty.websocket.api.FrameCallback;
|
||||||
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
import org.eclipse.jetty.websocket.common.WebSocketFrame;
|
||||||
import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
|
import org.eclipse.jetty.websocket.common.frames.BinaryFrame;
|
||||||
|
@ -46,7 +47,7 @@ public class MessageInputStreamTest
|
||||||
@Rule
|
@Rule
|
||||||
public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
|
public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test");
|
||||||
|
|
||||||
@Test(timeout=10000)
|
@Test(timeout=5000)
|
||||||
public void testBasicAppendRead() throws IOException
|
public void testBasicAppendRead() throws IOException
|
||||||
{
|
{
|
||||||
try (MessageInputStream stream = new MessageInputStream())
|
try (MessageInputStream stream = new MessageInputStream())
|
||||||
|
@ -58,9 +59,8 @@ public class MessageInputStreamTest
|
||||||
stream.accept(frame, new FrameCallback.Adapter());
|
stream.accept(frame, new FrameCallback.Adapter());
|
||||||
|
|
||||||
// Read entire message it from the stream.
|
// Read entire message it from the stream.
|
||||||
byte buf[] = new byte[32];
|
byte data[] = IO.readBytes(stream);
|
||||||
int len = stream.read(buf);
|
String message = new String(data,0,data.length,StandardCharsets.UTF_8);
|
||||||
String message = new String(buf,0,len,StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
// Test it
|
// Test it
|
||||||
Assert.assertThat("Message",message,is("Hello World"));
|
Assert.assertThat("Message",message,is("Hello World"));
|
||||||
|
@ -104,9 +104,8 @@ public class MessageInputStreamTest
|
||||||
startLatch.await();
|
startLatch.await();
|
||||||
|
|
||||||
// Read it from the stream.
|
// Read it from the stream.
|
||||||
byte buf[] = new byte[32];
|
byte data[] = IO.readBytes(stream);
|
||||||
int len = stream.read(buf);
|
String message = new String(data,0,data.length,StandardCharsets.UTF_8);
|
||||||
String message = new String(buf,0,len,StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
// Test it
|
// Test it
|
||||||
Assert.assertThat("Error when appending",hadError.get(),is(false));
|
Assert.assertThat("Error when appending",hadError.get(),is(false));
|
||||||
|
@ -114,7 +113,7 @@ public class MessageInputStreamTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout=10000)
|
@Test(timeout=5000)
|
||||||
public void testBlockOnReadInitial() throws IOException
|
public void testBlockOnReadInitial() throws IOException
|
||||||
{
|
{
|
||||||
try (MessageInputStream stream = new MessageInputStream())
|
try (MessageInputStream stream = new MessageInputStream())
|
||||||
|
@ -150,7 +149,7 @@ public class MessageInputStreamTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout=10000)
|
@Test(timeout=5000)
|
||||||
public void testReadByteNoBuffersClosed() throws IOException
|
public void testReadByteNoBuffersClosed() throws IOException
|
||||||
{
|
{
|
||||||
try (MessageInputStream stream = new MessageInputStream())
|
try (MessageInputStream stream = new MessageInputStream())
|
||||||
|
@ -164,14 +163,10 @@ public class MessageInputStreamTest
|
||||||
TimeUnit.MILLISECONDS.sleep(400);
|
TimeUnit.MILLISECONDS.sleep(400);
|
||||||
stream.close();
|
stream.close();
|
||||||
}
|
}
|
||||||
catch (InterruptedException e)
|
catch (Throwable t)
|
||||||
{
|
{
|
||||||
hadError.set(true);
|
hadError.set(true);
|
||||||
e.printStackTrace(System.err);
|
t.printStackTrace(System.err);
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace(System.err);
|
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
|
@ -180,12 +175,12 @@ public class MessageInputStreamTest
|
||||||
// Should be a -1, indicating the end of the stream.
|
// Should be a -1, indicating the end of the stream.
|
||||||
|
|
||||||
// Test it
|
// Test it
|
||||||
Assert.assertThat("Error when appending",hadError.get(),is(false));
|
Assert.assertThat("Error when closing",hadError.get(),is(false));
|
||||||
Assert.assertThat("Initial byte (Should be EOF)",b,is(-1));
|
Assert.assertThat("Initial byte (Should be EOF)",b,is(-1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout=10000)
|
@Test(timeout=5000)
|
||||||
public void testAppendEmptyPayloadRead() throws IOException
|
public void testAppendEmptyPayloadRead() throws IOException
|
||||||
{
|
{
|
||||||
try (MessageInputStream stream = new MessageInputStream())
|
try (MessageInputStream stream = new MessageInputStream())
|
||||||
|
@ -201,16 +196,15 @@ public class MessageInputStreamTest
|
||||||
stream.accept(msg3, new FrameCallback.Adapter());
|
stream.accept(msg3, new FrameCallback.Adapter());
|
||||||
|
|
||||||
// Read entire message it from the stream.
|
// Read entire message it from the stream.
|
||||||
byte buf[] = new byte[32];
|
byte data[] = IO.readBytes(stream);
|
||||||
int len = stream.read(buf);
|
String message = new String(data,0,data.length,StandardCharsets.UTF_8);
|
||||||
String message = new String(buf,0,len,StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
// Test it
|
// Test it
|
||||||
Assert.assertThat("Message",message,is("Hello World"));
|
Assert.assertThat("Message",message,is("Hello World"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(timeout=10000)
|
@Test(timeout=5000)
|
||||||
public void testAppendNullPayloadRead() throws IOException
|
public void testAppendNullPayloadRead() throws IOException
|
||||||
{
|
{
|
||||||
try (MessageInputStream stream = new MessageInputStream())
|
try (MessageInputStream stream = new MessageInputStream())
|
||||||
|
@ -227,9 +221,8 @@ public class MessageInputStreamTest
|
||||||
stream.accept(msg3, new FrameCallback.Adapter());
|
stream.accept(msg3, new FrameCallback.Adapter());
|
||||||
|
|
||||||
// Read entire message it from the stream.
|
// Read entire message it from the stream.
|
||||||
byte buf[] = new byte[32];
|
byte data[] = IO.readBytes(stream);
|
||||||
int len = stream.read(buf);
|
String message = new String(data,0,data.length,StandardCharsets.UTF_8);
|
||||||
String message = new String(buf,0,len,StandardCharsets.UTF_8);
|
|
||||||
|
|
||||||
// Test it
|
// Test it
|
||||||
Assert.assertThat("Message",message,is("Hello World"));
|
Assert.assertThat("Message",message,is("Hello World"));
|
||||||
|
|
|
@ -626,9 +626,10 @@ public class BlockheadClient implements OutgoingFrames, ConnectionStateListener,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFrame(Frame frame)
|
public boolean onFrame(Frame frame)
|
||||||
{
|
{
|
||||||
// TODO
|
// TODO: do something with frame?
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public EventQueue<WebSocketFrame> readFrames(int expectedFrameCount, int timeoutDuration, TimeUnit timeoutUnit) throws Exception
|
public EventQueue<WebSocketFrame> readFrames(int expectedFrameCount, int timeoutDuration, TimeUnit timeoutUnit) throws Exception
|
||||||
|
|
|
@ -101,7 +101,11 @@ public class BlockheadServerConnection implements IncomingFrames, OutgoingFrames
|
||||||
this.policy.setMaxTextMessageSize(100000);
|
this.policy.setMaxTextMessageSize(100000);
|
||||||
// This is a blockhead server connection, no point tracking leaks on this object.
|
// This is a blockhead server connection, no point tracking leaks on this object.
|
||||||
this.bufferPool = new MappedByteBufferPool(BUFFER_SIZE);
|
this.bufferPool = new MappedByteBufferPool(BUFFER_SIZE);
|
||||||
this.parser = new Parser(policy,bufferPool,frame -> extensionStack.incomingFrame(frame, new FrameCallbackAdapter()));
|
this.parser = new Parser(policy,bufferPool, frame ->
|
||||||
|
{
|
||||||
|
extensionStack.incomingFrame(frame, new FrameCallbackAdapter());
|
||||||
|
return true;
|
||||||
|
});
|
||||||
this.parseCount = new AtomicInteger(0);
|
this.parseCount = new AtomicInteger(0);
|
||||||
this.generator = new Generator(policy,bufferPool,false);
|
this.generator = new Generator(policy,bufferPool,false);
|
||||||
this.extensionRegistry = new WebSocketExtensionFactory(new SimpleContainerScope(policy,bufferPool));
|
this.extensionRegistry = new WebSocketExtensionFactory(new SimpleContainerScope(policy,bufferPool));
|
||||||
|
|
|
@ -126,12 +126,6 @@ public class DummyConnection implements LogicalConnection
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReading()
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void outgoingFrame(Frame frame, FrameCallback callback, BatchMode batchMode)
|
public void outgoingFrame(Frame frame, FrameCallback callback, BatchMode batchMode)
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,8 +25,6 @@ import java.util.Queue;
|
||||||
|
|
||||||
import org.eclipse.jetty.toolchain.test.EventQueue;
|
import org.eclipse.jetty.toolchain.test.EventQueue;
|
||||||
import org.eclipse.jetty.util.BufferUtil;
|
import org.eclipse.jetty.util.BufferUtil;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
|
||||||
import org.eclipse.jetty.websocket.api.FrameCallback;
|
import org.eclipse.jetty.websocket.api.FrameCallback;
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketException;
|
import org.eclipse.jetty.websocket.api.WebSocketException;
|
||||||
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
import org.eclipse.jetty.websocket.api.extensions.Frame;
|
||||||
|
@ -38,7 +36,6 @@ import org.junit.Assert;
|
||||||
|
|
||||||
public class IncomingFramesCapture implements Parser.Handler, IncomingFrames
|
public class IncomingFramesCapture implements Parser.Handler, IncomingFrames
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(IncomingFramesCapture.class);
|
|
||||||
private EventQueue<WebSocketFrame> frames = new EventQueue<>();
|
private EventQueue<WebSocketFrame> frames = new EventQueue<>();
|
||||||
private EventQueue<Throwable> errors = new EventQueue<>();
|
private EventQueue<Throwable> errors = new EventQueue<>();
|
||||||
|
|
||||||
|
@ -161,10 +158,11 @@ public class IncomingFramesCapture implements Parser.Handler, IncomingFrames
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFrame(Frame frame)
|
public boolean onFrame(Frame frame)
|
||||||
{
|
{
|
||||||
WebSocketFrame copy = WebSocketFrame.copy(frame);
|
WebSocketFrame copy = WebSocketFrame.copy(frame);
|
||||||
frames.add(copy);
|
frames.add(copy);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int size()
|
public int size()
|
||||||
|
|
Loading…
Reference in New Issue