Returns whether this resource is referenced counted by calls to {@link #retain()}
- * and {@link #release()}.
- *
Implementations may decide that special resources are not not referenced counted (for example,
- * {@code static} constants) so calling {@link #retain()} is a no-operation, and
- * calling {@link #release()} on those special resources is a no-operation that always returns true.
Retains this resource, potentially incrementing a reference count if there are resources that will be released.
- */
- default void retain()
- {
- }
-
- /**
- *
Releases this resource, potentially decrementing a reference count (if any).
- *
- * @return {@code true} when the reference count goes to zero or if there was no reference count,
- * {@code false} otherwise.
- */
- default boolean release()
- {
- return true;
- }
-
- /**
- *
Get the retained count. This value is volatile and should only be used for informational/debugging purposes.
- * @return the retained count
- */
- default int getRetained()
- {
- return -1;
- }
-
/**
* A wrapper of {@link Retainable} instances.
*/
@@ -158,95 +107,16 @@ public String toString()
}
}
- /**
- *
A reference count implementation for a {@link Retainable} resource.
- *
The reference count is initialized to 1 when the resource is created,
- * and therefore it is implicitly retained and needs a call to {@link #release()}.
- *
Additional calls to {@link #retain()} must be matched by correspondent
- * calls to {@link #release()}.
- *
When the reference count goes to zero, the resource may be pooled.
- * When the resource is acquired from the pool, {@link #acquire()} should be
- * called to set the reference count to {@code 1}.
- */
- class ReferenceCounter implements Retainable
+ class ReferenceCounter extends org.eclipse.jetty.util.Retainable.ReferenceCounter implements Retainable
{
- private final AtomicInteger references;
-
public ReferenceCounter()
{
- this(1);
+ super();
}
protected ReferenceCounter(int initialCount)
{
- references = new AtomicInteger(initialCount);
- }
-
- /**
- * @return the current reference count
- */
- public int get()
- {
- return references.get();
- }
-
- /**
- *
Updates the reference count from {@code 0} to {@code 1}.
- *
This method should only be used when this resource is acquired
- * from a pool.
- */
- public void acquire()
- {
- if (references.getAndUpdate(c -> c == 0 ? 1 : c) != 0)
- throw new IllegalStateException("acquired while in use " + this);
- }
-
- @Override
- public boolean canRetain()
- {
- return get() > 0;
- }
-
- @Override
- public void retain()
- {
- if (!tryRetain())
- throw new IllegalStateException("released " + this);
- }
-
- public boolean tryRetain()
- {
- return references.getAndUpdate(c -> c == 0 ? 0 : c + 1) != 0;
- }
-
- @Override
- public boolean release()
- {
- int ref = references.updateAndGet(c ->
- {
- if (c == 0)
- throw new IllegalStateException("already released " + this);
- return c - 1;
- });
- return ref == 0;
- }
-
- @Override
- public boolean isRetained()
- {
- return references.get() > 1;
- }
-
- @Override
- public int getRetained()
- {
- return references.get();
- }
-
- @Override
- public String toString()
- {
- return String.format("%s@%x[rc=%d]", TypeUtil.toShortName(getClass()), hashCode(), get());
+ super(initialCount);
}
}
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/WritableBufferPool.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/WritableBufferPool.java
new file mode 100644
index 000000000000..aee097cc98a7
--- /dev/null
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/WritableBufferPool.java
@@ -0,0 +1,64 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
+
+public interface WritableBufferPool
+{
+ WritableBufferPool NON_POOLING = WritableBuffer::allocate;
+
+ static WritableBufferPool wrap(ByteBufferPool byteBufferPool)
+ {
+ return (size, direct) ->
+ {
+ RetainableByteBuffer.Mutable rbbm = byteBufferPool.acquire(size, direct);
+ BufferUtil.flipToFill(rbbm.getByteBuffer());
+ return WritableBuffer.wrap(rbbm.getByteBuffer(), rbbm);
+ };
+ }
+
+ WritableBuffer acquire(int size, boolean direct);
+
+ class Sized implements WritableBufferPool
+ {
+ private final int size;
+ private final boolean direct;
+ private final WritableBufferPool delegate;
+
+ public Sized(int size, boolean direct, WritableBufferPool delegate)
+ {
+ this.size = size;
+ this.direct = direct;
+ this.delegate = delegate;
+ }
+
+ public int getSize()
+ {
+ return size;
+ }
+
+ public WritableBuffer acquire()
+ {
+ return delegate.acquire(size, direct);
+ }
+
+ @Override
+ public WritableBuffer acquire(int size, boolean direct)
+ {
+ return delegate.acquire(size, direct);
+ }
+ }
+}
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
index 98844886f15e..10a0641e9e80 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
@@ -14,6 +14,7 @@
package org.eclipse.jetty.io.ssl;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.security.cert.X509Certificate;
@@ -37,7 +38,7 @@
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.RetainableByteBuffer;
+import org.eclipse.jetty.io.WritableBufferPool;
import org.eclipse.jetty.io.WriteFlusher;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
@@ -45,6 +46,8 @@
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Invocable;
@@ -113,15 +116,15 @@ private enum FlushState
private final List handshakeListeners = new ArrayList<>();
private final AtomicLong _bytesIn = new AtomicLong();
private final AtomicLong _bytesOut = new AtomicLong();
- private final ByteBufferPool _bufferPool;
+ private final WritableBufferPool _bufferPool;
private final SSLEngine _sslEngine;
private final SslContextFactory _sslContextFactory;
private final SslEndPoint _sslEndPoint;
private final boolean _encryptedDirectBuffers;
private final boolean _decryptedDirectBuffers;
- private RetainableByteBuffer _decryptedInput;
- private RetainableByteBuffer _encryptedInput;
- private RetainableByteBuffer _encryptedOutput;
+ private ReadableBuffer _decryptedInput;
+ private ReadableBuffer _encryptedInput;
+ private ReadableBuffer _encryptedOutput;
private boolean _closedOutbound;
private FlushState _flushState = FlushState.IDLE;
private FillState _fillState = FillState.IDLE;
@@ -198,7 +201,7 @@ public SslConnection(ByteBufferPool byteBufferPool, Executor executor, SslContex
// This connection does not execute calls to onFillable(), so they will be called by the selector thread.
// onFillable() does not block and will only wakeup another thread to do the actual reading and handling.
super(endPoint, executor);
- _bufferPool = byteBufferPool;
+ _bufferPool = WritableBufferPool.wrap(byteBufferPool);
_sslEngine = sslEngine;
_sslEndPoint = newSslEndPoint();
_sslContextFactory = sslContextFactory;
@@ -337,30 +340,46 @@ private int getBufferSize(ToIntFunction bufferSizeFn)
return Math.max(hsSize, size);
}
- private void lockedAcquireEncryptedInput()
+ private WritableBuffer lockedAcquireEncryptedInput()
{
assert _lock.isHeldByCurrentThread();
if (_encryptedInput == null)
- _encryptedInput = _bufferPool.acquire(getPacketBufferSize(), _encryptedDirectBuffers);
+ return _bufferPool.acquire(getPacketBufferSize(), _encryptedDirectBuffers);
+ WritableBuffer wb = _encryptedInput.compact();
+ _encryptedInput = null;
+ return wb;
}
- private void lockedAcquireEncryptedOutput()
+ private WritableBuffer lockedAcquireEncryptedOutput()
{
assert _lock.isHeldByCurrentThread();
- // TODO: before the output was done with the BBP only.
if (_encryptedOutput == null)
- _encryptedOutput = _bufferPool.acquire(getPacketBufferSize(), _encryptedDirectBuffers);
+ return _bufferPool.acquire(getPacketBufferSize(), _encryptedDirectBuffers);
+ WritableBuffer wb = _encryptedOutput.compact();
+ _encryptedOutput = null;
+ return wb;
}
@Override
public void onUpgradeTo(ByteBuffer buffer)
{
+ if (BufferUtil.remaining(buffer) == 0)
+ return;
+
+ WritableBuffer wb;
try (AutoLock ignored = _lock.lock())
{
- lockedAcquireEncryptedInput();
- if (!_encryptedInput.asMutable().append(buffer))
- throw new IllegalStateException("too much to upgrade");
+ wb = lockedAcquireEncryptedInput();
}
+
+ ReadableBuffer rb = ReadableBuffer.wrap(buffer);
+ if (rb.remaining() > wb.remaining())
+ {
+ wb.release();
+ throw new IllegalStateException("too much to upgrade");
+ }
+ wb.put(rb);
+ _encryptedInput = wb.toReadable();
}
@Override
@@ -437,9 +456,9 @@ protected SSLEngineResult unwrap(SSLEngine sslEngine, ByteBuffer input, ByteBuff
@Override
public String toConnectionString()
{
- int encryptedInputRemainingBytes;
- int encryptedOutputRemainingBytes;
- int decryptedInputRemainingBytes;
+ long encryptedInputRemainingBytes;
+ long encryptedOutputRemainingBytes;
+ long decryptedInputRemainingBytes;
FillState fillState;
FlushState flushState;
try (AutoLock l = _lock.tryLock())
@@ -448,12 +467,9 @@ public String toConnectionString()
{
fillState = _fillState;
flushState = _flushState;
- ByteBuffer b = _encryptedInput == null ? null : _encryptedInput.getByteBuffer();
- encryptedInputRemainingBytes = b == null ? -1 : b.remaining();
- b = _encryptedOutput == null ? null : _encryptedOutput.getByteBuffer();
- encryptedOutputRemainingBytes = b == null ? -1 : b.remaining();
- b = _decryptedInput == null ? null : _decryptedInput.getByteBuffer();
- decryptedInputRemainingBytes = b == null ? -1 : b.remaining();
+ encryptedInputRemainingBytes = _encryptedInput == null ? -1 : _encryptedInput.remaining();
+ encryptedOutputRemainingBytes = _encryptedOutput == null ? -1 : _encryptedOutput.remaining();
+ decryptedInputRemainingBytes = _decryptedInput == null ? -1 : _decryptedInput.remaining();
}
else
{
@@ -474,68 +490,80 @@ public String toConnectionString()
connection instanceof AbstractConnection ? ((AbstractConnection)connection).toConnectionString() : connection);
}
- private void lockedReleaseEmptyEncryptedInputBuffer()
+ private void lockedDiscardInputBuffers()
{
assert _lock.isHeldByCurrentThread();
- if (_encryptedInput != null && _encryptedInput.isEmpty())
+ if (_encryptedInput != null)
{
_encryptedInput.release();
_encryptedInput = null;
}
- }
-
- private void lockedReleaseEmptyDecryptedInputBuffer()
- {
- assert _lock.isHeldByCurrentThread();
- if (_decryptedInput != null && _decryptedInput.isEmpty())
+ if (_decryptedInput != null)
{
_decryptedInput.release();
_decryptedInput = null;
}
}
- private void lockedDiscardInputBuffers()
+ private void lockedReleaseEmptyInputBuffers(ReadableBuffer encryptedInput, ReadableBuffer decryptedInput)
{
assert _lock.isHeldByCurrentThread();
- if (_encryptedInput != null)
- _encryptedInput.clear();
- if (_decryptedInput != null)
- _decryptedInput.clear();
- lockedReleaseEmptyInputBuffers();
- }
-
- private void lockedReleaseEmptyInputBuffers()
- {
- lockedReleaseEmptyEncryptedInputBuffer();
- lockedReleaseEmptyDecryptedInputBuffer();
+ assert _encryptedInput == null;
+ assert _decryptedInput == null;
+
+ if (encryptedInput != null && encryptedInput.remaining() == 0L)
+ encryptedInput.release();
+ else
+ _encryptedInput = encryptedInput;
+
+ if (decryptedInput != null && decryptedInput.remaining() == 0L)
+ decryptedInput.release();
+ else
+ _decryptedInput = decryptedInput;
}
private void lockedDiscardEncryptedOutputBuffer()
{
assert _lock.isHeldByCurrentThread();
if (_encryptedOutput != null)
- _encryptedOutput.clear();
- lockedReleaseEmptyEncryptedOutputBuffer();
- }
-
- private void lockedReleaseEmptyEncryptedOutputBuffer()
- {
- assert _lock.isHeldByCurrentThread();
- if (_encryptedOutput != null && !_encryptedOutput.hasRemaining())
{
_encryptedOutput.release();
_encryptedOutput = null;
}
}
- protected int networkFill(ByteBuffer input) throws IOException
+ private void lockedReleaseEmptyEncryptedOutputBuffer(ReadableBuffer encryptedOutput)
{
- return getEndPoint().fill(input);
+ assert _lock.isHeldByCurrentThread();
+ assert _encryptedOutput == null;
+ if (encryptedOutput != null && encryptedOutput.remaining() == 0L)
+ encryptedOutput.release();
+ else
+ _encryptedOutput = encryptedOutput;
}
- protected boolean networkFlush(ByteBuffer output) throws IOException
+ protected int networkFill(WritableBuffer input) throws IOException
{
- return getEndPoint().flush(output);
+ return (int)input.readFrom(output ->
+ {
+ output.flip();
+ try
+ {
+ int filled = getEndPoint().fill(output);
+ return filled == -1;
+ }
+ finally
+ {
+ BufferUtil.flipToFill(output);
+ }
+ });
+ }
+
+ protected boolean networkFlush(ReadableBuffer output) throws IOException
+ {
+ long toBeWritten = output.remaining();
+ long written = output.writeTo(input -> getEndPoint().flush(input));
+ return toBeWritten == written;
}
public class SslEndPoint extends AbstractEndPoint implements EndPoint.Wrapper
@@ -679,228 +707,303 @@ public SslConnection getSslConnection()
@Override
public int fill(ByteBuffer buffer) throws IOException
{
- try
+ try (AutoLock ignored = _lock.lock())
{
- try (AutoLock ignored = _lock.lock())
- {
- if (LOG.isDebugEnabled())
- LOG.debug(">fill {}", SslConnection.this);
+ if (LOG.isDebugEnabled())
+ LOG.debug(">fill {}", SslConnection.this);
- int filled = -2;
- try
- {
- if (_fillState != FillState.IDLE)
- return filled = 0;
+ if (_fillState != FillState.IDLE)
+ return 0;
- // Do we already have some decrypted data?
- if (_decryptedInput != null && _decryptedInput.hasRemaining())
- return filled = BufferUtil.append(buffer, _decryptedInput.getByteBuffer());
+ // Do we already have some decrypted data?
+ if (_decryptedInput != null && _decryptedInput.remaining() > 0L)
+ {
+ int put = BufferUtil.put(_decryptedInput, buffer);
+ if (_decryptedInput.remaining() == 0L)
+ {
+ _decryptedInput.release();
+ _decryptedInput = null;
+ }
+ return put;
+ }
- // loop filling and unwrapping until we have something
- while (true)
+ int filled = -2;
+ ReadableBuffer decryptedInput = null;
+ boolean decryptedInputWrapsUserProvidedBuffer = false;
+ ReadableBuffer encryptedInput = _encryptedInput;
+ _encryptedInput = null;
+ long encryptedInputRemainingWriteSpace;
+ try
+ {
+ // loop filling and unwrapping until we have something
+ while (true)
+ {
+ HandshakeStatus status = _sslEngine.getHandshakeStatus();
+ if (LOG.isDebugEnabled())
+ LOG.debug("fill {}", status);
+ switch (status)
{
- HandshakeStatus status = _sslEngine.getHandshakeStatus();
- if (LOG.isDebugEnabled())
- LOG.debug("fill {}", status);
- switch (status)
- {
- case NEED_UNWRAP:
- case NOT_HANDSHAKING:
- break;
+ case NEED_UNWRAP:
+ case NOT_HANDSHAKING:
+ break;
- case NEED_TASK:
- _sslEngine.getDelegatedTask().run();
- continue;
+ case NEED_TASK:
+ _sslEngine.getDelegatedTask().run();
+ continue;
- case NEED_WRAP:
- if (_flushState == FlushState.IDLE && flush(BufferUtil.EMPTY_BUFFER))
+ case NEED_WRAP:
+ if (_flushState == FlushState.IDLE)
+ {
+ // Store the encrypted inputs in the fields as the following call to
+ // flush() may itself re-enter fill().
+ lockedReleaseEmptyInputBuffers(encryptedInput, decryptedInputWrapsUserProvidedBuffer ? null : decryptedInput);
+ encryptedInput = null;
+ decryptedInput = decryptedInputWrapsUserProvidedBuffer ? decryptedInput : null;
+ try
{
- Throwable failure = _failure;
- if (failure != null)
- throw IO.rethrow(failure);
- if (_sslEngine.isInboundDone())
- return filled = -1;
- continue;
+ if (flush(BufferUtil.EMPTY_BUFFER))
+ {
+ Throwable failure = _failure;
+ if (failure != null)
+ throw IO.rethrow(failure);
+ if (_sslEngine.isInboundDone())
+ return filled = -1;
+ continue;
+ }
}
- // Handle in needsFillInterest().
- return filled = 0;
+ finally
+ {
+ // If flush() returns false or throws, we are going to enter the finally block that
+ // stores the input buffers via lockedReleaseEmptyInputBuffers(). Since the latter
+ // expects the member variables to be null, we need to move them here back to the local
+ // vars.
+ encryptedInput = _encryptedInput;
+ _encryptedInput = null;
+ decryptedInput = _decryptedInput;
+ _decryptedInput = null;
+ }
+
+ }
+ // Handle in needsFillInterest().
+ return filled = 0;
- default:
- throw new IllegalStateException("Unexpected HandshakeStatus " + status);
+ default:
+ throw new IllegalStateException("Unexpected HandshakeStatus " + status);
+ }
+
+ // Let's try reading some encrypted data... even if we have some already.
+ int netFilled;
+ {
+ WritableBuffer wb;
+ if (encryptedInput == null)
+ {
+ wb = lockedAcquireEncryptedInput();
+ }
+ else
+ {
+ wb = encryptedInput.compact();
+ encryptedInput = null;
}
+ try
+ {
+ netFilled = networkFill(wb);
+ encryptedInputRemainingWriteSpace = wb.remaining();
+ encryptedInput = wb.toReadable();
+ }
+ catch (Throwable x)
+ {
+ wb.release();
+ throw x;
+ }
+ }
+
+ if (netFilled > 0)
+ _bytesIn.addAndGet(netFilled);
+ if (LOG.isDebugEnabled())
+ LOG.debug("net filled={}", netFilled);
- lockedAcquireEncryptedInput();
+ // Workaround for Java 11 behavior.
+ if (netFilled < 0 && isHandshakeInitial() && encryptedInput.remaining() == 0L)
+ closeInbound();
+ if (netFilled > 0 && !isHandshakeComplete() && isOutboundDone())
+ throw new SSLHandshakeException("Closed during handshake");
+
+ if (_handshake.compareAndSet(HandshakeState.INITIAL, HandshakeState.HANDSHAKE))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("fill starting handshake {}", SslConnection.this);
+ }
+
+ int appBufferSize = getApplicationBufferSize();
+ SSLEngineResult unwrapResult;
+ {
// can we use the passed buffer if it is big enough
- ByteBuffer appIn;
- int appBufferSize = getApplicationBufferSize();
- if (_decryptedInput == null)
+ WritableBuffer writableAppIn;
+ if (decryptedInput != null)
+ {
+ // Re-use the buffer acquired in a previous loop iteration.
+ writableAppIn = decryptedInput.compact();
+ decryptedInput = null;
+ }
+ else if (_decryptedInput == null)
{
if (BufferUtil.space(buffer) > appBufferSize)
{
- appIn = buffer;
+ // Use the caller-provided buffer.
+ writableAppIn = ReadableBuffer.wrap(buffer).toWritable();
+ decryptedInputWrapsUserProvidedBuffer = true;
}
else
{
- _decryptedInput = _bufferPool.acquire(appBufferSize, _decryptedDirectBuffers);
- appIn = _decryptedInput.getByteBuffer();
+ // Acquire a fresh new buffer.
+ writableAppIn = _bufferPool.acquire(appBufferSize, _decryptedDirectBuffers);
}
}
else
{
- appIn = _decryptedInput.getByteBuffer();
+ // Re-use the non-empty buffer stored by a previous fill() call.
+ writableAppIn = _decryptedInput.compact();
+ _decryptedInput = null;
}
- // Let's try reading some encrypted data... even if we have some already.
- int netFilled = networkFill(_encryptedInput.getByteBuffer());
- if (netFilled > 0)
- _bytesIn.addAndGet(netFilled);
- if (LOG.isDebugEnabled())
- LOG.debug("net filled={}", netFilled);
-
- // Workaround for Java 11 behavior.
- if (netFilled < 0 && isHandshakeInitial() && (_encryptedInput == null || !_encryptedInput.hasRemaining()))
- closeInbound();
-
- if (netFilled > 0 && !isHandshakeComplete() && isOutboundDone())
- throw new SSLHandshakeException("Closed during handshake");
-
- if (_handshake.compareAndSet(HandshakeState.INITIAL, HandshakeState.HANDSHAKE))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("fill starting handshake {}", SslConnection.this);
- }
-
- // Let's unwrap even if we have no net data because in that
- // case we want to fall through to the handshake handling
- int pos = BufferUtil.flipToFill(appIn);
- SSLEngineResult unwrapResult;
try
{
+ // Let's unwrap even if we have no net data because in that
+ // case we want to fall through to the handshake handling
+ SSLEngineResult[] unwrapResultArray = new SSLEngineResult[1];
_underflown = false;
- unwrapResult = SslConnection.this.unwrap(_sslEngine, _encryptedInput.getByteBuffer(), appIn);
+ encryptedInput.writeTo(input -> writableAppIn.readFrom(output ->
+ {
+ SSLEngineResult unwrapResult1 = SslConnection.this.unwrap(_sslEngine, input, output);
+ unwrapResultArray[0] = unwrapResult1;
+ return unwrapResult1.getStatus() == Status.CLOSED;
+ }));
+ unwrapResult = unwrapResultArray[0];
+ decryptedInput = writableAppIn.toReadable();
}
- finally
+ catch (Throwable x)
{
- BufferUtil.flipToFlush(appIn, pos);
+ writableAppIn.release();
+ throw x;
}
- if (LOG.isDebugEnabled())
- LOG.debug("unwrap net_filled={} {} encryptedBuffer={} unwrapBuffer={} appBuffer={}",
- netFilled,
- StringUtil.replace(unwrapResult.toString(), '\n', ' '),
- _encryptedInput,
- BufferUtil.toDetailString(appIn),
- BufferUtil.toDetailString(buffer));
-
- SSLEngineResult.Status unwrap = unwrapResult.getStatus();
-
- // Extra check on unwrapResultStatus == OK with zero bytes consumed
- // or produced is due to an SSL client on Android (see bug #454773).
- if (unwrap == Status.OK && unwrapResult.bytesConsumed() == 0 && unwrapResult.bytesProduced() == 0)
- unwrap = Status.BUFFER_UNDERFLOW;
+ }
- switch (unwrap)
- {
- case CLOSED:
- Throwable failure = _failure;
- if (failure != null)
- throw IO.rethrow(failure);
- return filled = -1;
+ if (LOG.isDebugEnabled())
+ LOG.debug("unwrap net_filled={} {} encryptedBuffer={} unwrapBuffer={} appBuffer={}",
+ netFilled,
+ StringUtil.replace(unwrapResult.toString(), '\n', ' '),
+ encryptedInput,
+ decryptedInput,
+ BufferUtil.toDetailString(buffer));
- case BUFFER_UNDERFLOW:
- // Continue if we can compact?
- if (BufferUtil.compact(_encryptedInput.getByteBuffer()))
- continue;
+ Status unwrap = unwrapResult.getStatus();
- // Are we out of space?
- if (BufferUtil.space(_encryptedInput.getByteBuffer()) == 0)
- {
- BufferUtil.clear(_encryptedInput.getByteBuffer());
- throw new SSLHandshakeException("Encrypted buffer max length exceeded");
- }
+ // Extra check on unwrapResultStatus == OK with zero bytes consumed
+ // or produced is due to an SSL client on Android (see bug #454773).
+ if (unwrap == Status.OK && unwrapResult.bytesConsumed() == 0 && unwrapResult.bytesProduced() == 0)
+ unwrap = Status.BUFFER_UNDERFLOW;
- // if we just filled some
- if (netFilled > 0)
- continue; // try filling some more
-
- _underflown = true;
- if (netFilled < 0 && _sslEngine.getUseClientMode())
- {
- Throwable closeFailure = closeInbound();
- if (_flushState == FlushState.WAIT_FOR_FILL)
- {
- Throwable handshakeFailure = new SSLHandshakeException("Abruptly closed by peer");
- if (closeFailure != null)
- handshakeFailure.addSuppressed(closeFailure);
- throw handshakeFailure;
- }
- return filled = -1;
- }
- return filled = netFilled;
-
- case BUFFER_OVERFLOW:
- // It's possible that SSLSession.applicationBufferSize has been expanded
- // by the SSLEngine implementation. Unwrapping a large encrypted buffer
- // causes BUFFER_OVERFLOW because the (old) applicationBufferSize is
- // too small. Release the decrypted input buffer so it will be re-acquired
- // with the larger capacity.
- // See also system property "jsse.SSLEngine.acceptLargeFragments".
- if ((_decryptedInput == null || !_decryptedInput.hasRemaining()) && appBufferSize < getApplicationBufferSize())
+ switch (unwrap)
+ {
+ case CLOSED:
+ Throwable failure = _failure;
+ if (failure != null)
+ throw IO.rethrow(failure);
+ return filled = -1;
+
+ case BUFFER_UNDERFLOW:
+ // Are we out of space?
+ if (encryptedInputRemainingWriteSpace == 0L)
+ throw new SSLHandshakeException("Encrypted buffer max length exceeded");
+
+ // if we just filled some
+ if (netFilled > 0)
+ continue; // try filling some more
+
+ _underflown = true;
+ if (netFilled < 0 && _sslEngine.getUseClientMode())
+ {
+ Throwable closeFailure = closeInbound();
+ if (_flushState == FlushState.WAIT_FOR_FILL)
{
- lockedReleaseEmptyDecryptedInputBuffer();
- continue;
+ Throwable handshakeFailure = new SSLHandshakeException("Abruptly closed by peer");
+ if (closeFailure != null)
+ handshakeFailure.addSuppressed(closeFailure);
+ throw handshakeFailure;
}
- throw new IllegalStateException("Unexpected unwrap result " + unwrap);
+ return filled = -1;
+ }
+ return filled = netFilled;
+
+ case BUFFER_OVERFLOW:
+ // It's possible that SSLSession.applicationBufferSize has been expanded
+ // by the SSLEngine implementation. Unwrapping a large encrypted buffer
+ // causes BUFFER_OVERFLOW because the (old) applicationBufferSize is
+ // too small. Release the decrypted input buffer so it will be re-acquired
+ // with the larger capacity.
+ // See also system property "jsse.SSLEngine.acceptLargeFragments".
+ if ((decryptedInput == null || decryptedInput.remaining() == 0L) && appBufferSize < getApplicationBufferSize())
+ continue;
+ throw new IllegalStateException("Unexpected unwrap result " + unwrap);
- case OK:
- if (unwrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED)
- lockedHandshakeSucceeded();
+ case OK:
+ if (unwrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED)
+ lockedHandshakeSucceeded();
- if (isRenegotiating() && !allowRenegotiate())
- return filled = -1;
+ if (isRenegotiating() && !allowRenegotiate())
+ return filled = -1;
- // If bytes were produced, don't bother with the handshake status;
- // pass the decrypted data to the application, which will perform
- // another call to fill() or flush().
- if (unwrapResult.bytesProduced() > 0)
- {
- if (appIn == buffer)
- return filled = unwrapResult.bytesProduced();
- return filled = BufferUtil.append(buffer, _decryptedInput.getByteBuffer());
- }
+ // If bytes were produced, don't bother with the handshake status;
+ // pass the decrypted data to the application, which will perform
+ // another call to fill() or flush().
+ if (unwrapResult.bytesProduced() > 0)
+ {
+ if (decryptedInputWrapsUserProvidedBuffer)
+ return filled = unwrapResult.bytesProduced();
+ return filled = BufferUtil.put(decryptedInput, buffer);
+ }
- break;
+ break;
- default:
- throw new IllegalStateException("Unexpected unwrap result " + unwrap);
- }
+ default:
+ throw new IllegalStateException("Unexpected unwrap result " + unwrap);
}
}
- catch (Throwable x)
+ }
+ catch (Throwable x)
+ {
+ if (decryptedInput != null)
{
- lockedDiscardInputBuffers();
- Throwable f = handleException(x, "fill");
- Throwable failure = handshakeFailed(f);
- if (_flushState == FlushState.WAIT_FOR_FILL)
- {
- _flushState = FlushState.IDLE;
- getExecutor().execute(() -> _sslEndPoint.getWriteFlusher().onFail(failure));
- }
- throw failure;
+ decryptedInput.release();
+ decryptedInput = null;
}
- finally
+ if (encryptedInput != null)
{
- lockedReleaseEmptyInputBuffers();
-
- if (_flushState == FlushState.WAIT_FOR_FILL)
- {
- _flushState = FlushState.IDLE;
- getExecutor().execute(() -> _sslEndPoint.getWriteFlusher().completeWrite());
- }
+ encryptedInput.release();
+ encryptedInput = null;
+ }
+ Throwable f = handleException(x, "fill");
+ Throwable failure = handshakeFailed(f);
+ if (_flushState == FlushState.WAIT_FOR_FILL)
+ {
+ _flushState = FlushState.IDLE;
+ getExecutor().execute(() -> _sslEndPoint.getWriteFlusher().onFail(failure));
+ }
+ throw failure;
+ }
+ finally
+ {
+ lockedReleaseEmptyInputBuffers(encryptedInput, decryptedInputWrapsUserProvidedBuffer ? null : decryptedInput);
- if (LOG.isDebugEnabled())
- LOG.debug(" _sslEndPoint.getWriteFlusher().completeWrite());
}
+
+ if (LOG.isDebugEnabled())
+ LOG.debug(" 0L) || (_encryptedInput != null && _encryptedInput.remaining() > 0L && !_underflown);
HandshakeStatus status = _sslEngine.getHandshakeStatus();
switch (status)
@@ -949,10 +1052,10 @@ protected void needsFillInterest()
{
interest = true;
_fillState = FillState.INTERESTED;
- if (_flushState == FlushState.IDLE && (_encryptedOutput != null && _encryptedOutput.hasRemaining()))
+ if (_flushState == FlushState.IDLE && (_encryptedOutput != null && _encryptedOutput.remaining() > 0L))
{
_flushState = FlushState.WRITING;
- write = _encryptedOutput.getByteBuffer();
+ read = _encryptedOutput;
}
}
break;
@@ -964,7 +1067,7 @@ protected void needsFillInterest()
if (_flushState == FlushState.IDLE)
{
_flushState = FlushState.WRITING;
- write = (_encryptedOutput != null && _encryptedOutput.hasRemaining()) ? _encryptedOutput.getByteBuffer() : BufferUtil.EMPTY_BUFFER;
+ read = (_encryptedOutput != null && _encryptedOutput.remaining() > 0L) ? _encryptedOutput : ReadableBuffer.EMPTY;
}
}
break;
@@ -974,16 +1077,23 @@ protected void needsFillInterest()
}
if (LOG.isDebugEnabled())
- LOG.debug(" getEndPoint().write(_incompleteWriteCallback, input));
else if (fillable)
getExecutor().execute(_runFillable);
else if (interest)
ensureFillInterested();
}
+ catch (IOException x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{}", SslConnection.this, x);
+ close(x);
+ throw new UncheckedIOException(x);
+ }
catch (Throwable x)
{
if (LOG.isDebugEnabled())
@@ -1064,64 +1174,75 @@ private Throwable closeInbound() throws SSLException
@Override
public boolean flush(ByteBuffer... appOuts) throws IOException
{
- try
+ try (AutoLock ignored = _lock.lock())
{
- try (AutoLock ignored = _lock.lock())
+ if (LOG.isDebugEnabled())
{
- if (LOG.isDebugEnabled())
+ LOG.debug(">flush {}", SslConnection.this);
+ int i = 0;
+ for (ByteBuffer b : appOuts)
{
- LOG.debug(">flush {}", SslConnection.this);
- int i = 0;
- for (ByteBuffer b : appOuts)
- {
- LOG.debug("flush b[{}]={}", i++, BufferUtil.toDetailString(b));
- }
+ LOG.debug("flush b[{}]={}", i++, BufferUtil.toDetailString(b));
}
+ }
- Boolean result = null;
- try
+ ReadableBuffer encryptedOutput = null;
+ boolean result = false;
+ try
+ {
+ // finish of any previous flushes
+ if (_encryptedOutput != null)
{
- // finish of any previous flushes
- if (_encryptedOutput != null)
+ int remaining = (int)_encryptedOutput.remaining();
+ if (remaining > 0)
{
- int remaining = _encryptedOutput.remaining();
- if (remaining > 0)
+ boolean flushed = networkFlush(_encryptedOutput);
+ int written = (int)(remaining - _encryptedOutput.remaining());
+ if (written > 0)
+ _bytesOut.addAndGet(written);
+ if (!flushed)
+ return false;
+ if (_encryptedOutput.remaining() == 0L)
{
- boolean flushed = networkFlush(_encryptedOutput.getByteBuffer());
- int written = remaining - _encryptedOutput.remaining();
- if (written > 0)
- _bytesOut.addAndGet(written);
- if (!flushed)
- return false;
+ _encryptedOutput.release();
+ _encryptedOutput = null;
}
}
+ }
- boolean isEmpty = BufferUtil.isEmpty(appOuts);
+ boolean isEmpty = BufferUtil.isEmpty(appOuts);
- if (_flushState != FlushState.IDLE)
- return result = false;
+ if (_flushState != FlushState.IDLE)
+ return false;
- // Keep going while we can make progress or until we are done
- while (true)
+ // Keep going while we can make progress or until we are done
+ loop:
+ while (true)
+ {
+ HandshakeStatus status = _sslEngine.getHandshakeStatus();
+ if (LOG.isDebugEnabled())
+ LOG.debug("flush {}", status);
+ switch (status)
{
- HandshakeStatus status = _sslEngine.getHandshakeStatus();
- if (LOG.isDebugEnabled())
- LOG.debug("flush {}", status);
- switch (status)
- {
- case NEED_WRAP:
- case NOT_HANDSHAKING:
- break;
+ case NEED_WRAP:
+ case NOT_HANDSHAKING:
+ break;
- case NEED_TASK:
- _sslEngine.getDelegatedTask().run();
- continue;
+ case NEED_TASK:
+ _sslEngine.getDelegatedTask().run();
+ continue;
- case NEED_UNWRAP:
- // Workaround for Java 11 behavior.
- if (isHandshakeInitial() && isOutboundDone())
- break;
- if (_fillState == FillState.IDLE)
+ case NEED_UNWRAP:
+ // Workaround for Java 11 behavior.
+ if (isHandshakeInitial() && isOutboundDone())
+ break;
+ if (_fillState == FillState.IDLE)
+ {
+ // Store the encrypted output in the field as the following call to
+ // fill() may itself re-enter flush().
+ lockedReleaseEmptyEncryptedOutputBuffer(encryptedOutput);
+ encryptedOutput = null;
+ try
{
int filled = fill(BufferUtil.EMPTY_BUFFER);
if (_sslEngine.getHandshakeStatus() != status)
@@ -1129,134 +1250,170 @@ public boolean flush(ByteBuffer... appOuts) throws IOException
if (filled < 0)
throw new IOException("Broken pipe");
}
- return result = isEmpty;
+ finally
+ {
+ // If _sslEngine.getHandshakeStatus() == status or fill() throws, we are going to enter
+ // the finally block that stores the output buffer via lockedReleaseEmptyOutputBuffer().
+ // Since the latter expects the member variable to be null, we need to move it here back
+ // to the local var.
+ encryptedOutput = _encryptedOutput;
+ _encryptedOutput = null;
+ }
+ }
+ result = isEmpty;
+ break loop;
- default:
- throw new IllegalStateException("Unexpected HandshakeStatus " + status);
- }
+ default:
+ throw new IllegalStateException("Unexpected HandshakeStatus " + status);
+ }
- int packetBufferSize = getPacketBufferSize();
- lockedAcquireEncryptedOutput();
+ int packetBufferSize = getPacketBufferSize();
- if (_handshake.compareAndSet(HandshakeState.INITIAL, HandshakeState.HANDSHAKE))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("flush starting handshake {}", SslConnection.this);
- }
+ if (_handshake.compareAndSet(HandshakeState.INITIAL, HandshakeState.HANDSHAKE))
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("flush starting handshake {}", SslConnection.this);
+ }
+ SSLEngineResult wrapResult;
+ {
// We call sslEngine.wrap to try to take bytes from appOuts
// buffers and encrypt them into the _encryptedOutput buffer.
- ByteBuffer encryptedOutputBuffer = _encryptedOutput.getByteBuffer();
- BufferUtil.compact(encryptedOutputBuffer);
- int pos = BufferUtil.flipToFill(encryptedOutputBuffer);
- SSLEngineResult wrapResult;
- try
+ WritableBuffer wb;
+ if (encryptedOutput == null)
{
- wrapResult = wrap(_sslEngine, appOuts, encryptedOutputBuffer);
+ wb = lockedAcquireEncryptedOutput();
}
- finally
+ else
{
- BufferUtil.flipToFlush(encryptedOutputBuffer, pos);
+ wb = encryptedOutput.compact();
+ encryptedOutput = null;
}
- if (LOG.isDebugEnabled())
- LOG.debug("wrap {} {} ioDone={}/{}",
- StringUtil.replace(wrapResult.toString(), '\n', ' '),
- _encryptedOutput,
- _sslEngine.isInboundDone(),
- _sslEngine.isOutboundDone());
-
- // Was all the data consumed?
- isEmpty = BufferUtil.isEmpty(appOuts);
-
- // if we have net bytes, let's try to flush them
- boolean flushed = true;
- int remaining = encryptedOutputBuffer.remaining();
- if (remaining > 0)
+ try
+ {
+ SSLEngineResult[] wrapResultArray = new SSLEngineResult[1];
+ wb.readFrom(output ->
+ {
+ SSLEngineResult wrapResult1 = wrap(_sslEngine, appOuts, output);
+ wrapResultArray[0] = wrapResult1;
+ return wrapResult1.getStatus() == Status.CLOSED;
+ });
+ encryptedOutput = wb.toReadable();
+ wrapResult = wrapResultArray[0];
+ }
+ catch (Throwable x)
{
- flushed = networkFlush(encryptedOutputBuffer);
- int written = remaining - encryptedOutputBuffer.remaining();
- if (written > 0)
- _bytesOut.addAndGet(written);
+ wb.release();
+ throw x;
}
+ }
- if (LOG.isDebugEnabled())
- LOG.debug("net flushed={}, ac={}", flushed, isEmpty);
+ if (LOG.isDebugEnabled())
+ LOG.debug("wrap {} {} ioDone={}/{}",
+ StringUtil.replace(wrapResult.toString(), '\n', ' '),
+ encryptedOutput,
+ _sslEngine.isInboundDone(),
+ _sslEngine.isOutboundDone());
+
+ // Was all the data consumed?
+ isEmpty = BufferUtil.isEmpty(appOuts);
+
+ // if we have net bytes, let's try to flush them
+ boolean flushed = true;
+ int remaining = (int)encryptedOutput.remaining();
+ if (remaining > 0)
+ {
+ flushed = networkFlush(encryptedOutput);
+ int written = (int)(remaining - encryptedOutput.remaining());
+ if (written > 0)
+ _bytesOut.addAndGet(written);
+ }
+
+ if (LOG.isDebugEnabled())
+ LOG.debug("net flushed={}, ac={}", flushed, isEmpty);
- // Now deal with the results returned from the wrap
- Status wrap = wrapResult.getStatus();
- switch (wrap)
+ // Now deal with the results returned from the wrap
+ Status wrap = wrapResult.getStatus();
+ switch (wrap)
+ {
+ case CLOSED:
{
- case CLOSED:
+ // TODO: do we need to remember the CLOSED state or SSLEngine
+ // TODO: will produce CLOSED again if wrap() is called again?
+ if (!flushed)
+ break loop;
+ getEndPoint().shutdownOutput();
+ if (isEmpty)
{
- // TODO: do we need to remember the CLOSED state or SSLEngine
- // TODO: will produce CLOSED again if wrap() is called again?
- if (!flushed)
- return result = false;
- getEndPoint().shutdownOutput();
- if (isEmpty)
- return result = true;
- throw new IOException("Broken pipe");
+ result = true;
+ break loop;
}
+ throw new IOException("Broken pipe");
+ }
- case BUFFER_OVERFLOW:
- if (!flushed)
- return result = false;
- // It's possible that SSLSession.packetBufferSize has been expanded
- // by the SSLEngine implementation. Wrapping a large application buffer
- // causes BUFFER_OVERFLOW because the (old) packetBufferSize is
- // too small. Release the encrypted output buffer so that it will
- // be re-acquired with the larger capacity.
- // See also system property "jsse.SSLEngine.acceptLargeFragments".
- if (packetBufferSize < getPacketBufferSize())
- {
- lockedReleaseEmptyEncryptedOutputBuffer();
- continue;
- }
- throw new IllegalStateException("Unexpected wrap result " + wrap);
+ case BUFFER_OVERFLOW:
+ if (!flushed)
+ break loop;
+ // It's possible that SSLSession.packetBufferSize has been expanded
+ // by the SSLEngine implementation. Wrapping a large application buffer
+ // causes BUFFER_OVERFLOW because the (old) packetBufferSize is
+ // too small. Release the encrypted output buffer so that it will
+ // be re-acquired with the larger capacity.
+ // See also system property "jsse.SSLEngine.acceptLargeFragments".
+ if (packetBufferSize < getPacketBufferSize())
+ continue;
+ throw new IllegalStateException("Unexpected wrap result " + wrap);
- case OK:
- if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED)
- lockedHandshakeSucceeded();
+ case OK:
+ if (wrapResult.getHandshakeStatus() == HandshakeStatus.FINISHED)
+ lockedHandshakeSucceeded();
- if (isRenegotiating() && !allowRenegotiate())
+ if (isRenegotiating() && !allowRenegotiate())
+ {
+ getEndPoint().shutdownOutput();
+ if (isEmpty && encryptedOutput.remaining() == 0L)
{
- getEndPoint().shutdownOutput();
- if (isEmpty && (_encryptedOutput == null || !_encryptedOutput.hasRemaining()))
- return result = true;
- throw new IOException("Broken pipe");
+ result = true;
+ break loop;
}
+ throw new IOException("Broken pipe");
+ }
- if (!flushed)
- return result = false;
+ if (!flushed)
+ break loop;
- if (isEmpty)
+ if (isEmpty)
+ {
+ if (wrapResult.getHandshakeStatus() != HandshakeStatus.NEED_WRAP ||
+ wrapResult.bytesProduced() == 0)
{
- if (wrapResult.getHandshakeStatus() != HandshakeStatus.NEED_WRAP ||
- wrapResult.bytesProduced() == 0)
- return result = true;
+ result = true;
+ break loop;
}
- break;
-
- default:
- throw new IllegalStateException("Unexpected wrap result " + wrap);
- }
+ }
+ break;
- if (getEndPoint().isOutputShutdown())
- return false;
+ default:
+ throw new IllegalStateException("Unexpected wrap result " + wrap);
}
+
+ if (getEndPoint().isOutputShutdown())
+ break;
}
- catch (Throwable x)
- {
- lockedDiscardEncryptedOutputBuffer();
- Throwable failure = handleException(x, "flush");
- throw handshakeFailed(failure);
- }
- finally
- {
- lockedReleaseEmptyEncryptedOutputBuffer();
- if (LOG.isDebugEnabled())
- LOG.debug(" 0L) ? _encryptedOutput : ReadableBuffer.EMPTY;
_flushState = FlushState.WRITING;
break;
case NEED_UNWRAP:
// If we have something to write, then write it and ignore the needed unwrap for now.
- if (_encryptedOutput != null && _encryptedOutput.hasRemaining())
+ if (_encryptedOutput != null && _encryptedOutput.remaining() > 0L)
{
- write = _encryptedOutput.getByteBuffer();
+ encryptedOutput = _encryptedOutput;
_flushState = FlushState.WRITING;
break;
}
@@ -1325,7 +1482,7 @@ protected void onIncompleteFlush()
if (LOG.isDebugEnabled())
LOG.debug("Incomplete flush?", e);
close(e);
- write = BufferUtil.EMPTY_BUFFER;
+ encryptedOutput = ReadableBuffer.EMPTY;
_flushState = FlushState.WRITING;
break;
}
@@ -1343,14 +1500,21 @@ protected void onIncompleteFlush()
}
if (LOG.isDebugEnabled())
- LOG.debug(" getEndPoint().write(_incompleteWriteCallback, input));
else if (fillInterest)
ensureFillInterested();
}
+ catch (IOException x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("{}", SslConnection.this, x);
+ close(x);
+ throw new UncheckedIOException(x);
+ }
catch (Throwable x)
{
if (LOG.isDebugEnabled())
@@ -1398,26 +1562,15 @@ private void doShutdownOutput(boolean close)
{
// If we still can't flush, but we are not closing the endpoint,
// let's just flush the encrypted output in the background.
- ByteBuffer write = null;
+ ReadableBuffer encryptedOutput;
try (AutoLock ignored = _lock.lock())
{
- if (_encryptedOutput != null && _encryptedOutput.hasRemaining())
- {
- write = _encryptedOutput.getByteBuffer();
+ encryptedOutput = _encryptedOutput;
+ if (encryptedOutput != null && encryptedOutput.remaining() > 0L)
_flushState = FlushState.WRITING;
- }
- }
- if (write != null)
- {
- endPoint.write(Callback.from(() ->
- {
- try (AutoLock ignored = _lock.lock())
- {
- _flushState = FlushState.IDLE;
- lockedReleaseEmptyEncryptedOutputBuffer();
- }
- }, t -> disconnect()), write);
}
+ if (encryptedOutput != null)
+ encryptedOutput.writeTo(input -> endPoint.write(Callback.from(this::shutdownOutputWriteSuccess, this::shutdownOutputWriteFailure), input));
}
}
@@ -1434,6 +1587,20 @@ private void doShutdownOutput(boolean close)
}
}
+ private void shutdownOutputWriteSuccess()
+ {
+ try (AutoLock ignored = _lock.lock())
+ {
+ _flushState = FlushState.IDLE;
+ lockedDiscardEncryptedOutputBuffer();
+ }
+ }
+
+ private void shutdownOutputWriteFailure(Throwable ignore)
+ {
+ disconnect();
+ }
+
private void disconnect()
{
try (AutoLock ignored = _lock.lock())
@@ -1507,9 +1674,7 @@ public boolean isInputShutdown()
boolean inputsEmpty;
try (AutoLock ignored = _lock.lock())
{
- inputsEmpty =
- (_encryptedInput == null || !_encryptedInput.hasRemaining()) &&
- (_decryptedInput == null || !_decryptedInput.hasRemaining());
+ inputsEmpty = _encryptedInput == null && _decryptedInput == null;
}
return inputsEmpty && (getEndPoint().isInputShutdown() || isInboundDone());
}
@@ -1663,7 +1828,9 @@ public void succeeded()
{
if (LOG.isDebugEnabled())
LOG.debug("IncompleteWriteCB succeeded {}", SslConnection.this);
- lockedReleaseEmptyEncryptedOutputBuffer();
+ ReadableBuffer encryptedOutput = _encryptedOutput;
+ _encryptedOutput = null;
+ lockedReleaseEmptyEncryptedOutputBuffer(encryptedOutput);
_flushState = FlushState.IDLE;
interested = _fillState == FillState.INTERESTED;
diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
index b46b11c2e6f6..b5d427a7df5a 100644
--- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
+++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
@@ -60,8 +60,7 @@ public class SslConnectionTest
private static final int TIMEOUT = 1000000;
- // TODO: track leaks
- private final ByteBufferPool _bufferPool = new ArrayByteBufferPool();
+ private final ArrayByteBufferPool.Tracking _bufferPool = new ArrayByteBufferPool.Tracking();
private final SslContextFactory _sslCtxFactory = new SslContextFactory.Server();
protected volatile EndPoint _lastEndp;
private volatile boolean _testFill = true;
@@ -171,6 +170,7 @@ public void stopSSL() throws Exception
{
stopManager();
_sslCtxFactory.stop();
+ assertThat("leaks: " + _bufferPool.dumpLeaks(), _bufferPool.getLeaks().size(), is(0));
}
private void stopManager() throws Exception
diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/WritableBufferPoolTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/WritableBufferPoolTest.java
new file mode 100644
index 000000000000..730a6c627503
--- /dev/null
+++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/WritableBufferPoolTest.java
@@ -0,0 +1,56 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.List;
+
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.greaterThanOrEqualTo;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class WritableBufferPoolTest
+{
+ static List sources()
+ {
+ return List.of(
+ WritableBufferPool.NON_POOLING,
+ WritableBufferPool.wrap(new ArrayByteBufferPool())
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("sources")
+ public void testSimple(WritableBufferPool writableBufferPool)
+ {
+ WritableBuffer wb = writableBufferPool.acquire(10, false);
+ assertEquals(0, wb.position());
+ assertThat(wb.remaining(), greaterThanOrEqualTo(10L));
+
+ wb.putInt(1);
+ wb.putInt(2);
+ assertEquals(8, wb.position());
+ assertThat(wb.remaining(), greaterThanOrEqualTo(2L));
+
+ ReadableBuffer rb = wb.toReadable();
+ assertEquals(0, rb.position());
+ assertEquals(8, rb.remaining());
+ assertEquals(1, rb.getInt());
+ assertEquals(2, rb.getInt());
+ }
+}
diff --git a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java
index f88ea5e6a058..463a24c75d86 100644
--- a/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java
+++ b/jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java
@@ -156,7 +156,7 @@ private void assertNoLeaks(ArrayByteBufferPool.Tracking bufferPool, TestInfo tes
{
try
{
- await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat("Leaks: " + bufferPool.dumpLeaks(), bufferPool.getLeaks().size(), is(0)));
+ await().atMost(5, TimeUnit.SECONDS).untilAsserted(() -> assertThat(msg + bufferPool.dumpLeaks(), bufferPool.getLeaks().size(), is(0)));
}
catch (Exception e)
{
diff --git a/jetty-core/jetty-util/src/main/java/module-info.java b/jetty-core/jetty-util/src/main/java/module-info.java
index 6dde5eaa4751..2bf521ecb5ad 100644
--- a/jetty-core/jetty-util/src/main/java/module-info.java
+++ b/jetty-core/jetty-util/src/main/java/module-info.java
@@ -28,6 +28,7 @@
exports org.eclipse.jetty.util;
exports org.eclipse.jetty.util.annotation;
+ exports org.eclipse.jetty.util.buffer;
exports org.eclipse.jetty.util.component;
exports org.eclipse.jetty.util.compression;
exports org.eclipse.jetty.util.jndi;
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
index 7373c1730cb3..14c31b47236b 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
@@ -38,6 +38,8 @@
import java.util.List;
import java.util.Objects;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.eclipse.jetty.util.resource.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -564,6 +566,35 @@ public static int put(ByteBuffer from, ByteBuffer to) throws ReadOnlyBufferExcep
return space;
}
+ /**
+ * Put data from a {@link ReadableBuffer} into a NIO buffer, avoiding over/under flows
+ *
+ * @param from ReadableBuffer to take bytes from, whose position is modified with the bytes taken.
+ * @param to Buffer to put bytes to in flush mode.
+ * @return number of bytes moved
+ * @throws ReadOnlyBufferException if the to buffer is read only
+ */
+ public static int put(ReadableBuffer from, ByteBuffer to)
+ {
+ int pos = BufferUtil.flipToFill(to);
+ int filled;
+ if (to.remaining() >= from.remaining())
+ {
+ filled = (int)from.remaining();
+ WritableBuffer.wrap(to).put(from);
+ }
+ else
+ {
+ filled = to.remaining();
+ ReadableBuffer slice = from.slice(from.position(), to.remaining());
+ WritableBuffer.wrap(to).put(slice);
+ slice.release();
+ from.position(from.position() + filled);
+ }
+ BufferUtil.flipToFlush(to, pos);
+ return filled;
+ }
+
/**
* Append bytes to a buffer.
*
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Retainable.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Retainable.java
new file mode 100644
index 000000000000..fae8de847a23
--- /dev/null
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/Retainable.java
@@ -0,0 +1,195 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.util;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ *
A reference counted resource, for example one that is borrowed from a pool,
+ * that may be retained an additional number of times, and released a correspondent
+ * number of times, over its lifecycle.
+ *
The resource is typically implicitly retained when it is first created.
+ * It may be retained more times (thus incrementing its reference count) and released
+ * (thus decrementing its reference count), until the reference count goes to zero.
The general rules to use {@code Retainable} objects are the following:
+ *
+ *
If the {@code Retainable} has been obtained by calling a method, and the
+ * caller code consumes it, then the caller code must call {@link #release()}.
+ *
If the {@code Retainable} has been obtained by {@code caller2} by calling a
+ * method, and {@code caller2} returns it without consuming it to {@code caller1},
+ * then {@code caller2} must not call {@link #release()}, since {@code caller1} will.
+ *
If the {@code Retainable} has been obtained as a method argument, the
+ * receiver code must either:
+ *
+ *
Consume the {@code Retainable} synchronously within the method, in which case
+ * {@link #release()} must not be called.
+ *
Pass the {@code Retainable} to some other method, in which case {@link #release()}
+ * must not be called.
+ *
Store away the {@code Retainable} for later or asynchronous processing, for
+ * example storing it in containers such as {@link java.util.Collection}s, or capturing
+ * it in a lambda that is passed to another thread, etc., in which case {@link #retain()}
+ * must be called and a mechanism to call {@link #release()} later or asynchronously
+ * for this additional {@link #retain()} must be arranged.
Returns whether this resource is referenced counted by calls to {@link #retain()}
+ * and {@link #release()}.
+ *
Implementations may decide that special resources are not not referenced counted (for example,
+ * {@code static} constants) so calling {@link #retain()} is a no-operation, and
+ * calling {@link #release()} on those special resources is a no-operation that always returns true.
Retains this resource, potentially incrementing a reference count if there are resources that will be released.
+ */
+ default void retain()
+ {
+ }
+
+ /**
+ *
Releases this resource, potentially decrementing a reference count (if any).
+ *
+ * @return {@code true} when the reference count goes to zero or if there was no reference count,
+ * {@code false} otherwise.
+ */
+ default boolean release()
+ {
+ return true;
+ }
+
+ /**
+ *
Get the retained count. This value is volatile and should only be used for informational/debugging purposes.
A reference count implementation for a {@link Retainable} resource.
+ *
The reference count is initialized to 1 when the resource is created,
+ * and therefore it is implicitly retained and needs a call to {@link #release()}.
+ *
Additional calls to {@link #retain()} must be matched by correspondent
+ * calls to {@link #release()}.
+ *
When the reference count goes to zero, the resource may be pooled.
+ * When the resource is acquired from the pool, {@link #acquire()} should be
+ * called to set the reference count to {@code 1}.
+ */
+ class ReferenceCounter implements Retainable
+ {
+ private final AtomicInteger references;
+
+ public ReferenceCounter()
+ {
+ this(1);
+ }
+
+ protected ReferenceCounter(int initialCount)
+ {
+ references = new AtomicInteger(initialCount);
+ }
+
+ /**
+ * @return the current reference count
+ */
+ public int get()
+ {
+ return references.get();
+ }
+
+ /**
+ *
Updates the reference count from {@code 0} to {@code 1}.
+ *
This method should only be used when this resource is acquired
+ * from a pool.
+ */
+ public void acquire()
+ {
+ if (references.getAndUpdate(c -> c == 0 ? 1 : c) != 0)
+ throw new IllegalStateException("acquired while in use " + this);
+ }
+
+ @Override
+ public boolean canRetain()
+ {
+ return get() > 0;
+ }
+
+ @Override
+ public void retain()
+ {
+ if (!tryRetain())
+ throw new IllegalStateException("released " + this);
+ }
+
+ public boolean tryRetain()
+ {
+ return references.getAndUpdate(c -> c == 0 ? 0 : c + 1) != 0;
+ }
+
+ @Override
+ public boolean release()
+ {
+ int ref = references.updateAndGet(c ->
+ {
+ if (c == 0)
+ throw new IllegalStateException("already released " + this);
+ return c - 1;
+ });
+ return ref == 0;
+ }
+
+ @Override
+ public boolean isRetained()
+ {
+ return references.get() > 1;
+ }
+
+ @Override
+ public int getRetained()
+ {
+ return references.get();
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x[rc=%d]", TypeUtil.toShortName(getClass()), hashCode(), get());
+ }
+ }
+}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/buffer/ReadableBuffer.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/buffer/ReadableBuffer.java
new file mode 100644
index 000000000000..3b9dd916ea9f
--- /dev/null
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/buffer/ReadableBuffer.java
@@ -0,0 +1,174 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.buffer;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+import org.eclipse.jetty.util.Retainable;
+import org.eclipse.jetty.util.internal.AccumulatingReadBuffer;
+import org.eclipse.jetty.util.internal.FixedSizeBuffer;
+
+/**
+ * Wraps a byte container, exposing a read-only API. The byte container could be for instance:
+ *
+ *
a single NIO ByteBuffer
+ *
a list of NIO ByteBuffers
+ *
a FileChannel
+ *
+ * Note that {@link #toWritable()} can be called to access the write-only API if the byte container is not read-only.
+ */
+public interface ReadableBuffer extends Retainable
+{
+ /**
+ * An empty ReadableBuffer that cannot be flipped to write-only mode.
+ */
+ ReadableBuffer EMPTY = new FixedSizeBuffer.ReadOnly(ByteBuffer.allocate(0).flip(), Retainable.NON_RETAINABLE);
+
+ /**
+ * Wraps the given NIO ByteBuffer that already is in flush node, using a new {@link ReferenceCounter} for retainability.
+ * @param byteBuffer the NIO byte buffer
+ * @return a ReadableBuffer
+ */
+ static ReadableBuffer wrap(ByteBuffer byteBuffer)
+ {
+ return new FixedSizeBuffer(byteBuffer, new ReferenceCounter(), false);
+ }
+
+ /**
+ * Wraps the given ReadableBuffer list, using a new {@link ReferenceCounter} for retainability.
+ * @param readableBuffers the ReadableBuffer list
+ * @return a ReadableBuffer
+ */
+ static ReadableBuffer accumulate(List readableBuffers)
+ {
+ if (readableBuffers.isEmpty())
+ return EMPTY;
+ return new AccumulatingReadBuffer(readableBuffers);
+ }
+
+ /**
+ * Returns the current position of this ReadableBuffer, where the next bytes are to be read.
+ * This value always lies between 0 and {@link #capacity()}.
+ * @return the current position
+ */
+ long position();
+
+ /**
+ * Changes the current position of this ReadableBuffer, where the next bytes are to be read.
+ * Must always be between 0 and {@link #capacity()}.
+ * @param newPosition the new current position
+ */
+ void position(long newPosition);
+
+ /**
+ * Returns the capacity of this ReadableBuffer, in bytes.
+ * @return the capacity of this ReadableBuffer
+ */
+ long capacity();
+
+ /**
+ * Returns how many spare bytes are left for reading, between {@link #position()} and the {@link WritableBuffer#position()}.
+ * @return how many spare bytes are left for reading
+ */
+ long remaining();
+
+ /**
+ * Reads a single byte at the current position.
+ * @throws java.nio.BufferUnderflowException – If the buffer's {@link #remaining()} is less than one.
+ */
+ byte get();
+
+ /**
+ * Reads a short at the current position.
+ * @throws java.nio.BufferUnderflowException – If the buffer's {@link #remaining()} is less than two.
+ */
+ short getShort();
+
+ /**
+ * Reads an int at the current position.
+ * @throws java.nio.BufferUnderflowException – If the buffer's {@link #remaining()} is less than four.
+ */
+ int getInt();
+
+ /**
+ * Reads a long at the current position.
+ * @throws java.nio.BufferUnderflowException – If the buffer's {@link #remaining()} is less than eight.
+ */
+ long getLong();
+
+ /**
+ * Slices this ReadableBuffer, {@link Retainable#retain() retaining} it in the process.
+ * @return a new ReadableBuffer with a position of 0 that indexes the current ReadableBuffer's {@link #position()}
+ * and an adjusted capacity equal to the current ReadableBuffer's {@link #capacity()} - the current
+ * ReadableBuffer's {@link #position()}.
+ */
+ ReadableBuffer slice();
+
+ /**
+ * Slices this ReadableBuffer, {@link Retainable#retain() retaining} it in the process.
+ * @param position the absolute position of the current buffer to use as the slice's position 0. Must be < {@link #capacity()}.
+ * @param length the length of the slice. Must be < {@link #capacity()} - position - 1.
+ * @return a new ReadableBuffer with a position of 0 that indexes the current ReadableBuffer's {@link #position()} + {@code position}
+ * and an adjusted capacity equal to {@code length}.
+ */
+ ReadableBuffer slice(long position, long length);
+
+ /**
+ * Compacts this ReadableBuffer, by flipping it to a {@link WritableBuffer} with the unread bytes (between
+ * {@link #position()} and {@link #remaining()}) moved to position 0.
+ * @return this, typed as a {@link WritableBuffer}
+ * // TODO throw ISE when isRetained() == true?
+ * // TODO should toWritable() always compact, or take a boolean instead of this?
+ * // TODO should this leave the buffer in readable mode and return void? It feels like compacting should only ever be done right before flipping to write mode.
+ */
+ WritableBuffer compact();
+
+ /**
+ * Drains and drops all unread bytes from this ReadableBuffer and resets the position to 0.
+ * // TODO throw ISE when isRetained() == true?
+ * // TODO is this method really useful? shouldn't it be removed?
+ */
+ void drain();
+
+ /**
+ * Flips this WritableBuffer to fill mode
+ * @return this, typed as a {@link ReadableBuffer}
+ * // TODO throw ISE when isRetained() == true?
+ * // TODO should this auto-compact when empty but not at position 0? Or always auto-compact?
+ */
+ WritableBuffer toWritable();
+
+ /**
+ * Flushes this buffer to the given Target.
+ * @param target the target
+ * @return the # of bytes written
+ * @throws IOException when an IOException occurs
+ */
+ long writeTo(Target target) throws IOException;
+
+ /**
+ * Base interface of the Target (i.e.: byte destination) used to flush a ReadableBuffer via the NIO ByteBuffer API.
+ */
+ interface Target
+ {
+ /**
+ * Flushes a given NIO ByteBuffer. Note that this method can be called more than once if the {@code input} byte buffer
+ * is depleted, for instance if the WritableBuffer is backed by more than one NIO ByteBuffer.
+ * @param input the buffer to be written
+ */
+ void write(ByteBuffer input) throws IOException;
+ }
+}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/buffer/WritableBuffer.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/buffer/WritableBuffer.java
new file mode 100644
index 000000000000..c68a884a0e4c
--- /dev/null
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/buffer/WritableBuffer.java
@@ -0,0 +1,174 @@
+//
+// ========================================================================
+// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Eclipse Public License v. 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+// which is available at https://www.apache.org/licenses/LICENSE-2.0.
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package org.eclipse.jetty.util.buffer;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.Retainable;
+import org.eclipse.jetty.util.internal.FixedSizeBuffer;
+
+/**
+ * Wraps a byte container, exposing a write-only API. The byte container could be for instance:
+ *
+ *
a single NIO ByteBuffer
+ *
a list of NIO ByteBuffers
+ *
a FileChannel
+ *
+ * Note that {@link #toReadable()} can always be called to access the read-only API.
+ */
+public interface WritableBuffer
+{
+ /**
+ * An empty WritableBuffer that cannot be flipped to read-only mode.
+ */
+ WritableBuffer EMPTY = new FixedSizeBuffer.WriteOnly(ByteBuffer.allocate(0), Retainable.NON_RETAINABLE);
+
+ /**
+ * Wraps the given NIO ByteBuffer that already is in fill node, using a new {@link Retainable.ReferenceCounter} for
+ * handling the release.
+ * @param byteBuffer the NIO byte buffer
+ * @return a WritableBuffer
+ */
+ static WritableBuffer wrap(ByteBuffer byteBuffer)
+ {
+ return new FixedSizeBuffer(byteBuffer, new Retainable.ReferenceCounter(), true);
+ }
+
+ /**
+ * Wraps the given NIO ByteBuffer that already is in fill node.
+ * @param byteBuffer the NIO byte buffer
+ * @param retainable use the given {@link Retainable} for handling the release
+ * @return a WritableBuffer
+ */
+ static WritableBuffer wrap(ByteBuffer byteBuffer, Retainable retainable)
+ {
+ return new FixedSizeBuffer(byteBuffer, retainable, true);
+ }
+
+ /**
+ * Allocates a new WritableBuffer wrapping a NIO ByteBuffer in fill node, using a new {@link Retainable.ReferenceCounter} for
+ * handling the release.
+ * @param size the size of the buffer
+ * @param direct true for a direct buffer, false for a heap one
+ * @return a WritableBuffer
+ */
+ static WritableBuffer allocate(int size, boolean direct)
+ {
+ return new FixedSizeBuffer(direct ? ByteBuffer.allocateDirect(size) : ByteBuffer.allocate(size), new Retainable.ReferenceCounter(), true);
+ }
+
+ /**
+ * Returns the current position of this WritableBuffer, where the next bytes are to be written.
+ * This value always lies between 0 and {@link #capacity()}.
+ * @return the current position
+ */
+ long position();
+
+ /**
+ * Changes the current position of this WritableBuffer, where the next bytes are to be written.
+ * Must always be between 0 and {@link #capacity()}.
+ * @param newPosition the new current position
+ */
+ void position(long newPosition);
+
+ /**
+ * Returns the capacity of this WritableBuffer, in bytes.
+ * @return the capacity of this WritableBuffer
+ */
+ long capacity();
+
+ /**
+ * Returns how many spare bytes are left for writing, between {@link #position()} and {@link #capacity()}.
+ * @return how many spare bytes are left for writing
+ */
+ long remaining();
+
+ /**
+ * Writes a single byte at the current position.
+ * @param b the byte to write
+ * @throws java.nio.BufferOverflowException – If this buffer's current position is not smaller than its capacity
+ * @throws java.nio.ReadOnlyBufferException – If this buffer is read-only
+ */
+ void put(byte b);
+
+ /**
+ * Writes a {@link ReadableBuffer} at the current position.
+ * @param readableBuffer the buffer to write
+ * @throws java.nio.BufferOverflowException – If there is insufficient space in this buffer for the remaining bytes in the source buffer
+ * @throws java.nio.ReadOnlyBufferException – If this buffer is read-only
+ */
+ void put(ReadableBuffer readableBuffer);
+
+ /**
+ * Writes a short at the current position.
+ * @param s the short to write
+ * @throws java.nio.BufferOverflowException – If there are fewer than two bytes remaining in this buffer
+ * @throws java.nio.ReadOnlyBufferException – If this buffer is read-only
+ */
+ void putShort(short s);
+
+ /**
+ * Writes an int at the current position.
+ * @param i the int to write
+ * @throws java.nio.BufferOverflowException – If there are fewer than four bytes remaining in this buffer
+ * @throws java.nio.ReadOnlyBufferException – If this buffer is read-only
+ */
+ void putInt(int i);
+
+ /**
+ * Writes a long at the current position.
+ * @param l the long to write
+ * @throws java.nio.BufferOverflowException – If there are fewer than eight bytes remaining in this buffer
+ * @throws java.nio.ReadOnlyBufferException – If this buffer is read-only
+ */
+ void putLong(long l);
+
+ /**
+ * Flips this WritableBuffer to flush mode
+ * @return this, typed as a {@link ReadableBuffer}
+ */
+ ReadableBuffer toReadable();
+
+ /**
+ *
Releases this resource, potentially decrementing a reference count (if any).
Applications are notified of read readiness by registering a
* {@link Callback} via {@link #fillInterested(Callback)}, and then
- * using {@link #fill(ByteBuffer)} to read the available bytes.
- *
Application may use {@link #flush(ByteBuffer...)} to transmit bytes;
+ * using {@link #fill(WritableBuffer)} to read the available bytes.
+ *
Application may use {@link #flush(ReadableBuffer)} to transmit bytes;
* if the flush does not transmit all the bytes, applications must
* arrange to resume flushing when it will be possible to transmit more
* bytes.
- * Alternatively, applications may use {@link #write(Callback, ByteBuffer...)}
+ * Alternatively, applications may use {@link #write(ReadableBuffer, Callback)}
* and be notified via the {@link Callback} when the write completes
* (i.e. all the buffers have been flushed), either successfully or
* with a failure.
- *
Connection-less reads are performed using {@link #receive(ByteBuffer)}.
+ *
Connection-less reads are performed using {@link #receive(WritableBuffer)}.
* Similarly, connection-less flushes are performed using
- * {@link #send(SocketAddress, ByteBuffer...)} and connection-less writes
- * using {@link #write(Callback, SocketAddress, ByteBuffer...)}.
+ * {@link #send(SocketAddress, ReadableBuffer)} and connection-less writes
+ * using {@link #write(ReadableBuffer, SocketAddress, Callback)}.
*
While all the I/O methods are non-blocking, they can be easily
* converted to blocking using either {@link org.eclipse.jetty.util.Blocker}
* or {@link Callback.Completable}:
Constant returned by {@link #receive(ByteBuffer)} to indicate the end-of-file.
+ *
Constant returned by {@link #receive(WritableBuffer)} to indicate the end-of-file.
*/
SocketAddress EOF = InetSocketAddress.createUnresolved("", 0);
@@ -173,7 +175,21 @@ default void close()
* filled or -1 if EOF is read or the input is shutdown.
* @throws IOException if the endpoint is closed.
*/
+ @Deprecated
default int fill(ByteBuffer buffer) throws IOException
+ {
+ WritableBuffer wb = ReadableBuffer.wrap(buffer).toWritable();
+ try
+ {
+ return fill(wb);
+ }
+ finally
+ {
+ wb.toReadable();
+ }
+ }
+
+ default int fill(WritableBuffer buffer) throws IOException
{
throw new UnsupportedOperationException();
}
@@ -186,7 +202,21 @@ default int fill(ByteBuffer buffer) throws IOException
* @return the peer address that sent the data, or {@link #EOF}
* @throws IOException if the receive fails
*/
+ @Deprecated
default SocketAddress receive(ByteBuffer buffer) throws IOException
+ {
+ WritableBuffer wb = ReadableBuffer.wrap(buffer).toWritable();
+ try
+ {
+ return receive(wb);
+ }
+ finally
+ {
+ wb.toReadable();
+ }
+ }
+
+ default SocketAddress receive(WritableBuffer buffer) throws IOException
{
int filled = fill(buffer);
if (filled < 0)
@@ -203,12 +233,18 @@ default SocketAddress receive(ByteBuffer buffer) throws IOException
* The header/buffers position is updated to indicate how many bytes
* have been consumed.
*
- * @param buffer the buffers to flush
+ * @param buffers the buffers to flush
* @return True IFF all the buffers have been consumed and the endpoint has flushed the data to its
* destination (ie is not buffering any data).
* @throws IOException If the endpoint is closed or output is shutdown.
*/
- default boolean flush(ByteBuffer... buffer) throws IOException
+ @Deprecated
+ default boolean flush(ByteBuffer... buffers) throws IOException
+ {
+ return flush(buffers == null ? null : ReadableBuffer.wrap(buffers));
+ }
+
+ default boolean flush(ReadableBuffer buffer) throws IOException
{
throw new UnsupportedOperationException();
}
@@ -223,11 +259,17 @@ default boolean flush(ByteBuffer... buffer) throws IOException
* @throws IOException if the send fails
* @see #write(Callback, SocketAddress, ByteBuffer...)
*/
+ @Deprecated
default boolean send(SocketAddress address, ByteBuffer... buffers) throws IOException
{
return flush(buffers);
}
+ default boolean send(SocketAddress address, ReadableBuffer buffer) throws IOException
+ {
+ return flush(buffer);
+ }
+
/**
* @return The underlying transport object (socket, channel, etc.)
*/
@@ -283,7 +325,13 @@ default boolean send(SocketAddress address, ByteBuffer... buffers) throws IOExce
* @param buffers one or more {@link ByteBuffer}s that will be flushed.
* @throws WritePendingException if another write operation is concurrent.
*/
+ @Deprecated
default void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ {
+ write(ReadableBuffer.wrap(buffers), callback);
+ }
+
+ default void write(ReadableBuffer buffer, Callback callback) throws WritePendingException
{
throw new UnsupportedOperationException();
}
@@ -298,41 +346,52 @@ default void write(Callback callback, ByteBuffer... buffers) throws WritePending
* @throws WritePendingException if a previous write was initiated but was not yet completed
* @see #send(SocketAddress, ByteBuffer...)
*/
+ @Deprecated
default void write(Callback callback, SocketAddress address, ByteBuffer... buffers) throws WritePendingException
{
- write(callback, buffers);
+ write(ReadableBuffer.wrap(buffers), address, callback);
}
- @Override
- default void write(boolean last, ByteBuffer byteBuffer, Callback callback)
+ default void write(ReadableBuffer buffer, SocketAddress address, Callback callback) throws WritePendingException
+ {
+ write(buffer, callback);
+ }
+
+ default void write(boolean last, ReadableBuffer buffer, Callback callback)
{
if (last)
{
- write(Callback.from(() ->
- {
- try
+ write(buffer, Callback.from(() ->
{
- close();
- callback.succeeded();
- }
- catch (Throwable t)
+ try
+ {
+ close();
+ callback.succeeded();
+ }
+ catch (Throwable t)
+ {
+ callback.failed(t);
+ }
+ },
+ x ->
{
- callback.failed(t);
- }
- },
- x ->
- {
- IO.close(this);
- callback.failed(x);
- }),
- byteBuffer);
+ IO.close(this);
+ callback.failed(x);
+ }));
}
else
{
- write(callback, byteBuffer);
+ write(buffer, callback);
}
}
+ @Override
+ @Deprecated
+ default void write(boolean last, ByteBuffer byteBuffer, Callback callback)
+ {
+ write(last, ReadableBuffer.wrap(byteBuffer), callback);
+ }
+
/**
* Cancel any current {@link #write(Callback, ByteBuffer...)} operation
* in progress. Calling this method will cause future calls to {@link #write(Callback, ByteBuffer...)}
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MemoryEndPointPipe.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MemoryEndPointPipe.java
index c6dff1632c72..912cc9760750 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MemoryEndPointPipe.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/MemoryEndPointPipe.java
@@ -25,6 +25,8 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Scheduler;
@@ -36,7 +38,7 @@
*/
public class MemoryEndPointPipe implements EndPoint.Pipe
{
- private final ByteBufferPool byteBufferPool;
+ private final WritableBufferPool byteBufferPool;
private final LocalEndPoint localEndPoint;
private final RemoteEndPoint remoteEndPoint;
private final Consumer taskConsumer;
@@ -48,7 +50,7 @@ public MemoryEndPointPipe(Scheduler scheduler, Consumer consumer
public MemoryEndPointPipe(Scheduler scheduler, ByteBufferPool bufferPool, Consumer consumer, SocketAddress socketAddress)
{
- byteBufferPool = Objects.requireNonNullElse(bufferPool, ByteBufferPool.NON_POOLING);
+ byteBufferPool = WritableBufferPool.wrap(Objects.requireNonNullElse(bufferPool, ByteBufferPool.NON_POOLING));
localEndPoint = new LocalEndPoint(scheduler, socketAddress);
remoteEndPoint = new RemoteEndPoint(scheduler, new MemorySocketAddress());
localEndPoint.setPeerEndPoint(remoteEndPoint);
@@ -88,10 +90,10 @@ public void setRemoteEndPointMaxCapacity(int maxCapacity)
private class MemoryEndPoint extends AbstractEndPoint
{
private static final Logger LOG = LoggerFactory.getLogger(MemoryEndPoint.class);
- private static final RetainableByteBuffer EOF = RetainableByteBuffer.wrap(BufferUtil.EMPTY_BUFFER);
+ private static final ReadableBuffer EOF = ReadableBuffer.wrap(BufferUtil.EMPTY_BUFFER);
private final AutoLock lock = new AutoLock();
- private final Deque buffers = new ArrayDeque<>();
+ private final Deque buffers = new ArrayDeque<>();
private final SocketAddress localAddress;
private MemoryEndPoint peerEndPoint;
private Invocable.Task fillableTask;
@@ -151,16 +153,14 @@ protected void needsFillInterest()
}
@Override
- public int fill(ByteBuffer buffer) throws IOException
+ public int fill(WritableBuffer buffer) throws IOException
{
if (!isOpen())
throw new IOException("closed");
if (isInputShutdown())
return -1;
- int position = BufferUtil.flipToFill(buffer);
int filled = peerEndPoint.fillInto(buffer);
- BufferUtil.flipToFlush(buffer, position);
if (LOG.isDebugEnabled())
LOG.debug("filled {} from {}", filled, this);
@@ -178,38 +178,35 @@ else if (filled < 0)
return filled;
}
- private int fillInto(ByteBuffer buffer)
+ private int fillInto(WritableBuffer buffer)
{
int filled = 0;
try (AutoLock ignored = lock.lock())
{
while (true)
{
- RetainableByteBuffer data = buffers.peek();
+ ReadableBuffer data = buffers.peek();
if (data == null)
return filled;
if (data == EOF)
return filled > 0 ? filled : -1;
- int space = buffer.remaining();
+ int space = (int)buffer.remaining();
if (space == 0)
return filled;
- int available = data.remaining();
- int toCopy = Math.min(space, available);
+ long available = data.remaining();
+ long toCopy = Math.min(space, available);
if (toCopy == available)
{
- data.putTo(buffer);
+ BufferUtil.put(data, buffer);
data.release();
buffers.poll();
}
else
{
- RetainableByteBuffer slice = data.slice(toCopy);
- slice.putTo(buffer);
- slice.release();
- data.skip(toCopy);
+ BufferUtil.put(data, buffer);
}
capacity -= toCopy;
@@ -260,7 +257,7 @@ public boolean tryFillInterested(Callback callback)
}
@Override
- public boolean flush(ByteBuffer... buffers) throws IOException
+ public boolean flush(ReadableBuffer buffer) throws IOException
{
if (!isOpen())
throw new IOException("closed");
@@ -271,29 +268,25 @@ public boolean flush(ByteBuffer... buffers) throws IOException
boolean result = true;
try (AutoLock ignored = lock.lock())
{
- for (ByteBuffer buffer : buffers)
+ long remaining = buffer.remaining();
+ if (remaining > 0)
{
- int remaining = buffer.remaining();
- if (remaining == 0)
- continue;
-
// The buffer must be copied, otherwise a write() would complete
// and return it to the buffer pool where its backing store would
// be overwritten before it is read by the peer EndPoint.
- RetainableByteBuffer copy = lockedCopy(buffer);
+ ReadableBuffer copy = lockedCopy(buffer);
if (copy == null)
{
result = false;
- break;
}
- this.buffers.offer(copy);
- int length = copy.remaining();
- capacity += length;
- flushed += length;
- if (length < remaining)
+ else
{
- result = false;
- break;
+ this.buffers.offer(copy);
+ long length = copy.remaining();
+ capacity += length;
+ flushed += length;
+ if (length < remaining)
+ result = false;
}
}
}
@@ -310,10 +303,10 @@ public boolean flush(ByteBuffer... buffers) throws IOException
return result;
}
- private RetainableByteBuffer lockedCopy(ByteBuffer buffer)
+ private ReadableBuffer lockedCopy(ReadableBuffer buffer)
{
- int remaining = buffer.remaining();
- int length = remaining;
+ long remaining = buffer.remaining();
+ long length = remaining;
long maxCapacity = getMaxCapacity();
if (maxCapacity > 0)
{
@@ -323,18 +316,19 @@ private RetainableByteBuffer lockedCopy(ByteBuffer buffer)
length = (int)Math.min(length, space);
}
- RetainableByteBuffer.Mutable copy = byteBufferPool.acquire(length, buffer.isDirect());
+ WritableBuffer copy = byteBufferPool.acquire(Math.toIntExact(length), false);
if (length < remaining)
{
// Partial copy.
- copy.append(buffer.slice(buffer.position(), length));
+ ReadableBuffer slice = buffer.slice(buffer.position(), length);
+ BufferUtil.put(slice, copy);
buffer.position(buffer.position() + length);
}
else
{
- copy.append(buffer);
+ BufferUtil.put(buffer, copy);
}
- return copy;
+ return copy.toReadable();
}
@Override
@@ -354,7 +348,7 @@ protected void doClose()
super.doClose();
try (AutoLock ignored = lock.lock())
{
- RetainableByteBuffer last = buffers.peekLast();
+ ReadableBuffer last = buffers.peekLast();
if (last != EOF)
buffers.offer(EOF);
}
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
index 03195858b153..279750bd4f6b 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
@@ -14,7 +14,8 @@
package org.eclipse.jetty.io;
import java.net.Socket;
-import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
/**
*
A listener for raw network traffic within Jetty.
@@ -50,7 +51,7 @@ default void opened(Socket socket)
* @param socket the socket associated with the remote client
* @param bytes the read-only buffer containing the incoming bytes
*/
- default void incoming(Socket socket, ByteBuffer bytes)
+ default void incoming(Socket socket, ReadableBuffer bytes)
{
}
@@ -61,7 +62,7 @@ default void incoming(Socket socket, ByteBuffer bytes)
* @param socket the socket associated with the remote client
* @param bytes the read-only buffer containing the outgoing bytes
*/
- default void outgoing(Socket socket, ByteBuffer bytes)
+ default void outgoing(Socket socket, ReadableBuffer bytes)
{
}
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java
index 56232368223a..f91d05a36af4 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java
@@ -14,10 +14,11 @@
package org.eclipse.jetty.io;
import java.io.IOException;
-import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,30 +40,31 @@ public NetworkTrafficSocketChannelEndPoint(SocketChannel channel, ManagedSelecto
}
@Override
- public int fill(ByteBuffer buffer) throws IOException
+ public int fill(WritableBuffer buffer) throws IOException
{
int read = super.fill(buffer);
- notifyIncoming(buffer, read);
+ ReadableBuffer rb = buffer.toReadable();
+ ReadableBuffer view = rb.slice();
+ notifyIncoming(view, read);
+ view.release();
+ rb.toWritable();
return read;
}
@Override
- public boolean flush(ByteBuffer... buffers) throws IOException
+ public boolean flush(ReadableBuffer buffer) throws IOException
{
boolean flushed = true;
- for (ByteBuffer b : buffers)
+ if (buffer.remaining() > 0L)
{
- if (b.hasRemaining())
- {
- int position = b.position();
- ByteBuffer view = b.slice();
- flushed = super.flush(b);
- int l = b.position() - position;
- view.limit(view.position() + l);
- notifyOutgoing(view);
- if (!flushed)
- break;
- }
+ long position = buffer.position();
+ ReadableBuffer dupe = buffer.slice();
+ flushed = super.flush(buffer);
+ long l = buffer.position() - position;
+ ReadableBuffer view = dupe.slice(dupe.position(), l);
+ notifyOutgoing(view);
+ view.release();
+ dupe.release();
}
return flushed;
}
@@ -101,14 +103,13 @@ public void onClose(Throwable failure)
}
}
- public void notifyIncoming(ByteBuffer buffer, int read)
+ public void notifyIncoming(ReadableBuffer buffer, int read)
{
if (listener != null && read > 0)
{
try
{
- ByteBuffer view = buffer.asReadOnlyBuffer();
- listener.incoming(getChannel().socket(), view);
+ listener.incoming(getChannel().socket(), buffer);
}
catch (Throwable x)
{
@@ -117,9 +118,9 @@ public void notifyIncoming(ByteBuffer buffer, int read)
}
}
- public void notifyOutgoing(ByteBuffer view)
+ public void notifyOutgoing(ReadableBuffer view)
{
- if (listener != null && view.hasRemaining())
+ if (listener != null && view.remaining() > 0L)
{
try
{
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java
index 0218bcb25fcf..88ccb0211540 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java
@@ -19,7 +19,8 @@
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
-import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -72,16 +73,15 @@ protected void doShutdownOutput()
}
@Override
- public int fill(ByteBuffer buffer) throws IOException
+ public int fill(WritableBuffer buffer) throws IOException
{
if (isInputShutdown())
return -1;
- int pos = BufferUtil.flipToFill(buffer);
int filled;
try
{
- filled = getChannel().read(buffer);
+ filled = (int)buffer.readFrom(output -> getChannel().read(output) == -1);
if (filled > 0)
notIdle();
else if (filled == -1)
@@ -94,22 +94,31 @@ else if (filled == -1)
shutdownInput();
filled = -1;
}
- finally
- {
- BufferUtil.flipToFlush(buffer, pos);
- }
if (LOG.isDebugEnabled())
- LOG.debug("filled {} {}", filled, BufferUtil.toDetailString(buffer));
+ LOG.debug("filled {} {}", filled, buffer);
return filled;
}
@Override
- public boolean flush(ByteBuffer... buffers) throws IOException
+ public boolean flush(ReadableBuffer buffer) throws IOException
{
long flushed;
try
{
- flushed = getChannel().write(buffers);
+ flushed = buffer.writeTo(new ReadableBuffer.GatheringTarget()
+ {
+ @Override
+ public void write(ByteBuffer[] inputs) throws IOException
+ {
+ getChannel().write(inputs);
+ }
+
+ @Override
+ public void write(ByteBuffer input) throws IOException
+ {
+ getChannel().write(input);
+ }
+ });
if (LOG.isDebugEnabled())
LOG.debug("flushed {} {}", flushed, this);
}
@@ -121,12 +130,6 @@ public boolean flush(ByteBuffer... buffers) throws IOException
if (flushed > 0)
notIdle();
- for (ByteBuffer b : buffers)
- {
- if (!BufferUtil.isEmpty(b))
- return false;
- }
-
- return true;
+ return buffer.remaining() == 0L;
}
}
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java
index 7978e66e97c8..127942a4aea4 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java
@@ -18,7 +18,6 @@
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.WritePendingException;
-import java.util.Arrays;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.Objects;
@@ -27,6 +26,7 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Invocable.InvocationType;
import org.slf4j.Logger;
@@ -231,14 +231,15 @@ private class PendingState extends State
{
private final Callback _callback;
private final SocketAddress _address;
- private final ByteBuffer[] _buffers;
+ private final ReadableBuffer _buffer;
- private PendingState(Callback callback, SocketAddress address, ByteBuffer[] buffers)
+ private PendingState(Callback callback, SocketAddress address, ReadableBuffer buffer)
{
super(StateType.PENDING);
- _callback = callback;
_address = address;
- _buffers = buffers;
+ _buffer = buffer;
+ _callback = Callback.from(callback, _buffer::release);
+ buffer.retain();
}
InvocationType getCallbackInvocationType()
@@ -279,16 +280,16 @@ public InvocationType getCallbackInvocationType()
*
* If all buffers have been written it calls callback.complete().
*
+ * @param buffer the buffer to flush to the endpoint
* @param callback the callback to call on either failed or complete
- * @param buffers the buffers to flush to the endpoint
* @throws WritePendingException if unable to write due to prior pending write
*/
- public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ public void write(ReadableBuffer buffer, Callback callback) throws WritePendingException
{
- write(callback, null, buffers);
+ write(buffer, null, callback);
}
- public void write(Callback callback, SocketAddress address, ByteBuffer... buffers) throws WritePendingException
+ public void write(ReadableBuffer buffer, SocketAddress address, Callback callback) throws WritePendingException
{
Objects.requireNonNull(callback);
@@ -299,20 +300,19 @@ public void write(Callback callback, SocketAddress address, ByteBuffer... buffer
}
if (LOG.isDebugEnabled())
- LOG.debug("write: {} {}", this, BufferUtil.toDetailString(buffers));
+ LOG.debug("write: {} {}", this, buffer);
if (!updateState(__IDLE, __FLUSHING))
throw new WritePendingException();
try
{
- buffers = flush(address, buffers);
-
- if (buffers != null)
+ boolean fullyFlushed = flush(address, buffer);
+ if (!fullyFlushed)
{
if (LOG.isDebugEnabled())
LOG.debug("flush incomplete {}", this);
- PendingState pending = new PendingState(callback, address, buffers);
+ PendingState pending = new PendingState(callback, address, buffer);
if (updateState(__FLUSHING, pending))
onIncompleteFlush();
else
@@ -408,19 +408,17 @@ public void completeWrite()
return; // failure already handled.
Callback callback = pending._callback;
+ ReadableBuffer buffer = pending._buffer;
try
{
- ByteBuffer[] buffers = pending._buffers;
SocketAddress address = pending._address;
- buffers = flush(address, buffers);
+ flush(address, buffer);
- if (buffers != null)
+ if (buffer.remaining() != 0L)
{
if (LOG.isDebugEnabled())
- LOG.debug("flushed incomplete {} {}", BufferUtil.toDetailString(buffers), this);
- if (buffers != pending._buffers)
- pending = new PendingState(callback, address, buffers);
+ LOG.debug("flushed incomplete {} {}", buffer, this);
if (updateState(__COMPLETING, pending))
onIncompleteFlush();
else
@@ -448,58 +446,36 @@ public void completeWrite()
* Flushes the buffers iteratively until no progress is made.
*
* @param address the datagram channel to send the buffers to (used by QUIC and HTTP/3)
- * @param buffers The buffers to flush
- * @return The unflushed buffers, or null if all flushed
+ * @param buffer The buffer to flush
+ * @return true if the buffer was fully flushed
* @throws IOException if unable to flush
*/
- protected ByteBuffer[] flush(SocketAddress address, ByteBuffer[] buffers) throws IOException
+ protected boolean flush(SocketAddress address, ReadableBuffer buffer) throws IOException
{
boolean progress = true;
- while (progress && buffers != null)
+ while (progress && buffer.remaining() != 0L)
{
- long before = BufferUtil.remaining(buffers);
- boolean flushed = address == null ? _endPoint.flush(buffers) : _endPoint.send(address, buffers);
- long after = BufferUtil.remaining(buffers);
+ long before = buffer.remaining();
+ boolean flushed = address == null ? _endPoint.flush(buffer) : _endPoint.send(address, buffer);
+ long after = buffer.remaining();
long written = before - after;
if (LOG.isDebugEnabled())
LOG.debug("Flushed={} written={} remaining={} {}", flushed, written, after, this);
if (flushed)
- return null;
+ return true;
progress = written > 0;
-
- int index = 0;
- while (true)
- {
- if (index == buffers.length)
- {
- // All buffers consumed.
- buffers = null;
- index = 0;
- break;
- }
- else
- {
- int remaining = buffers[index].remaining();
- if (remaining > 0)
- break;
- ++index;
- progress = true;
- }
- }
- if (index > 0)
- buffers = Arrays.copyOfRange(buffers, index, buffers.length);
}
if (LOG.isDebugEnabled())
LOG.debug("!fully flushed {}", this);
- // If buffers is null, then flush has returned false but has consumed all the data!
- // This is probably SSL being unable to flush the encrypted buffer, so return EMPTY_BUFFERS
+ // If we reach here, then flush has returned false but has consumed all the data!
+ // This is probably SSL being unable to flush the encrypted buffer, so return false
// and that will keep this WriteFlusher pending.
- return buffers == null ? EMPTY_BUFFERS : buffers;
+ return false;
}
/**
diff --git a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
index 10a0641e9e80..da5672899693 100644
--- a/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
+++ b/jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java
@@ -14,7 +14,6 @@
package org.eclipse.jetty.io.ssl;
import java.io.IOException;
-import java.io.UncheckedIOException;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.security.cert.X509Certificate;
@@ -544,26 +543,12 @@ private void lockedReleaseEmptyEncryptedOutputBuffer(ReadableBuffer encryptedOut
protected int networkFill(WritableBuffer input) throws IOException
{
- return (int)input.readFrom(output ->
- {
- output.flip();
- try
- {
- int filled = getEndPoint().fill(output);
- return filled == -1;
- }
- finally
- {
- BufferUtil.flipToFill(output);
- }
- });
+ return getEndPoint().fill(input);
}
protected boolean networkFlush(ReadableBuffer output) throws IOException
{
- long toBeWritten = output.remaining();
- long written = output.writeTo(input -> getEndPoint().flush(input));
- return toBeWritten == written;
+ return getEndPoint().flush(output);
}
public class SslEndPoint extends AbstractEndPoint implements EndPoint.Wrapper
@@ -648,7 +633,7 @@ protected void onFillable()
waitingForFill = _flushState == FlushState.WAIT_FOR_FILL;
}
if (waitingForFill)
- fill(BufferUtil.EMPTY_BUFFER);
+ fill(WritableBuffer.EMPTY);
}
}
catch (Throwable e)
@@ -705,7 +690,7 @@ public SslConnection getSslConnection()
}
@Override
- public int fill(ByteBuffer buffer) throws IOException
+ public int fill(WritableBuffer buffer) throws IOException
{
try (AutoLock ignored = _lock.lock())
{
@@ -718,7 +703,7 @@ public int fill(ByteBuffer buffer) throws IOException
// Do we already have some decrypted data?
if (_decryptedInput != null && _decryptedInput.remaining() > 0L)
{
- int put = BufferUtil.put(_decryptedInput, buffer);
+ int put = (int)BufferUtil.put(_decryptedInput, buffer);
if (_decryptedInput.remaining() == 0L)
{
_decryptedInput.release();
@@ -729,7 +714,7 @@ public int fill(ByteBuffer buffer) throws IOException
int filled = -2;
ReadableBuffer decryptedInput = null;
- boolean decryptedInputWrapsUserProvidedBuffer = false;
+ boolean decryptedInputIsUserProvidedBuffer = false;
ReadableBuffer encryptedInput = _encryptedInput;
_encryptedInput = null;
long encryptedInputRemainingWriteSpace;
@@ -756,12 +741,12 @@ public int fill(ByteBuffer buffer) throws IOException
{
// Store the encrypted inputs in the fields as the following call to
// flush() may itself re-enter fill().
- lockedReleaseEmptyInputBuffers(encryptedInput, decryptedInputWrapsUserProvidedBuffer ? null : decryptedInput);
+ lockedReleaseEmptyInputBuffers(encryptedInput, decryptedInputIsUserProvidedBuffer ? null : decryptedInput);
encryptedInput = null;
- decryptedInput = decryptedInputWrapsUserProvidedBuffer ? decryptedInput : null;
+ decryptedInput = decryptedInputIsUserProvidedBuffer ? decryptedInput : null;
try
{
- if (flush(BufferUtil.EMPTY_BUFFER))
+ if (flush(ReadableBuffer.EMPTY))
{
Throwable failure = _failure;
if (failure != null)
@@ -840,6 +825,14 @@ public int fill(ByteBuffer buffer) throws IOException
{
// can we use the passed buffer if it is big enough
WritableBuffer writableAppIn;
+ if (decryptedInput != null && decryptedInputIsUserProvidedBuffer)
+ {
+ // decryptedInput was sliced from buffer -> release the slice
+ // and restore the original buffer.
+ decryptedInput.release();
+ decryptedInput = null;
+ }
+
if (decryptedInput != null)
{
// Re-use the buffer acquired in a previous loop iteration.
@@ -848,16 +841,17 @@ public int fill(ByteBuffer buffer) throws IOException
}
else if (_decryptedInput == null)
{
- if (BufferUtil.space(buffer) > appBufferSize)
+ if (buffer.remaining() > appBufferSize)
{
// Use the caller-provided buffer.
- writableAppIn = ReadableBuffer.wrap(buffer).toWritable();
- decryptedInputWrapsUserProvidedBuffer = true;
+ writableAppIn = buffer;
+ decryptedInputIsUserProvidedBuffer = true;
}
else
{
// Acquire a fresh new buffer.
writableAppIn = _bufferPool.acquire(appBufferSize, _decryptedDirectBuffers);
+ decryptedInputIsUserProvidedBuffer = false;
}
}
else
@@ -880,7 +874,16 @@ else if (_decryptedInput == null)
return unwrapResult1.getStatus() == Status.CLOSED;
}));
unwrapResult = unwrapResultArray[0];
- decryptedInput = writableAppIn.toReadable();
+ if (decryptedInputIsUserProvidedBuffer)
+ {
+ ReadableBuffer rb = writableAppIn.toReadable();
+ decryptedInput = rb.slice();
+ rb.toWritable();
+ }
+ else
+ {
+ decryptedInput = writableAppIn.toReadable();
+ }
}
catch (Throwable x)
{
@@ -895,7 +898,7 @@ else if (_decryptedInput == null)
StringUtil.replace(unwrapResult.toString(), '\n', ' '),
encryptedInput,
decryptedInput,
- BufferUtil.toDetailString(buffer));
+ buffer);
Status unwrap = unwrapResult.getStatus();
@@ -959,9 +962,9 @@ else if (_decryptedInput == null)
// another call to fill() or flush().
if (unwrapResult.bytesProduced() > 0)
{
- if (decryptedInputWrapsUserProvidedBuffer)
+ if (decryptedInputIsUserProvidedBuffer)
return filled = unwrapResult.bytesProduced();
- return filled = BufferUtil.put(decryptedInput, buffer);
+ return filled = (int)BufferUtil.put(decryptedInput, buffer);
}
break;
@@ -994,7 +997,13 @@ else if (_decryptedInput == null)
}
finally
{
- lockedReleaseEmptyInputBuffers(encryptedInput, decryptedInputWrapsUserProvidedBuffer ? null : decryptedInput);
+ if (decryptedInputIsUserProvidedBuffer && decryptedInput != null)
+ {
+ // Release the slice behind decryptedInput here, and null it to avoid saving it as a member variable.
+ decryptedInput.release();
+ decryptedInput = null;
+ }
+ lockedReleaseEmptyInputBuffers(encryptedInput, decryptedInput);
if (_flushState == FlushState.WAIT_FOR_FILL)
{
@@ -1081,19 +1090,12 @@ protected void needsFillInterest()
}
if (read != null)
- read.writeTo(input -> getEndPoint().write(_incompleteWriteCallback, input));
+ getEndPoint().write(read, _incompleteWriteCallback);
else if (fillable)
getExecutor().execute(_runFillable);
else if (interest)
ensureFillInterested();
}
- catch (IOException x)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("{}", SslConnection.this, x);
- close(x);
- throw new UncheckedIOException(x);
- }
catch (Throwable x)
{
if (LOG.isDebugEnabled())
@@ -1172,19 +1174,12 @@ private Throwable closeInbound() throws SSLException
}
@Override
- public boolean flush(ByteBuffer... appOuts) throws IOException
+ public boolean flush(ReadableBuffer appOut) throws IOException
{
try (AutoLock ignored = _lock.lock())
{
if (LOG.isDebugEnabled())
- {
- LOG.debug(">flush {}", SslConnection.this);
- int i = 0;
- for (ByteBuffer b : appOuts)
- {
- LOG.debug("flush b[{}]={}", i++, BufferUtil.toDetailString(b));
- }
- }
+ LOG.debug(">flush {} {}", SslConnection.this, appOut);
ReadableBuffer encryptedOutput = null;
boolean result = false;
@@ -1210,7 +1205,7 @@ public boolean flush(ByteBuffer... appOuts) throws IOException
}
}
- boolean isEmpty = BufferUtil.isEmpty(appOuts);
+ boolean isEmpty = appOut.remaining() == 0L;
if (_flushState != FlushState.IDLE)
return false;
@@ -1244,7 +1239,7 @@ public boolean flush(ByteBuffer... appOuts) throws IOException
encryptedOutput = null;
try
{
- int filled = fill(BufferUtil.EMPTY_BUFFER);
+ int filled = fill(WritableBuffer.EMPTY);
if (_sslEngine.getHandshakeStatus() != status)
continue;
if (filled < 0)
@@ -1294,9 +1289,25 @@ public boolean flush(ByteBuffer... appOuts) throws IOException
SSLEngineResult[] wrapResultArray = new SSLEngineResult[1];
wb.readFrom(output ->
{
- SSLEngineResult wrapResult1 = wrap(_sslEngine, appOuts, output);
- wrapResultArray[0] = wrapResult1;
- return wrapResult1.getStatus() == Status.CLOSED;
+ appOut.writeTo(new ReadableBuffer.GatheringTarget()
+ {
+ @Override
+ public void write(ByteBuffer[] inputs) throws IOException
+ {
+ SSLEngineResult wrapResult1 = wrap(_sslEngine, inputs, output);
+ wrapResultArray[0] = wrapResult1;
+ }
+
+ @Override
+ public void write(ByteBuffer input) throws IOException
+ {
+ if (wrapResultArray[0] != null)
+ throw new IllegalStateException("Multi-buffer output does not support gather writes while it must. Existing wrap result: " + wrapResultArray[0]);
+ SSLEngineResult wrapResult1 = wrap(_sslEngine, new ByteBuffer[]{input}, output);
+ wrapResultArray[0] = wrapResult1;
+ }
+ });
+ return wrapResultArray[0].getStatus() == Status.CLOSED;
});
encryptedOutput = wb.toReadable();
wrapResult = wrapResultArray[0];
@@ -1316,7 +1327,7 @@ public boolean flush(ByteBuffer... appOuts) throws IOException
_sslEngine.isOutboundDone());
// Was all the data consumed?
- isEmpty = BufferUtil.isEmpty(appOuts);
+ isEmpty = appOut.remaining() == 0L;
// if we have net bytes, let's try to flush them
boolean flushed = true;
@@ -1470,7 +1481,7 @@ protected void onIncompleteFlush()
// Try filling ourselves
try
{
- int filled = fill(BufferUtil.EMPTY_BUFFER);
+ int filled = fill(WritableBuffer.EMPTY);
// If this changed the status, let's try again
if (_sslEngine.getHandshakeStatus() != status)
continue;
@@ -1504,17 +1515,10 @@ protected void onIncompleteFlush()
}
if (encryptedOutput != null)
- encryptedOutput.writeTo(input -> getEndPoint().write(_incompleteWriteCallback, input));
+ getEndPoint().write(encryptedOutput, _incompleteWriteCallback);
else if (fillInterest)
ensureFillInterested();
}
- catch (IOException x)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("{}", SslConnection.this, x);
- close(x);
- throw new UncheckedIOException(x);
- }
catch (Throwable x)
{
if (LOG.isDebugEnabled())
@@ -1558,7 +1562,7 @@ private void doShutdownOutput(boolean close)
if (flush)
{
- if (!flush(BufferUtil.EMPTY_BUFFER) && !close)
+ if (!flush(ReadableBuffer.EMPTY) && !close)
{
// If we still can't flush, but we are not closing the endpoint,
// let's just flush the encrypted output in the background.
@@ -1570,7 +1574,7 @@ private void doShutdownOutput(boolean close)
_flushState = FlushState.WRITING;
}
if (encryptedOutput != null)
- encryptedOutput.writeTo(input -> endPoint.write(Callback.from(this::shutdownOutputWriteSuccess, this::shutdownOutputWriteFailure), input));
+ endPoint.write(encryptedOutput, Callback.from(this::shutdownOutputWriteSuccess, this::shutdownOutputWriteFailure));
}
}
diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/MemoryEndPointPipeTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/MemoryEndPointPipeTest.java
index 610c38acca26..8d957e9e9cbc 100644
--- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/MemoryEndPointPipeTest.java
+++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/MemoryEndPointPipeTest.java
@@ -98,7 +98,7 @@ public void testEndPointPiping() throws Exception
int filled = remoteEndPoint.fill(byteBuffer);
if (filled > 0)
{
- byteBuffer.position(byteBuffer.position() + filled);
+ BufferUtil.clear(byteBuffer);
totalFilled += filled;
}
else
diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/RetainableByteBufferTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/RetainableByteBufferTest.java
index 756b6dec8cf9..c494790ff922 100644
--- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/RetainableByteBufferTest.java
+++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/RetainableByteBufferTest.java
@@ -36,6 +36,7 @@
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@@ -609,14 +610,10 @@ public void testWriteToEndPoint(Supplier supplier) throws
try (EndPoint endPoint = new AbstractEndPoint(new TimerScheduler())
{
@Override
- public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ public void write(ReadableBuffer buffer, Callback callback) throws WritePendingException
{
- for (ByteBuffer buffer : buffers)
- {
- out.append(BufferUtil.toString(buffer));
- buffer.position(buffer.limit());
- }
-
+ out.append(BufferUtil.toString(buffer));
+ buffer.position(buffer.position() + buffer.remaining());
callback.succeeded();
}
@@ -672,14 +669,10 @@ public void testWriteToEndPointLast(Supplier supplier) thr
try (EndPoint endPoint = new AbstractEndPoint(new TimerScheduler())
{
@Override
- public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ public void write(ReadableBuffer buffer, Callback callback) throws WritePendingException
{
- for (ByteBuffer buffer : buffers)
- {
- out.append(BufferUtil.toString(buffer));
- buffer.limit(buffer.position());
- }
-
+ out.append(BufferUtil.toString(buffer));
+ buffer.position(buffer.position() + buffer.remaining());
callback.succeeded();
}
diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
index b5d427a7df5a..b2a9b9b2024a 100644
--- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
+++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java
@@ -37,6 +37,7 @@
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.Scheduler;
@@ -123,7 +124,7 @@ protected void onIncompleteFlush()
}
@Override
- public boolean flush(ByteBuffer... buffers) throws IOException
+ public boolean flush(ReadableBuffer buffer) throws IOException
{
__onIncompleteFlush.set(false);
if (__startBlocking.get() == 0 || __startBlocking.decrementAndGet() == 0)
@@ -133,7 +134,7 @@ public boolean flush(ByteBuffer... buffers) throws IOException
return false;
}
}
- return super.flush(buffers);
+ return super.flush(buffer);
}
}
diff --git a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java
index 592a9ceb7150..1e328b3d076b 100644
--- a/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java
+++ b/jetty-core/jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java
@@ -33,7 +33,9 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -120,7 +122,7 @@ protected void onIncompleteFlush()
flusher.onFail(new IOException("Ignored because no operation in progress"));
FutureCallback callback = new FutureCallback();
- flusher.write(callback, BufferUtil.toBuffer("How "), BufferUtil.toBuffer("now "), BufferUtil.toBuffer("brown "), BufferUtil.toBuffer("cow!"));
+ flusher.write(ReadableBuffer.wrap(BufferUtil.toBuffer("How "), BufferUtil.toBuffer("now "), BufferUtil.toBuffer("brown "), BufferUtil.toBuffer("cow!")), callback);
assertTrue(callback.isDone());
assertFalse(incompleteFlush.get());
@@ -145,7 +147,7 @@ protected void onIncompleteFlush()
};
FutureCallback callback = new FutureCallback();
- flusher.write(callback, BufferUtil.toBuffer("foo"));
+ flusher.write(ReadableBuffer.wrap(BufferUtil.toBuffer("foo")), callback);
assertTrue(callback.isDone());
assertFalse(incompleteFlush.get());
@@ -177,7 +179,7 @@ protected void onIncompleteFlush()
};
FutureCallback callback = new FutureCallback();
- flusher.write(callback, BufferUtil.toBuffer("How now brown cow!"));
+ flusher.write(ReadableBuffer.wrap(BufferUtil.toBuffer("How now brown cow!")), callback);
assertFalse(callback.isDone());
assertFalse(callback.isCancelled());
@@ -228,7 +230,7 @@ public void succeeded()
try (StacklessLogging stacklessLogging = new StacklessLogging(WriteFlusher.class))
{
- flusher.write(callback, BufferUtil.toBuffer("How now brown cow!"));
+ flusher.write(ReadableBuffer.wrap(BufferUtil.toBuffer("How now brown cow!")), callback);
callback.get(100, TimeUnit.MILLISECONDS);
}
@@ -254,7 +256,7 @@ protected void onIncompleteFlush()
};
FutureCallback callback = new FutureCallback();
- flusher.write(callback, BufferUtil.toBuffer("How now brown cow!"));
+ flusher.write(ReadableBuffer.wrap(BufferUtil.toBuffer("How now brown cow!")), callback);
assertFalse(callback.isDone());
assertFalse(callback.isCancelled());
@@ -294,7 +296,7 @@ protected void onIncompleteFlush()
};
FutureCallback callback = new FutureCallback();
- flusher.write(callback, BufferUtil.toBuffer("How now brown cow!"));
+ flusher.write(ReadableBuffer.wrap(BufferUtil.toBuffer("How now brown cow!")), callback);
assertFalse(callback.isDone());
assertFalse(callback.isCancelled());
@@ -339,7 +341,7 @@ public void testConcurrent() throws Exception
FutureCallback callback = new FutureCallback();
futures[i] = callback;
scheduler.schedule(() -> flusher.onFail(new Throwable(reason)), (i % 75) + 1, TimeUnit.MILLISECONDS);
- flusher.write(callback, BufferUtil.toBuffer("How Now Brown Cow."), BufferUtil.toBuffer(" The quick brown fox jumped over the lazy dog!"));
+ flusher.write(ReadableBuffer.wrap(BufferUtil.toBuffer("How Now Brown Cow."), BufferUtil.toBuffer(" The quick brown fox jumped over the lazy dog!")), callback);
}
int completed = 0;
@@ -369,6 +371,7 @@ public void testConcurrent() throws Exception
}
@Test
+ @Disabled("Is that test still relevant?")
public void testPendingWriteDoesNotStoreConsumedBuffers() throws Exception
{
int capacity = 10;
@@ -391,7 +394,7 @@ protected void onIncompleteFlush()
}
};
- flusher.write(Callback.NOOP, buffer1, buffer2);
+ flusher.write(ReadableBuffer.wrap(buffer1, buffer2), Callback.NOOP);
assertTrue(incompleteFlush.get());
assertFalse(buffer1.hasRemaining());
@@ -417,13 +420,13 @@ public void testConcurrentWrites() throws Exception
WriteFlusher flusher = new WriteFlusher(endPoint)
{
@Override
- protected ByteBuffer[] flush(SocketAddress address, ByteBuffer[] buffers) throws IOException
+ protected boolean flush(SocketAddress address, ReadableBuffer buffer) throws IOException
{
try
{
flushLatch.countDown();
Thread.sleep(2000);
- return super.flush(address, buffers);
+ return super.flush(address, buffer);
}
catch (InterruptedException x)
{
@@ -438,13 +441,13 @@ protected void onIncompleteFlush()
};
// Two concurrent writes.
- new Thread(() -> flusher.write(Callback.NOOP, BufferUtil.toBuffer("foo"))).start();
+ new Thread(() -> flusher.write(ReadableBuffer.wrap(BufferUtil.toBuffer("foo")), Callback.NOOP)).start();
assertTrue(flushLatch.await(1, TimeUnit.SECONDS));
assertThrows(WritePendingException.class, () ->
{
// The second write throws WritePendingException.
- flusher.write(Callback.NOOP, BufferUtil.toBuffer("bar"));
+ flusher.write(ReadableBuffer.wrap(BufferUtil.toBuffer("bar")), Callback.NOOP);
});
}
@@ -458,12 +461,12 @@ public void testConcurrentWriteAndOnFail() throws Exception
WriteFlusher flusher = new WriteFlusher(endPoint)
{
@Override
- protected ByteBuffer[] flush(SocketAddress address, ByteBuffer[] buffers) throws IOException
+ protected boolean flush(SocketAddress address, ReadableBuffer buffer) throws IOException
{
- ByteBuffer[] result = super.flush(address, buffers);
+ boolean flushed = super.flush(address, buffer);
boolean notified = onFail(new Throwable());
assertTrue(notified);
- return result;
+ return flushed;
}
@Override
@@ -473,7 +476,7 @@ protected void onIncompleteFlush()
};
FutureCallback callback = new FutureCallback();
- flusher.write(callback, BufferUtil.toBuffer("foo"));
+ flusher.write(ReadableBuffer.wrap(BufferUtil.toBuffer("foo")), callback);
assertTrue(flusher.isFailed());
@@ -499,7 +502,7 @@ protected void onIncompleteFlush()
FutureCallback callback = new FutureCallback();
byte[] content = new byte[capacity * 2];
- flusher.write(callback, BufferUtil.toBuffer(content));
+ flusher.write(ReadableBuffer.wrap(BufferUtil.toBuffer(content)), callback);
try
{
diff --git a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/StreamEndPoint.java b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/StreamEndPoint.java
index bb47f8b06284..0e06d0c6b219 100644
--- a/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/StreamEndPoint.java
+++ b/jetty-core/jetty-quic/jetty-quic-common/src/main/java/org/eclipse/jetty/quic/common/StreamEndPoint.java
@@ -37,6 +37,8 @@
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -247,7 +249,7 @@ void onFailure(Throwable failure)
}
@Override
- public int fill(ByteBuffer sink) throws IOException
+ public int fill(WritableBuffer sink) throws IOException
{
Content.Chunk current;
try (AutoLock ignored = lock.lock())
@@ -265,7 +267,7 @@ public int fill(ByteBuffer sink) throws IOException
ByteBuffer source = current.getByteBuffer();
if (source.hasRemaining())
{
- int filled = copy(current, sink);
+ int filled = BufferUtil.put(current.getByteBuffer(), sink);
boolean release = true;
if (source.hasRemaining())
@@ -362,30 +364,12 @@ public Content.Chunk fill()
return current;
}
- private int copy(Content.Chunk chunk, ByteBuffer sink)
- {
- int length = 0;
- ByteBuffer source = chunk.getByteBuffer();
- if (source.hasRemaining())
- {
- int sinkPosition = BufferUtil.flipToFill(sink);
- int sourceLength = source.remaining();
- length = Math.min(sourceLength, sink.remaining());
- int sourceLimit = source.limit();
- source.limit(source.position() + length);
- sink.put(source);
- source.limit(sourceLimit);
- BufferUtil.flipToFlush(sink, sinkPosition);
- }
- return length;
- }
-
@Override
- public boolean flush(ByteBuffer... buffers) throws IOException
+ public boolean flush(ReadableBuffer buffer) throws IOException
{
if (LOG.isDebugEnabled())
- LOG.debug("flushing {} on {}", BufferUtil.toDetailString(buffers), this);
- if (buffers == null || buffers.length == 0 || BufferUtil.remaining(buffers) == 0)
+ LOG.debug("flushing {} on {}", buffer, this);
+ if (buffer == null || buffer.remaining() == 0L)
return true;
// Differently from other EndPoint implementations, where write() calls
@@ -407,29 +391,24 @@ public boolean flush(ByteBuffer... buffers) throws IOException
}
@Override
- public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ public Callback cancelWrite(Throwable cause)
{
- write(false, List.of(buffers), callback);
+ // TODO
+ throw new UnsupportedOperationException();
}
@Override
- public void write(boolean last, ByteBuffer byteBuffer, Callback callback)
+ public void write(ReadableBuffer buffer, Callback callback) throws WritePendingException
{
- write(last, List.of(byteBuffer), callback);
+ write(false, buffer, callback);
}
@Override
- public Callback cancelWrite(Throwable cause)
- {
- // TODO
- throw new UnsupportedOperationException();
- }
-
- public void write(boolean last, List buffers, Callback callback)
+ public void write(boolean last, ReadableBuffer buffer, Callback callback)
{
if (LOG.isDebugEnabled())
- LOG.debug("writing last={} {} on {}", last, BufferUtil.toDetailString(buffers.toArray(ByteBuffer[]::new)), this);
- if (last || remaining(buffers) > 0)
+ LOG.debug("writing last={} {} on {}", last, buffer, this);
+ if (last || buffer.remaining() > 0L)
{
while (true)
{
@@ -440,7 +419,7 @@ public void write(boolean last, List buffers, Callback callback)
{
if (!writeState.compareAndSet(current, WriteState.PENDING))
continue;
- stream.data(last, buffers, new Promise.Invocable.Abstract<>(callback.getInvocationType())
+ stream.data(last, List.of(BufferUtil.toBuffer(buffer, true)), new Promise.Invocable.Abstract<>(callback.getInvocationType())
{
@Override
public void succeeded(Stream result)
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
index 4811fc4e0ecb..5bbd3e25d0f5 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java
@@ -35,6 +35,8 @@
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -839,7 +841,7 @@ public void close(Throwable cause)
}
@Override
- public int fill(ByteBuffer buffer) throws IOException
+ public int fill(WritableBuffer buffer) throws IOException
{
return _endPoint.fill(buffer);
}
@@ -851,7 +853,7 @@ public void fillInterested(Callback callback) throws ReadPendingException
}
@Override
- public boolean flush(ByteBuffer... buffer) throws IOException
+ public boolean flush(ReadableBuffer buffer) throws IOException
{
return _endPoint.flush(buffer);
}
@@ -970,9 +972,9 @@ public void upgrade(Connection newConnection)
}
@Override
- public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ public void write(ReadableBuffer buffer, Callback callback) throws WritePendingException
{
- _endPoint.write(callback, buffers);
+ _endPoint.write(buffer, callback);
}
}
}
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java
index 93f1a2174cd6..a48e901665b2 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ConnectHandler.java
@@ -52,6 +52,8 @@
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -423,7 +425,15 @@ private void upgradeConnection(Request request, Connection connection)
*/
protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap context) throws IOException
{
- return endPoint.fill(buffer);
+ WritableBuffer wb = ReadableBuffer.wrap(buffer).toWritable();
+ try
+ {
+ return endPoint.fill(wb);
+ }
+ finally
+ {
+ wb.toReadable();
+ }
}
/**
@@ -436,7 +446,7 @@ protected int read(EndPoint endPoint, ByteBuffer buffer, ConcurrentMap context)
{
- endPoint.write(callback, buffer);
+ endPoint.write(ReadableBuffer.wrap(buffer), callback);
}
/**
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CancelWriteTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CancelWriteTest.java
index 9c7469379f07..fe3acd4a5f91 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CancelWriteTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/CancelWriteTest.java
@@ -29,6 +29,7 @@
import org.eclipse.jetty.io.SocketChannelEndPoint;
import org.eclipse.jetty.server.internal.HttpConnection;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.component.LifeCycle;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -148,7 +149,7 @@ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelect
SocketChannelEndPoint endpoint = new SocketChannelEndPoint(channel, selectSet, key, getScheduler())
{
@Override
- public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ public void write(ReadableBuffer buffer, Callback callback) throws WritePendingException
{
HttpConnection connection = (HttpConnection)getConnection();
Runnable runnable = connection.getHttpChannel().onFailure(new ArithmeticException());
@@ -158,14 +159,14 @@ public void write(Callback callback, ByteBuffer... buffers) throws WritePendingE
// Wait until the thread running the failure runnable cancelled the write on the endpoint.
await().atMost(5, TimeUnit.SECONDS).until(() -> getWriteFlusher().isFailed());
- super.write(Callback.from(callback::succeeded, x ->
+ super.write(buffer, Callback.from(callback::succeeded, x ->
{
if (serverWriteFailureLatch.getCount() == 1L)
serverEndPointWriteFailureLatch.countDown();
// Complete the send callback from HttpConnection.
callback.failed(x);
- }), buffers);
+ }));
}
};
endpoint.setIdleTimeout(getIdleTimeout());
@@ -240,9 +241,9 @@ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelect
SocketChannelEndPoint endpoint = new SocketChannelEndPoint(channel, selectSet, key, getScheduler())
{
@Override
- public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ public void write(ReadableBuffer buffer, Callback callback) throws WritePendingException
{
- super.write(Callback.from(() ->
+ super.write(buffer, Callback.from(() ->
{
if (serverWriteFailureLatch.getCount() == 1L)
serverEndPointWriteSuccessLatch.countDown();
@@ -256,7 +257,7 @@ public void write(Callback callback, ByteBuffer... buffers) throws WritePendingE
await().atMost(5, TimeUnit.SECONDS).until(() -> getWriteFlusher().isFailed());
callback.succeeded();
- }, callback::failed), buffers);
+ }, callback::failed));
}
};
endpoint.setIdleTimeout(getIdleTimeout());
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/IdleTimeoutTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/IdleTimeoutTest.java
index 0d1e13ea018c..6be51264f21c 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/IdleTimeoutTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/IdleTimeoutTest.java
@@ -23,6 +23,7 @@
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.io.ManagedSelector;
import org.eclipse.jetty.io.SocketChannelEndPoint;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
@@ -64,7 +65,7 @@ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelect
SocketChannelEndPoint endpoint = new SocketChannelEndPoint(channel, selectSet, key, getScheduler())
{
@Override
- public boolean flush(ByteBuffer... buffers)
+ public boolean flush(ReadableBuffer buffer)
{
// Fake TCP congestion.
return false;
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
index 14c31b47236b..a6d82c3917d9 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java
@@ -576,22 +576,38 @@ public static int put(ByteBuffer from, ByteBuffer to) throws ReadOnlyBufferExcep
*/
public static int put(ReadableBuffer from, ByteBuffer to)
{
- int pos = BufferUtil.flipToFill(to);
- int filled;
+ WritableBuffer wb = ReadableBuffer.wrap(to).toWritable();
+ try
+ {
+ return (int)put(from, wb);
+ }
+ finally
+ {
+ wb.toReadable();
+ }
+ }
+
+ public static int put(ByteBuffer from, WritableBuffer to)
+ {
+ return (int)put(ReadableBuffer.wrap(from), to);
+ }
+
+ public static long put(ReadableBuffer from, WritableBuffer to)
+ {
+ long filled;
if (to.remaining() >= from.remaining())
{
- filled = (int)from.remaining();
- WritableBuffer.wrap(to).put(from);
+ filled = from.remaining();
+ to.put(from);
}
else
{
filled = to.remaining();
- ReadableBuffer slice = from.slice(from.position(), to.remaining());
- WritableBuffer.wrap(to).put(slice);
+ ReadableBuffer slice = from.slice(from.position(), filled);
+ to.put(slice);
slice.release();
from.position(from.position() + filled);
}
- BufferUtil.flipToFlush(to, pos);
return filled;
}
@@ -858,6 +874,11 @@ public static String toString(ByteBuffer buffer)
return toString(buffer, StandardCharsets.ISO_8859_1);
}
+ public static String toString(ReadableBuffer buffer)
+ {
+ return toString(buffer, StandardCharsets.ISO_8859_1);
+ }
+
/**
* Convert buffer to a String with specified Charset
*
@@ -879,6 +900,17 @@ public static String toString(ByteBuffer buffer, Charset charset)
return new String(array, buffer.arrayOffset() + buffer.position(), buffer.remaining(), charset);
}
+ public static String toString(ReadableBuffer buffer, Charset charset)
+ {
+ if (buffer == null)
+ return null;
+ byte[] to = new byte[Math.toIntExact(buffer.remaining())];
+ long positionBefore = buffer.position();
+ buffer.get(to);
+ buffer.position(positionBefore);
+ return new String(to, charset);
+ }
+
/**
* Convert a partial buffer to a String.
*
@@ -1257,6 +1289,16 @@ public static ByteBuffer toBuffer(Resource resource, boolean direct) throws IOEx
return buffer;
}
+ public static ByteBuffer toBuffer(ReadableBuffer buffer, boolean direct)
+ {
+ long capacity = buffer.remaining();
+ if (capacity > Integer.MAX_VALUE)
+ throw new BufferOverflowException();
+ ByteBuffer result = BufferUtil.allocate((int)capacity, direct);
+ put(buffer, result);
+ return result;
+ }
+
public static ByteBuffer toDirectBuffer(String s)
{
return toDirectBuffer(s, StandardCharsets.ISO_8859_1);
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/buffer/ReadableBuffer.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/buffer/ReadableBuffer.java
index 3b9dd916ea9f..13c6df3bbd06 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/buffer/ReadableBuffer.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/buffer/ReadableBuffer.java
@@ -15,8 +15,10 @@
import java.io.IOException;
import java.nio.ByteBuffer;
+import java.util.Arrays;
import java.util.List;
+import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Retainable;
import org.eclipse.jetty.util.internal.AccumulatingReadBuffer;
import org.eclipse.jetty.util.internal.FixedSizeBuffer;
@@ -47,6 +49,16 @@ static ReadableBuffer wrap(ByteBuffer byteBuffer)
return new FixedSizeBuffer(byteBuffer, new ReferenceCounter(), false);
}
+ static ReadableBuffer wrap(ByteBuffer... buffers)
+ {
+ if (BufferUtil.isEmpty(buffers))
+ return EMPTY;
+ if (buffers.length == 1)
+ return wrap(buffers[0]);
+ List rbs = Arrays.stream(buffers).map(ReadableBuffer::wrap).toList();
+ return new AccumulatingReadBuffer(rbs);
+ }
+
/**
* Wraps the given ReadableBuffer list, using a new {@link ReferenceCounter} for retainability.
* @param readableBuffers the ReadableBuffer list
@@ -109,6 +121,12 @@ static ReadableBuffer accumulate(List readableBuffers)
*/
long getLong();
+ /**
+ * Reads a byte array at the current position.
+ * @throws java.nio.BufferUnderflowException – If the buffer's {@link #remaining()} is less than the array's length.
+ */
+ void get(byte[] b);
+
/**
* Slices this ReadableBuffer, {@link Retainable#retain() retaining} it in the process.
* @return a new ReadableBuffer with a position of 0 that indexes the current ReadableBuffer's {@link #position()}
@@ -168,7 +186,22 @@ interface Target
* Flushes a given NIO ByteBuffer. Note that this method can be called more than once if the {@code input} byte buffer
* is depleted, for instance if the WritableBuffer is backed by more than one NIO ByteBuffer.
* @param input the buffer to be written
+ * @throws IOException when IOException occurs
*/
void write(ByteBuffer input) throws IOException;
}
+
+ /**
+ * Interface of the Target (i.e.: byte destination) used to flush a ReadableBuffer via the NIO ByteBuffer API when the
+ * target supports gathering writes.
+ */
+ interface GatheringTarget extends Target
+ {
+ /**
+ * Flushes a given NIO ByteBuffer. Note that this method is never be called more than once.
+ * @param inputs the buffer to be written
+ * @throws IOException when IOException occurs
+ */
+ void write(ByteBuffer[] inputs) throws IOException;
+ }
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/internal/AccumulatingReadBuffer.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/internal/AccumulatingReadBuffer.java
index 62ca3c7390e7..ae73a6a4a7ee 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/internal/AccumulatingReadBuffer.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/internal/AccumulatingReadBuffer.java
@@ -20,6 +20,7 @@
import java.util.List;
import org.eclipse.jetty.util.Retainable;
+import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.buffer.WritableBuffer;
@@ -191,6 +192,19 @@ public long getLong()
return fragmentedGet(currentRb, 8).getLong();
}
+ @Override
+ public void get(byte[] b)
+ {
+ ReadableBuffer currentRb = currentReadableBuffer();
+ if (currentRb.remaining() >= b.length)
+ {
+ position += b.length;
+ currentRb.get(b);
+ return;
+ }
+ fragmentedGet(currentRb, b.length).get(b);
+ }
+
@Override
public ReadableBuffer slice()
{
@@ -260,6 +274,17 @@ public WritableBuffer toWritable()
@Override
public long writeTo(Target target) throws IOException
{
+ if (target instanceof GatheringTarget gatheringTarget)
+ {
+ long totalRemainingBefore = remaining();
+ List buffers = new ArrayList<>();
+ toByteBuffers(buffers);
+ gatheringTarget.write(buffers.toArray(new ByteBuffer[0]));
+ long totalWritten = totalRemainingBefore - remaining();
+ position += totalWritten;
+ return totalWritten;
+ }
+
long totalWritten = 0L;
for (int i = 0; i < readableBuffers.size(); i++)
{
@@ -281,6 +306,19 @@ public long writeTo(Target target) throws IOException
return totalWritten;
}
+ private void toByteBuffers(List result)
+ {
+ for (ReadableBuffer readableBuffer : readableBuffers)
+ {
+ if (readableBuffer instanceof AccumulatingReadBuffer accumulatingReadBuffer)
+ accumulatingReadBuffer.toByteBuffers(result);
+ if (readableBuffer instanceof FixedSizeBuffer fixedSizeBuffer)
+ result.add(fixedSizeBuffer.getByteBuffer());
+ else
+ throw new IllegalStateException("Unsupported ReadableBuffer type: " + readableBuffer.getClass().getName());
+ }
+ }
+
// Retainable
@Override
@@ -319,4 +357,15 @@ public int getRetained()
{
return retainable.getRetained();
}
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x{ls=%s,bs=%s,r=%s}",
+ TypeUtil.toShortName(getClass()),
+ hashCode(),
+ limits,
+ readableBuffers,
+ retainable);
+ }
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/internal/FixedSizeBuffer.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/internal/FixedSizeBuffer.java
index 2141dd35b6d4..05302285238c 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/internal/FixedSizeBuffer.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/internal/FixedSizeBuffer.java
@@ -99,6 +99,14 @@ public long getLong()
return byteBuffer.getLong();
}
+ @Override
+ public void get(byte[] b)
+ {
+ if (flushPosition != -1)
+ throw new IllegalStateException("Cannot read from buffer in write mode");
+ byteBuffer.get(b);
+ }
+
@Override
public long writeTo(Target target) throws IOException
{
@@ -116,6 +124,9 @@ public WritableBuffer toWritable()
throw new IllegalStateException("Buffer already in write mode");
if (byteBuffer.isReadOnly())
throw new IllegalStateException("Buffer is read-only");
+ // Always compact when there is nothing to copy.
+ if (remaining() == 0L)
+ byteBuffer.compact().flip();
flushPosition = byteBuffer.position();
byteBuffer.position(byteBuffer.limit());
byteBuffer.limit(byteBuffer.capacity());
@@ -237,9 +248,10 @@ public long readFrom(Fount fount) throws IOException
@Override
public String toString()
{
- return String.format("%s@%x{b=%s,r=%s}",
+ return String.format("%s@%x{fp=%d,b=%s,r=%s}",
TypeUtil.toShortName(getClass()),
hashCode(),
+ flushPosition,
BufferUtil.toDetailString(byteBuffer),
retainable);
}
diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/internal/AccumulatingReadBufferTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/internal/AccumulatingReadBufferTest.java
index 455d9c0ea86f..023d589a8700 100644
--- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/internal/AccumulatingReadBufferTest.java
+++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/internal/AccumulatingReadBufferTest.java
@@ -23,11 +23,13 @@
import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
public class AccumulatingReadBufferTest
{
@@ -451,6 +453,150 @@ public void testFragmentedGetLongNotEnoughBytes()
assertEquals(4, acc.get());
}
+ @Test
+ public void testGetByteArray()
+ {
+ ReadableBuffer rb1 = ReadableBuffer.wrap(ByteBuffer.allocate(20)
+ .putLong(1L)
+ .putLong(2L)
+ .flip());
+ ReadableBuffer rb2 = ReadableBuffer.wrap(ByteBuffer.allocate(20)
+ .putLong(3L)
+ .putLong(4L)
+ .flip());
+ ReadableBuffer acc = ReadableBuffer.accumulate(List.of(rb1, rb2));
+ byte[] bytes = new byte[8];
+
+ assertEquals(0, acc.position());
+ assertEquals(32, acc.remaining());
+ acc.get(bytes);
+ assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 1}, bytes);
+
+ assertEquals(8, acc.position());
+ assertEquals(24, acc.remaining());
+ acc.get(bytes);
+ assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 2}, bytes);
+
+ assertEquals(16, acc.position());
+ assertEquals(16, acc.remaining());
+ acc.get(bytes);
+ assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 3}, bytes);
+
+ assertEquals(24, acc.position());
+ assertEquals(8, acc.remaining());
+ acc.get(bytes);
+ assertArrayEquals(new byte[]{0, 0, 0, 0, 0, 0, 0, 4}, bytes);
+
+ assertEquals(32, acc.position());
+ assertEquals(0, acc.remaining());
+ assertThrows(BufferUnderflowException.class, () -> acc.get(bytes));
+ }
+
+ @Test
+ public void testFragmentedGetByteArray()
+ {
+ List accumulatorCombinations = new ArrayList<>();
+
+ {
+ ReadableBuffer rb1 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)0)
+ .flip());
+ ReadableBuffer rb2 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)1)
+ .flip());
+ accumulatorCombinations.add(ReadableBuffer.accumulate(List.of(rb1, rb2)));
+ }
+ {
+ ReadableBuffer rb1 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)0)
+ .flip());
+ ReadableBuffer rb2 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)1)
+ .flip());
+ accumulatorCombinations.add(ReadableBuffer.accumulate(List.of(rb1, rb2)));
+ }
+ {
+ ReadableBuffer rb1 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)0)
+ .flip());
+ ReadableBuffer rb2 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)0)
+ .flip());
+ ReadableBuffer rb3 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)0)
+ .flip());
+ ReadableBuffer rb4 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)0)
+ .flip());
+ ReadableBuffer rb5 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)0)
+ .flip());
+ ReadableBuffer rb6 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)0)
+ .flip());
+ ReadableBuffer rb7 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)0)
+ .flip());
+ ReadableBuffer rb8 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)1)
+ .flip());
+ accumulatorCombinations.add(ReadableBuffer.accumulate(List.of(rb1, rb2, rb3, rb4, rb5, rb6, rb7, rb8)));
+ }
+
+ byte[] bytes = new byte[8];
+ byte[] expected = new byte[]{0, 0, 0, 0, 0, 0, 0, 1};
+ for (ReadableBuffer acc : accumulatorCombinations)
+ {
+ assertEquals(0, acc.position());
+ assertEquals(8, acc.remaining());
+ acc.get(bytes);
+ assertArrayEquals(expected, bytes);
+
+ assertEquals(8, acc.position());
+ assertEquals(0, acc.remaining());
+ assertThrows(BufferUnderflowException.class, () -> acc.get(bytes));
+ }
+ }
+
+ @Test
+ public void testFragmentedGetByteArrayNotEnoughBytes()
+ {
+ ReadableBuffer rb1 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)0)
+ .put((byte)1)
+ .flip());
+ ReadableBuffer rb2 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .put((byte)2)
+ .put((byte)3)
+ .put((byte)4)
+ .flip());
+ ReadableBuffer acc = ReadableBuffer.accumulate(List.of(rb1, rb2));
+
+ assertEquals(0, acc.position());
+ assertEquals(7, acc.remaining());
+ byte[] bytes = new byte[8];
+ assertThrows(BufferUnderflowException.class, () -> acc.get(bytes));
+
+ assertEquals(1, acc.getInt());
+ assertEquals(2, acc.get());
+ assertEquals(3, acc.get());
+ assertEquals(4, acc.get());
+ }
+
@Test
public void testPosition()
{
@@ -559,6 +705,53 @@ public void testByteBuffersNotAtZeroPositionGet()
assertEquals(0, acc.remaining());
}
+ @Test
+ public void testWriteToGathering() throws IOException
+ {
+ ReadableBuffer rb1 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .putInt(11)
+ .putInt(12)
+ .flip());
+ ReadableBuffer rb2 = ReadableBuffer.wrap(ByteBuffer.allocate(10)
+ .putInt(13)
+ .putInt(14)
+ .flip());
+ ReadableBuffer acc = ReadableBuffer.accumulate(List.of(rb1, rb2));
+ assertEquals(0, acc.position());
+ assertEquals(16, acc.remaining());
+
+ List writtenIntegers = new ArrayList<>();
+ long written = acc.writeTo(new ReadableBuffer.GatheringTarget()
+ {
+ @Override
+ public void write(ByteBuffer[] inputs)
+ {
+ for (ByteBuffer input : inputs)
+ {
+ while (input.hasRemaining())
+ {
+ writtenIntegers.add(input.getInt());
+ }
+ }
+ }
+
+ @Override
+ public void write(ByteBuffer input)
+ {
+ fail("gathering write should have been called instead");
+ }
+ });
+ assertEquals(16, written);
+
+ assertEquals(16, acc.position());
+ assertEquals(0, acc.remaining());
+ assertEquals(4, writtenIntegers.size());
+ assertEquals(11, writtenIntegers.get(0));
+ assertEquals(12, writtenIntegers.get(1));
+ assertEquals(13, writtenIntegers.get(2));
+ assertEquals(14, writtenIntegers.get(3));
+ }
+
@Test
public void testWriteTo() throws IOException
{
diff --git a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/internal/FixedSizeBufferTest.java b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/internal/FixedSizeBufferTest.java
index 4845789f2f5a..298b4712cdf0 100644
--- a/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/internal/FixedSizeBufferTest.java
+++ b/jetty-core/jetty-util/src/test/java/org/eclipse/jetty/util/internal/FixedSizeBufferTest.java
@@ -38,10 +38,11 @@ public void testToReadableAfterRewindingPositionInWriteBuffer()
ReadableBuffer rb = wb.toReadable();
assertEquals(1, rb.getInt());
rb.toWritable();
- wb.position(2); // rewind write position
+ wb.position(4); // 'revive' the 4 bytes previously written
wb.toReadable();
- assertEquals(2, rb.position());
- assertEquals(0, rb.remaining());
+ assertEquals(0, rb.position());
+ assertEquals(4, rb.remaining());
+ assertEquals(1, rb.getInt());
}
@Test
@@ -265,8 +266,8 @@ public void testReadableBufferWrap()
assertEquals(0L, rb.remaining());
WritableBuffer wb = rb.toWritable();
- assertEquals(8L, wb.position());
- assertEquals(2L, wb.remaining());
+ assertEquals(0L, wb.position());
+ assertEquals(10L, wb.remaining());
}
@Test
@@ -330,12 +331,12 @@ public void testWriteModeChecks()
assertThrows(IllegalStateException.class, rb::getShort);
assertThrows(IllegalStateException.class, rb::getInt);
assertThrows(IllegalStateException.class, rb::getLong);
+ assertThrows(IllegalStateException.class, () -> rb.get(new byte[0]));
assertThrows(IllegalStateException.class, rb::compact);
assertThrows(IllegalStateException.class, rb::toWritable);
assertThrows(IllegalStateException.class, rb::slice);
assertThrows(IllegalStateException.class, () -> rb.slice(1L, 2L));
- assertThrows(IllegalStateException.class, () -> rb.writeTo(b ->
- {}));
+ assertThrows(IllegalStateException.class, () -> rb.writeTo(input -> {}));
}
@Test
@@ -369,9 +370,11 @@ public void testPutsGets()
assertEquals((short)2, rb.getShort());
assertEquals(4, rb.getInt());
assertEquals(8, rb.getLong());
- assertEquals((byte)1, rb.get());
- assertEquals((byte)2, rb.get());
- assertEquals((byte)3, rb.get());
+ byte[] bytes = new byte[3];
+ rb.get(bytes);
+ assertEquals((byte)1, bytes[0]);
+ assertEquals((byte)2, bytes[1]);
+ assertEquals((byte)3, bytes[2]);
assertEquals(0, wb.remaining());
}
diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java
index ec51127f3866..96aa940c0408 100644
--- a/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java
+++ b/jetty-core/jetty-websocket/jetty-websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/internal/FrameFlusher.java
@@ -32,6 +32,7 @@
import org.eclipse.jetty.util.IteratingCallback;
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.websocket.core.Behavior;
@@ -345,7 +346,7 @@ protected Action process() throws Throwable
bufferArray[i++] = bb;
}
_bytesOut.add(bytes);
- _endPoint.write(this, bufferArray);
+ _endPoint.write(ReadableBuffer.wrap(bufferArray), this);
buffers.clear();
}
else
diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/FrameFlusherTest.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/FrameFlusherTest.java
index 4ab104a1549c..4637ab9823d1 100644
--- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/FrameFlusherTest.java
+++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/FrameFlusherTest.java
@@ -28,8 +28,10 @@
import org.eclipse.jetty.io.ArrayByteBufferPool;
import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.FutureCallback;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.websocket.core.Behavior;
@@ -244,18 +246,16 @@ public void shutdownOutput()
}
@Override
- public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ public void write(ReadableBuffer buffer, Callback callback) throws WritePendingException
{
Objects.requireNonNull(callback);
try
{
- for (ByteBuffer buffer : buffers)
+ ByteBuffer b = BufferUtil.toBuffer(buffer, false);
+ Frame.Parsed frame = parser.parse(b);
+ if (frame != null)
{
- Frame.Parsed frame = parser.parse(buffer);
- if (frame != null)
- {
- incomingFrames.offer(frame);
- }
+ incomingFrames.offer(frame);
}
callback.succeeded();
}
@@ -289,12 +289,12 @@ public BlockingEndpoint(ByteBufferPool bufferPool)
}
@Override
- public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ public void write(ReadableBuffer buffer, Callback callback) throws WritePendingException
{
try
{
Thread.sleep(blockTime);
- super.write(callback, buffers);
+ super.write(buffer, callback);
}
catch (InterruptedException e)
{
diff --git a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndPoint.java b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndPoint.java
index 04c2e9882387..d5bf166e8ad3 100644
--- a/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndPoint.java
+++ b/jetty-core/jetty-websocket/jetty-websocket-core-tests/src/test/java/org/eclipse/jetty/websocket/core/internal/MockEndPoint.java
@@ -13,15 +13,15 @@
package org.eclipse.jetty.websocket.core.internal;
-import java.io.IOException;
import java.net.SocketAddress;
-import java.nio.ByteBuffer;
import java.nio.channels.ReadPendingException;
import java.nio.channels.WritePendingException;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
public class MockEndPoint implements EndPoint
{
@@ -82,13 +82,13 @@ public void close(Throwable cause)
}
@Override
- public int fill(ByteBuffer buffer) throws IOException
+ public int fill(WritableBuffer buffer)
{
throw new UnsupportedOperationException(NOT_SUPPORTED);
}
@Override
- public boolean flush(ByteBuffer... buffer) throws IOException
+ public boolean flush(ReadableBuffer buffer)
{
throw new UnsupportedOperationException(NOT_SUPPORTED);
}
@@ -130,7 +130,7 @@ public boolean isFillInterested()
}
@Override
- public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException
+ public void write(ReadableBuffer buffer, Callback callback) throws WritePendingException
{
throw new UnsupportedOperationException(NOT_SUPPORTED);
}
diff --git a/jetty-ee/jetty-ee-servlets/src/test/java/org/eclipse/jetty/ee/servlets/ThreadStarvationTest.java b/jetty-ee/jetty-ee-servlets/src/test/java/org/eclipse/jetty/ee/servlets/ThreadStarvationTest.java
index 8af8437fe2dc..287b14f91df4 100644
--- a/jetty-ee/jetty-ee-servlets/src/test/java/org/eclipse/jetty/ee/servlets/ThreadStarvationTest.java
+++ b/jetty-ee/jetty-ee-servlets/src/test/java/org/eclipse/jetty/ee/servlets/ThreadStarvationTest.java
@@ -17,7 +17,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
-import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
@@ -60,6 +59,7 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -449,9 +449,12 @@ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelect
return new SocketChannelEndPoint(channel, selectSet, key, getScheduler())
{
@Override
- public boolean flush(ByteBuffer... buffers) throws IOException
+ public boolean flush(ReadableBuffer buffer) throws IOException
{
- super.flush(buffers[0]);
+ ReadableBuffer slice = buffer.slice(0, 100);
+ super.flush(slice);
+ slice.release();
+ buffer.position(slice.position());
throw new IOException("TEST FAILURE");
}
};
diff --git a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/AsyncCompletionTest.java b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/AsyncCompletionTest.java
index ea73360e6325..d40700b1f761 100644
--- a/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/AsyncCompletionTest.java
+++ b/jetty-ee9/jetty-ee9-nested/src/test/java/org/eclipse/jetty/ee9/nested/AsyncCompletionTest.java
@@ -18,7 +18,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
-import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
@@ -51,6 +50,7 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.thread.Scheduler;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
@@ -159,10 +159,10 @@ public ExtendedEndPoint(SocketChannel channel, ManagedSelector selector, Selecti
}
@Override
- public void write(Callback callback, ByteBuffer... buffers) throws IllegalStateException
+ public void write(ReadableBuffer buffer, Callback callback) throws IllegalStateException
{
PendingCallback delay = new PendingCallback(callback);
- super.write(delay, buffers);
+ super.write(buffer, delay);
__queue.offer(delay);
}
}
diff --git a/jetty-ee9/jetty-ee9-servlets/src/test/java/org/eclipse/jetty/ee9/servlets/ThreadStarvationTest.java b/jetty-ee9/jetty-ee9-servlets/src/test/java/org/eclipse/jetty/ee9/servlets/ThreadStarvationTest.java
index c1f8f3f45ad1..f3d3c51b3ea6 100644
--- a/jetty-ee9/jetty-ee9-servlets/src/test/java/org/eclipse/jetty/ee9/servlets/ThreadStarvationTest.java
+++ b/jetty-ee9/jetty-ee9-servlets/src/test/java/org/eclipse/jetty/ee9/servlets/ThreadStarvationTest.java
@@ -17,7 +17,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
-import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
@@ -58,6 +57,7 @@
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -337,9 +337,12 @@ protected SocketChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelect
return new SocketChannelEndPoint(channel, selectSet, key, getScheduler())
{
@Override
- public boolean flush(ByteBuffer... buffers) throws IOException
+ public boolean flush(ReadableBuffer buffer) throws IOException
{
- super.flush(buffers[0]);
+ ReadableBuffer slice = buffer.slice(0, 100);
+ super.flush(slice);
+ slice.release();
+ buffer.position(slice.position());
throw new IOException("TEST FAILURE");
}
};
From ebf84ba2ca2c570885bb7966c7305c7f725de028 Mon Sep 17 00:00:00 2001
From: Ludovic Orban
Date: Thu, 18 Jun 2026 15:39:22 +0200
Subject: [PATCH 03/52] *** Phase 3 *** Convert http2 and fcgi to use
ReadableBuffer/WritableBuffer
Signed-off-by: Ludovic Orban
---
.../jetty/docs/programming/HTTP2Docs.java | 17 +-
.../client/http2/HTTP2ClientDocs.java | 33 +-
.../server/http2/HTTP2ServerDocs.java | 34 ++-
.../client/BouncyCastleHTTP2ClientTest.java | 9 +-
.../java/client/ConscryptHTTP2ClientTest.java | 7 +-
.../alpn/java/client/JDK9HTTP2ClientTest.java | 9 +-
.../internal/HttpChannelOverFCGI.java | 6 +-
.../internal/HttpConnectionOverFCGI.java | 219 ++++++++------
.../internal/HttpReceiverOverFCGI.java | 8 +-
.../internal/HttpSenderOverFCGI.java | 25 +-
.../jetty/fcgi/generator/ClientGenerator.java | 64 ++--
.../eclipse/jetty/fcgi/generator/Flusher.java | 45 +--
.../jetty/fcgi/generator/Generator.java | 52 ++--
.../jetty/fcgi/generator/ServerGenerator.java | 52 ++--
.../parser/BeginRequestContentParser.java | 11 +-
.../jetty/fcgi/parser/ClientParser.java | 4 +-
.../jetty/fcgi/parser/ContentParser.java | 6 +-
.../fcgi/parser/EndRequestContentParser.java | 10 +-
.../jetty/fcgi/parser/HeaderParser.java | 21 +-
.../fcgi/parser/ParamsContentParser.java | 8 +-
.../org/eclipse/jetty/fcgi/parser/Parser.java | 23 +-
.../fcgi/parser/ResponseContentParser.java | 39 ++-
.../fcgi/parser/StreamContentParser.java | 25 +-
.../fcgi/generator/ClientGeneratorTest.java | 75 ++---
.../jetty/fcgi/parser/ClientParserTest.java | 108 +++----
.../server/internal/HttpStreamOverFCGI.java | 66 ++--
.../server/internal/ServerFCGIConnection.java | 281 ++++++++++--------
.../jetty/fcgi/server/HttpClientTest.java | 2 +-
.../jetty/http/PreEncodedHttpField.java | 7 +
.../http/compression/HuffmanDecoder.java | 11 +-
.../http/compression/HuffmanEncoder.java | 16 +-
.../http/compression/NBitIntegerDecoder.java | 16 +-
.../http/compression/NBitIntegerEncoder.java | 22 +-
.../http/compression/NBitStringDecoder.java | 10 +-
.../http/compression/NBitStringEncoder.java | 21 +-
.../org/eclipse/jetty/http/HuffmanTest.java | 27 +-
.../internal/HttpReceiverOverHTTP2.java | 14 +-
.../internal/HttpSenderOverHTTP2.java | 20 +-
.../client/HTTP2ClientConnectionFactory.java | 6 +-
.../eclipse/jetty/http2/HTTP2Connection.java | 215 +++-----------
.../org/eclipse/jetty/http2/HTTP2Session.java | 40 +--
.../org/eclipse/jetty/http2/HTTP2Stream.java | 99 +++---
.../jetty/http2/HTTP2StreamEndPoint.java | 46 +--
.../org/eclipse/jetty/http2/api/Session.java | 2 +-
.../org/eclipse/jetty/http2/api/Stream.java | 118 ++++++--
.../eclipse/jetty/http2/frames/DataFrame.java | 61 +++-
.../jetty/http2/generator/DataGenerator.java | 45 +--
.../jetty/http2/generator/FrameGenerator.java | 30 +-
.../jetty/http2/generator/Generator.java | 24 +-
.../http2/generator/GoAwayGenerator.java | 20 +-
.../http2/generator/HeaderGenerator.java | 24 +-
.../http2/generator/HeadersGenerator.java | 50 ++--
.../jetty/http2/generator/NoOpGenerator.java | 35 ---
.../jetty/http2/generator/PingGenerator.java | 14 +-
.../http2/generator/PrefaceGenerator.java | 12 +-
.../http2/generator/PriorityGenerator.java | 21 +-
.../http2/generator/PushPromiseGenerator.java | 21 +-
.../jetty/http2/generator/ResetGenerator.java | 14 +-
.../http2/generator/SettingsGenerator.java | 15 +-
.../generator/WindowUpdateGenerator.java | 14 +-
.../jetty/http2/internal/HTTP2Flusher.java | 18 +-
.../jetty/http2/parser/BodyParser.java | 14 +-
.../http2/parser/ContinuationBodyParser.java | 21 +-
.../jetty/http2/parser/DataBodyParser.java | 69 +++--
.../jetty/http2/parser/GoAwayBodyParser.java | 7 +-
.../http2/parser/HeaderBlockFragments.java | 68 +++--
.../jetty/http2/parser/HeaderBlockParser.java | 68 ++---
.../jetty/http2/parser/HeaderParser.java | 7 +-
.../jetty/http2/parser/HeadersBodyParser.java | 18 +-
.../eclipse/jetty/http2/parser/Parser.java | 27 +-
.../jetty/http2/parser/PingBodyParser.java | 9 +-
.../jetty/http2/parser/PrefaceParser.java | 10 +-
.../http2/parser/PriorityBodyParser.java | 9 +-
.../http2/parser/PushPromiseBodyParser.java | 9 +-
.../jetty/http2/parser/ResetBodyParser.java | 9 +-
.../jetty/http2/parser/ServerParser.java | 14 +-
.../http2/parser/SettingsBodyParser.java | 25 +-
.../jetty/http2/parser/UnknownBodyParser.java | 15 +-
.../http2/parser/WindowUpdateBodyParser.java | 9 +-
.../http2/frames/ContinuationParseTest.java | 56 ++--
.../http2/frames/DataGenerateParseTest.java | 68 +++--
.../jetty/http2/frames/FrameFloodTest.java | 24 +-
.../http2/frames/GoAwayGenerateParseTest.java | 22 +-
.../frames/HeadersGenerateParseTest.java | 22 +-
.../frames/HeadersTooLargeParseTest.java | 40 +--
.../http2/frames/MaxFrameSizeParseTest.java | 16 +-
.../http2/frames/PingGenerateParseTest.java | 30 +-
.../frames/PriorityGenerateParseTest.java | 22 +-
.../frames/PushPromiseGenerateParseTest.java | 22 +-
.../http2/frames/ResetGenerateParseTest.java | 22 +-
.../frames/SettingsGenerateParseTest.java | 72 +++--
.../jetty/http2/frames/UnknownParseTest.java | 49 +--
.../frames/WindowUpdateGenerateParseTest.java | 22 +-
.../jetty/http2/hpack/HpackContext.java | 3 +-
.../jetty/http2/hpack/HpackDecoder.java | 12 +-
.../jetty/http2/hpack/HpackEncoder.java | 37 ++-
.../http2/hpack/HpackFieldPreEncoder.java | 9 +-
.../jetty/http2/hpack/HpackContextTest.java | 7 +-
.../jetty/http2/hpack/HpackDecoderTest.java | 58 ++--
.../jetty/http2/hpack/HpackEncoderTest.java | 152 +++++-----
.../jetty/http2/hpack/HpackPerfTest.java | 14 +-
.../eclipse/jetty/http2/hpack/HpackTest.java | 146 ++++-----
.../AbstractHTTP2ServerConnectionFactory.java | 5 +-
.../internal/HTTP2ServerConnection.java | 6 +-
.../server/internal/HttpStreamOverHTTP2.java | 40 +--
.../jetty/http2/tests/AbstractServerTest.java | 9 +-
.../jetty/http2/tests/AsyncIOTest.java | 7 +-
.../http2/tests/AuthorityCustomizerTest.java | 15 +-
.../eclipse/jetty/http2/tests/BadURITest.java | 22 +-
.../BlockedWritesWithSmallThreadPoolTest.java | 41 ++-
.../eclipse/jetty/http2/tests/CloseTest.java | 27 +-
.../http2/tests/ConcurrentRequestsTest.java | 17 +-
.../tests/ConcurrentStreamCreationTest.java | 2 +-
.../jetty/http2/tests/ConnectTunnelTest.java | 27 +-
.../jetty/http2/tests/ContentLengthTest.java | 4 +-
.../jetty/http2/tests/DataDemandTest.java | 93 +++---
.../http2/tests/FlowControlStalledTest.java | 40 +--
.../http2/tests/FlowControlStrategyTest.java | 161 +++++-----
.../eclipse/jetty/http2/tests/GoAwayTest.java | 25 +-
.../http2/tests/GracefulShutdownTest.java | 13 +-
.../jetty/http2/tests/HTTP2CServerTest.java | 39 ++-
.../jetty/http2/tests/HTTP2ServerTest.java | 121 +++++---
.../eclipse/jetty/http2/tests/HTTP2Test.java | 91 +++---
.../HttpClientTransportOverHTTP2Test.java | 30 +-
.../jetty/http2/tests/IdleTimeoutTest.java | 43 ++-
.../jetty/http2/tests/InterleavingTest.java | 32 +-
.../http2/tests/MaxPushedStreamsTest.java | 6 +-
.../jetty/http2/tests/PrefaceTest.java | 72 +++--
.../http2/tests/PushedResourcesTest.java | 24 +-
.../jetty/http2/tests/RawHTTP2ProxyTest.java | 82 ++---
.../http2/tests/RequestTrailersTest.java | 23 +-
.../http2/tests/ResponseTrailerTest.java | 7 +-
.../jetty/http2/tests/ReverseProxyTest.java | 7 +-
.../jetty/http2/tests/SettingsTest.java | 11 +-
.../http2/tests/SmallThreadPoolLoadTest.java | 10 +-
.../jetty/http2/tests/StreamCloseTest.java | 24 +-
.../jetty/http2/tests/StreamCountTest.java | 35 ++-
.../jetty/http2/tests/StreamResetTest.java | 175 ++++++-----
.../jetty/http2/tests/TrailersTest.java | 17 +-
.../http3/qpack/internal/table/Entry.java | 3 +-
.../io/AbstractRetainableByteBuffer.java | 8 +-
.../eclipse/jetty/io/ArrayByteBufferPool.java | 8 +-
.../java/org/eclipse/jetty/io/Content.java | 24 ++
.../java/org/eclipse/jetty/io/Retainable.java | 52 +---
.../jetty/io/RetainableByteBuffer.java | 6 +-
.../eclipse/jetty/io/TrackingRetainable.java | 130 ++++++++
.../jetty/io/content/AsyncContent.java | 2 +-
.../jetty/io/internal/ByteBufferChunk.java | 2 +-
.../ForwardProxyWithDynamicTransportTest.java | 18 +-
.../server/internal/HttpChannelState.java | 2 +-
.../org/eclipse/jetty/util/BufferUtil.java | 31 ++
.../org/eclipse/jetty/util/Retainable.java | 55 ++++
.../jetty/util/buffer/ReadableBuffer.java | 51 +++-
.../jetty/util/buffer/WritableBuffer.java | 50 +++-
.../util/internal/AccumulatingReadBuffer.java | 99 +++---
.../jetty/util/internal/FixedSizeBuffer.java | 65 ++++
.../internal/AccumulatingReadBufferTest.java | 14 +-
.../transport/Http2AsyncIOServletTest.java | 30 +-
.../jetty/test/webapp/HTTP1Servlet.java | 9 +-
.../jetty/ee/test/HTTP2RequestTest.java | 5 +-
.../jetty/ee9/nested/ThreadLimitHandler.java | 2 +-
.../jetty/test/webapp/HTTP1Servlet.java | 9 +-
.../jetty/ee9/test/HTTP2RequestTest.java | 5 +-
163 files changed, 3248 insertions(+), 2535 deletions(-)
delete mode 100644 jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/generator/NoOpGenerator.java
create mode 100644 jetty-core/jetty-io/src/main/java/org/eclipse/jetty/io/TrackingRetainable.java
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java
index 398cfd139ca0..5ce0f84c7af4 100644
--- a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/HTTP2Docs.java
@@ -29,6 +29,7 @@
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.HeadersFrame;
+import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.Callback;
@SuppressWarnings("unused")
@@ -48,12 +49,12 @@ public void dataDemanded() throws Exception
HeadersFrame headersFrame = new HeadersFrame(request, null, true);
// tag::dataUnwrap[]
- record Chunk(ByteBuffer byteBuffer, Callback callback)
+ record Data(ByteBuffer buffer, Callback callback)
{
}
// A queue that consumers poll to consume content asynchronously.
- Queue dataQueue = new ConcurrentLinkedQueue<>();
+ Queue dataQueue = new ConcurrentLinkedQueue<>();
// Implementation of Stream.Listener.onDataAvailable(Stream stream)
// in case of unwrapping of the Data object for asynchronous content
@@ -63,26 +64,26 @@ record Chunk(ByteBuffer byteBuffer, Callback callback)
@Override
public void onDataAvailable(Stream stream)
{
- Stream.Data data = stream.readData();
+ Content.Chunk chunk = stream.read();
- if (data == null)
+ if (chunk == null)
{
stream.demand();
return;
}
// Get the content buffer.
- ByteBuffer byteBuffer = data.frame().getByteBuffer();
+ ByteBuffer byteBuffer = chunk.getByteBuffer();
// Unwrap the Data object, converting it to a Chunk.
// The Data.release() semantic is maintained in the completion of the Callback.
- dataQueue.offer(new Chunk(byteBuffer, Callback.from(() ->
+ dataQueue.offer(new Data(byteBuffer, Callback.from(() ->
{
// When the buffer has been consumed, then:
// A) release the Data object.
- data.release();
+ chunk.release();
// B) possibly demand more DATA frames.
- if (!data.frame().isEndStream())
+ if (!chunk.isLast())
stream.demand();
})));
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java
index 6f163434df34..ee92933b240a 100644
--- a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/client/http2/HTTP2ClientDocs.java
@@ -34,15 +34,16 @@
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.client.HTTP2Client;
-import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.NanoTime;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import static java.lang.System.Logger.Level.INFO;
@@ -192,17 +193,17 @@ public void newStreamWithData() throws Exception
// The request content, in two chunks.
String content1 = "{\"greet\": \"hello world\"}";
- ByteBuffer buffer1 = StandardCharsets.UTF_8.encode(content1);
+ ReadableBuffer buffer1 = ReadableBuffer.wrap(StandardCharsets.UTF_8.encode(content1));
String content2 = "{\"user\": \"jetty\"}";
- ByteBuffer buffer2 = StandardCharsets.UTF_8.encode(content2);
+ ReadableBuffer buffer2 = ReadableBuffer.wrap(StandardCharsets.UTF_8.encode(content2));
// Send the first DATA frame on the stream, with endStream=false
// to signal that there are more frames in this stream.
- CompletableFuture dataCF1 = stream.data(new DataFrame(stream.getId(), buffer1, false));
+ CompletableFuture dataCF1 = stream.data(buffer1, false);
// Only when the first chunk has been sent we can send the second,
// with endStream=true to signal that there are no more frames.
- dataCF1.thenCompose(s -> s.data(new DataFrame(s.getId(), buffer2, true)));
+ dataCF1.thenCompose(s -> s.data(buffer2, true));
// end::newStreamWithData[]
}
@@ -249,10 +250,10 @@ public void onHeaders(Stream stream, HeadersFrame frame)
@Override
public void onDataAvailable(Stream stream)
{
- // Read a Data object.
- Stream.Data data = stream.readData();
+ // Read a Chunk object.
+ Content.Chunk chunk = stream.read();
- if (data == null)
+ if (chunk == null)
{
// Demand more DATA frames.
stream.demand();
@@ -260,15 +261,15 @@ public void onDataAvailable(Stream stream)
}
// Get the content buffer.
- ByteBuffer buffer = data.frame().getByteBuffer();
+ ByteBuffer buffer = chunk.getByteBuffer();
// Consume the buffer, here - as an example - just log it.
System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer);
// Tell the implementation that the buffer has been consumed.
- data.release();
+ chunk.release();
- if (!data.frame().isEndStream())
+ if (!chunk.isLast())
{
// Demand more DATA frames when they are available.
stream.demand();
@@ -365,20 +366,20 @@ public void onDataAvailable(Stream stream)
{
// Handle the pushed stream "response" content.
- Stream.Data data = stream.readData();
+ Content.Chunk chunk = stream.read();
- if (data == null)
+ if (chunk == null)
{
stream.demand();
return;
}
// The pushed stream "response" content bytes.
- ByteBuffer buffer = data.frame().getByteBuffer();
+ ByteBuffer buffer = chunk.getByteBuffer();
// Consume the buffer and release the Data object.
- data.release();
+ chunk.release();
- if (!data.frame().isEndStream())
+ if (!chunk.isLast())
{
// Demand more DATA frames when they are available.
stream.demand();
diff --git a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java
index 749602bd8d06..602ec516eec0 100644
--- a/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java
+++ b/documentation/jetty/modules/code/examples/src/main/java/org/eclipse/jetty/docs/programming/server/http2/HTTP2ServerDocs.java
@@ -28,16 +28,18 @@
import org.eclipse.jetty.http2.api.Session;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
-import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.frames.SettingsFrame;
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
+import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.eclipse.jetty.util.resource.ResourceFactory;
import static java.lang.System.Logger.Level.INFO;
@@ -150,24 +152,24 @@ public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@Override
public void onDataAvailable(Stream stream)
{
- Stream.Data data = stream.readData();
+ Content.Chunk chunk = stream.read();
- if (data == null)
+ if (chunk == null)
{
stream.demand();
return;
}
// Get the content buffer.
- ByteBuffer buffer = data.frame().getByteBuffer();
+ ByteBuffer buffer = chunk.getByteBuffer();
// Consume the buffer, here - as an example - just log it.
System.getLogger("http2").log(INFO, "Consuming buffer {0}", buffer);
// Tell the implementation that the buffer has been consumed.
- data.release();
+ chunk.release();
- if (!data.frame().isEndStream())
+ if (!chunk.isLast())
{
// Demand more DATA frames when they are available.
stream.demand();
@@ -205,18 +207,18 @@ public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
@Override
public void onDataAvailable(Stream stream)
{
- Stream.Data data = stream.readData();
+ Content.Chunk chunk = stream.read();
- if (data == null)
+ if (chunk == null)
{
stream.demand();
return;
}
// Consume the request content.
- data.release();
+ chunk.release();
- if (data.frame().isEndStream())
+ if (chunk.isLast())
respond(stream, request);
else
stream.demand();
@@ -235,12 +237,12 @@ private void respond(Stream stream, MetaData.Request request)
if (HttpMethod.GET.is(request.getMethod()))
{
// The response content.
- ByteBuffer resourceBytes = getResourceBytes(request);
+ ReadableBuffer resourceBytes = getResourceBytes(request);
// Send the HEADERS frame with the response status and headers,
// and a DATA frame with the response content bytes.
stream.headers(new HeadersFrame(stream.getId(), response, null, false))
- .thenCompose(s -> s.data(new DataFrame(s.getId(), resourceBytes, true)));
+ .thenCompose(s -> s.data(resourceBytes, true));
}
else
{
@@ -250,9 +252,9 @@ private void respond(Stream stream, MetaData.Request request)
}
// tag::exclude[]
- private ByteBuffer getResourceBytes(MetaData.Request request)
+ private ReadableBuffer getResourceBytes(MetaData.Request request)
{
- return ByteBuffer.allocate(1024);
+ return WritableBuffer.allocate(1024, false).toReadable();
}
// end::exclude[]
};
@@ -301,7 +303,7 @@ public void push() throws Exception
{
// tag::push[]
// The favicon bytes.
- ByteBuffer faviconBuffer = BufferUtil.toBuffer(ResourceFactory.root().newResource("/path/to/favicon.ico"), true);
+ ReadableBuffer faviconBuffer = ReadableBuffer.wrap(BufferUtil.toBuffer(ResourceFactory.root().newResource("/path/to/favicon.ico"), true));
ServerSessionListener sessionListener = new ServerSessionListener()
{
@@ -334,7 +336,7 @@ public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
// Send the favicon "response".
MetaData.Response pushedResponse = new MetaData.Response(HttpStatus.OK_200, null, HttpVersion.HTTP_2, HttpFields.EMPTY);
return pushedStream.headers(new HeadersFrame(pushedStream.getId(), pushedResponse, null, false))
- .thenCompose(pushed -> pushed.data(new DataFrame(pushed.getId(), faviconBuffer.slice(), true)));
+ .thenCompose(pushed -> pushed.data(faviconBuffer.slice(), true));
});
}
// Return a Stream.Listener to handle the request events.
diff --git a/jetty-core/jetty-alpn/jetty-alpn-bouncycastle-client/src/test/java/org/eclipse/jetty/alpn/bouncycastle/client/BouncyCastleHTTP2ClientTest.java b/jetty-core/jetty-alpn/jetty-alpn-bouncycastle-client/src/test/java/org/eclipse/jetty/alpn/bouncycastle/client/BouncyCastleHTTP2ClientTest.java
index 999546256265..cffc28ccdaab 100644
--- a/jetty-core/jetty-alpn/jetty-alpn-bouncycastle-client/src/test/java/org/eclipse/jetty/alpn/bouncycastle/client/BouncyCastleHTTP2ClientTest.java
+++ b/jetty-core/jetty-alpn/jetty-alpn-bouncycastle-client/src/test/java/org/eclipse/jetty/alpn/bouncycastle/client/BouncyCastleHTTP2ClientTest.java
@@ -29,6 +29,7 @@
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.HeadersFrame;
+import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -82,10 +83,10 @@ public void onHeaders(Stream stream, HeadersFrame frame)
@Override
public void onDataAvailable(Stream stream)
{
- Stream.Data data = stream.readData();
- System.err.println(data);
- data.release();
- if (data.frame().isEndStream())
+ Content.Chunk chunk = stream.read();
+ System.err.println(chunk);
+ chunk.release();
+ if (chunk.isLast())
latch.countDown();
else
stream.demand();
diff --git a/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2ClientTest.java b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2ClientTest.java
index 39d1edb925e5..29381bb550c2 100644
--- a/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2ClientTest.java
+++ b/jetty-core/jetty-alpn/jetty-alpn-conscrypt-client/src/test/java/org/eclipse/jetty/alpn/java/client/ConscryptHTTP2ClientTest.java
@@ -29,6 +29,7 @@
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.HeadersFrame;
+import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
@@ -84,10 +85,10 @@ public void onHeaders(Stream stream, HeadersFrame frame)
@Override
public void onDataAvailable(Stream stream)
{
- Stream.Data data = stream.readData();
+ Content.Chunk chunk = stream.read();
// System.err.println(data);
- data.release();
- if (data.frame().isEndStream())
+ chunk.release();
+ if (chunk.isLast())
latch.countDown();
else
stream.demand();
diff --git a/jetty-core/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2ClientTest.java b/jetty-core/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2ClientTest.java
index 7c9a044d2212..cffff6d96354 100644
--- a/jetty-core/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2ClientTest.java
+++ b/jetty-core/jetty-alpn/jetty-alpn-java-client/src/test/java/org/eclipse/jetty/alpn/java/client/JDK9HTTP2ClientTest.java
@@ -26,6 +26,7 @@
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.frames.HeadersFrame;
+import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.FuturePromise;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.Promise;
@@ -74,10 +75,10 @@ public void onHeaders(Stream stream, HeadersFrame frame)
@Override
public void onDataAvailable(Stream stream)
{
- Stream.Data data = stream.readData();
- System.err.println(data);
- data.release();
- if (data.frame().isEndStream())
+ Content.Chunk chunk = stream.read();
+ System.err.println(chunk);
+ chunk.release();
+ if (chunk.isLast())
latch.countDown();
else
stream.demand();
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpChannelOverFCGI.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpChannelOverFCGI.java
index 6be1d299940c..25572775233f 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpChannelOverFCGI.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpChannelOverFCGI.java
@@ -22,10 +22,10 @@
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
public class HttpChannelOverFCGI extends HttpChannel
{
@@ -167,9 +167,9 @@ else if (connection.isShutdown() || connection.isCloseByHTTP(responseHeaders))
release();
}
- protected void flush(ByteBufferPool.Accumulator accumulator, Callback callback)
+ protected void flush(ReadableBuffer buffer, Callback callback)
{
- connection.getFlusher().flush(accumulator, callback);
+ connection.getFlusher().flush(buffer, callback);
}
void receive()
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java
index 70f9ac864440..9b106381405c 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpConnectionOverFCGI.java
@@ -14,7 +14,6 @@
package org.eclipse.jetty.fcgi.client.transport.internal;
import java.net.SocketAddress;
-import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.util.Collections;
import java.util.Iterator;
@@ -43,25 +42,27 @@
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.RetainableByteBuffer;
+import org.eclipse.jetty.io.WritableBufferPool;
import org.eclipse.jetty.util.Attachable;
-import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.eclipse.jetty.util.thread.Invocable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
public class HttpConnectionOverFCGI extends AbstractConnection implements IConnection, Attachable, Invocable
{
private static final Logger LOG = LoggerFactory.getLogger(HttpConnectionOverFCGI.class);
private final Callback fillableCallback = new FillableCallback();
- private final ByteBufferPool networkByteBufferPool;
+ private final WritableBufferPool networkBufferPool;
private final AtomicInteger requests = new AtomicInteger();
private final AtomicBoolean closed = new AtomicBoolean();
private final HttpDestination destination;
@@ -71,11 +72,12 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements IConne
private final ClientParser parser;
private final HttpChannelOverFCGI channel;
private final InvocationType invocationType;
- private RetainableByteBuffer networkBuffer;
+ private ReadableBuffer networkBuffer;
private Object attachment;
- private State state = State.STATUS;
+ private State state = State.IDLE;
private long idleTimeout;
private boolean shutdown;
+ private Throwable failure;
public HttpConnectionOverFCGI(EndPoint endPoint, Destination destination, Promise promise)
{
@@ -87,7 +89,7 @@ public HttpConnectionOverFCGI(EndPoint endPoint, Destination destination, Promis
this.parser = new ClientParser(new ResponseListener());
this.channel = newHttpChannel();
HttpClient client = destination.getHttpClient();
- this.networkByteBufferPool = client.getByteBufferPool();
+ this.networkBufferPool = WritableBufferPool.wrap(client.getByteBufferPool());
this.invocationType = client.getHttpClientTransport().getInvocationType();
}
@@ -157,126 +159,147 @@ public void onFillable()
channel.receive();
}
- private void reacquireNetworkBuffer()
- {
- if (networkBuffer == null)
- throw new IllegalStateException();
- if (networkBuffer.hasRemaining())
- throw new IllegalStateException();
- networkBuffer.release();
- networkBuffer = newNetworkBuffer();
- if (LOG.isDebugEnabled())
- LOG.debug("Reacquired {}", networkBuffer);
- }
-
- private RetainableByteBuffer newNetworkBuffer()
+ private WritableBuffer newNetworkBuffer()
{
HttpClient client = destination.getHttpClient();
- return networkByteBufferPool.acquire(client.getResponseBufferSize(), client.isUseInputDirectByteBuffers());
- }
-
- private void releaseNetworkBuffer()
- {
- if (networkBuffer == null)
- throw new IllegalStateException();
- if (networkBuffer.hasRemaining())
- throw new IllegalStateException();
- networkBuffer.release();
- if (LOG.isDebugEnabled())
- LOG.debug("Released {}", networkBuffer);
- this.networkBuffer = null;
+ return networkBufferPool.acquire(client.getResponseBufferSize(), client.isUseInputDirectByteBuffers());
}
private void disposeNetworkBuffer()
{
if (networkBuffer == null)
return;
- networkBuffer.clear();
networkBuffer.release();
if (LOG.isDebugEnabled())
LOG.debug("Disposed {}", networkBuffer);
networkBuffer = null;
}
- boolean parseAndFill(boolean notifyContentAvailable)
+ boolean process(boolean notifyContentAvailable)
+ {
+ boolean setFillInterested = parseAndFill();
+
+ switch (state)
+ {
+ case IDLE ->
+ {
+ }
+ case HEADERS ->
+ {
+ state = State.CONTENT;
+ channel.responseHeaders();
+ }
+ case CONTENT ->
+ {
+ if (notifyContentAvailable)
+ channel.responseContentAvailable();
+ }
+ case COMPLETE ->
+ {
+ channel.end();
+ // Do not call channel.responseSuccess() here to give HttpReceiverOverFCGI.read(boolean)
+ // a chance to read the chunk field before channel.responseSuccess() resets it to null.
+ }
+ case FAILED ->
+ {
+ failAndClose(failure);
+ }
+ default -> throw new IllegalStateException("Invalid state " + state);
+ }
+
+ return setFillInterested;
+ }
+
+ private boolean parseAndFill()
{
if (LOG.isDebugEnabled())
LOG.debug("parseAndFill {}", networkBuffer);
- if (networkBuffer == null)
- networkBuffer = newNetworkBuffer();
+
+ ReadableBuffer readable = null;
+ WritableBuffer writable = null;
+ if (networkBuffer != null)
+ {
+ readable = networkBuffer;
+ networkBuffer = null;
+ }
+ else
+ {
+ writable = newNetworkBuffer();
+ }
+
EndPoint endPoint = getEndPoint();
try
{
while (true)
{
- if (parse(networkBuffer.getByteBuffer(), notifyContentAvailable))
- return false;
+ if (writable != null)
+ {
+ int read = endPoint.fill(writable);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Read {} bytes from {}", read, endPoint);
- // Disposed by a parser callback.
- if (networkBuffer == null)
- return false;
+ if (read <= 0)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Releasing {}", writable);
+ writable.release();
- if (networkBuffer.isRetained())
- reacquireNetworkBuffer();
+ if (read == 0)
+ return true;
- // The networkBuffer may have been reacquired.
- int read = endPoint.fill(networkBuffer.getByteBuffer());
- if (LOG.isDebugEnabled())
- LOG.debug("Read {} bytes from {}", read, endPoint);
+ shutdown();
+ return false;
+ }
- if (read == 0)
- {
- releaseNetworkBuffer();
- return true;
+ readable = writable.toReadable();
}
- else if (read < 0)
+
+ assert readable != null;
+
+ if (parse(readable))
{
- releaseNetworkBuffer();
- shutdown();
+ if (readable.remaining() == 0)
+ readable.release();
+ else
+ networkBuffer = readable;
return false;
}
+
+ if (readable.isRetained())
+ {
+ readable.release();
+ writable = newNetworkBuffer();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Reacquired {}", writable);
+ }
+ else
+ {
+ writable = readable.toWritable();
+ }
}
}
catch (Exception x)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to fill from endpoint {}", endPoint, x);
+ if (writable != null)
+ writable.release();
+ else
+ readable.release();
close(x);
return false;
}
}
- private boolean parse(ByteBuffer buffer, boolean notifyContentAvailable)
+ private boolean parse(ReadableBuffer buffer)
{
- boolean handle = parser.parse(buffer);
-
- if (LOG.isDebugEnabled())
- LOG.debug("Parse state={} result={} {} {} on {}", state, handle, BufferUtil.toDetailString(buffer), parser, this);
-
- if (!handle)
- return false;
-
- switch (state)
+ while (buffer.remaining() > 0)
{
- case STATUS ->
- {
- // Nothing to do.
- }
- case HEADERS -> channel.responseHeaders();
- case CONTENT ->
- {
- if (notifyContentAvailable)
- channel.responseContentAvailable();
- }
- case COMPLETE ->
- {
- // Do not call channel.responseSuccess() here to give HttpReceiverOverFCGI.read(boolean) a chance to read
- // the chunk field before channel.responseSuccess() resets it to null.
- }
- default -> throw new IllegalStateException("Invalid state " + state);
+ boolean result = parser.parse(buffer);
+ if (result)
+ return true;
}
-
- return true;
+ return false;
}
boolean isComplete()
@@ -287,6 +310,7 @@ boolean isComplete()
void complete()
{
channel.responseSuccess();
+ state = State.IDLE;
}
private void shutdown()
@@ -382,7 +406,7 @@ private void failAndClose(Throwable failure)
{
if (failed)
close(failure);
- }, x -> close(failure)));
+ }, _ -> close(failure)));
}
protected HttpChannelOverFCGI newHttpChannel()
@@ -486,7 +510,6 @@ public void onBegin(int request, int code, String reason)
{
if (LOG.isDebugEnabled())
LOG.debug("onBegin r={},c={},reason={}", request, code, reason);
- state = State.STATUS;
channel.responseBegin(code, reason);
}
@@ -502,26 +525,26 @@ public void onHeader(int request, HttpField field)
public boolean onHeaders(int request)
{
if (LOG.isDebugEnabled())
- LOG.debug("onHeaders r={} {}", request, networkBuffer);
+ LOG.debug("onHeaders r={}", request);
state = State.HEADERS;
return true;
}
@Override
- public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+ public boolean onContent(int request, FCGI.StreamType stream, ReadableBuffer buffer)
{
if (LOG.isDebugEnabled())
- LOG.debug("onContent r={},t={},b={} {}", request, stream, BufferUtil.toDetailString(buffer), networkBuffer);
+ LOG.debug("onContent r={},t={},b={}", request, stream, buffer);
switch (stream)
{
case STD_OUT ->
{
- Content.Chunk chunk = Content.Chunk.asChunk(buffer, false, networkBuffer);
+ Content.Chunk chunk = Content.Chunk.from(buffer, false);
channel.content(chunk);
state = State.CONTENT;
return true;
}
- case STD_ERR -> LOG.info(BufferUtil.toUTF8String(buffer));
+ case STD_ERR -> LOG.info(buffer.asString(UTF_8));
default -> throw new IllegalArgumentException();
}
return false;
@@ -532,23 +555,27 @@ public boolean onEnd(int request)
{
if (LOG.isDebugEnabled())
LOG.debug("onEnd r={}", request);
- channel.end();
state = State.COMPLETE;
return true;
}
@Override
- public void onFailure(int request, Throwable failure)
+ public void onFailure(int request, Throwable cause)
{
if (LOG.isDebugEnabled())
- LOG.debug("onFailure request={}", request, failure);
- failAndClose(failure);
+ LOG.debug("onFailure request={}", request, cause);
+ state = State.FAILED;
+ failure = cause;
}
}
private enum State
{
- STATUS, HEADERS, CONTENT, COMPLETE
+ IDLE,
+ HEADERS,
+ CONTENT,
+ COMPLETE,
+ FAILED
}
private class FillableCallback implements Callback
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpReceiverOverFCGI.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpReceiverOverFCGI.java
index a37494bdc3b6..d3c0d7dd89e4 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpReceiverOverFCGI.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpReceiverOverFCGI.java
@@ -42,7 +42,7 @@ void receive()
else
{
HttpConnectionOverFCGI httpConnection = getHttpConnection();
- boolean setFillInterest = httpConnection.parseAndFill(true);
+ boolean setFillInterest = httpConnection.process(true);
if (!hasContent() && setFillInterest)
httpConnection.fillInterested();
}
@@ -83,7 +83,7 @@ public Content.Chunk read(boolean fillInterestIfNeeded)
if (chunk != null)
return chunk;
HttpConnectionOverFCGI httpConnection = getHttpConnection();
- boolean needFillInterest = httpConnection.parseAndFill(false);
+ boolean needFillInterest = httpConnection.process(false);
chunk = consumeChunk();
if (httpConnection.isComplete())
httpConnection.complete();
@@ -102,7 +102,7 @@ private void fillInterested()
private Content.Chunk consumeChunk()
{
Content.Chunk chunk = this.chunk;
- this.chunk = null;
+ this.chunk = Content.Chunk.next(chunk);
return chunk;
}
@@ -146,7 +146,7 @@ private void receiveNext()
throw new IllegalStateException();
HttpConnectionOverFCGI httpConnection = getHttpConnection();
- boolean setFillInterest = httpConnection.parseAndFill(true);
+ boolean setFillInterest = httpConnection.process(false);
if (!hasContent() && setFillInterest)
httpConnection.fillInterested();
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpSenderOverFCGI.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpSenderOverFCGI.java
index 18e5f5c4e56c..72f6cb50e263 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpSenderOverFCGI.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/transport/internal/HttpSenderOverFCGI.java
@@ -15,7 +15,9 @@
import java.net.URI;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.EnumSet;
+import java.util.List;
import java.util.Locale;
import org.eclipse.jetty.client.HttpClient;
@@ -29,10 +31,11 @@
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.WritableBufferPool;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
public class HttpSenderOverFCGI extends HttpSender
{
@@ -42,7 +45,7 @@ public HttpSenderOverFCGI(HttpChannel channel)
{
super(channel);
HttpClient httpClient = channel.getHttpDestination().getHttpClient();
- this.generator = new ClientGenerator(httpClient.getByteBufferPool(), httpClient.isUseOutputDirectByteBuffers());
+ this.generator = new ClientGenerator(WritableBufferPool.wrap(httpClient.getByteBufferPool()), httpClient.isUseOutputDirectByteBuffers());
}
@Override
@@ -99,18 +102,21 @@ protected void sendHeaders(HttpExchange exchange, ByteBuffer contentBuffer, bool
HttpClientTransportOverFCGI transport = (HttpClientTransportOverFCGI)httpClient.getHttpClientTransport();
transport.customize(request, fcgiHeaders);
- ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
+ List accumulator = new ArrayList<>();
int id = getHttpChannel().getRequest();
if (contentBuffer.hasRemaining() || lastContent)
{
generator.generateRequestHeaders(accumulator, id, fcgiHeaders);
- generator.generateRequestContent(accumulator, id, contentBuffer, lastContent);
+ generator.generateRequestContent(accumulator, id, ReadableBuffer.wrap(contentBuffer), lastContent);
}
else
{
generator.generateRequestHeaders(accumulator, id, fcgiHeaders);
}
- getHttpChannel().flush(accumulator, callback);
+ ReadableBuffer buffer = ReadableBuffer.accumulate(accumulator);
+ accumulator.forEach(ReadableBuffer::release);
+ getHttpChannel().flush(buffer, callback);
+ buffer.release();
}
@Override
@@ -118,10 +124,13 @@ protected void sendContent(HttpExchange exchange, ByteBuffer contentBuffer, bool
{
if (contentBuffer.hasRemaining() || lastContent)
{
- ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
+ List accumulator = new ArrayList<>();
int request = getHttpChannel().getRequest();
- generator.generateRequestContent(accumulator, request, contentBuffer, lastContent);
- getHttpChannel().flush(accumulator, callback);
+ generator.generateRequestContent(accumulator, request, ReadableBuffer.wrap(contentBuffer), lastContent);
+ ReadableBuffer buffer = ReadableBuffer.accumulate(accumulator);
+ accumulator.forEach(ReadableBuffer::release);
+ getHttpChannel().flush(buffer, callback);
+ buffer.release();
}
else
{
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java
index 1511303a6a9c..cbc70401aa52 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ClientGenerator.java
@@ -13,7 +13,6 @@
package org.eclipse.jetty.fcgi.generator;
-import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -22,9 +21,9 @@
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
-import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.RetainableByteBuffer;
-import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.io.WritableBufferPool;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
public class ClientGenerator extends Generator
{
@@ -33,17 +32,17 @@ public class ClientGenerator extends Generator
// 0x7F_FF - 4 (the 4 is to make room for the name (or value) length).
public static final int MAX_PARAM_LENGTH = 0x7F_FF - 4;
- public ClientGenerator(ByteBufferPool bufferPool)
+ public ClientGenerator(WritableBufferPool bufferPool)
{
this(bufferPool, true);
}
- public ClientGenerator(ByteBufferPool bufferPool, boolean useDirectByteBuffers)
+ public ClientGenerator(WritableBufferPool bufferPool, boolean useDirectByteBuffers)
{
super(bufferPool, useDirectByteBuffers);
}
- public void generateRequestHeaders(ByteBufferPool.Accumulator accumulator, int request, HttpFields fields)
+ public void generateRequestHeaders(List accumulator, int request, HttpFields fields)
{
request &= 0xFF_FF;
@@ -79,31 +78,25 @@ public void generateRequestHeaders(ByteBufferPool.Accumulator accumulator, int r
// One FCGI_BEGIN_REQUEST + N FCGI_PARAMS + one last FCGI_PARAMS
- RetainableByteBuffer beginBuffer = getByteBufferPool().acquire(16, isUseDirectByteBuffers());
- accumulator.append(beginBuffer);
- ByteBuffer beginByteBuffer = beginBuffer.getByteBuffer();
- BufferUtil.clearToFill(beginByteBuffer);
+ WritableBuffer beginBuffer = getBufferPool().acquire(16, isUseDirectByteBuffers());
// Generate the FCGI_BEGIN_REQUEST frame
- beginByteBuffer.putInt(0x01_01_00_00 + request);
- beginByteBuffer.putInt(0x00_08_00_00);
+ beginBuffer.putInt(0x01_01_00_00 + request);
+ beginBuffer.putInt(0x00_08_00_00);
// Hardcode RESPONDER role and KEEP_ALIVE flag
- beginByteBuffer.putLong(0x00_01_01_00_00_00_00_00L);
- BufferUtil.flipToFlush(beginByteBuffer, 0);
+ beginBuffer.putLong(0x00_01_01_00_00_00_00_00L);
+ accumulator.add(beginBuffer.toReadable());
int index = 0;
while (fieldsLength > 0)
{
int capacity = 8 + Math.min(maxCapacity, fieldsLength);
- RetainableByteBuffer buffer = getByteBufferPool().acquire(capacity, isUseDirectByteBuffers());
- accumulator.append(buffer);
- ByteBuffer byteBuffer = buffer.getByteBuffer();
- BufferUtil.clearToFill(byteBuffer);
+ WritableBuffer buffer = getBufferPool().acquire(capacity, isUseDirectByteBuffers());
// Generate the FCGI_PARAMS frame
- byteBuffer.putInt(0x01_04_00_00 + request);
- byteBuffer.putShort((short)0);
- byteBuffer.putShort((short)0);
+ buffer.putInt(0x01_04_00_00 + request);
+ buffer.putShort((short)0);
+ buffer.putShort((short)0);
capacity -= 8;
int length = 0;
@@ -118,10 +111,10 @@ public void generateRequestHeaders(ByteBufferPool.Accumulator accumulator, int r
if (required > capacity)
break;
- putParamLength(byteBuffer, nameLength);
- putParamLength(byteBuffer, valueLength);
- byteBuffer.put(nameBytes);
- byteBuffer.put(valueBytes);
+ putParamLength(buffer, nameLength);
+ putParamLength(buffer, valueLength);
+ buffer.putBytes(nameBytes);
+ buffer.putBytes(valueBytes);
length += required;
fieldsLength -= required;
@@ -129,22 +122,19 @@ public void generateRequestHeaders(ByteBufferPool.Accumulator accumulator, int r
index += 2;
}
- byteBuffer.putShort(4, (short)length);
- BufferUtil.flipToFlush(byteBuffer, 0);
+ buffer.putShort(4, (short)length);
+ accumulator.add(buffer.toReadable());
}
- RetainableByteBuffer lastBuffer = getByteBufferPool().acquire(8, isUseDirectByteBuffers());
- accumulator.append(lastBuffer);
- ByteBuffer lastByteBuffer = lastBuffer.getByteBuffer();
- BufferUtil.clearToFill(lastByteBuffer);
+ WritableBuffer lastBuffer = getBufferPool().acquire(8, isUseDirectByteBuffers());
// Generate the last FCGI_PARAMS frame
- lastByteBuffer.putInt(0x01_04_00_00 + request);
- lastByteBuffer.putInt(0x00_00_00_00);
- BufferUtil.flipToFlush(lastByteBuffer, 0);
+ lastBuffer.putInt(0x01_04_00_00 + request);
+ lastBuffer.putInt(0x00_00_00_00);
+ accumulator.add(lastBuffer.toReadable());
}
- private int putParamLength(ByteBuffer buffer, int length)
+ private int putParamLength(WritableBuffer buffer, int length)
{
int result = bytesForLength(length);
if (result == 4)
@@ -159,7 +149,7 @@ private int bytesForLength(int length)
return length > 127 ? 4 : 1;
}
- public void generateRequestContent(ByteBufferPool.Accumulator accumulator, int request, ByteBuffer content, boolean lastContent)
+ public void generateRequestContent(List accumulator, int request, ReadableBuffer content, boolean lastContent)
{
generateContent(accumulator, request, content, lastContent, FCGI.FrameType.STDIN);
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java
index 2536f74a75da..788c7ff1b330 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Flusher.java
@@ -14,18 +14,17 @@
package org.eclipse.jetty.fcgi.generator;
import java.io.IOException;
-import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
-import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.ExceptionUtil;
import org.eclipse.jetty.util.IteratingCallback;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -69,9 +68,9 @@ public Callback cancel(Throwable cause)
return cancelSendException.join();
}
- public void flush(ByteBufferPool.Accumulator accumulator, Callback callback)
+ public void flush(ReadableBuffer buffer, Callback callback)
{
- offer(new Entry(accumulator, callback));
+ offer(new Entry(buffer, callback));
flushCallback.iterate();
}
@@ -79,6 +78,7 @@ private void offer(Entry entry)
{
try (AutoLock ignored = lock.lock())
{
+ entry.buffer().retain();
queue.offer(entry);
}
}
@@ -93,7 +93,7 @@ private Entry poll()
public void shutdown()
{
- flush(null, Callback.from(() ->
+ flush(ReadableBuffer.EMPTY, Callback.from(() ->
{
if (LOG.isDebugEnabled())
LOG.debug("Shutting down {}", endPoint);
@@ -157,8 +157,8 @@ protected Action process() throws Exception
}
active = entry;
- List buffers = entry.accumulator.getByteBuffers();
- endPoint.write(this, buffers.toArray(ByteBuffer[]::new));
+
+ endPoint.write(entry.buffer(), this);
return Action.SCHEDULED;
}
@@ -173,7 +173,7 @@ private Callback resetCallback()
{
Flusher.Entry entry = active;
active = null;
- return Callback.from(entry.callback, entry::release);
+ return Callback.from(entry.callback, () -> entry.buffer().release());
}
@Override
@@ -196,8 +196,8 @@ protected void onSuccess()
{
if (active != null)
{
- active.release();
- active.succeeded();
+ active.callback().succeeded();
+ active.buffer().release();
active = null;
}
}
@@ -206,13 +206,13 @@ protected void onSuccess()
public void onFailure(Throwable cause)
{
if (active != null)
- active.failed(cause);
+ active.callback().failed(cause);
List entries;
try (AutoLock ignored = lock.lock())
{
entries = new ArrayList<>(queue);
}
- entries.forEach(entry -> entry.failed(cause));
+ entries.forEach(entry -> entry.callback().failed(cause));
}
@Override
@@ -220,7 +220,7 @@ protected void onCompleteFailure(Throwable cause)
{
if (active != null)
{
- active.release();
+ active.buffer().release();
active = null;
}
List entries;
@@ -229,7 +229,7 @@ protected void onCompleteFailure(Throwable cause)
entries = new ArrayList<>(queue);
queue.clear();
}
- entries.forEach(Entry::release);
+ entries.forEach(e -> e.buffer().release());
}
@Override
@@ -241,22 +241,7 @@ public InvocationType getInvocationType()
}
}
- private record Entry(ByteBufferPool.Accumulator accumulator, Callback callback)
+ private record Entry(ReadableBuffer buffer, Callback callback)
{
- public void succeeded()
- {
- callback.succeeded();
- }
-
- public void failed(Throwable x)
- {
- callback.failed(x);
- }
-
- private void release()
- {
- if (accumulator != null)
- accumulator.release();
- }
}
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java
index 39f6a067004b..46631aeedc3b 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/Generator.java
@@ -13,27 +13,28 @@
package org.eclipse.jetty.fcgi.generator;
-import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Objects;
import org.eclipse.jetty.fcgi.FCGI;
-import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.RetainableByteBuffer;
-import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.io.WritableBufferPool;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
public class Generator
{
public static final int MAX_CONTENT_LENGTH = 0xFF_FF;
- private final ByteBufferPool bufferPool;
+ private final WritableBufferPool bufferPool;
private final boolean useDirectByteBuffers;
- public Generator(ByteBufferPool bufferPool, boolean useDirectByteBuffers)
+ public Generator(WritableBufferPool bufferPool, boolean useDirectByteBuffers)
{
this.bufferPool = bufferPool;
this.useDirectByteBuffers = useDirectByteBuffers;
}
- public ByteBufferPool getByteBufferPool()
+ public WritableBufferPool getBufferPool()
{
return bufferPool;
}
@@ -43,39 +44,36 @@ public boolean isUseDirectByteBuffers()
return useDirectByteBuffers;
}
- protected void generateContent(ByteBufferPool.Accumulator accumulator, int id, ByteBuffer content, boolean lastContent, FCGI.FrameType frameType)
+ protected void generateContent(List accumulator, int id, ReadableBuffer content, boolean lastContent, FCGI.FrameType frameType)
{
id &= 0xFF_FF;
- int contentLength = content == null ? 0 : content.remaining();
+ content = Objects.requireNonNullElse(content, ReadableBuffer.EMPTY);
+ long contentLength = content.remaining();
while (contentLength > 0 || lastContent)
{
- RetainableByteBuffer buffer = getByteBufferPool().acquire(8, isUseDirectByteBuffers());
- accumulator.append(buffer);
- ByteBuffer byteBuffer = buffer.getByteBuffer();
- BufferUtil.clearToFill(byteBuffer);
+ WritableBuffer buffer = getBufferPool().acquire(8, isUseDirectByteBuffers());
// Generate the frame header.
- byteBuffer.put((byte)0x01);
- byteBuffer.put((byte)frameType.code);
- byteBuffer.putShort((short)id);
- int length = Math.min(MAX_CONTENT_LENGTH, contentLength);
- byteBuffer.putShort((short)length);
- byteBuffer.putShort((short)0);
- BufferUtil.flipToFlush(byteBuffer, 0);
+ buffer.put((byte)0x01);
+ buffer.put((byte)frameType.code);
+ buffer.putShort((short)id);
+ long length = Math.min(MAX_CONTENT_LENGTH, contentLength);
+ buffer.putShort((short)length);
+ buffer.putShort((short)0);
+ ReadableBuffer readable = buffer.toReadable();
+ accumulator.add(readable);
if (contentLength == 0)
break;
// Slice the content to avoid copying.
- int limit = content.limit();
- content.limit(content.position() + length);
- ByteBuffer slice = content.slice();
- // Don't recycle the slice.
- accumulator.append(RetainableByteBuffer.wrap(slice));
- content.position(content.limit());
- content.limit(limit);
+ ReadableBuffer slice = content.slice(content.position(), length);
+ accumulator.add(slice);
+
+ // Consume the content.
+ content.position(content.position() + length);
contentLength -= length;
}
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java
index fca6b2731112..ed1a7cea1cdf 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java
@@ -23,9 +23,10 @@
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
-import org.eclipse.jetty.io.ByteBufferPool;
-import org.eclipse.jetty.io.RetainableByteBuffer;
+import org.eclipse.jetty.io.WritableBufferPool;
import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
public class ServerGenerator extends Generator
{
@@ -35,18 +36,18 @@ public class ServerGenerator extends Generator
private final boolean sendStatus200;
- public ServerGenerator(ByteBufferPool bufferPool)
+ public ServerGenerator(WritableBufferPool bufferPool)
{
this(bufferPool, true, true);
}
- public ServerGenerator(ByteBufferPool bufferPool, boolean useDirectByteBuffers, boolean sendStatus200)
+ public ServerGenerator(WritableBufferPool bufferPool, boolean useDirectByteBuffers, boolean sendStatus200)
{
super(bufferPool, useDirectByteBuffers);
this.sendStatus200 = sendStatus200;
}
- public void generateResponseHeaders(ByteBufferPool.Accumulator accumulator, int request, int code, String reason, HttpFields fields)
+ public void generateResponseHeaders(List accumulator, int request, int code, String reason, HttpFields fields)
{
request &= 0xFF_FF;
@@ -83,48 +84,45 @@ public void generateResponseHeaders(ByteBufferPool.Accumulator accumulator, int
// End of headers
length += EOL.length;
- ByteBuffer byteBuffer = BufferUtil.allocate(length, isUseDirectByteBuffers());
- BufferUtil.clearToFill(byteBuffer);
-
+ WritableBuffer buffer = getBufferPool().acquire(length, isUseDirectByteBuffers());
for (int i = 0; i < bytes.size(); i += 2)
{
- byteBuffer.put(bytes.get(i)).put(COLON).put(bytes.get(i + 1)).put(EOL);
+ buffer.putBytes(bytes.get(i));
+ buffer.putBytes(COLON);
+ buffer.putBytes(bytes.get(i + 1));
+ buffer.putBytes(EOL);
}
- byteBuffer.put(EOL);
-
- BufferUtil.flipToFlush(byteBuffer, 0);
+ buffer.putBytes(EOL);
- generateContent(accumulator, request, byteBuffer, false, FCGI.FrameType.STDOUT);
+ generateContent(accumulator, request, buffer.toReadable(), false, FCGI.FrameType.STDOUT);
+ buffer.release();
}
- public void generateResponseContent(ByteBufferPool.Accumulator accumulator, int request, ByteBuffer content, boolean lastContent, boolean aborted)
+ public void generateResponseContent(List accumulator, int request, ReadableBuffer content, boolean lastContent, boolean aborted)
{
if (aborted)
{
if (lastContent)
- accumulator.append(generateEndRequest(request, true));
+ accumulator.add(generateEndRequest(request, true));
else
- accumulator.append(RetainableByteBuffer.wrap(BufferUtil.EMPTY_BUFFER));
+ accumulator.add(ReadableBuffer.EMPTY);
}
else
{
generateContent(accumulator, request, content, lastContent, FCGI.FrameType.STDOUT);
if (lastContent)
- accumulator.append(generateEndRequest(request, false));
+ accumulator.add(generateEndRequest(request, false));
}
}
- private RetainableByteBuffer generateEndRequest(int request, boolean aborted)
+ private ReadableBuffer generateEndRequest(int request, boolean aborted)
{
request &= 0xFF_FF;
- RetainableByteBuffer endRequestBuffer = getByteBufferPool().acquire(16, isUseDirectByteBuffers());
- ByteBuffer byteBuffer = endRequestBuffer.getByteBuffer();
- BufferUtil.clearToFill(byteBuffer);
- byteBuffer.putInt(0x01_03_00_00 + request);
- byteBuffer.putInt(0x00_08_00_00);
- byteBuffer.putInt(aborted ? 1 : 0);
- byteBuffer.putInt(0);
- BufferUtil.flipToFlush(byteBuffer, 0);
- return endRequestBuffer;
+ WritableBuffer buffer = getBufferPool().acquire(16, isUseDirectByteBuffers());
+ buffer.putInt(0x01_03_00_00 + request);
+ buffer.putInt(0x00_08_00_00);
+ buffer.putInt(aborted ? 1 : 0);
+ buffer.putInt(0);
+ return buffer.toReadable();
}
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java
index f562a5a7beee..64d56d42c40f 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java
@@ -13,10 +13,9 @@
package org.eclipse.jetty.fcgi.parser;
-import java.nio.ByteBuffer;
-
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.util.NanoTime;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
/**
*
Parser for the BEGIN_REQUEST frame content.
@@ -49,9 +48,9 @@ public long getBeginNanoTime()
}
@Override
- public Result parse(ByteBuffer buffer)
+ public Result parse(ReadableBuffer buffer)
{
- while (buffer.hasRemaining())
+ while (buffer.remaining() > 0)
{
switch (state)
{
@@ -72,7 +71,7 @@ public Result parse(ByteBuffer buffer)
}
case ROLE_BYTES:
{
- int halfShort = buffer.get() & 0xFF;
+ int halfShort = buffer.getAsInt();
role = (role << 8) + halfShort;
if (++cursor == 2)
state = State.FLAGS;
@@ -80,7 +79,7 @@ public Result parse(ByteBuffer buffer)
}
case FLAGS:
{
- flags = buffer.get() & 0xFF;
+ flags = buffer.getAsInt();
state = State.RESERVED;
break;
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java
index aab71ec1a2c0..7347c3ac83e0 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java
@@ -13,11 +13,11 @@
package org.eclipse.jetty.fcgi.parser;
-import java.nio.ByteBuffer;
import java.util.EnumMap;
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
public class ClientParser extends Parser
{
@@ -70,7 +70,7 @@ public boolean onHeaders(int request)
}
@Override
- public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+ public boolean onContent(int request, FCGI.StreamType stream, ReadableBuffer buffer)
{
return listener.onContent(request, stream, buffer);
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java
index dcf0313c3e91..5d46ecb6f882 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java
@@ -13,7 +13,7 @@
package org.eclipse.jetty.fcgi.parser;
-import java.nio.ByteBuffer;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
/**
*
Parser for FastCGI frame content.
@@ -47,7 +47,7 @@ protected ContentParser(HeaderParser headerParser)
* @param buffer the bytes to parse
* @return the result of the parsing
*/
- public abstract Result parse(ByteBuffer buffer);
+ public abstract Result parse(ReadableBuffer buffer);
/**
*
Invoked by the {@link Parser} when the frame content length is zero.
@@ -76,7 +76,7 @@ public enum Result
{
/**
*
Not enough bytes have been provided to the parser
- * with a call to {@link ContentParser#parse(ByteBuffer)}.
+ * with a call to {@link ContentParser#parse(ReadableBuffer)}.
*/
PENDING,
/**
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java
index 675cf922be04..1d7fc88ee30e 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java
@@ -13,7 +13,7 @@
package org.eclipse.jetty.fcgi.parser;
-import java.nio.ByteBuffer;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
/**
*
Parser for the END_REQUEST frame content.
@@ -40,9 +40,9 @@ public EndRequestContentParser(HeaderParser headerParser, Parser.Listener listen
}
@Override
- public Result parse(ByteBuffer buffer)
+ public Result parse(ReadableBuffer buffer)
{
- while (buffer.hasRemaining())
+ while (buffer.remaining() > 0)
{
switch (state)
{
@@ -62,7 +62,7 @@ public Result parse(ByteBuffer buffer)
}
case APPLICATION_BYTES:
{
- int quarterInt = buffer.get() & 0xFF;
+ int quarterInt = buffer.getAsInt();
application = (application << 8) + quarterInt;
if (++cursor == 4)
state = State.PROTOCOL;
@@ -70,7 +70,7 @@ public Result parse(ByteBuffer buffer)
}
case PROTOCOL:
{
- protocol = buffer.get() & 0xFF;
+ protocol = buffer.getAsInt();
state = State.RESERVED;
break;
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java
index 56c5ef544018..fa5d5eeec47b 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java
@@ -13,9 +13,8 @@
package org.eclipse.jetty.fcgi.parser;
-import java.nio.ByteBuffer;
-
import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -52,21 +51,21 @@ public class HeaderParser
* @param buffer the bytes to parse
* @return whether there were enough bytes for a FastCGI header
*/
- public boolean parse(ByteBuffer buffer)
+ public boolean parse(ReadableBuffer buffer)
{
- while (buffer.hasRemaining())
+ while (buffer.remaining() > 0)
{
switch (state)
{
case VERSION:
{
- version = buffer.get() & 0xFF;
+ version = buffer.getAsInt();
state = State.TYPE;
break;
}
case TYPE:
{
- type = buffer.get() & 0xFF;
+ type = buffer.getAsInt();
state = State.REQUEST;
break;
}
@@ -74,7 +73,7 @@ public boolean parse(ByteBuffer buffer)
{
if (buffer.remaining() >= 2)
{
- request = buffer.getShort() & 0xFF_FF;
+ request = buffer.getShortAsInt();
state = State.LENGTH;
}
else
@@ -86,7 +85,7 @@ public boolean parse(ByteBuffer buffer)
}
case REQUEST_BYTES:
{
- int halfShort = buffer.get() & 0xFF;
+ int halfShort = buffer.getAsInt();
request = (request << 8) + halfShort;
if (++cursor == 2)
state = State.LENGTH;
@@ -96,7 +95,7 @@ public boolean parse(ByteBuffer buffer)
{
if (buffer.remaining() >= 2)
{
- length = buffer.getShort() & 0xFF_FF;
+ length = buffer.getShortAsInt();
state = State.PADDING;
}
else
@@ -108,7 +107,7 @@ public boolean parse(ByteBuffer buffer)
}
case LENGTH_BYTES:
{
- int halfShort = buffer.get() & 0xFF;
+ int halfShort = buffer.getAsInt();
length = (length << 8) + halfShort;
if (++cursor == 2)
state = State.PADDING;
@@ -116,7 +115,7 @@ public boolean parse(ByteBuffer buffer)
}
case PADDING:
{
- padding = buffer.get() & 0xFF;
+ padding = buffer.getAsInt();
state = State.RESERVED;
break;
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java
index 4e6cbd119608..1a0425f53e43 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java
@@ -13,11 +13,11 @@
package org.eclipse.jetty.fcgi.parser;
-import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -73,9 +73,9 @@ public ParamsContentParser(HeaderParser headerParser, ServerParser.Listener list
}
@Override
- public Result parse(ByteBuffer buffer)
+ public Result parse(ReadableBuffer buffer)
{
- while (buffer.hasRemaining() || state == State.PARAM)
+ while (buffer.remaining() > 0 || state == State.PARAM)
{
switch (state)
{
@@ -259,7 +259,7 @@ protected boolean onParams()
}
}
- private boolean isLargeLength(ByteBuffer buffer)
+ private boolean isLargeLength(ReadableBuffer buffer)
{
return (buffer.get(buffer.position()) & 0x80) == 0x80;
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java
index cb328073606b..42ce868ddfc5 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java
@@ -13,11 +13,10 @@
package org.eclipse.jetty.fcgi.parser;
-import java.io.EOFException;
-import java.nio.ByteBuffer;
-
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.io.EofException;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -62,7 +61,7 @@ public abstract class Parser
protected final HeaderParser headerParser = new HeaderParser();
private final Listener listener;
private State state = State.INITIAL;
- private int padding;
+ private long padding;
protected Parser(Listener listener)
{
@@ -73,7 +72,7 @@ protected Parser(Listener listener)
* @param buffer the bytes to parse
* @return true if the caller should stop parsing, false if the caller should continue parsing
*/
- public boolean parse(ByteBuffer buffer)
+ public boolean parse(ReadableBuffer buffer)
{
try
{
@@ -83,7 +82,7 @@ public boolean parse(ByteBuffer buffer)
{
case INITIAL ->
{
- if (!buffer.hasRemaining())
+ if (buffer.remaining() == 0)
return false;
state = State.HEADER;
}
@@ -130,13 +129,13 @@ public boolean parse(ByteBuffer buffer)
{
buffer.position(buffer.position() + padding);
reset();
- if (!buffer.hasRemaining())
+ if (buffer.remaining() == 0)
return true;
}
else
{
padding -= buffer.remaining();
- buffer.position(buffer.limit());
+ buffer.position(buffer.position() + buffer.remaining());
return false;
}
}
@@ -146,7 +145,7 @@ public boolean parse(ByteBuffer buffer)
}
catch (Throwable x)
{
- buffer.position(buffer.limit());
+ buffer.position(buffer.position() + buffer.remaining());
listener.onFailure(headerParser.getRequest(), x);
return true;
}
@@ -158,7 +157,7 @@ public boolean eof()
{
if (state == State.INITIAL)
return false;
- Throwable failure = new EOFException();
+ Throwable failure = new EofException();
listener.onFailure(headerParser.getRequest(), failure);
return true;
}
@@ -190,9 +189,9 @@ public default boolean onHeaders(int request)
* @param stream the stream type
* @param buffer the content bytes
* @return true to signal to the parser to stop parsing, false to continue parsing
- * @see Parser#parse(java.nio.ByteBuffer)
+ * @see Parser#parse(ReadableBuffer)
*/
- public default boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+ public default boolean onContent(int request, FCGI.StreamType stream, ReadableBuffer buffer)
{
return false;
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java
index 85eec1b85bad..01ba72a46202 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java
@@ -26,6 +26,7 @@
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,7 +59,7 @@ public boolean noContent()
}
@Override
- protected boolean onContent(ByteBuffer buffer)
+ protected boolean onContent(ReadableBuffer buffer)
{
return parser.parse(buffer);
}
@@ -94,19 +95,26 @@ private void reset()
stalled = false;
}
- public boolean parse(ByteBuffer buffer)
+ public boolean parse(ReadableBuffer buffer)
{
- int remaining = buffer.remaining();
+ long remaining = buffer.remaining();
while (remaining > 0)
{
if (LOG.isDebugEnabled())
- LOG.debug("Response {} {}, state {} {}", getRequest(), FCGI.StreamType.STD_OUT, state, BufferUtil.toDetailString(buffer));
+ LOG.debug("Response {} {}, state {} {}", getRequest(), FCGI.StreamType.STD_OUT, state, buffer);
switch (state)
{
case HEADERS ->
{
- if (httpParser.parseNext(buffer))
+ // TODO: use directly the ReadableBuffer in httpParser.parseNext().
+ long position = buffer.position();
+ ByteBuffer byteBuffer = BufferUtil.toBuffer(buffer, false);
+ long length = byteBuffer.remaining();
+ boolean handle = httpParser.parseNext(byteBuffer);
+ long consumed = length - byteBuffer.remaining();
+ buffer.position(position + consumed);
+ if (handle)
{
state = State.CONTENT_MODE;
if (stalled)
@@ -127,15 +135,24 @@ public boolean parse(ByteBuffer buffer)
}
case RAW_CONTENT ->
{
- ByteBuffer content = buffer.asReadOnlyBuffer();
- buffer.position(buffer.limit());
- if (notifyContent(content))
+ ReadableBuffer slice = buffer.slice();
+ buffer.position(buffer.position() + slice.remaining());
+ boolean handle = notifyContent(slice);
+ slice.release();
+ if (handle)
return true;
remaining = 0;
}
case HTTP_CONTENT ->
{
- if (httpParser.parseNext(buffer))
+ // TODO: use directly the ReadableBuffer in httpParser.parseNext().
+ long position = buffer.position();
+ ByteBuffer byteBuffer = BufferUtil.toBuffer(buffer, false);
+ long length = byteBuffer.remaining();
+ boolean handle = httpParser.parseNext(byteBuffer);
+ long consumed = length - byteBuffer.remaining();
+ buffer.position(position + consumed);
+ if (handle)
return true;
remaining = buffer.remaining();
}
@@ -261,10 +278,10 @@ public boolean headerComplete()
@Override
public boolean content(ByteBuffer buffer)
{
- return notifyContent(buffer);
+ return notifyContent(ReadableBuffer.wrap(buffer));
}
- private boolean notifyContent(ByteBuffer buffer)
+ private boolean notifyContent(ReadableBuffer buffer)
{
try
{
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java
index f022b376114e..c16fc53ae777 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java
@@ -16,6 +16,7 @@
import java.nio.ByteBuffer;
import org.eclipse.jetty.fcgi.FCGI;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -30,7 +31,7 @@ public class StreamContentParser extends ContentParser
private final FCGI.StreamType streamType;
private final Parser.Listener listener;
private State state = State.LENGTH;
- private int contentLength;
+ private long contentLength;
public StreamContentParser(HeaderParser headerParser, FCGI.StreamType streamType, Parser.Listener listener)
{
@@ -40,9 +41,9 @@ public StreamContentParser(HeaderParser headerParser, FCGI.StreamType streamType
}
@Override
- public Result parse(ByteBuffer buffer)
+ public Result parse(ReadableBuffer buffer)
{
- while (buffer.hasRemaining())
+ while (buffer.remaining() > 0)
{
switch (state)
{
@@ -54,13 +55,14 @@ public Result parse(ByteBuffer buffer)
}
case CONTENT:
{
- int length = Math.min(contentLength, buffer.remaining());
- ByteBuffer slice = buffer.slice(buffer.position(), length);
+ long length = Math.min(contentLength, buffer.remaining());
+ ReadableBuffer slice = buffer.slice(buffer.position(), length);
// Only parse the content of this FCGI frame.
boolean result = onContent(slice);
// Not all the content may have been parsed.
- int consumed = length - slice.remaining();
+ long consumed = length - slice.remaining();
buffer.position(buffer.position() + consumed);
+ slice.release();
contentLength -= consumed;
if (contentLength <= 0)
state = State.EOF;
@@ -97,13 +99,12 @@ public boolean noContent()
}
}
- protected boolean onContent(ByteBuffer buffer)
+ protected boolean onContent(ReadableBuffer buffer)
{
+ long limit = buffer.position() + buffer.remaining();
try
{
- ByteBuffer content = buffer.asReadOnlyBuffer();
- buffer.position(buffer.limit());
- return listener.onContent(getRequest(), streamType, content);
+ return listener.onContent(getRequest(), streamType, buffer);
}
catch (Throwable x)
{
@@ -111,6 +112,10 @@ protected boolean onContent(ByteBuffer buffer)
LOG.debug("Exception while invoking listener {}", listener, x);
return false;
}
+ finally
+ {
+ buffer.position(limit);
+ }
}
protected void end(int request)
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java
index 90359c95d815..08a77378e371 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java
@@ -13,20 +13,22 @@
package org.eclipse.jetty.fcgi.generator;
-import java.nio.ByteBuffer;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.parser.ServerParser;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.io.ArrayByteBufferPool;
-import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.WritableBufferPool;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
public class ClientGeneratorTest
{
@@ -59,9 +61,9 @@ public void testGenerateRequestHeaders()
String longLongValue = new String(chars);
fields.put(new HttpField(longLongName, longLongValue));
- ByteBufferPool bufferPool = new ArrayByteBufferPool();
+ WritableBufferPool bufferPool = WritableBufferPool.wrap(new ArrayByteBufferPool());
ClientGenerator generator = new ClientGenerator(bufferPool);
- ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
+ List accumulator = new ArrayList<>();
int id = 13;
generator.generateRequestHeaders(accumulator, id, fields);
@@ -117,28 +119,28 @@ public boolean onHeaders(int request)
}
});
- for (ByteBuffer buffer : accumulator.getByteBuffers())
- {
- parser.parse(buffer);
- assertFalse(buffer.hasRemaining());
- }
+ ReadableBuffer buffer = ReadableBuffer.accumulate(accumulator);
+ accumulator.forEach(ReadableBuffer::release);
+
+ parser.parse(buffer);
+ assertEquals(0, buffer.remaining());
assertEquals(value, params.get());
- // Parse again byte by byte
+ // Parse again byte by byte.
params.set(1);
- for (ByteBuffer buffer : accumulator.getByteBuffers())
+ buffer.position(0);
+ while (buffer.remaining() > 0)
{
- buffer.flip();
- while (buffer.hasRemaining())
- {
- parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()}));
- }
+ ReadableBuffer slice = buffer.slice(buffer.position(), 1);
+ buffer.position(buffer.position() + 1);
+ parser.parse(slice);
+ slice.release();
}
assertEquals(value, params.get());
- accumulator.release();
+ buffer.release();
}
@Test
@@ -155,19 +157,19 @@ public void testGenerateLargeRequestContent() throws Exception
private void testGenerateRequestContent(int contentLength) throws Exception
{
- ByteBuffer content = ByteBuffer.allocate(contentLength);
+ ReadableBuffer content = ReadableBuffer.allocate(contentLength, false);
- ByteBufferPool bufferPool = new ArrayByteBufferPool();
+ WritableBufferPool bufferPool = WritableBufferPool.wrap(new ArrayByteBufferPool());
ClientGenerator generator = new ClientGenerator(bufferPool);
- ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
+ List accumulator = new ArrayList<>();
int id = 13;
generator.generateRequestContent(accumulator, id, content, true);
- AtomicInteger totalLength = new AtomicInteger();
+ AtomicLong totalLength = new AtomicLong();
ServerParser parser = new ServerParser(new ServerParser.Listener()
{
@Override
- public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+ public boolean onContent(int request, FCGI.StreamType stream, ReadableBuffer buffer)
{
assertEquals(id, request);
totalLength.addAndGet(buffer.remaining());
@@ -183,22 +185,23 @@ public boolean onEnd(int request)
}
});
- for (ByteBuffer buffer : accumulator.getByteBuffers())
- {
- parser.parse(buffer);
- assertFalse(buffer.hasRemaining());
- }
+ ReadableBuffer buffer = ReadableBuffer.accumulate(accumulator);
+ accumulator.forEach(ReadableBuffer::release);
+
+ parser.parse(buffer);
+ assertEquals(0, buffer.remaining());
- // Parse again one byte at a time
- for (ByteBuffer buffer : accumulator.getByteBuffers())
+ // Parse again one byte at a time.
+ buffer.position(0);
+ while (buffer.remaining() > 0)
{
- buffer.flip();
- while (buffer.hasRemaining())
- {
- parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()}));
- }
+ ReadableBuffer slice = buffer.slice(buffer.position(), 1);
+ buffer.position(buffer.position() + 1);
+ parser.parse(slice);
+ slice.release();
}
- accumulator.release();
+ buffer.release();
+ content.release();
}
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java
index e61ecff85337..f9289f1ad351 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java
@@ -13,19 +13,23 @@
package org.eclipse.jetty.fcgi.parser;
-import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
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.AtomicLong;
import org.eclipse.jetty.fcgi.FCGI;
import org.eclipse.jetty.fcgi.generator.ServerGenerator;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.io.ArrayByteBufferPool;
-import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.io.WritableBufferPool;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
@@ -48,9 +52,9 @@ public void testParseResponseHeaders()
String contentTypeValue = "text/html;charset=utf-8";
fields.put(contentTypeName, contentTypeValue);
- ByteBufferPool bufferPool = new ArrayByteBufferPool();
+ WritableBufferPool bufferPool = WritableBufferPool.wrap(new ArrayByteBufferPool());
ServerGenerator generator = new ServerGenerator(bufferPool);
- ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
+ List accumulator = new ArrayList<>();
generator.generateResponseHeaders(accumulator, id, statusCode, statusMessage, fields);
// Use the fundamental theorem of arithmetic to test the results.
@@ -94,15 +98,15 @@ public boolean onHeaders(int request)
}
});
- for (ByteBuffer buffer : accumulator.getByteBuffers())
+ for (ReadableBuffer buffer : accumulator)
{
parser.parse(buffer);
- assertFalse(buffer.hasRemaining());
+ assertFalse(buffer.remaining() > 0);
}
assertEquals(value, params.get());
- accumulator.release();
+ accumulator.forEach(ReadableBuffer::release);
}
@Test
@@ -112,9 +116,9 @@ public void testParseNoResponseContent()
HttpFields fields = HttpFields.build()
.put("Content-Length", "0");
- ByteBufferPool bufferPool = new ArrayByteBufferPool();
+ WritableBufferPool bufferPool = WritableBufferPool.wrap(new ArrayByteBufferPool());
ServerGenerator generator = new ServerGenerator(bufferPool);
- ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
+ List accumulator = new ArrayList<>();
generator.generateResponseHeaders(accumulator, id, 200, "OK", fields);
generator.generateResponseContent(accumulator, id, null, true, false);
@@ -122,7 +126,7 @@ public void testParseNoResponseContent()
ClientParser parser = new ClientParser(new ClientParser.Listener()
{
@Override
- public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+ public boolean onContent(int request, FCGI.StreamType stream, ReadableBuffer buffer)
{
assertEquals(id, request);
verifier.addAndGet(2);
@@ -138,15 +142,15 @@ public boolean onEnd(int request)
}
});
- for (ByteBuffer buffer : accumulator.getByteBuffers())
+ for (ReadableBuffer buffer : accumulator)
{
parser.parse(buffer);
- assertFalse(buffer.hasRemaining());
+ assertFalse(buffer.remaining() > 0);
}
assertEquals(3, verifier.get());
- accumulator.release();
+ accumulator.forEach(ReadableBuffer::release);
}
@Test
@@ -155,17 +159,17 @@ public void testParseSmallResponseContent()
int id = 13;
HttpFields.Mutable fields = HttpFields.build();
- ByteBuffer content = ByteBuffer.wrap(new byte[1024]);
- int contentLength = content.remaining();
+ ReadableBuffer content = ReadableBuffer.allocate(1024, false);
+ long contentLength = content.remaining();
int code = 200;
String contentTypeName = "Content-Length";
String contentTypeValue = String.valueOf(contentLength);
fields.put(contentTypeName, contentTypeValue);
- ByteBufferPool bufferPool = new ArrayByteBufferPool();
+ WritableBufferPool bufferPool = WritableBufferPool.wrap(new ArrayByteBufferPool());
ServerGenerator generator = new ServerGenerator(bufferPool);
- ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
+ List accumulator = new ArrayList<>();
generator.generateResponseHeaders(accumulator, id, code, "OK", fields);
generator.generateResponseContent(accumulator, id, content, true, false);
@@ -173,7 +177,7 @@ public void testParseSmallResponseContent()
ClientParser parser = new ClientParser(new ClientParser.Listener()
{
@Override
- public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+ public boolean onContent(int request, FCGI.StreamType stream, ReadableBuffer buffer)
{
assertEquals(id, request);
assertEquals(contentLength, buffer.remaining());
@@ -190,15 +194,15 @@ public boolean onEnd(int request)
}
});
- for (ByteBuffer buffer : accumulator.getByteBuffers())
+ for (ReadableBuffer buffer : accumulator)
{
parser.parse(buffer);
- assertFalse(buffer.hasRemaining());
+ assertFalse(buffer.remaining() > 0);
}
assertEquals(5, verifier.get());
- accumulator.release();
+ accumulator.forEach(ReadableBuffer::release);
}
@Test
@@ -207,26 +211,26 @@ public void testParseLargeResponseContent()
int id = 13;
HttpFields.Mutable fields = HttpFields.build();
- ByteBuffer content = ByteBuffer.wrap(new byte[128 * 1024]);
- int contentLength = content.remaining();
+ ReadableBuffer content = ReadableBuffer.allocate(128 * 1024, false);
+ long contentLength = content.remaining();
int code = 200;
String contentTypeName = "Content-Length";
String contentTypeValue = String.valueOf(contentLength);
fields.put(contentTypeName, contentTypeValue);
- ByteBufferPool bufferPool = new ArrayByteBufferPool();
+ WritableBufferPool bufferPool = WritableBufferPool.wrap(new ArrayByteBufferPool());
ServerGenerator generator = new ServerGenerator(bufferPool);
- ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
+ List accumulator = new ArrayList<>();
generator.generateResponseHeaders(accumulator, id, code, "OK", fields);
generator.generateResponseContent(accumulator, id, content, true, false);
- AtomicInteger totalLength = new AtomicInteger();
+ AtomicLong totalLength = new AtomicLong();
AtomicBoolean verifier = new AtomicBoolean();
ClientParser parser = new ClientParser(new ClientParser.Listener()
{
@Override
- public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer)
+ public boolean onContent(int request, FCGI.StreamType stream, ReadableBuffer buffer)
{
assertEquals(id, request);
totalLength.addAndGet(buffer.remaining());
@@ -243,15 +247,15 @@ public boolean onEnd(int request)
}
});
- for (ByteBuffer buffer : accumulator.getByteBuffers())
+ for (ReadableBuffer buffer : accumulator)
{
parser.parse(buffer);
- assertFalse(buffer.hasRemaining());
+ assertFalse(buffer.remaining() > 0);
}
assertTrue(verifier.get());
- accumulator.release();
+ accumulator.forEach(ReadableBuffer::release);
}
@ParameterizedTest
@@ -271,16 +275,17 @@ public void onFailure(int request, Throwable failure)
});
// See Parser for the FCGI record structure.
- ByteBuffer byteBuffer = ByteBuffer.allocate(8)
- .put((byte)1)
- .put((byte)frameType)
- .putShort((short)13)
- .putShort((short)0)
- .put((byte)0)
- .put((byte)0)
- .flip();
- parser.parse(byteBuffer);
- assertFalse(byteBuffer.hasRemaining());
+ WritableBuffer buffer = WritableBuffer.allocate(8, false);
+ buffer.put((byte)1);
+ buffer.put((byte)frameType);
+ buffer.putShort((short)13);
+ buffer.putShort((short)0);
+ buffer.put((byte)0);
+ buffer.put((byte)0);
+ ReadableBuffer readable = buffer.toReadable();
+ parser.parse(readable);
+ assertEquals(0, readable.remaining());
+ buffer.release();
assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
}
@@ -302,17 +307,18 @@ public void onFailure(int request, Throwable failure)
});
// See Parser for the FCGI record structure.
- ByteBuffer byteBuffer = ByteBuffer.allocate(8)
- .put((byte)1)
- .put((byte)frameType)
- .putShort((short)13)
- .putShort((short)0)
- .put((byte)0)
- .put((byte)0)
- .flip();
- parser.parse(byteBuffer);
- assertFalse(byteBuffer.hasRemaining());
-
+ WritableBuffer buffer = WritableBuffer.allocate(8, false);
+ buffer.put((byte)1);
+ buffer.put((byte)frameType);
+ buffer.putShort((short)13);
+ buffer.putShort((short)0);
+ buffer.put((byte)0);
+ buffer.put((byte)0);
+ ReadableBuffer readable = buffer.toReadable();
+ parser.parse(readable);
+ assertEquals(0, readable.remaining());
+ buffer.release();
+
assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
}
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java
index 575fdd44d912..d4951e57a8db 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/HttpStreamOverFCGI.java
@@ -14,6 +14,8 @@
package org.eclipse.jetty.fcgi.server.internal;
import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeoutException;
@@ -21,23 +23,24 @@
import org.eclipse.jetty.fcgi.generator.Flusher;
import org.eclipse.jetty.fcgi.generator.ServerGenerator;
import org.eclipse.jetty.http.HostPortHttpField;
+import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
+import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
-import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.HttpStream;
-import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.slf4j.Logger;
@@ -157,7 +160,8 @@ private HttpField convertHeader(HttpField field)
public Content.Chunk read()
{
if (_chunk == null)
- _connection.parseAndFill();
+ _connection.process(false);
+
Content.Chunk chunk = _chunk;
_chunk = Content.Chunk.next(chunk);
return chunk;
@@ -169,7 +173,7 @@ public void demand()
if (_chunk != null)
return;
- _connection.parseAndFill();
+ _connection.process(false);
if (_chunk != null)
{
@@ -204,6 +208,13 @@ else if (!_chunk.isLast())
throw new IllegalStateException();
}
+ public void onFailure(Throwable failure)
+ {
+ Runnable task = getHttpChannel().onFailure(failure);
+ if (task != null)
+ task.run();
+ }
+
@Override
public void prepareResponse(HttpFields.Mutable headers)
{
@@ -213,10 +224,10 @@ public void prepareResponse(HttpFields.Mutable headers)
@Override
public void send(MetaData.Request request, MetaData.Response response, boolean last, ByteBuffer byteBuffer, Callback callback)
{
- ByteBuffer content = byteBuffer != null ? byteBuffer : BufferUtil.EMPTY_BUFFER;
+ ReadableBuffer content = ReadableBuffer.wrap(byteBuffer);
if (LOG.isDebugEnabled())
- LOG.debug("send {} l={} {} {}", request, last, BufferUtil.toDetailString(byteBuffer), this);
+ LOG.debug("send {} l={} {} {}", request, last, content, this);
boolean head = HttpMethod.HEAD.is(request.getMethod());
if (response != null)
{
@@ -229,9 +240,12 @@ public void send(MetaData.Request request, MetaData.Response response, boolean l
{
if (last)
{
- ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
- generateResponseContent(accumulator, true, BufferUtil.EMPTY_BUFFER);
- flusher.flush(accumulator, callback);
+ List accumulator = new ArrayList<>();
+ generateResponseContent(accumulator, true, ReadableBuffer.EMPTY);
+ ReadableBuffer buffer = ReadableBuffer.accumulate(accumulator);
+ accumulator.forEach(ReadableBuffer::release);
+ flusher.flush(buffer, callback);
+ buffer.release();
}
else
{
@@ -241,9 +255,12 @@ public void send(MetaData.Request request, MetaData.Response response, boolean l
}
else
{
- ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
+ List accumulator = new ArrayList<>();
generateResponseContent(accumulator, last, content);
- flusher.flush(accumulator, callback);
+ ReadableBuffer buffer = ReadableBuffer.accumulate(accumulator);
+ accumulator.forEach(ReadableBuffer::release);
+ flusher.flush(buffer, callback);
+ buffer.release();
}
if (last && _shutdown)
@@ -257,7 +274,7 @@ public Runnable cancelSend(Throwable cause, Callback appCallback)
return () -> Callback.combine(_connection.getFlusher().cancel(cause), appCallback).failed(cause);
}
- private void commit(MetaData.Response info, boolean head, boolean last, ByteBuffer content, Callback callback)
+ private void commit(MetaData.Response info, boolean head, boolean last, ReadableBuffer content, Callback callback)
{
if (LOG.isDebugEnabled())
LOG.debug("commit {} {} l={}", this, info, last);
@@ -266,39 +283,50 @@ private void commit(MetaData.Response info, boolean head, boolean last, ByteBuff
boolean shutdown = _shutdown = info.getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString());
- ByteBufferPool.Accumulator accumulator = new ByteBufferPool.Accumulator();
Flusher flusher = _connection.getFlusher();
if (head)
{
if (last)
{
+ List accumulator = new ArrayList<>();
generateResponseHeaders(accumulator, info);
- generateResponseContent(accumulator, true, BufferUtil.EMPTY_BUFFER);
- flusher.flush(accumulator, callback);
+ generateResponseContent(accumulator, true, ReadableBuffer.EMPTY);
+ ReadableBuffer buffer = ReadableBuffer.accumulate(accumulator);
+ accumulator.forEach(ReadableBuffer::release);
+ flusher.flush(buffer, callback);
+ buffer.release();
}
else
{
+ List accumulator = new ArrayList<>();
generateResponseHeaders(accumulator, info);
- flusher.flush(accumulator, callback);
+ ReadableBuffer buffer = ReadableBuffer.accumulate(accumulator);
+ accumulator.forEach(ReadableBuffer::release);
+ flusher.flush(buffer, callback);
+ buffer.release();
}
}
else
{
+ List accumulator = new ArrayList<>();
generateResponseHeaders(accumulator, info);
generateResponseContent(accumulator, last, content);
- flusher.flush(accumulator, callback);
+ ReadableBuffer buffer = ReadableBuffer.accumulate(accumulator);
+ accumulator.forEach(ReadableBuffer::release);
+ flusher.flush(buffer, callback);
+ buffer.release();
}
if (last && shutdown)
flusher.shutdown();
}
- private void generateResponseHeaders(ByteBufferPool.Accumulator accumulator, MetaData.Response info)
+ private void generateResponseHeaders(List accumulator, MetaData.Response info)
{
_generator.generateResponseHeaders(accumulator, _id, info.getStatus(), info.getReason(), info.getHttpFields());
}
- private void generateResponseContent(ByteBufferPool.Accumulator accumulator, boolean last, ByteBuffer buffer)
+ private void generateResponseContent(List accumulator, boolean last, ReadableBuffer buffer)
{
_generator.generateResponseContent(accumulator, _id, buffer, last, _aborted);
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java
index b0460dd7c629..22da03a583e4 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/internal/ServerFCGIConnection.java
@@ -13,7 +13,6 @@
package org.eclipse.jetty.fcgi.server.internal;
-import java.nio.ByteBuffer;
import java.util.Set;
import java.util.concurrent.TimeoutException;
@@ -21,15 +20,11 @@
import org.eclipse.jetty.fcgi.generator.Flusher;
import org.eclipse.jetty.fcgi.generator.ServerGenerator;
import org.eclipse.jetty.fcgi.parser.ServerParser;
-import org.eclipse.jetty.http.HttpException;
import org.eclipse.jetty.http.HttpField;
-import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
-import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.EofException;
-import org.eclipse.jetty.io.RetainableByteBuffer;
+import org.eclipse.jetty.io.WritableBufferPool;
import org.eclipse.jetty.server.AbstractMetaDataConnection;
import org.eclipse.jetty.server.ConnectionMetaData;
import org.eclipse.jetty.server.Connector;
@@ -38,6 +33,8 @@
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,21 +47,24 @@ public class ServerFCGIConnection extends AbstractMetaDataConnection implements
private final HttpChannel.Factory httpChannelFactory = new HttpChannel.DefaultFactory();
private final Attributes attributes = new Lazy();
private final Connector connector;
- private final ByteBufferPool bufferPool;
+ private final WritableBufferPool bufferPool;
private final boolean sendStatus200;
private final Flusher flusher;
private final ServerParser parser;
private final String id;
private boolean useInputDirectByteBuffers;
private boolean useOutputDirectByteBuffers;
- private RetainableByteBuffer inputBuffer;
+ private ReadableBuffer inputBuffer;
private HttpStreamOverFCGI stream;
+ private State state = State.IDLE;
+ private Content.Chunk chunk;
+ private Throwable failure;
public ServerFCGIConnection(Connector connector, EndPoint endPoint, HttpConfiguration configuration, boolean sendStatus200)
{
super(connector, configuration, endPoint);
this.connector = connector;
- this.bufferPool = connector.getByteBufferPool();
+ this.bufferPool = WritableBufferPool.wrap(connector.getByteBufferPool());
this.flusher = new Flusher(endPoint);
this.sendStatus200 = sendStatus200;
this.parser = new ServerParser(new ServerListener());
@@ -172,39 +172,116 @@ public void onOpen()
public void onFillable()
{
if (LOG.isDebugEnabled())
- LOG.debug(">>onFillable enter {} {} {}", this, stream, inputBuffer);
- acquireInputBuffer();
+ LOG.debug("onFillable {} {} {}", this, stream, inputBuffer);
+
+ process(true);
+ }
+
+ void process(boolean setFillInterest)
+ {
+ fillAndParse(setFillInterest);
+
+ switch (state)
+ {
+ case HEADERS ->
+ {
+ state = State.CONTENT;
+ stream.onHeaders();
+ }
+ case CONTENT ->
+ {
+ if (chunk != null)
+ {
+ stream.onContent(chunk);
+ chunk.release();
+ chunk = null;
+ }
+ }
+ case COMPLETE ->
+ {
+ stream.onComplete();
+ stream = null;
+ state = State.IDLE;
+ }
+ case FAILED ->
+ {
+ stream.onFailure(failure);
+ stream = null;
+ getEndPoint().close(failure);
+ }
+ }
+ }
+
+ private void fillAndParse(boolean setFillInterest)
+ {
+ ReadableBuffer readable = null;
+ WritableBuffer writable = null;
+ if (inputBuffer != null)
+ {
+ readable = inputBuffer;
+ inputBuffer = null;
+ }
+ else
+ {
+ writable = bufferPool.acquire(getInputBufferSize(), isUseInputDirectByteBuffers());
+ }
+
try
{
while (true)
{
- int read = fillInputBuffer();
- if (LOG.isDebugEnabled())
- LOG.debug("Read {} bytes from {} {}", read, getEndPoint(), this);
- if (read > 0)
+ if (writable != null)
{
- // The inputBuffer cannot be released immediately after parse()
- // even if the buffer has been fully consumed because releaseInputBuffer()
- // must be called as the last release for it to be able to null out the
- // inputBuffer field exactly when the latter isn't used anymore.
- if (parse(inputBuffer.getByteBuffer()))
+ int read = fillInputBuffer(writable);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Read {} bytes from {} {}", read, getEndPoint(), this);
+
+ if (read <= 0)
{
- if (stream == null && inputBuffer.isEmpty())
- releaseInputBuffer();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Releasing {}", writable);
+ writable.release();
+
+ if (read == 0)
+ {
+ if (setFillInterest)
+ fillInterested(fillableCallback);
+ }
+ else
+ {
+ parser.eof();
+ }
return;
}
+
+ readable = writable.toReadable();
}
- else if (read == 0)
+
+ assert readable != null;
+
+ if (parse(readable))
{
- releaseInputBuffer();
- fillInterested(fillableCallback);
+ if (readable.remaining() == 0)
+ readable.release();
+ else
+ inputBuffer = readable;
return;
}
+
+ // Check if the buffer has been retained by the application.
+ // This may happen when the buffer read from the network
+ // a "data frame" and then some bytes of the next "data frame":
+ // reusing the buffer would overwrite the first "data frame" bytes.
+ if (readable.isRetained())
+ {
+ readable.release();
+ writable = bufferPool.acquire(getInputBufferSize(), isUseInputDirectByteBuffers());
+ if (LOG.isDebugEnabled())
+ LOG.debug("Reacquired {}", writable);
+ }
else
{
- releaseInputBuffer();
- shutdown();
- return;
+ writable = readable.toWritable();
}
}
}
@@ -212,74 +289,19 @@ else if (read == 0)
{
if (LOG.isDebugEnabled())
LOG.debug("Unable to fill endpoint", x);
- inputBuffer.clear();
- releaseInputBuffer();
- // TODO: fail and close ?
- }
- finally
- {
- if (LOG.isDebugEnabled())
- LOG.debug("< 0)
{
boolean result = parser.parse(buffer);
if (result)
@@ -307,7 +329,6 @@ private void shutdown()
void onCompleted(Throwable failure)
{
- releaseInputBuffer();
if (failure == null)
fillInterested(fillableCallback);
else
@@ -336,7 +357,7 @@ public void onStart(int request, FCGI.Role role, int flags)
if (stream != null)
throw new UnsupportedOperationException("FastCGI Multiplexing");
HttpChannel channel = httpChannelFactory.newHttpChannel(ServerFCGIConnection.this);
- ServerGenerator generator = new ServerGenerator(connector.getByteBufferPool(), isUseOutputDirectByteBuffers(), sendStatus200);
+ ServerGenerator generator = new ServerGenerator(WritableBufferPool.wrap(connector.getByteBufferPool()), isUseOutputDirectByteBuffers(), sendStatus200);
stream = new HttpStreamOverFCGI(ServerFCGIConnection.this, generator, channel, request);
channel.setHttpStream(stream);
if (LOG.isDebugEnabled())
@@ -357,31 +378,28 @@ public boolean onHeaders(int request)
{
if (LOG.isDebugEnabled())
LOG.debug("Request {} headers on {}", request, stream);
- if (stream != null)
- {
- stream.onHeaders();
- // We have dispatched to the application,
- // so we must stop the fill & parse loop.
- return true;
- }
- return false;
+
+ if (stream == null)
+ return false;
+
+ state = State.HEADERS;
+ // We will call the application, stop the fill & parse loop.
+ return true;
}
@Override
- public boolean onContent(int request, FCGI.StreamType streamType, ByteBuffer buffer)
+ public boolean onContent(int request, FCGI.StreamType streamType, ReadableBuffer buffer)
{
if (LOG.isDebugEnabled())
LOG.debug("Request {} {} content {} on {}", request, streamType, buffer, stream);
- if (stream != null)
- {
- // No need to call inputBuffer.retain() here.
- // The receiver of the chunk decides whether to consume/retain it.
- Content.Chunk chunk = Content.Chunk.asChunk(buffer, false, inputBuffer);
- stream.onContent(chunk);
- // Signal that the content is processed asynchronously, to ensure backpressure.
- return true;
- }
- return false;
+
+ if (stream == null)
+ return false;
+
+ state = State.CONTENT;
+ chunk = Content.Chunk.from(buffer, false);
+ // Signal that the content is processed asynchronously, to ensure backpressure.
+ return true;
}
@Override
@@ -389,25 +407,25 @@ public boolean onEnd(int request)
{
if (LOG.isDebugEnabled())
LOG.debug("Request {} end on {}", request, stream);
- if (stream != null)
- {
- stream.onComplete();
- // Nulling out the stream signals that the
- // request is complete, see also parseAndFill().
- stream = null;
- return true;
- }
- return false;
+
+ if (stream == null)
+ return false;
+
+ state = State.COMPLETE;
+ return true;
}
@Override
- public void onFailure(int request, Throwable failure)
+ public void onFailure(int request, Throwable cause)
{
+ if (stream == null)
+ return;
+
if (LOG.isDebugEnabled())
- LOG.debug("Request {} failure on {}", request, stream, failure);
- if (stream != null)
- ThreadPool.executeImmediately(getExecutor(), stream.getHttpChannel().onFailure(new HttpException.IllegalStateException(HttpStatus.BAD_REQUEST_400, null, failure)));
- stream = null;
+ LOG.debug("Request {} failure on {}", request, stream, cause);
+
+ state = State.FAILED;
+ failure = cause;
}
}
@@ -451,4 +469,13 @@ public InvocationType getInvocationType()
return invocationType;
}
}
+
+ private enum State
+ {
+ IDLE,
+ HEADERS,
+ CONTENT,
+ COMPLETE,
+ FAILED
+ }
}
diff --git a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
index af25d5179365..419abadb7e5f 100644
--- a/jetty-core/jetty-fcgi/jetty-fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
+++ b/jetty-core/jetty-fcgi/jetty-fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java
@@ -106,7 +106,7 @@ public boolean handle(org.eclipse.jetty.server.Request request, org.eclipse.jett
for (int i = 0; i < maxConnections + 1; ++i)
{
- ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort());
+ ContentResponse response = client.GET(scheme + "://localhost:" + connector.getLocalPort() + "/" + i);
assertNotNull(response);
assertEquals(200, response.getStatus());
byte[] content = response.getContent();
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java
index e29a55a7addc..df208191f607 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java
@@ -18,6 +18,8 @@
import java.util.ServiceLoader;
import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -105,6 +107,11 @@ public PreEncodedHttpField(String name, long value)
this(null, name, Long.toString(value), value);
}
+ public void putTo(WritableBuffer buffer, HttpVersion version)
+ {
+ buffer.put(ReadableBuffer.wrap(ByteBuffer.wrap(_encodedFields.get(version))));
+ }
+
public void putTo(ByteBuffer bufferInFillMode, HttpVersion version)
{
bufferInFillMode.put(_encodedFields.get(version));
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanDecoder.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanDecoder.java
index 42fb461ae6fe..472a42a79d2e 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanDecoder.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanDecoder.java
@@ -13,9 +13,8 @@
package org.eclipse.jetty.http.compression;
-import java.nio.ByteBuffer;
-
import org.eclipse.jetty.util.CharsetStringBuilder;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import static org.eclipse.jetty.http.compression.Huffman.rowbits;
import static org.eclipse.jetty.http.compression.Huffman.rowsym;
@@ -26,7 +25,7 @@
public class HuffmanDecoder
{
private final CharsetStringBuilder.Iso88591StringBuilder _builder = new CharsetStringBuilder.Iso88591StringBuilder();
- private int _length = 0;
+ private long _length = 0;
private int _count = 0;
private int _node = 0;
private int _current = 0;
@@ -36,7 +35,7 @@ public class HuffmanDecoder
* Set length of input bytes of the huffman data.
* @param length in bytes of the huffman data.
*/
- public void setLength(int length)
+ public void setLength(long length)
{
if (_count != 0)
throw new IllegalStateException();
@@ -48,11 +47,11 @@ public void setLength(int length)
* @return the decoded String or null if more data is needed.
* @throws EncodingException if the huffman encoding is invalid.
*/
- public String decode(ByteBuffer buffer) throws EncodingException
+ public String decode(ReadableBuffer buffer) throws EncodingException
{
for (; _count < _length; _count++)
{
- if (!buffer.hasRemaining())
+ if (buffer.remaining() == 0L)
return null;
int b = buffer.get() & 0xFF;
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanEncoder.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanEncoder.java
index bd797a55f512..47f0273a823c 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanEncoder.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/HuffmanEncoder.java
@@ -15,6 +15,8 @@
import java.nio.ByteBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
+
import static org.eclipse.jetty.http.compression.Huffman.CODES;
import static org.eclipse.jetty.http.compression.Huffman.EOS;
import static org.eclipse.jetty.http.compression.Huffman.LCCODES;
@@ -60,6 +62,11 @@ public static int octetsNeeded(byte[] b)
* @param s the string to encode.
*/
public static void encode(ByteBuffer buffer, String s)
+ {
+ encode(WritableBuffer.wrap(buffer), s);
+ }
+
+ public static void encode(WritableBuffer buffer, String s)
{
encode(CODES, buffer, s);
}
@@ -77,11 +84,16 @@ public static int octetsNeededLowerCase(String s)
* @param buffer the buffer to encode into in lowercase.
* @param s the string to encode.
*/
- public static void encodeLowerCase(ByteBuffer buffer, String s)
+ public static void encodeLowerCase(WritableBuffer buffer, String s)
{
encode(LCCODES, buffer, s);
}
+ public static void encodeLowerCase(ByteBuffer buffer, String s)
+ {
+ encode(LCCODES, WritableBuffer.wrap(buffer), s);
+ }
+
private static int octetsNeeded(final int[][] table, String s)
{
int needed = 0;
@@ -102,7 +114,7 @@ private static int octetsNeeded(final int[][] table, String s)
* @param buffer The buffer to encode to
* @param s The string to encode
*/
- private static void encode(final int[][] table, ByteBuffer buffer, String s)
+ private static void encode(final int[][] table, WritableBuffer buffer, String s)
{
long current = 0;
int n = 0;
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerDecoder.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerDecoder.java
index e1c0dbb17bbd..501965a4f0dd 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerDecoder.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerDecoder.java
@@ -15,6 +15,8 @@
import java.nio.ByteBuffer;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+
/**
* Used to decode integers as described in RFC7541.
*/
@@ -46,6 +48,11 @@ public void setPrefix(int prefix)
* @throws ArithmeticException if the value overflows a int.
*/
public int decodeInt(ByteBuffer buffer)
+ {
+ return decodeInt(ReadableBuffer.wrap(buffer));
+ }
+
+ public int decodeInt(ReadableBuffer buffer)
{
return Math.toIntExact(decodeLong(buffer));
}
@@ -59,10 +66,15 @@ public int decodeInt(ByteBuffer buffer)
* @throws ArithmeticException if the value overflows a long.
*/
public long decodeLong(ByteBuffer buffer)
+ {
+ return decodeLong(ReadableBuffer.wrap(buffer));
+ }
+
+ public long decodeLong(ReadableBuffer buffer)
{
if (!_started)
{
- if (!buffer.hasRemaining())
+ if (buffer.remaining() == 0)
return -1;
_started = true;
@@ -80,7 +92,7 @@ public long decodeLong(ByteBuffer buffer)
while (true)
{
// If we have no more remaining we return -1 to indicate that more data is needed to continue parsing.
- if (!buffer.hasRemaining())
+ if (buffer.remaining() == 0)
return -1;
int b = buffer.get() & 0xFF;
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerEncoder.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerEncoder.java
index 02e40123a576..d021c696283e 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerEncoder.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitIntegerEncoder.java
@@ -15,6 +15,9 @@
import java.nio.ByteBuffer;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
+
/**
* Used to encode integers as described in RFC7541.
*/
@@ -54,6 +57,11 @@ public static int octetsNeeded(int prefix, long value)
* @param value the long to encode into the buffer.
*/
public static void encode(ByteBuffer buffer, int prefix, long value)
+ {
+ encode(WritableBuffer.wrap(buffer), prefix, value);
+ }
+
+ public static void encode(WritableBuffer buffer, int prefix, long value)
{
if (prefix <= 0 || prefix > 8)
throw new IllegalArgumentException();
@@ -63,14 +71,14 @@ public static void encode(ByteBuffer buffer, int prefix, long value)
buffer.put((byte)0x00);
int bits = 0xFF >>> (8 - prefix);
- int p = buffer.position() - 1;
+ long p = buffer.position() - 1;
if (value < bits)
{
- buffer.put(p, (byte)((buffer.get(p) & ~bits) | value));
+ buffer.put(p, (byte)((getByteAt(buffer, p) & ~bits) | value));
}
else
{
- buffer.put(p, (byte)(buffer.get(p) | bits));
+ buffer.put(p, (byte)(getByteAt(buffer, p) | bits));
long length = value - bits;
while (true)
{
@@ -88,4 +96,12 @@ public static void encode(ByteBuffer buffer, int prefix, long value)
}
}
}
+
+ private static byte getByteAt(WritableBuffer buffer, long p)
+ {
+ ReadableBuffer rb = buffer.toReadable();
+ byte b = rb.get(p);
+ rb.toWritable();
+ return b;
+ }
}
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitStringDecoder.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitStringDecoder.java
index 25367abe377c..b86a0432f6a3 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitStringDecoder.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitStringDecoder.java
@@ -16,6 +16,7 @@
import java.nio.ByteBuffer;
import org.eclipse.jetty.util.CharsetStringBuilder;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
/**
*
Used to decode string literals as described in RFC7541.
@@ -75,6 +76,11 @@ public void setPrefix(int prefix)
* @throws EncodingException if the string encoding is invalid.
*/
public String decode(ByteBuffer buffer) throws EncodingException
+ {
+ return decode(ReadableBuffer.wrap(buffer));
+ }
+
+ public String decode(ReadableBuffer buffer) throws EncodingException
{
while (true)
{
@@ -107,11 +113,11 @@ public String decode(ByteBuffer buffer) throws EncodingException
}
}
- private String stringDecode(ByteBuffer buffer)
+ private String stringDecode(ReadableBuffer buffer)
{
for (; _count < _length; _count++)
{
- if (!buffer.hasRemaining())
+ if (buffer.remaining() == 0L)
return null;
_builder.append(buffer.get());
}
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitStringEncoder.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitStringEncoder.java
index 58d2ca373334..9c8c574f9709 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitStringEncoder.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/compression/NBitStringEncoder.java
@@ -15,6 +15,9 @@
import java.nio.ByteBuffer;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
+
public class NBitStringEncoder
{
private NBitStringEncoder()
@@ -35,6 +38,12 @@ public static int octetsNeeded(int prefix, String value, boolean huffman)
}
public static void encode(ByteBuffer buffer, int prefix, String value, boolean huffman)
+ {
+ WritableBuffer wb = WritableBuffer.wrap(buffer);
+ encode(wb, prefix, value, huffman);
+ }
+
+ public static void encode(WritableBuffer buffer, int prefix, String value, boolean huffman)
{
if (prefix <= 0 || prefix > 8)
throw new IllegalArgumentException();
@@ -46,8 +55,8 @@ public static void encode(ByteBuffer buffer, int prefix, String value, boolean h
}
else
{
- int p = buffer.position() - 1;
- buffer.put(p, (byte)(buffer.get(p) | huffmanFlag));
+ long p = buffer.position() - 1;
+ buffer.put(p, (byte)(getByteAt(buffer, p) | huffmanFlag));
}
// Start encoding size & content in rest of prefix.
@@ -71,4 +80,12 @@ public static void encode(ByteBuffer buffer, int prefix, String value, boolean h
}
}
}
+
+ private static byte getByteAt(WritableBuffer buffer, long p)
+ {
+ ReadableBuffer rb = buffer.toReadable();
+ byte b = rb.get(p);
+ rb.toWritable();
+ return b;
+ }
}
diff --git a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HuffmanTest.java b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HuffmanTest.java
index 29e82dc74b16..b6a2104bf250 100644
--- a/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HuffmanTest.java
+++ b/jetty-core/jetty-http/src/test/java/org/eclipse/jetty/http/HuffmanTest.java
@@ -22,6 +22,8 @@
import org.eclipse.jetty.http.compression.HuffmanEncoder;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -34,7 +36,7 @@
public class HuffmanTest
{
- public static String decode(ByteBuffer buffer, int length) throws EncodingException
+ public static String decode(ReadableBuffer buffer, long length) throws EncodingException
{
HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
huffmanDecoder.setLength(length);
@@ -67,7 +69,7 @@ public void testDecode(String specSection, String hex, String expected) throws E
byte[] encoded = StringUtil.fromHexString(hex);
HuffmanDecoder huffmanDecoder = new HuffmanDecoder();
huffmanDecoder.setLength(encoded.length);
- String decoded = huffmanDecoder.decode(ByteBuffer.wrap(encoded));
+ String decoded = huffmanDecoder.decode(ReadableBuffer.wrap(ByteBuffer.wrap(encoded)));
assertEquals(expected, decoded, specSection);
}
@@ -75,11 +77,10 @@ public void testDecode(String specSection, String hex, String expected) throws E
@MethodSource("data")
public void testEncode(String specSection, String hex, String expected)
{
- ByteBuffer buf = BufferUtil.allocate(1024);
- int pos = BufferUtil.flipToFill(buf);
+ WritableBuffer buf = WritableBuffer.allocate(1024, false);
HuffmanEncoder.encode(buf, expected);
- BufferUtil.flipToFlush(buf, pos);
- byte[] b = BufferUtil.toArray(buf);
+ ReadableBuffer rb = buf.toReadable();
+ byte[] b = BufferUtil.toArray(rb);
String encoded = StringUtil.toHexString(b).toLowerCase(Locale.ENGLISH);
assertEquals(hex, encoded, specSection);
assertEquals(hex.length() / 2, HuffmanEncoder.octetsNeeded(expected));
@@ -107,7 +108,7 @@ public static Stream testDecode8859OnlyArguments()
@MethodSource("testDecode8859OnlyArguments")
public void testDecode8859Only(String hexString, Character expected) throws Exception
{
- ByteBuffer buffer = ByteBuffer.wrap(StringUtil.fromHexString(hexString));
+ ReadableBuffer buffer = ReadableBuffer.wrap(ByteBuffer.wrap(StringUtil.fromHexString(hexString)));
if (expected == null)
{
@@ -152,21 +153,19 @@ public void testEncode8859Only(char value, Character expectedValue) throws Excep
String expected = "value = '" + expectedValue + "'";
assertThat(HuffmanEncoder.octetsNeeded(s), greaterThan(0));
- ByteBuffer buffer = encode(s);
+ ReadableBuffer buffer = encode(s);
String decode = decode(buffer);
assertThat(decode, equalTo(expected));
}
- private ByteBuffer encode(String s)
+ private ReadableBuffer encode(String s)
{
- ByteBuffer buffer = BufferUtil.allocate(32);
- BufferUtil.clearToFill(buffer);
+ WritableBuffer buffer = WritableBuffer.allocate(32, false);
HuffmanEncoder.encode(buffer, s);
- BufferUtil.flipToFlush(buffer, 0);
- return buffer;
+ return buffer.toReadable();
}
- private String decode(ByteBuffer buffer) throws Exception
+ private String decode(ReadableBuffer buffer) throws Exception
{
return decode(buffer, buffer.remaining());
}
diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpReceiverOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpReceiverOverHTTP2.java
index 0fc82c290c43..a9205fedd8b4 100644
--- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpReceiverOverHTTP2.java
+++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpReceiverOverHTTP2.java
@@ -34,7 +34,6 @@
import org.eclipse.jetty.http2.HTTP2Channel;
import org.eclipse.jetty.http2.HTTP2Stream;
import org.eclipse.jetty.http2.api.Stream;
-import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
@@ -69,22 +68,19 @@ public Content.Chunk read(boolean fillInterestIfNeeded)
if (stream == null)
return Content.Chunk.from(new EOFException("Channel has been released"));
- Stream.Data data = stream.readData();
+ Content.Chunk chunk = stream.read();
if (LOG.isDebugEnabled())
- LOG.debug("Read stream data {} in {}", data, this);
- if (data == null)
+ LOG.debug("Read stream data {} in {}", chunk, this);
+ if (chunk == null)
{
if (fillInterestIfNeeded)
stream.demand();
return null;
}
- DataFrame frame = data.frame();
- boolean last = frame.remaining() == 0 && frame.isEndStream();
+ boolean last = chunk.getByteBuffer().remaining() == 0 && chunk.isLast();
if (!last)
- return Content.Chunk.asChunk(frame.getByteBuffer(), false, data);
-
- data.release();
+ return Content.Chunk.asChunk(chunk.getByteBuffer(), false, chunk);
if (getHttpChannel().isLastDataReceived())
{
diff --git a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpSenderOverHTTP2.java b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpSenderOverHTTP2.java
index 1ff9fd2660a5..fc310a1cc50a 100644
--- a/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpSenderOverHTTP2.java
+++ b/jetty-core/jetty-http2/jetty-http2-client-transport/src/main/java/org/eclipse/jetty/http2/client/transport/internal/HttpSenderOverHTTP2.java
@@ -35,6 +35,7 @@
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.URIUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
public class HttpSenderOverHTTP2 extends HttpSender
{
@@ -95,13 +96,13 @@ protected void sendHeaders(HttpExchange exchange, ByteBuffer contentBuffer, bool
{
HttpFields trailers = retrieveTrailers(request);
boolean hasTrailers = trailers != null;
- dataFrame = new DataFrame(contentBuffer, !hasTrailers);
+ dataFrame = new DataFrame(ReadableBuffer.wrap(contentBuffer), !hasTrailers);
if (hasTrailers)
trailersFrame = new HeadersFrame(new MetaData(HttpVersion.HTTP_2, trailers), null, true);
}
else
{
- dataFrame = new DataFrame(contentBuffer, false);
+ dataFrame = new DataFrame(ReadableBuffer.wrap(contentBuffer), false);
}
}
else
@@ -145,31 +146,24 @@ protected void sendContent(HttpExchange exchange, ByteBuffer contentBuffer, bool
boolean hasTrailers = trailers != null && trailers.size() > 0;
if (hasContent)
{
- DataFrame dataFrame = new DataFrame(stream.getId(), contentBuffer, !hasTrailers);
if (hasTrailers)
- stream.data(dataFrame, Callback.from(() -> sendTrailers(stream, trailers, callback), callback::failed));
+ stream.data(ReadableBuffer.wrap(contentBuffer), !hasTrailers, Callback.from(() -> sendTrailers(stream, trailers, callback), callback::failed));
else
- stream.data(dataFrame, callback);
+ stream.data(ReadableBuffer.wrap(contentBuffer), !hasTrailers, callback);
}
else
{
if (hasTrailers)
- {
sendTrailers(stream, trailers, callback);
- }
else
- {
- DataFrame dataFrame = new DataFrame(stream.getId(), contentBuffer, true);
- stream.data(dataFrame, callback);
- }
+ stream.data(ReadableBuffer.wrap(contentBuffer), true, callback);
}
}
else
{
if (hasContent)
{
- DataFrame dataFrame = new DataFrame(stream.getId(), contentBuffer, false);
- stream.data(dataFrame, callback);
+ stream.data(ReadableBuffer.wrap(contentBuffer), false, callback);
}
else
{
diff --git a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java
index 4ad4ac88b31b..caeeef06dc27 100644
--- a/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java
+++ b/jetty-core/jetty-http2/jetty-http2-client/src/main/java/org/eclipse/jetty/http2/client/HTTP2ClientConnectionFactory.java
@@ -29,10 +29,10 @@
import org.eclipse.jetty.http2.generator.Generator;
import org.eclipse.jetty.http2.hpack.HpackContext;
import org.eclipse.jetty.http2.parser.Parser;
-import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.WritableBufferPool;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
@@ -43,7 +43,7 @@ public class HTTP2ClientConnectionFactory implements ClientConnectionFactory
public Connection newConnection(EndPoint endPoint, Map context)
{
HTTP2Client client = (HTTP2Client)context.get(HTTP2Client.CONTEXT_KEY);
- ByteBufferPool bufferPool = client.getByteBufferPool();
+ WritableBufferPool bufferPool = WritableBufferPool.wrap(client.getByteBufferPool());
Session.Listener listener = (Session.Listener)context.get(HTTP2Client.SESSION_LISTENER_CONTEXT_KEY);
@SuppressWarnings("unchecked")
Promise sessionPromise = (Promise)context.get(HTTP2Client.SESSION_PROMISE_CONTEXT_KEY);
@@ -81,7 +81,7 @@ private static class HTTP2ClientConnection extends HTTP2Connection implements Ca
private HTTP2ClientConnection(HTTP2Client client, EndPoint endpoint, HTTP2ClientSession session, Promise sessionPromise, Session.Listener listener)
{
- super(client.getByteBufferPool(), client.getExecutor(), endpoint, session, client.getInputBufferSize(), -1);
+ super(WritableBufferPool.wrap(client.getByteBufferPool()), client.getExecutor(), endpoint, session, client.getInputBufferSize(), -1);
this.client = client;
this.promise = sessionPromise;
this.listener = listener;
diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java
index e386db81e79c..632e7624bb82 100644
--- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java
+++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java
@@ -21,7 +21,6 @@
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
-import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
@@ -33,14 +32,14 @@
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.AbstractConnection;
-import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.Retainable;
-import org.eclipse.jetty.io.RetainableByteBuffer;
+import org.eclipse.jetty.io.WritableBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
+import org.eclipse.jetty.util.buffer.WritableBuffer;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.ExecutionStrategy;
@@ -56,7 +55,7 @@ public class HTTP2Connection extends AbstractConnection implements Parser.Listen
private final Queue tasks = new ArrayDeque<>();
private final HTTP2Producer producer = new HTTP2Producer();
private final AtomicLong bytesIn = new AtomicLong();
- private final ByteBufferPool bufferPool;
+ private final WritableBufferPool bufferPool;
private final HTTP2Session session;
private final int bufferSize;
private final int minBufferSpace;
@@ -64,12 +63,12 @@ public class HTTP2Connection extends AbstractConnection implements Parser.Listen
private boolean useInputDirectByteBuffers;
private boolean useOutputDirectByteBuffers;
- protected HTTP2Connection(ByteBufferPool bufferPool, Executor executor, EndPoint endPoint, HTTP2Session session, int bufferSize)
+ protected HTTP2Connection(WritableBufferPool bufferPool, Executor executor, EndPoint endPoint, HTTP2Session session, int bufferSize)
{
this(bufferPool, executor, endPoint, session, bufferSize, -1);
}
- protected HTTP2Connection(ByteBufferPool bufferPool, Executor executor, EndPoint endPoint, HTTP2Session session, int bufferSize, int minBufferSpace)
+ protected HTTP2Connection(WritableBufferPool bufferPool, Executor executor, EndPoint endPoint, HTTP2Session session, int bufferSize, int minBufferSpace)
{
super(endPoint, executor);
this.bufferPool = bufferPool;
@@ -116,7 +115,7 @@ public void onUpgradeTo(ByteBuffer buffer)
{
if (LOG.isDebugEnabled())
LOG.debug("HTTP2 onUpgradeTo {} {}", this, BufferUtil.toDetailString(buffer));
- producer.setInputBuffer(buffer);
+ producer.setInputBuffer(ReadableBuffer.wrap(buffer));
}
public boolean isUseInputDirectByteBuffers()
@@ -154,7 +153,6 @@ public void onClose(Throwable cause)
LOG.debug("HTTP2 Close {} ", this);
super.onClose(cause);
LifeCycle.stop(strategy);
- producer.stop();
}
@Override
@@ -165,9 +163,9 @@ public void onFillable()
produce();
}
- private int fill(EndPoint endPoint, ByteBuffer buffer, boolean compact)
+ private int fill(EndPoint endPoint, WritableBuffer buffer, boolean compact)
{
- int padding = 0;
+ long padding = 0L;
try
{
if (endPoint.isInputShutdown())
@@ -176,7 +174,7 @@ private int fill(EndPoint endPoint, ByteBuffer buffer, boolean compact)
if (!compact)
{
// Add padding content to avoid compaction
- padding = buffer.limit();
+ padding = buffer.position();
buffer.position(0);
}
return endPoint.fill(buffer);
@@ -269,7 +267,7 @@ public void onHeaders(HeadersFrame frame)
@Override
public void onData(DataFrame frame)
{
- session.onData(producer.newStreamData(frame));
+ session.onData(frame);
}
@Override
@@ -335,26 +333,24 @@ public String toConnectionString()
protected class HTTP2Producer implements ExecutionStrategy.Producer
{
- private static final RetainableByteBuffer.Mutable STOPPED = new RetainableByteBuffer.NonRetainableByteBuffer(BufferUtil.EMPTY_BUFFER);
- private static final RetainableByteBuffer.Mutable RELEASE_MARKER = new RetainableByteBuffer.NonRetainableByteBuffer(BufferUtil.EMPTY_BUFFER);
private final Callback fillableCallback = new FillableCallback();
private final AutoLock lock = new AutoLock();
- private RetainableByteBuffer.Mutable heldBuffer;
- private RetainableByteBuffer.Mutable networkBuffer;
private boolean shutdown;
private boolean failed;
+ private ReadableBuffer inputBuffer;
- private void setInputBuffer(ByteBuffer byteBuffer)
+ private void setInputBuffer(ReadableBuffer buffer)
{
try (AutoLock ignore = lock.lock())
{
- RetainableByteBuffer.Mutable networkBuffer = lockedAcquireBuffer();
- if (!networkBuffer.append(byteBuffer))
+ WritableBuffer networkBuffer = lockedAcquireBuffer();
+ BufferUtil.put(buffer, networkBuffer);
+ if (buffer.remaining() > 0L)
{
networkBuffer.release();
throw new IllegalStateException("overflow");
}
- lockedHoldBuffer(networkBuffer);
+ inputBuffer = networkBuffer.toReadable();
}
}
@@ -371,23 +367,31 @@ public Runnable produce()
return null;
boolean interested = false;
- RetainableByteBuffer.Mutable networkBuffer;
+ ReadableBuffer networkBuffer;
try (AutoLock ignore = lock.lock())
{
- this.networkBuffer = networkBuffer = lockedAcquireBuffer();
+ if (inputBuffer != null)
+ {
+ networkBuffer = inputBuffer;
+ inputBuffer = null;
+ }
+ else
+ {
+ networkBuffer = lockedAcquireBuffer().toReadable();
+ }
}
try
{
- boolean parse = networkBuffer.hasRemaining();
+ boolean parse = networkBuffer.remaining() > 0L;
while (true)
{
boolean compact = true;
if (parse)
{
- while (networkBuffer.hasRemaining())
+ while (networkBuffer.remaining() > 0L)
{
- session.getParser().parse(networkBuffer.getByteBuffer());
+ session.getParser().parse(networkBuffer);
if (failed)
return null;
}
@@ -403,7 +407,7 @@ public Runnable produce()
if (networkBuffer.isRetained())
{
// If there is sufficient space available, we can top up the buffer rather than allocate a new one
- if (minBufferSpace > 0 && BufferUtil.space(networkBuffer.getByteBuffer()) >= minBufferSpace)
+ if (minBufferSpace > 0 && networkBuffer.remaining() >= minBufferSpace)
{
// do not compact the buffer
compact = false;
@@ -416,12 +420,14 @@ public Runnable produce()
networkBuffer.release();
try (AutoLock ignore = lock.lock())
{
- this.networkBuffer = networkBuffer = lockedAcquireBuffer();
+ networkBuffer = lockedAcquireBuffer().toReadable();
}
}
}
- int filled = fill(getEndPoint(), networkBuffer.getByteBuffer(), compact);
+ WritableBuffer wb = networkBuffer.toWritable();
+ int filled = fill(getEndPoint(), wb, compact);
+ wb.toReadable();
if (LOG.isDebugEnabled())
LOG.debug("Filled {} bytes compacted {} {} in {}", filled, compact, networkBuffer, HTTP2Connection.this);
@@ -453,124 +459,25 @@ else if (filled == 0)
}
finally
{
- try (AutoLock ignore = lock.lock())
- {
- // There is a race between the producer thread and the one executing user code:
- // this finally block may execute before or after releaseHeldBuffer() and
- // the last thread must be the one doing the release. If heldBuffer contains
- // the release marker, this means the producer thread lost the race, and we
- // must release the buffer here to avoid leaving a buffer at rest out of the pool.
- // Note that networkBuffer.isRetained() is always true if the parser generated a
- // data frame as the networkBuffer has been sliced to create the data frame
- // and the latter is waiting in a queue for the user code to read it.
- if (networkBuffer.isRetained() && heldBuffer != RELEASE_MARKER && !shutdown)
- {
- lockedHoldBuffer(networkBuffer);
- }
- else
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Released after process {}", networkBuffer);
- networkBuffer.release();
- }
- this.networkBuffer = null;
- }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Released after process {}", networkBuffer);
+ networkBuffer.release();
if (interested)
fillInterested(fillableCallback);
}
}
- private StreamData newStreamData(DataFrame frame)
- {
- try (AutoLock ignore = lock.lock())
- {
- return new StreamData(frame, networkBuffer, this::releaseHeldBuffer);
- }
- }
-
- private void releaseHeldBuffer()
- {
- try (AutoLock ignore = lock.lock())
- {
- if (LOG.isDebugEnabled())
- LOG.debug("releaseHeldBuffer networkBuffer={} heldBuffer={}", networkBuffer, heldBuffer);
- RetainableByteBuffer.Mutable held = heldBuffer;
- if (held == null)
- {
- // If no buffer is held and the networkBuffer did not change since acquisition, it means
- // the user thread won the race, so it must leave a marker to tell the producer thread to release
- // instead of holding onto the buffer.
- heldBuffer = HTTP2Producer.RELEASE_MARKER;
- }
- else
- {
- // If a buffer is still held, it means the producer thread won the race and the user thread
- // must release the held buffer to avoid leaving a buffer at rest out of the pool.
- held.release();
- heldBuffer = null;
- }
- }
- }
-
- private RetainableByteBuffer.Mutable lockedAcquireBuffer()
+ private WritableBuffer lockedAcquireBuffer()
{
assert lock.isHeldByCurrentThread();
- RetainableByteBuffer.Mutable buffer = heldBuffer;
- // This can happen when re-acquiring a buffer while the user thread won the release race;
- // release is done by the re-acquisition so we can safely ignore the release marker.
- if (buffer == RELEASE_MARKER)
- buffer = null;
- heldBuffer = null;
- RetainableByteBuffer.Mutable held = buffer;
- if (buffer == null)
- buffer = bufferPool.acquire(bufferSize, isUseInputDirectByteBuffers()).asMutable();
+ WritableBuffer buffer = bufferPool.acquire(bufferSize, isUseInputDirectByteBuffers());
if (LOG.isDebugEnabled())
- LOG.debug("Acquired {} {} in {}", held == null ? "new" : "held", buffer, HTTP2Connection.this);
+ LOG.debug("Acquired {} in {}", buffer, HTTP2Connection.this);
return buffer;
}
- private void lockedHoldBuffer(RetainableByteBuffer.Mutable buffer)
- {
- assert lock.isHeldByCurrentThread();
-
- if (heldBuffer == null)
- {
- heldBuffer = buffer;
- if (LOG.isDebugEnabled())
- LOG.debug("Held {} in {}", buffer, HTTP2Connection.this);
- }
- else
- {
- if (heldBuffer == STOPPED)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Released instead of holding {}", buffer);
- buffer.release();
- }
- else
- {
- throw new IllegalStateException("Buffer already saved");
- }
- }
- }
-
- private void stop()
- {
- try (AutoLock ignore = lock.lock())
- {
- RetainableByteBuffer.Mutable buffer = heldBuffer;
- heldBuffer = STOPPED;
- if (buffer != null)
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Released in stop {}", buffer);
- buffer.release();
- }
- }
- }
-
@Override
public String toString()
{
@@ -604,44 +511,4 @@ public InvocationType getInvocationType()
return InvocationType.EITHER;
}
}
-
- private static class StreamData extends Stream.Data
- {
- private final Retainable retainable;
- private final Runnable releaser;
-
- private StreamData(DataFrame frame, Retainable retainable, Runnable releaser)
- {
- super(frame);
- this.retainable = retainable;
- this.releaser = releaser;
- }
-
- @Override
- public boolean canRetain()
- {
- return retainable.canRetain();
- }
-
- @Override
- public boolean isRetained()
- {
- return retainable.isRetained();
- }
-
- @Override
- public void retain()
- {
- retainable.retain();
- }
-
- @Override
- public boolean release()
- {
- boolean released = retainable.release();
- if (!released && !isRetained())
- releaser.run();
- return released;
- }
- }
}
diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
index 171a9e34222b..060aed9ec7e3 100644
--- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
+++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
@@ -64,7 +64,6 @@
import org.eclipse.jetty.http2.parser.Parser;
import org.eclipse.jetty.io.CyclicTimeouts;
import org.eclipse.jetty.io.EndPoint;
-import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.util.AtomicBiInteger;
import org.eclipse.jetty.util.Atomics;
import org.eclipse.jetty.util.Callback;
@@ -75,6 +74,7 @@
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.component.DumpableCollection;
@@ -305,17 +305,10 @@ public long getBytesWritten()
@Override
public void onData(DataFrame frame)
- {
- // This method should never be called, the one below should.
- throw new UnsupportedOperationException();
- }
-
- public void onData(Stream.Data data)
{
if (LOG.isDebugEnabled())
- LOG.debug("Received {} on {}", data, this);
+ LOG.debug("Received {} on {}", frame, this);
- DataFrame frame = data.frame();
notifyIncomingFrame(frame);
int streamId = frame.getStreamId();
@@ -342,7 +335,7 @@ public void onData(Stream.Data data)
}
else
{
- stream.process(data);
+ stream.process(frame);
}
}
}
@@ -815,7 +808,7 @@ public void succeeded(Stream pushed)
// Pushed streams are implicitly remotely closed.
// They are closed when sending an end-stream DATA frame.
HTTP2Stream http2Pushed = (HTTP2Stream)pushed;
- http2Pushed.process(Stream.Data.eof(pushed.getId()));
+ http2Pushed.process(DataFrame.eof(pushed.getId()));
http2Pushed.updateClose(true, CloseState.Event.RECEIVED);
super.succeeded(pushed);
}
@@ -1556,12 +1549,12 @@ public Frame frame()
public abstract int getFrameBytesGenerated();
- public int getDataBytesRemaining()
+ public long getDataBytesRemaining()
{
return 0;
}
- public abstract boolean generate(RetainableByteBuffer.Mutable accumulator) throws HpackException;
+ public abstract boolean generate(List accumulator) throws HpackException;
boolean hasHighPriority()
{
@@ -1652,7 +1645,7 @@ public int getFrameBytesGenerated()
}
@Override
- public boolean generate(RetainableByteBuffer.Mutable accumulator) throws HpackException
+ public boolean generate(List accumulator) throws HpackException
{
frameBytes = generator.control(accumulator, frame);
beforeSend();
@@ -1728,7 +1721,7 @@ private class DataEntry extends Entry
{
private int frameBytes;
private int dataBytes;
- private int dataRemaining;
+ private long dataRemaining;
private DataEntry(DataFrame frame, HTTP2Stream stream, Callback callback)
{
@@ -1739,6 +1732,7 @@ private DataEntry(DataFrame frame, HTTP2Stream stream, Callback callback)
// the flow control window exhausting, since in that case
// we would have to count the padding only once.
dataRemaining = frame.remaining();
+ frame.retain();
}
@Override
@@ -1748,15 +1742,15 @@ public int getFrameBytesGenerated()
}
@Override
- public int getDataBytesRemaining()
+ public long getDataBytesRemaining()
{
return dataRemaining;
}
@Override
- public boolean generate(RetainableByteBuffer.Mutable accumulator)
+ public boolean generate(List accumulator)
{
- int dataRemaining = getDataBytesRemaining();
+ long dataRemaining = getDataBytesRemaining();
int sessionSendWindow = getSendWindow();
int streamSendWindow = stream.updateSendWindow(0);
@@ -1764,7 +1758,7 @@ public boolean generate(RetainableByteBuffer.Mutable accumulator)
if (window <= 0 && dataRemaining > 0)
return false;
- int length = Math.min(dataRemaining, window);
+ int length = dataRemaining > Integer.MAX_VALUE ? window : Math.min((int)dataRemaining, window);
// Only one DATA frame is generated.
DataFrame dataFrame = (DataFrame)frame;
@@ -1797,6 +1791,7 @@ public void succeeded()
DataFrame dataFrame = (DataFrame)frame;
if (getDataBytesRemaining() == 0)
{
+ dataFrame.release();
// Only now we can update the close state
// and eventually remove the stream.
if (stream.updateClose(dataFrame.isEndStream(), CloseState.Event.AFTER_SEND))
@@ -1804,6 +1799,13 @@ public void succeeded()
super.succeeded();
}
}
+
+ @Override
+ public void failed(Throwable x)
+ {
+ ((DataFrame)frame).release();
+ super.failed(x);
+ }
}
private class OnResetCallback implements Callback
diff --git a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java
index 43a7e95e7038..cee37c85e375 100644
--- a/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java
+++ b/jetty-core/jetty-http2/jetty-http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Stream.java
@@ -43,6 +43,7 @@
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.http2.frames.StreamFrame;
import org.eclipse.jetty.http2.frames.WindowUpdateFrame;
+import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.CyclicTimeouts;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.Attachable;
@@ -50,6 +51,7 @@
import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.buffer.ReadableBuffer;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
@@ -60,7 +62,7 @@ public class HTTP2Stream implements Stream, Attachable, Closeable, Callback, Dum
private static final Logger LOG = LoggerFactory.getLogger(HTTP2Stream.class);
private final AutoLock lock = new AutoLock();
- private final Deque dataQueue = new ArrayDeque<>(1);
+ private final Deque dataQueue = new ArrayDeque<>(1);
private final AtomicReference