Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.function.Supplier;

import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Index;
import org.eclipse.jetty.util.StringUtil;
Expand Down Expand Up @@ -246,7 +247,7 @@ public Result generateRequest(MetaData.Request info, ByteBuffer header, ByteBuff

case COMMITTED:
{
return committed(chunk, content, last);
return committed(info, chunk, content, last);
}

case COMPLETING:
Expand All @@ -268,11 +269,14 @@ public Result generateRequest(MetaData.Request info, ByteBuffer header, ByteBuff
}
}

private Result committed(ByteBuffer chunk, ByteBuffer content, boolean last)
private Result committed(MetaData info, ByteBuffer chunk, ByteBuffer content, boolean last)
{
int len = BufferUtil.length(content);
long len = BufferUtil.length(content);
Content.Source source = info.getContentSource();

// handle the content.
// Handle the content.
if (len == 0 && source != null)
len = source.getLength();
if (len > 0)
{
if (isChunking())
Expand Down Expand Up @@ -401,15 +405,18 @@ else if (status == HttpStatus.NO_CONTENT_204 || status == HttpStatus.NOT_MODIFIE

generateHeaders(header, content, last);

// handle the content.
int len = BufferUtil.length(content);
// Handle the given content.
long len = BufferUtil.length(content);
Content.Source source = info.getContentSource();
if (len == 0 && source != null)
len = source.getLength();
if (len > 0)
{
_contentPrepared += len;
if (isChunking() && !head)
prepareChunk(header, len);
}
_state = last ? State.COMPLETING : State.COMMITTED;
_state = last && source == null ? State.COMPLETING : State.COMMITTED;
}
catch (BufferOverflowException e)
{
Expand All @@ -432,7 +439,7 @@ else if (status == HttpStatus.NO_CONTENT_204 || status == HttpStatus.NOT_MODIFIE

case COMMITTED:
{
return committed(chunk, content, last);
return committed(info, chunk, content, last);
}

case COMPLETING_1XX:
Expand Down Expand Up @@ -474,7 +481,7 @@ public void servletUpgrade()
startTunnel();
}

private void prepareChunk(ByteBuffer chunk, int remaining)
private void prepareChunk(ByteBuffer chunk, long remaining)
{
// if we need CRLF add this to header
if (_needCRLF)
Expand All @@ -483,7 +490,8 @@ private void prepareChunk(ByteBuffer chunk, int remaining)
// Add the chunk size to the header
if (remaining > 0)
{
BufferUtil.putHexInt(chunk, remaining);
// TODO: we need a long as required by RFC 9110.
BufferUtil.putHexInt(chunk, (int)remaining);
BufferUtil.putCRLF(chunk);
_needCRLF = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import java.util.Objects;
import java.util.function.Supplier;

import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.util.NanoTime;

/**
Expand All @@ -25,7 +26,7 @@
* <p>Specific HTTP response information is captured by {@link Response}.</p>
* <p>HTTP trailers information is captured by {@link MetaData}.</p>
*/
public class MetaData implements Iterable<HttpField>
public class MetaData implements Iterable<HttpField>, Content.Source.Seekable.Aware
{
/**
* <p>Returns whether the given HTTP request method and HTTP response status code
Expand All @@ -44,6 +45,7 @@ public static boolean isTunnel(String method, int status)
private final HttpFields _httpFields;
private final long _contentLength;
private final Supplier<HttpFields> _trailers;
private Content.Source.Seekable _source;

public MetaData(HttpVersion version, HttpFields fields)
{
Expand Down Expand Up @@ -105,6 +107,18 @@ public Supplier<HttpFields> getTrailersSupplier()
return _trailers;
}

@Override
public Content.Source.Seekable getContentSource()
{
return _source;
}

@Override
public void setContentSource(Content.Source.Seekable source)
{
_source = source;
}

/**
* Get the length of the content in bytes.
* @return the length of the content in bytes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1574,7 +1574,7 @@ public Frame frame()

public abstract int getFrameBytesGenerated();

public int getDataBytesRemaining()
public long getDataBytesRemaining()
{
return 0;
}
Expand Down Expand Up @@ -1747,7 +1747,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)
{
Expand All @@ -1757,7 +1757,7 @@ private DataEntry(DataFrame frame, HTTP2Stream stream, Callback callback)
// of data frames that cannot be completely written due to
// the flow control window exhausting, since in that case
// we would have to count the padding only once.
dataRemaining = frame.remaining();
dataRemaining = frame.bytesLeft();
}

@Override
Expand All @@ -1767,25 +1767,25 @@ public int getFrameBytesGenerated()
}

@Override
public int getDataBytesRemaining()
public long getDataBytesRemaining()
{
return dataRemaining;
}

@Override
public boolean generate(RetainableByteBuffer.Mutable accumulator)
{
int dataRemaining = getDataBytesRemaining();
long dataRemaining = getDataBytesRemaining();

int sessionSendWindow = getSendWindow();
int streamSendWindow = stream.updateSendWindow(0);
int window = Math.min(streamSendWindow, sessionSendWindow);
if (window <= 0 && dataRemaining > 0)
return false;

int length = Math.min(dataRemaining, window);
int length = (int)Math.min(dataRemaining, window);

// Only one DATA frame is generated.
// Only one DATA frame is generated to support interleaving.
DataFrame dataFrame = (DataFrame)frame;
int frameBytes = generator.data(accumulator, dataFrame, length);
this.frameBytes += frameBytes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@

import java.nio.ByteBuffer;

import org.eclipse.jetty.io.Content;

public class DataFrame extends StreamFrame
{
private final ByteBuffer data;
private final Content.Source.Seekable source;
private final boolean endStream;
private final int length;
private final int padding;
Expand All @@ -29,15 +32,26 @@ public DataFrame(ByteBuffer data, boolean endStream)

public DataFrame(int streamId, ByteBuffer data, boolean endStream)
{
this(streamId, data, endStream, 0);
this(streamId, data, null, endStream, 0);
}

public DataFrame(int streamId, Content.Source.Seekable source, boolean endStream)
{
this(streamId, Content.Sink.TRANSFER_TO, source, endStream, 0);
}

public DataFrame(int streamId, ByteBuffer data, boolean endStream, int padding)
{
this(streamId, data, null, endStream, padding);
}

private DataFrame(int streamId, ByteBuffer data, Content.Source.Seekable source, boolean endStream, int padding)
{
super(FrameType.DATA, streamId);
this.data = data;
this.source = source;
this.endStream = endStream;
this.length = data.remaining();
this.length = remaining();
this.padding = padding;
}

Expand All @@ -46,6 +60,11 @@ public ByteBuffer getByteBuffer()
return data;
}

public Content.Source.Seekable getContentSource()
{
return source;
}

public boolean isEndStream()
{
return endStream;
Expand All @@ -56,7 +75,15 @@ public boolean isEndStream()
*/
public int remaining()
{
return data.remaining();
return Math.toIntExact(bytesLeft());
}

/**
* @return the number of data bytes remaining, as a {@code long}
*/
public long bytesLeft()
{
return data == Content.Sink.TRANSFER_TO ? source.remaining() : data.remaining();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@

package org.eclipse.jetty.http2.generator;

import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;

import org.eclipse.jetty.http2.Flags;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.Frame;
import org.eclipse.jetty.http2.frames.FrameType;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.RetainableByteBuffer;
import org.eclipse.jetty.util.Callback;

public class DataGenerator
{
Expand All @@ -32,14 +35,19 @@ public DataGenerator(HeaderGenerator headerGenerator)

public int generate(RetainableByteBuffer.Mutable accumulator, DataFrame frame, int maxLength)
{
return generateData(accumulator, frame.getStreamId(), frame.getByteBuffer(), frame.isEndStream(), maxLength);
int streamId = frame.getStreamId();
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);

ByteBuffer byteBuffer = frame.getByteBuffer();
if (byteBuffer == Content.Sink.TRANSFER_TO)
return generateData(accumulator, streamId, frame.getContentSource(), frame.isEndStream(), maxLength);
else
return generateData(accumulator, streamId, byteBuffer, frame.isEndStream(), maxLength);
}

public int generateData(RetainableByteBuffer.Mutable accumulator, int streamId, ByteBuffer data, boolean last, int maxLength)
{
if (streamId < 0)
throw new IllegalArgumentException("Invalid stream id: " + streamId);

int dataLength = data.remaining();
int maxFrameSize = headerGenerator.getMaxFrameSize();
int length = Math.min(dataLength, Math.min(maxFrameSize, maxLength));
Expand All @@ -49,12 +57,9 @@ public int generateData(RetainableByteBuffer.Mutable accumulator, int streamId,
}
else
{
int limit = data.limit();
int newLimit = data.position() + length;
data.limit(newLimit);
ByteBuffer slice = data.slice();
data.position(newLimit);
data.limit(limit);
int position = data.position();
ByteBuffer slice = data.slice(position, length);
data.position(position + length);
generateFrame(accumulator, streamId, slice, false);
}
return Frame.HEADER_LENGTH + length;
Expand All @@ -73,4 +78,60 @@ private void generateFrame(RetainableByteBuffer.Mutable accumulator, int streamI
if (data.remaining() > 0)
accumulator.add(data);
}

public int generateData(RetainableByteBuffer.Mutable accumulator, int streamId, Content.Source.Seekable source, boolean last, int maxLength)
{
long dataLength = source.remaining();
int maxFrameSize = headerGenerator.getMaxFrameSize();
int length = (int)Math.min(dataLength, Math.min(maxFrameSize, maxLength));

last = last && length == dataLength;

int flags = Flags.NONE;
if (last)
flags |= Flags.END_STREAM;

headerGenerator.generate(accumulator, FrameType.DATA, Frame.HEADER_LENGTH + length, length, flags, streamId);
Content.Source.Seekable slice = source.slice(source.position(), length);
source.position(source.position() + length);
accumulator.add(new TransferableRetainableByteBuffer(slice));

return Frame.HEADER_LENGTH + length;
}

private static class TransferableRetainableByteBuffer implements RetainableByteBuffer
{
private final Content.Source.Seekable source;

public TransferableRetainableByteBuffer(Content.Source.Seekable source)
{
this.source = source;
}

@Override
public ByteBuffer getByteBuffer() throws BufferOverflowException
{
return Content.Sink.TRANSFER_TO;
}

@Override
public long size()
{
return source.remaining();
}

@Override
public int remaining()
{
return Math.toIntExact(size());
}

@Override
public void writeTo(Content.Sink sink, boolean last, Callback callback)
{
// The "last" parameter is not used here, since "last-ness" has
// already been encoded by the generator in DATA frame header bytes.
Content.transfer(source, sink, callback);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public HTTP2Flusher(HTTP2Session session)
this.session = session;
EndPoint endPoint = session.getEndPoint();
boolean direct = endPoint != null && endPoint.getConnection() instanceof HTTP2Connection http2Connection && http2Connection.isUseOutputDirectByteBuffers();
this.accumulator = new RetainableByteBuffer.DynamicCapacity(session.getGenerator().getByteBufferPool(), direct, -1);
this.accumulator = new RetainableByteBuffer.DynamicCapacity(session.getGenerator().getByteBufferPool(), direct, -1, -1, 0);
}

@Override
Expand Down
Loading