Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve DelayedHandler. #9051 #12077

Open
wants to merge 67 commits into
base: jetty-12.1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
5d1a9f6
Revert delayed dispatch handling to the connections.
gregw Jul 23, 2024
8c07278
Revert delayed dispatch handling to the connections.
gregw Jul 23, 2024
548770c
Cleanup of HttpConnection
gregw Aug 6, 2024
ec0d325
Delay until MultiPartFormData
gregw Aug 7, 2024
fb85057
Merge remote-tracking branch 'origin/jetty-12.1.x' into experiment/je…
gregw Aug 7, 2024
da5f09f
Wait for release
gregw Aug 7, 2024
5993364
fixed removal of deprecated methods
gregw Aug 7, 2024
3ef3d6e
fixed no mimeType
gregw Aug 7, 2024
5ede88a
PR #12077 - add test for DelayedHandler with multipart
lachlan-roberts Aug 8, 2024
e8ab483
Merge remote-tracking branch 'origin/experiment/jetty-12.1.x/delayedD…
lachlan-roberts Aug 8, 2024
9e1f431
updates from review
gregw Aug 22, 2024
e0f5b7e
fixed test
gregw Aug 22, 2024
b7c8bcb
Merge remote-tracking branch 'origin/jetty-12.1.x' into experiment/je…
gregw Sep 12, 2024
d4dcd6a
WIP updates from review
gregw Sep 17, 2024
9eb9e7d
WIP updates from review
gregw Sep 18, 2024
72ab339
WIP updates from review
gregw Sep 18, 2024
e45ecad
WIP updates from review
gregw Sep 18, 2024
cac0d62
Merge branch 'jetty-12.1.x' into experiment/jetty-12.1.x/delayedDispatch
gregw Sep 18, 2024
a585cfc
WIP updates from review
gregw Sep 18, 2024
8c33aa4
Use lowercase for charsets #11741
gregw Oct 3, 2024
ccec3e4
Use lowercase for charsets #11741
gregw Oct 4, 2024
1001c31
Use lowercase for charsets #11741
gregw Oct 4, 2024
8c25183
Use lowercase for charsets #11741
gregw Oct 4, 2024
3d7f5da
Use lowercase for charsets #11741
gregw Oct 6, 2024
6c0b6f9
Use lowercase for charsets #11741
gregw Oct 6, 2024
c87adb6
javadoc
gregw Oct 9, 2024
1172d59
updates from review
gregw Oct 14, 2024
7913cbb
updates from review
gregw Oct 14, 2024
33aaadb
Merge branch 'jetty-12.1.x' into experiment/jetty-12.1.x/delayedDispatch
gregw Oct 14, 2024
2cb7da6
Merge branch 'fix/jetty-12.1.x/11741/mimetypes' into experiment/jetty…
gregw Oct 14, 2024
4c5be88
WIP
gregw Oct 15, 2024
bb37636
Experiment to reuse buffer in HttpConnection to make retaining chunks…
gregw Oct 15, 2024
af81974
Experiment to reuse buffer in HttpConnection to make retaining chunks…
gregw Oct 15, 2024
907da62
Merge branch 'jetty-12.1.x' into experiment/jetty-12.1.x/delayedDispatch
gregw Oct 16, 2024
b48cfda
fixed mimetype lookup
gregw Oct 16, 2024
039e1c9
Merge remote-tracking branch 'origin/jetty-12.1.x' into experiment/je…
gregw Oct 21, 2024
ba98543
Delay content until 75% of an input buffer is read.
gregw Oct 22, 2024
472233b
Delay content until 75% of an input buffer is read.
gregw Oct 22, 2024
b32294e
Merge remote-tracking branch 'origin/jetty-12.1.x' into experiment/je…
gregw Oct 22, 2024
0dd3279
updates from review
gregw Oct 23, 2024
346adea
updates from review
gregw Oct 23, 2024
da8e70f
updates from review
gregw Oct 23, 2024
8ded76f
Merge remote-tracking branch 'origin/jetty-12.1.x' into experiment/je…
gregw Oct 24, 2024
847f06e
Merge branch 'jetty-12.1.x' into experiment/jetty-12.1.x/delayedDispatch
gregw Oct 24, 2024
a9b1578
updates from review
gregw Oct 24, 2024
353a350
Merge branch 'jetty-12.1.x' into experiment/jetty-12.1.x/delayedDispatch
gregw Oct 29, 2024
2fc4aa1
configurable space
gregw Oct 29, 2024
f400278
update javadoc and updates from review
gregw Oct 30, 2024
4692460
Merge remote-tracking branch 'origin/jetty-12.1.x' into experiment/je…
gregw Oct 30, 2024
775bef8
Merge branch 'jetty-12.1.x' into experiment/jetty-12.1.x/delayedDispatch
gregw Oct 31, 2024
ecbf249
Merge branch 'jetty-12.1.x' into experiment/jetty-12.1.x/delayedDispatch
gregw Oct 31, 2024
8fe31fe
Merge remote-tracking branch 'origin/jetty-12.1.x' into experiment/je…
gregw Nov 4, 2024
0543ae0
Implement non-compact algorithm in HTTP and HTTP/2
gregw Nov 4, 2024
687a7bc
Implement non-compact algorithm in HTTP and HTTP/2
gregw Nov 4, 2024
7cae30e
Merge remote-tracking branch 'origin/jetty-12.1.x' into experiment/je…
gregw Nov 4, 2024
1657145
fixed comment
gregw Nov 5, 2024
a9c709b
Merge remote-tracking branch 'origin/jetty-12.1.x' into experiment/je…
gregw Nov 5, 2024
acfba41
improved comments
gregw Nov 5, 2024
1e838f7
improved comments
gregw Nov 6, 2024
a23c8f4
updates from review
gregw Nov 7, 2024
0e9c9ec
Merge remote-tracking branch 'origin/jetty-12.1.x' into experiment/je…
gregw Nov 7, 2024
1f89ff5
Fix test leaks
gregw Nov 7, 2024
18f655c
Fix test leaks
gregw Nov 7, 2024
73e21e7
updates from review
gregw Nov 8, 2024
278e925
Implemented for H3
gregw Nov 9, 2024
4685621
Updates from review
gregw Nov 9, 2024
4a0e1c1
Configurable chunk overhead
gregw Nov 11, 2024
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 @@ -489,15 +489,18 @@ public void setMaxParts(long maxParts)
*/
public void configure(MultiPartConfig config)
{
parser.setMaxParts(config.getMaxParts());
maxMemoryFileSize = config.getMaxMemoryPartSize();
maxFileSize = config.getMaxPartSize();
maxLength = config.getMaxSize();
parser.setPartHeadersMaxLength(config.getMaxHeadersSize());
useFilesForPartsWithoutFileName = config.isUseFilesForPartsWithoutFileName();
filesDirectory = config.getLocation();
complianceListener = config.getViolationListener();
compliance = config.getMultiPartCompliance();
if (config != null)
gregw marked this conversation as resolved.
Show resolved Hide resolved
{
parser.setMaxParts(config.getMaxParts());
maxMemoryFileSize = config.getMaxMemoryPartSize();
maxFileSize = config.getMaxPartSize();
maxLength = config.getMaxSize();
parser.setPartHeadersMaxLength(config.getMaxHeadersSize());
useFilesForPartsWithoutFileName = config.isUseFilesForPartsWithoutFileName();
filesDirectory = config.getLocation();
complianceListener = config.getViolationListener();
compliance = config.getMultiPartCompliance();
}
}

// Only used for testing.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,9 @@ protected AbstractConnection(EndPoint endPoint, Executor executor)
_readCallback = new ReadCallback();
}

@Deprecated
gregw marked this conversation as resolved.
Show resolved Hide resolved
@Override
public InvocationType getInvocationType()
{
// TODO consider removing the #fillInterested method from the connection and only use #fillInterestedCallback
// so a connection need not be Invocable
gregw marked this conversation as resolved.
Show resolved Hide resolved
return Invocable.super.getInvocationType();
}

Expand Down Expand Up @@ -170,7 +167,7 @@ public boolean isFillInterested()
protected void onFillInterestedFailed(Throwable cause)
{
if (LOG.isDebugEnabled())
LOG.debug("{} onFillInterestedFailed {}", this, cause);
LOG.debug("onFillInterestedFailed {}", this, cause);
if (_endPoint.isOpen())
{
boolean close = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@

import java.util.Objects;

import org.eclipse.jetty.http.ComplianceViolation;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.internal.HttpConnection;
import org.eclipse.jetty.util.annotation.Name;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A Connection Factory for HTTP Connections.
Expand All @@ -32,10 +29,7 @@
*/
public class HttpConnectionFactory extends AbstractConnectionFactory implements HttpConfiguration.ConnectionFactory
{
private static final Logger LOG = LoggerFactory.getLogger(HttpConnectionFactory.class);
private final HttpConfiguration _config;
private boolean _useInputDirectByteBuffers;
private boolean _useOutputDirectByteBuffers;

public HttpConnectionFactory()
{
Expand All @@ -57,45 +51,26 @@ public HttpConfiguration getHttpConfiguration()
return _config;
}

/**
* @deprecated use {@link HttpConfiguration#getComplianceViolationListeners()} instead to know if there
* are any {@link ComplianceViolation.Listener} to notify. this method will be removed in Jetty 12.1.0
*/
@Deprecated(since = "12.0.6", forRemoval = true)
public boolean isRecordHttpComplianceViolations()
{
return !_config.getComplianceViolationListeners().isEmpty();
}

/**
* Does nothing.
* @deprecated use {@link HttpConfiguration#addComplianceViolationListener(ComplianceViolation.Listener)} instead.
* this method will be removed in Jetty 12.1.0
*/
@Deprecated(since = "12.0.6", forRemoval = true)
public void setRecordHttpComplianceViolations(boolean recordHttpComplianceViolations)
{
_config.addComplianceViolationListener(new ComplianceViolation.LoggingListener());
}

public boolean isUseInputDirectByteBuffers()
{
return _useInputDirectByteBuffers;
return _config.isUseInputDirectByteBuffers();
gregw marked this conversation as resolved.
Show resolved Hide resolved
}

@Deprecated(forRemoval = true, since = "12.1.0")
public void setUseInputDirectByteBuffers(boolean useInputDirectByteBuffers)
{
_useInputDirectByteBuffers = useInputDirectByteBuffers;
_config.setUseInputDirectByteBuffers(useInputDirectByteBuffers);
}

public boolean isUseOutputDirectByteBuffers()
{
return _useOutputDirectByteBuffers;
return _config.isUseOutputDirectByteBuffers();
}

@Deprecated(forRemoval = true, since = "12.1.0")
public void setUseOutputDirectByteBuffers(boolean useOutputDirectByteBuffers)
{
_useOutputDirectByteBuffers = useOutputDirectByteBuffers;
_config.setUseOutputDirectByteBuffers(useOutputDirectByteBuffers);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.http.MultiPartConfig;
import org.eclipse.jetty.http.MultiPartFormData;
gregw marked this conversation as resolved.
Show resolved Hide resolved
import org.eclipse.jetty.server.FormFields;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
Expand Down Expand Up @@ -100,19 +100,27 @@ protected DelayedProcess newDelayedProcess(boolean contentExpected, String conte
if (!contentExpected)
return null;

// if we are not configured to delay dispatch, then no delay
if (!request.getConnectionMetaData().getHttpConfiguration().isDelayDispatchUntilContent())
return null;

// If there is no known content type, then delay only until content is available
// if no mimeType, then no delay
if (mimeType == null)
return new UntilContentDelayedProcess(handler, request, response, callback);
return null;

// Otherwise, delay until a known content type is fully read; or if the type is not known then until the content is available
return switch (mimeType)
{
case FORM_ENCODED -> new UntilFormDelayedProcess(handler, request, response, callback, contentType);
default -> new UntilContentDelayedProcess(handler, request, response, callback);
case MULTIPART_FORM_DATA ->
{
MultiPartConfig config;
if (request.getContext().getAttribute(MultiPartConfig.class.getName()) instanceof MultiPartConfig mpc)
config = mpc;
else if (getHandler().getServer().getAttribute(MultiPartConfig.class.getName()) instanceof MultiPartConfig mpc)
config = mpc;
else
yield null;

yield new UntilMultipartDelayedProcess(handler, request, response, callback, contentType, config);
}
default -> null;
};
}

Expand Down Expand Up @@ -167,96 +175,82 @@ protected void process()
protected abstract void delay() throws Exception;
}

protected static class UntilContentDelayedProcess extends DelayedProcess
protected static class UntilFormDelayedProcess extends DelayedProcess
{
public UntilContentDelayedProcess(Handler handler, Request request, Response response, Callback callback)
private final Charset _charset;

public UntilFormDelayedProcess(Handler handler, Request request, Response response, Callback callback, String contentType)
{
super(handler, request, response, callback);

String cs = MimeTypes.getCharsetFromContentType(contentType);
_charset = StringUtil.isEmpty(cs) ? StandardCharsets.UTF_8 : Charset.forName(cs);
}

@Override
protected void delay()
{
Content.Chunk chunk = super.getRequest().read();
if (chunk == null)
{
getRequest().demand(this::onContent);
}
else
{
RewindChunkRequest request = new RewindChunkRequest(getRequest(), chunk);
try
{
getHandler().handle(request, getResponse(), getCallback());
}
catch (Throwable x)
{
// Use the wrapped request so that the error handling can
// consume the request content and release the already read chunk.
Response.writeError(request, getResponse(), getCallback(), x);
}
}
CompletableFuture<Fields> futureFormFields = FormFields.from(getRequest(), _charset);

// if we are done already, then we are still in the scope of the original process call and can
// process directly, otherwise we must execute a call to process as we are within a serialized
// demand callback.
futureFormFields.whenComplete(futureFormFields.isDone() ? this::process : this::executeProcess);
}

public void onContent()
private void process(Fields fields, Throwable x)
{
// We must execute here, because demand callbacks are serialized and process may block on a demand callback
getRequest().getContext().execute(this::process);
if (x == null)
super.process();
else
Response.writeError(getRequest(), getResponse(), getCallback(), x);
}

private static class RewindChunkRequest extends Request.Wrapper
private void executeProcess(Fields fields, Throwable x)
{
private final AtomicReference<Content.Chunk> _chunk;

public RewindChunkRequest(Request wrapped, Content.Chunk chunk)
{
super(wrapped);
_chunk = new AtomicReference<>(chunk);
}

@Override
public Content.Chunk read()
{
Content.Chunk chunk = _chunk.getAndSet(null);
if (chunk != null)
return chunk;
return super.read();
}
if (x == null)
// We must execute here as even though we have consumed all the input, we are probably
// invoked in a demand runnable that is serialized with any write callbacks that might be done in process
getRequest().getContext().execute(super::process);
else
Response.writeError(getRequest(), getResponse(), getCallback(), x);
}
}

protected static class UntilFormDelayedProcess extends DelayedProcess
protected static class UntilMultipartDelayedProcess extends DelayedProcess
{
private final Charset _charset;
private final String _contentType;
private final MultiPartConfig _config;

public UntilFormDelayedProcess(Handler handler, Request wrapped, Response response, Callback callback, String contentType)
public UntilMultipartDelayedProcess(Handler handler, Request request, Response response, Callback callback, String contentType, MultiPartConfig config)
{
super(handler, wrapped, response, callback);

String cs = MimeTypes.getCharsetFromContentType(contentType);
_charset = StringUtil.isEmpty(cs) ? StandardCharsets.UTF_8 : Charset.forName(cs);
super(handler, request, response, callback);
_contentType = contentType;
_config = config;
}

@Override
protected void delay()
{
CompletableFuture<Fields> futureFormFields = FormFields.from(getRequest(), _charset);
Request request = getRequest();

CompletableFuture<MultiPartFormData.Parts> futureMultiPart = MultiPartFormData.from(request, request, _contentType, _config);

// if we are done already, then we are still in the scope of the original process call and can
// process directly, otherwise we must execute a call to process as we are within a serialized
// demand callback.
futureFormFields.whenComplete(futureFormFields.isDone() ? this::process : this::executeProcess);
futureMultiPart.whenComplete(futureMultiPart.isDone() ? this::process : this::executeProcess);
gregw marked this conversation as resolved.
Show resolved Hide resolved
}

private void process(Fields fields, Throwable x)
private void process(MultiPartFormData.Parts parts, Throwable failure)
{
if (x == null)
if (failure == null)
super.process();
else
Response.writeError(getRequest(), getResponse(), getCallback(), x);
Response.writeError(getRequest(), getResponse(), getCallback(), failure);
}

private void executeProcess(Fields fields, Throwable x)
private void executeProcess(MultiPartFormData.Parts parts, Throwable x)
{
if (x == null)
// We must execute here as even though we have consumed all the input, we are probably
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -690,18 +690,22 @@ public void run()
@Override
public void succeeded()
{
HttpStream stream;
boolean completeStream;
HttpStream completeStream = null;
gregw marked this conversation as resolved.
Show resolved Hide resolved
Throwable failure = null;
try (AutoLock ignored = _lock.lock())
{
assert _callbackCompleted;
_streamSendState = StreamSendState.LAST_COMPLETE;
completeStream = _handling == null;
stream = _stream;
if (_handling == null)
{
completeStream = _stream;
_stream = null;
failure = _callbackFailure;
gregw marked this conversation as resolved.
Show resolved Hide resolved
}
}

if (completeStream)
completeStream(stream, null);
if (completeStream != null)
completeStream(completeStream, failure);
}

/**
Expand Down
Loading
Loading