An ordered list of {@link Part}s that can be accessed by name.
*/
From b1c8252df53b2baf709737811c29888588a8f4b1 Mon Sep 17 00:00:00 2001
From: gregw
Date: Wed, 23 Oct 2024 06:41:53 +1100
Subject: [PATCH 20/27] javadoc
---
.../main/java/org/eclipse/jetty/util/thread/Invocable.java | 4 ++++
.../java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java | 1 +
.../eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java | 2 ++
3 files changed, 7 insertions(+)
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java
index 2fa064e97543..232cd677c1b7 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java
@@ -269,6 +269,10 @@ public InvocationType getInvocationType()
* The {@link InvocationType} is the type passed in construction (default NON_BLOCKING).
* Methods on {@link java.util.concurrent.CompletableFuture} that may act in contradiction to the passed
* {@link InvocationType} are extended to throw {@link IllegalStateException} in those circumstances.
+ *
+ * Counterintuitively, if the blocking APIs like {@link #get()} are to be used, the {@link InvocationType}
+ * should be {@link InvocationType#NON_BLOCKING}, as the wake-up callbacks used will not block.
+ *
* @param The type of the result
*/
class InvocableCompletableFuture extends java.util.concurrent.CompletableFuture implements Invocable
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java
index dfea1e06afc4..3b1cf33f99c1 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java
@@ -32,6 +32,7 @@
*/
public class EagerFormHandler extends Handler.Wrapper
{
+ // TODO replace with DelayedDispatchHandler
public EagerFormHandler()
{
this(null);
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java
index d9ee03fb3c1e..34935490bf23 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java
@@ -67,6 +67,7 @@ public static CompletableFuture from(ServletRequest servletRequest)
/**
* Get future {@link ServletMultiPartFormData.Parts} from a servlet request.
* @param servletRequest A servlet request
+ * @param invocationType The invocation type of the resulting CompletableFuture.
* @return A future {@link ServletMultiPartFormData.Parts}, which may have already been created and/or completed.
* @see #from(ServletRequest, String)
*/
@@ -89,6 +90,7 @@ public static CompletableFuture from(ServletRequest servletRequest, Strin
/**
* Get future {@link ServletMultiPartFormData.Parts} from a servlet request.
* @param servletRequest A servlet request
+ * @param invocationType The invocation type of the resulting CompletableFuture.
* @param contentType The contentType, passed as an optimization as it has likely already been retrieved.
* @return A future {@link ServletMultiPartFormData.Parts}, which may have already been created and/or completed.
*/
From 6b0843581d38bc0b2c4ce46834d973727c299c5c Mon Sep 17 00:00:00 2001
From: gregw
Date: Wed, 23 Oct 2024 10:40:20 +1100
Subject: [PATCH 21/27] Deprecated the CF APIs and replaced with explicit
getXxx onXxx methods
---
.../eclipse/jetty/http/MultiPartFormData.java | 66 ++++++++-
.../authentication/FormAuthenticator.java | 27 ++--
.../org/eclipse/jetty/server/FormFields.java | 134 +++++++++++++-----
.../jetty/server/handler/DelayedHandler.java | 57 ++++++--
.../server/handler/DelayedHandlerTest.java | 73 ++++++++++
.../eclipse/jetty/util/thread/Invocable.java | 25 ++++
.../jetty/ee10/servlet/EagerFormHandler.java | 105 ++++++++++----
.../jetty/ee10/servlet/ServletApiRequest.java | 16 +--
.../servlet/ServletMultiPartFormData.java | 55 ++++++-
9 files changed, 449 insertions(+), 109 deletions(-)
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormData.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormData.java
index 7ee0a98f0636..cbe5af83e618 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormData.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormData.java
@@ -26,6 +26,7 @@
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
import java.util.function.Function;
import org.eclipse.jetty.io.Content;
@@ -34,6 +35,7 @@
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.thread.AutoLock;
+import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Invocable.InvocationType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -79,6 +81,53 @@ private MultiPartFormData()
{
}
+ /**
+ * Returns {@code multipart/form-data} parts using the given {@link Content.Source} and {@link MultiPartConfig},
+ * blocking if necessary.
+ *
+ * @param content the source of the multipart content.
+ * @param attributes the attributes where the futureParts are tracked.
+ * @param contentType the value of the {@link HttpHeader#CONTENT_TYPE} header.
+ * @param config the multipart configuration.
+ * @return the parts
+ */
+ public static MultiPartFormData.Parts getParts(Content.Source content, Attributes attributes, String contentType, MultiPartConfig config)
+ {
+ return from(content, InvocationType.NON_BLOCKING, attributes, contentType, config).join();
+ }
+
+ /**
+ * @param content the source of the multipart content.
+ * @param attributes the attributes where the futureParts are tracked.
+ * @param contentType the value of the {@link HttpHeader#CONTENT_TYPE} header.
+ * @param config the multipart configuration.
+ * @param immediate The action to take if the FormFields are available immediately (from within the scope of the call to this method).
+ * @param future The action to take when the FormFields are available, if they are not available immediately. The {@link org.eclipse.jetty.util.thread.Invocable.InvocationType}
+ * of this parameter will be used as the type for any implementation calls to {@link Content.Source#demand(Runnable)}.
+ */
+ public static void onParts(Content.Source content, Attributes attributes, String contentType, MultiPartConfig config, BiConsumer immediate, Invocable.InvocableBiConsumer future)
+ {
+ CompletableFuture futureParts = from(content, future.getInvocationType(), attributes, contentType, config);
+ if (futureParts.isDone())
+ {
+ Parts parts = null;
+ Throwable error = null;
+ try
+ {
+ parts = futureParts.get();
+ }
+ catch (Throwable t)
+ {
+ error = t;
+ }
+ immediate.accept(parts, error);
+ }
+ else
+ {
+ futureParts.whenComplete(future);
+ }
+ }
+
/**
* Returns {@code multipart/form-data} parts using the given {@link Content.Source} and {@link MultiPartConfig}.
*
@@ -87,7 +136,10 @@ private MultiPartFormData()
* @param contentType the value of the {@link HttpHeader#CONTENT_TYPE} header.
* @param config the multipart configuration.
* @return the future parts
+ * @deprecated use {@link #getParts(Content.Source, Attributes, String, MultiPartConfig)}
+ * and/or {@link #onParts(Content.Source, Attributes, String, MultiPartConfig, BiConsumer, Invocable.InvocableBiConsumer)}
*/
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture from(Content.Source content, Attributes attributes, String contentType, MultiPartConfig config)
{
return from(content, InvocationType.NON_BLOCKING, attributes, contentType, config);
@@ -101,7 +153,10 @@ public static CompletableFuture from(Content.Source con
* @param contentType the value of the {@link HttpHeader#CONTENT_TYPE} header.
* @param config the multipart configuration.
* @return the future parts
+ * @deprecated use {@link #getParts(Content.Source, Attributes, String, MultiPartConfig)}
+ * and/or {@link #onParts(Content.Source, Attributes, String, MultiPartConfig, BiConsumer, Invocable.InvocableBiConsumer)}
*/
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture from(Content.Source content, InvocationType invocationType, Attributes attributes, String contentType, MultiPartConfig config)
{
// Look for an existing future (we use the future here rather than the parts as it can remember any failure).
@@ -130,9 +185,10 @@ public static CompletableFuture from(Content.Source con
/**
* Returns {@code multipart/form-data} parts using {@link MultiPartCompliance#RFC7578}.
- * @deprecated use {@link #from(Content.Source, Attributes, String, MultiPartConfig)}.
+ * @deprecated use {@link #getParts(Content.Source, Attributes, String, MultiPartConfig)}
+ * and/or {@link #onParts(Content.Source, Attributes, String, MultiPartConfig, BiConsumer, Invocable.InvocableBiConsumer)}
*/
- @Deprecated
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture from(Attributes attributes, String boundary, Function> parse)
{
return from(attributes, MultiPartCompliance.RFC7578, ComplianceViolation.Listener.NOOP, boundary, parse);
@@ -147,9 +203,10 @@ public static CompletableFuture from(Attributes attributes, String bounda
* @param boundary the boundary for the {@code multipart/form-data} parts
* @param parse the parser completable future
* @return the future parts
- * @deprecated use {@link #from(Content.Source, Attributes, String, MultiPartConfig)}.
+ * @deprecated use {@link #getParts(Content.Source, Attributes, String, MultiPartConfig)}
+ * and/or {@link #onParts(Content.Source, Attributes, String, MultiPartConfig, BiConsumer, Invocable.InvocableBiConsumer)}
*/
- @Deprecated
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture from(Attributes attributes, MultiPartCompliance compliance, ComplianceViolation.Listener listener, String boundary, Function> parse)
{
CompletableFuture futureParts = get(attributes);
@@ -168,6 +225,7 @@ public static CompletableFuture from(Attributes attributes, MultiPartComp
* @return the future parts
*/
@SuppressWarnings("unchecked")
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture get(Attributes attributes)
{
return (CompletableFuture)attributes.getAttribute(MultiPartFormData.class.getName());
diff --git a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
index 79e2d5828917..864fa04cc885 100644
--- a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
+++ b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
@@ -13,8 +13,7 @@
package org.eclipse.jetty.security.authentication;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ExecutionException;
+import java.util.concurrent.CompletionException;
import java.util.function.Function;
import org.eclipse.jetty.http.HttpHeader;
@@ -202,8 +201,8 @@ public Request prepareRequest(Request request, AuthenticationState authenticatio
session.removeAttribute(__J_URI);
Object post = session.removeAttribute(__J_POST);
- if (post instanceof CompletableFuture> futureFields)
- FormFields.set(request, (CompletableFuture)futureFields);
+ if (post instanceof Fields futureFields)
+ FormFields.set(request, futureFields);
String method = (String)session.removeAttribute(__J_METHOD);
if (method != null && request.getMethod().equals(method))
@@ -225,16 +224,9 @@ public String getMethod()
protected Fields getParameters(Request request)
{
- try
- {
- Fields queryFields = Request.extractQueryParameters(request);
- Fields formFields = FormFields.from(request).get();
- return Fields.combine(queryFields, formFields);
- }
- catch (InterruptedException | ExecutionException e)
- {
- throw new RuntimeException(e);
- }
+ Fields queryFields = Request.extractQueryParameters(request);
+ Fields formFields = FormFields.getFields(request);
+ return Fields.combine(queryFields, formFields);
}
protected String encodeURL(String url, Request request)
@@ -334,15 +326,14 @@ public AuthenticationState validateRequest(Request request, Response response, C
{
try
{
- CompletableFuture futureFields = FormFields.from(request);
- futureFields.get();
+ Fields futureFields = FormFields.getFields(request);
session.setAttribute(__J_POST, futureFields);
}
- catch (ExecutionException e)
+ catch (CompletionException e)
{
throw new ServerAuthException(e.getCause());
}
- catch (InterruptedException e)
+ catch (Exception e)
{
throw new ServerAuthException(e);
}
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java
index 8980733b2377..d24c08a2ed97 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java
@@ -18,6 +18,7 @@
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.MimeTypes;
@@ -82,17 +83,100 @@ public static Charset getFormEncodedCharset(Request request)
* @param request The request to which to associate the fields with
* @param fields A {@link CompletableFuture} that will provide either the fields or a failure.
*/
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static void set(Request request, CompletableFuture fields)
{
request.setAttribute(FormFields.class.getName(), fields);
}
+ /**
+ * Set a {@link Fields} or related failure for the request
+ * @param request The request to which to associate the fields with
+ * @param fields A {@link CompletableFuture} that will provide either the fields or a failure.
+ */
+ public static void set(Request request, Fields fields)
+ {
+ request.setAttribute(FormFields.class.getName(), fields);
+ }
+
+ /**
+ * Get the Fields from a request, blocking if necessary.
+ * @param request The request to get the Fields from
+ * @return the Fields
+ */
+ public static Fields getFields(Request request)
+ {
+ CompletableFuture fields = from(request, InvocationType.NON_BLOCKING);
+ return fields.join();
+ }
+
+ /**
+ * Get the Fields from a request, blocking if necessary.
+ * @param request The request to get the Fields from
+ * @param maxFields The maximum number of fields to accept
+ * @param maxLength The maximum length of fields
+ * @return the Fields
+ */
+ public static Fields getFields(Request request, int maxFields, int maxLength)
+ {
+ CompletableFuture fields = from(request, InvocationType.NON_BLOCKING, getFormEncodedCharset(request), maxFields, maxLength);
+ return fields.join();
+ }
+
+ /**
+ * Actions to take when parsing FormFields asynchronously from a request is complete
+ * @param request The request to parse FormFields from
+ * @param immediate The action to take if the FormFields are available immediately (from within the scope of the call to this method).
+ * @param future The action to take when the FormFields are available, if they are not available immediately. The {@link org.eclipse.jetty.util.thread.Invocable.InvocationType}
+ * of this parameter will be used as the type for any implementation calls to {@link Content.Source#demand(Runnable)}.
+ */
+ public static void onFields(Request request, BiConsumer immediate, InvocableBiConsumer future)
+ {
+ onFields(from(request, future.getInvocationType()), immediate, future);
+ }
+
+ /**
+ * Actions to take when parsing FormFields asynchronously from a request is complete
+ * @param request The request to parse FormFields from
+ * @param charset The charset of the form.
+ * @param immediate The action to take if the FormFields are available immediately (from within the scope of the call to this method).
+ * @param future The action to take when the FormFields are available, if they are not available immediately. The {@link org.eclipse.jetty.util.thread.Invocable.InvocationType}
+ * of this parameter will be used as the type for any implementation calls to {@link Content.Source#demand(Runnable)}.
+ */
+ public static void onFields(Request request, Charset charset, BiConsumer immediate, InvocableBiConsumer future)
+ {
+ onFields(from(request, future.getInvocationType(), charset), immediate, future);
+ }
+
+ private static void onFields(CompletableFuture futureFields, BiConsumer immediate, InvocableBiConsumer future)
+ {
+ if (futureFields.isDone())
+ {
+ Fields fields = null;
+ Throwable error = null;
+ try
+ {
+ fields = futureFields.get();
+ }
+ catch (Throwable t)
+ {
+ error = t;
+ }
+ immediate.accept(fields, error);
+ }
+ else
+ {
+ futureFields.whenComplete(future);
+ }
+ }
+
/**
* @param request The request to enquire from
* @return A {@link CompletableFuture} that will provide either the fields or a failure, or null if none set.
* @see #from(Request)
*
*/
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture get(Request request)
{
Object attr = request.getAttribute(FormFields.class.getName());
@@ -110,6 +194,7 @@ else if (attr instanceof Fields fields)
* as a {@link Content.Source} from which to read the fields and set the attribute.
* @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
*/
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture from(Request request)
{
int maxFields = getContextAttribute(request.getContext(), FormFields.MAX_FIELDS_ATTRIBUTE, FormFields.MAX_FIELDS_DEFAULT);
@@ -117,14 +202,8 @@ public static CompletableFuture from(Request request)
return from(request, maxFields, maxLength);
}
- /**
- * Find or create a {@link FormFields} from a {@link Content.Source}.
- * @param request The {@link Request} in which to look for an existing {@link FormFields} attribute,
- * using the classname as the attribute name, else the request is used
- * as a {@link Content.Source} from which to read the fields and set the attribute.
- * @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
- */
- public static CompletableFuture from(Request request, InvocationType invocationType)
+ @Deprecated(forRemoval = true, since = "12.0.15")
+ private static CompletableFuture from(Request request, InvocationType invocationType)
{
int maxFields = getContextAttribute(request.getContext(), FormFields.MAX_FIELDS_ATTRIBUTE, FormFields.MAX_FIELDS_DEFAULT);
int maxLength = getContextAttribute(request.getContext(), FormFields.MAX_LENGTH_ATTRIBUTE, FormFields.MAX_LENGTH_DEFAULT);
@@ -139,6 +218,7 @@ public static CompletableFuture from(Request request, InvocationType inv
* @param charset the {@link Charset} to use for byte to string conversion.
* @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
*/
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture from(Request request, Charset charset)
{
int maxFields = getContextAttribute(request.getContext(), FormFields.MAX_FIELDS_ATTRIBUTE, FormFields.MAX_FIELDS_DEFAULT);
@@ -146,15 +226,8 @@ public static CompletableFuture from(Request request, Charset charset)
return from(request, charset, maxFields, maxLength);
}
- /**
- * Find or create a {@link FormFields} from a {@link Content.Source}.
- * @param request The {@link Request} in which to look for an existing {@link FormFields} attribute,
- * using the classname as the attribute name, else the request is used
- * as a {@link Content.Source} from which to read the fields and set the attribute.
- * @param charset the {@link Charset} to use for byte to string conversion.
- * @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
- */
- public static CompletableFuture from(Request request, InvocationType invocationType, Charset charset)
+ @Deprecated(forRemoval = true, since = "12.0.15")
+ private static CompletableFuture from(Request request, InvocationType invocationType, Charset charset)
{
int maxFields = getContextAttribute(request.getContext(), FormFields.MAX_FIELDS_ATTRIBUTE, FormFields.MAX_FIELDS_DEFAULT);
int maxLength = getContextAttribute(request.getContext(), FormFields.MAX_LENGTH_ATTRIBUTE, FormFields.MAX_FIELDS_DEFAULT);
@@ -170,6 +243,7 @@ public static CompletableFuture from(Request request, InvocationType inv
* @param maxLength The maximum total size of the fields
* @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
*/
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture from(Request request, int maxFields, int maxLength)
{
return from(request, getFormEncodedCharset(request), maxFields, maxLength);
@@ -185,37 +259,19 @@ public static CompletableFuture from(Request request, int maxFields, int
* @param maxLength The maximum total size of the fields
* @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
*/
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture from(Request request, Charset charset, int maxFields, int maxLength)
{
return from(request, InvocationType.NON_BLOCKING, request, charset, maxFields, maxLength);
}
- /**
- * Find or create a {@link FormFields} from a {@link Content.Source}.
- * @param request The {@link Request} in which to look for an existing {@link FormFields} attribute,
- * using the classname as the attribute name, else the request is used
- * as a {@link Content.Source} from which to read the fields and set the attribute.
- * @param charset the {@link Charset} to use for byte to string conversion.
- * @param maxFields The maximum number of fields to be parsed
- * @param maxLength The maximum total size of the fields
- * @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
- */
- public static CompletableFuture from(Request request, InvocationType invocationType, Charset charset, int maxFields, int maxLength)
+ @Deprecated(forRemoval = true, since = "12.0.15")
+ private static CompletableFuture from(Request request, InvocationType invocationType, Charset charset, int maxFields, int maxLength)
{
return from(request, invocationType, request, charset, maxFields, maxLength);
}
- /**
- * Find or create a {@link FormFields} from a {@link Content.Source}.
- * @param source The {@link Content.Source} from which to read the fields.
- * @param attributes The {@link Attributes} in which to look for an existing {@link CompletableFuture} of
- * {@link FormFields}, using the classname as the attribute name. If not found the attribute
- * is set with the created {@link CompletableFuture} of {@link FormFields}.
- * @param charset the {@link Charset} to use for byte to string conversion.
- * @param maxFields The maximum number of fields to be parsed
- * @param maxLength The maximum total size of the fields
- * @return A {@link CompletableFuture} that will provide the {@link Fields} or a failure.
- */
+ @Deprecated(forRemoval = true, since = "12.0.15")
static CompletableFuture from(Content.Source source, InvocationType invocationType, Attributes attributes, Charset charset, int maxFields, int maxLength)
{
Object attr = attributes.getAttribute(FormFields.class.getName());
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java
index e0dbdeecdc61..c5317e9dc378 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DelayedHandler.java
@@ -16,7 +16,6 @@
import java.nio.charset.Charset;
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;
@@ -24,6 +23,8 @@
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.MultiPartConfig;
+import org.eclipse.jetty.http.MultiPartFormData;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.FormFields;
import org.eclipse.jetty.server.Handler;
@@ -113,6 +114,14 @@ protected DelayedProcess newDelayedProcess(boolean contentExpected, String conte
return switch (mimeType)
{
case FORM_ENCODED -> new UntilFormDelayedProcess(handler, request, response, callback, contentType);
+ case MULTIPART_FORM_DATA ->
+ {
+ if (request.getContext().getAttribute(MultiPartConfig.class.getName()) instanceof MultiPartConfig mpc)
+ yield new UntilMultipartDelayedProcess(handler, request, response, callback, contentType, mpc);
+ if (getServer().getAttribute(MultiPartConfig.class.getName()) instanceof MultiPartConfig mpc)
+ yield new UntilMultipartDelayedProcess(handler, request, response, callback, contentType, mpc);
+ yield null;
+ }
default -> new UntilContentDelayedProcess(handler, request, response, callback);
};
}
@@ -241,13 +250,7 @@ public UntilFormDelayedProcess(Handler handler, Request wrapped, Response respon
@Override
protected void delay()
{
- CompletableFuture futureFormFields = FormFields.from(getRequest(), InvocationType.BLOCKING, _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.
- boolean done = futureFormFields.isDone();
- futureFormFields.whenComplete(done ? this::process : this::executeProcess);
+ FormFields.onFields(getRequest(), _charset, this::process, Invocable.from(InvocationType.NON_BLOCKING, this::executeProcess));
}
private void process(Fields fields, Throwable x)
@@ -268,4 +271,42 @@ private void executeProcess(Fields fields, Throwable x)
Response.writeError(getRequest(), getResponse(), getCallback(), x);
}
}
+
+ protected static class UntilMultipartDelayedProcess extends DelayedProcess
+ {
+ private final String _contentType;
+ private final MultiPartConfig _config;
+
+ public UntilMultipartDelayedProcess(Handler handler, Request request, Response response, Callback callback, String contentType, MultiPartConfig config)
+ {
+ super(handler, request, response, callback);
+ _contentType = contentType;
+ _config = config;
+ }
+
+ @Override
+ protected void delay()
+ {
+ Request request = getRequest();
+ MultiPartFormData.onParts(request, request, _contentType, _config, this::process, Invocable.from(InvocationType.NON_BLOCKING, this::executeProcess));
+ }
+
+ private void process(MultiPartFormData.Parts fields, Throwable x)
+ {
+ if (x == null)
+ super.process();
+ else
+ Response.writeError(getRequest(), getResponse(), getCallback(), x);
+ }
+
+ private void executeProcess(MultiPartFormData.Parts fields, Throwable x)
+ {
+ 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);
+ }
+ }
}
diff --git a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DelayedHandlerTest.java b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DelayedHandlerTest.java
index 88c213a3132d..3d4c4ce68c1d 100644
--- a/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DelayedHandlerTest.java
+++ b/jetty-core/jetty-server/src/test/java/org/eclipse/jetty/server/handler/DelayedHandlerTest.java
@@ -18,14 +18,18 @@
import java.io.PrintStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
import java.util.concurrent.TimeUnit;
import org.awaitility.Awaitility;
+import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.http.MultiPartConfig;
+import org.eclipse.jetty.http.MultiPartFormData;
import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.server.FormFields;
import org.eclipse.jetty.server.Handler;
@@ -33,6 +37,7 @@
import org.eclipse.jetty.server.Response;
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.Fields;
import org.junit.jupiter.api.AfterEach;
@@ -503,4 +508,72 @@ public boolean handle(Request request, Response response, Callback callback) thr
assertThat(content, containsString("x=[1, 2, 3]"));
}
}
+
+ @Test
+ public void testDelayedMultipart() throws Exception
+ {
+ DelayedHandler delayedHandler = new DelayedHandler();
+ _server.setAttribute(MultiPartConfig.class.getName(), new MultiPartConfig.Builder().build());
+ _server.setHandler(delayedHandler);
+ delayedHandler.setHandler(new Handler.Abstract()
+ {
+ @Override
+ public boolean handle(Request request, Response response, Callback callback) throws Exception
+ {
+ CompletableFuture future = MultiPartFormData.get(request);
+ assertNotNull(future);
+ assertTrue(future.isDone());
+ MultiPartFormData.Parts parts = future.get();
+ assertNotNull(parts);
+ assertThat(parts.size(), equalTo(3));
+ for (int i = 0; i < 3; i++)
+ {
+ assertThat(parts.get(i).getName(), equalTo("part" + i));
+ assertThat(parts.get(i).getContentAsString(StandardCharsets.ISO_8859_1),
+ equalTo("This is the content of Part" + i));
+ }
+
+ response.getHeaders().put(HttpHeader.CONTENT_TYPE, "text/plain");
+ response.write(true, BufferUtil.toBuffer("success"), callback);
+ return true;
+ }
+ });
+ _server.start();
+
+ try (Socket socket = new Socket("localhost", _connector.getLocalPort()))
+ {
+ String requestContent = """
+ --jettyBoundary123\r
+ Content-Disposition: form-data; name="part0"\r
+ \r
+ This is the content of Part0\r
+ --jettyBoundary123\r
+ Content-Disposition: form-data; name="part1"\r
+ \r
+ This is the content of Part1\r
+ --jettyBoundary123\r
+ Content-Disposition: form-data; name="part2"\r
+ \r
+ This is the content of Part2\r
+ --jettyBoundary123--\r
+ """;
+ String requestHeaders = String.format("""
+ POST / HTTP/1.1\r
+ Host: localhost\r
+ Content-Type: multipart/form-data; boundary=jettyBoundary123\r
+ Content-Length: %s\r
+ \r
+ """, requestContent.getBytes(StandardCharsets.UTF_8).length);
+ OutputStream output = socket.getOutputStream();
+ output.write((requestHeaders + requestContent).getBytes(StandardCharsets.UTF_8));
+ output.flush();
+
+ HttpTester.Input input = HttpTester.from(socket.getInputStream());
+ HttpTester.Response response = HttpTester.parseResponse(input);
+ assertNotNull(response);
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ String content = new String(response.getContentBytes(), StandardCharsets.UTF_8);
+ assertThat(content, equalTo("success"));
+ }
+ }
}
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java
index 232cd677c1b7..78f281b96bb2 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java
@@ -95,6 +95,29 @@ interface Callable extends Invocable
void call() throws Exception;
}
+ // TODO javadoc
+ interface InvocableBiConsumer extends Invocable, BiConsumer
+ {
+ }
+
+ static InvocableBiConsumer from(InvocationType invocationType, BiConsumer biConsumer)
+ {
+ return new InvocableBiConsumer()
+ {
+ @Override
+ public InvocationType getInvocationType()
+ {
+ return invocationType;
+ }
+
+ @Override
+ public void accept(T t, U u)
+ {
+ biConsumer.accept(t, u);
+ }
+ };
+ }
+
/**
* A {@link Runnable} decorated with an {@link InvocationType}.
*/
@@ -274,7 +297,9 @@ public InvocationType getInvocationType()
* should be {@link InvocationType#NON_BLOCKING}, as the wake-up callbacks used will not block.
*
* @param The type of the result
+ * @deprecated This class in only used for deprecated usages of CompletableFuture
*/
+ @Deprecated(forRemoval = true, since = "12.0.15")
class InvocableCompletableFuture extends java.util.concurrent.CompletableFuture implements Invocable
{
private final InvocationType _invocationType;
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java
index 3b1cf33f99c1..493988c0eb13 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java
@@ -13,7 +13,7 @@
package org.eclipse.jetty.ee10.servlet;
-import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
import jakarta.servlet.ServletRequest;
import org.eclipse.jetty.http.HttpHeader;
@@ -21,7 +21,9 @@
import org.eclipse.jetty.server.FormFields;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.DelayedHandler;
import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.Fields;
/**
* Handler to eagerly and asynchronously read and parse {@link MimeTypes.Type#FORM_ENCODED} and
@@ -29,10 +31,11 @@
* which can then consume them with blocking APIs but without blocking.
* @see FormFields#from(Request)
* @see ServletMultiPartFormData#from(ServletRequest)
+ * @deprecated use {@link DelayedHandler}
*/
-public class EagerFormHandler extends Handler.Wrapper
+@Deprecated(forRemoval = true, since = "12.0.15")
+public class EagerFormHandler extends DelayedHandler
{
- // TODO replace with DelayedDispatchHandler
public EagerFormHandler()
{
this(null);
@@ -54,39 +57,85 @@ public boolean handle(Request request, org.eclipse.jetty.server.Response respons
if (mimeType == null)
return super.handle(request, response, callback);
- CompletableFuture> future = switch (mimeType)
+ return switch (mimeType)
{
- case FORM_ENCODED -> FormFields.from(request, InvocationType.BLOCKING);
- case MULTIPART_FORM_DATA -> ServletMultiPartFormData.from(Request.as(request, ServletContextRequest.class).getServletApiRequest(), InvocationType.BLOCKING, contentType);
- default -> null;
+ case FORM_ENCODED -> handleFormFields(request, contentType, response, callback);
+ case MULTIPART_FORM_DATA -> handleMultiPartFormData(request, contentType, response, callback);
+ default -> super.handle(request, response, callback);
};
+ }
- if (future == null)
- return super.handle(request, response, callback);
-
- if (future.isDone())
+ protected boolean handleFormFields(Request request, String contentType, org.eclipse.jetty.server.Response response, Callback callback) throws Exception
+ {
+ BiConsumer onFields = (fields, error) ->
{
- if (!super.handle(request, response, callback))
- callback.failed(new IllegalStateException("Not Handled"));
- }
- else
+ try
+ {
+ if (!super.handle(request, response, callback))
+ callback.failed(new IllegalStateException("Not Handled"));
+ }
+ catch (Throwable t)
+ {
+ callback.failed(t);
+ }
+ };
+
+ InvocableBiConsumer executeOnFields = new InvocableBiConsumer<>()
{
- future.whenComplete((result, failure) ->
+ @Override
+ public void accept(Fields fields, Throwable error)
{
- // The result and failure are not handled here. Rather we call the next handler
- // to allow the normal processing to handle the result or failure, which will be
- // provided via the attribute to ServletApiRequest#getParts()
- try
+ request.getContext().execute(() ->
{
- if (!super.handle(request, response, callback))
- callback.failed(new IllegalStateException("Not Handled"));
- }
- catch (Throwable x)
+ onFields.accept(fields, error);
+ });
+ }
+
+ @Override
+ public InvocationType getInvocationType()
+ {
+ return InvocationType.NON_BLOCKING;
+ }
+ };
+
+ FormFields.onFields(request, onFields, executeOnFields);
+ return true;
+ }
+
+ protected boolean handleMultiPartFormData(Request request, String contentType, org.eclipse.jetty.server.Response response, Callback callback) throws Exception
+ {
+ BiConsumer onParts = (fields, error) ->
+ {
+ try
+ {
+ if (!super.handle(request, response, callback))
+ callback.failed(new IllegalStateException("Not Handled"));
+ }
+ catch (Throwable t)
+ {
+ callback.failed(t);
+ }
+ };
+
+ InvocableBiConsumer executeOnParts = new InvocableBiConsumer<>()
+ {
+ @Override
+ public void accept(ServletMultiPartFormData.Parts fields, Throwable error)
+ {
+ request.getContext().execute(() ->
{
- callback.failed(x);
- }
- });
- }
+ onParts.accept(fields, error);
+ });
+ }
+
+ @Override
+ public InvocationType getInvocationType()
+ {
+ return InvocationType.NON_BLOCKING;
+ }
+ };
+
+ ServletMultiPartFormData.onParts(Request.as(request, ServletContextRequest.class).getServletApiRequest(), contentType, onParts, executeOnParts);
return true;
}
}
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java
index 4d7083accfc7..c13f403f077a 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletApiRequest.java
@@ -33,7 +33,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import jakarta.servlet.AsyncContext;
@@ -634,9 +634,7 @@ public Collection getParts() throws IOException, ServletException
{
try
{
- CompletableFuture futureServletMultiPartFormData = ServletMultiPartFormData.from(this);
-
- _parts = futureServletMultiPartFormData.get();
+ _parts = ServletMultiPartFormData.getParts(this);
Collection parts = _parts.getParts();
@@ -998,10 +996,9 @@ private void extractContentParameters() throws BadMessageException
ServletContextHandler contextHandler = getServletRequestInfo().getServletContextHandler();
int maxKeys = contextHandler.getMaxFormKeys();
int maxContentSize = contextHandler.getMaxFormContentSize();
- _contentParameters = FormFields.from(getRequest(), maxKeys, maxContentSize).get();
+ _contentParameters = FormFields.getFields(getRequest(), maxKeys, maxContentSize);
}
- catch (IllegalStateException | IllegalArgumentException | ExecutionException |
- InterruptedException e)
+ catch (IllegalStateException | IllegalArgumentException | CompletionException e)
{
LOG.warn(e.toString());
throw new BadMessageException("Unable to parse form content", e);
@@ -1037,10 +1034,9 @@ else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(baseType) &&
{
try
{
- _contentParameters = FormFields.get(getRequest()).get();
+ _contentParameters = FormFields.getFields(getRequest());
}
- catch (IllegalStateException | IllegalArgumentException | ExecutionException |
- InterruptedException e)
+ catch (IllegalStateException | IllegalArgumentException | CompletionException e)
{
LOG.warn(e.toString());
throw new BadMessageException("Unable to parse form content", e);
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java
index 34935490bf23..57b82c093bf1 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java
@@ -22,6 +22,8 @@
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
import java.util.function.Function;
import jakarta.servlet.MultipartConfigElement;
@@ -53,12 +55,58 @@
*/
public class ServletMultiPartFormData
{
+ /**
+ * Get {@link ServletMultiPartFormData.Parts} from a servlet request, blocking if necessary.
+ * @param servletRequest A servlet request
+ */
+ static Parts getParts(ServletRequest servletRequest)
+ {
+ CompletableFuture futureParts = from(servletRequest, Invocable.InvocationType.NON_BLOCKING);
+ return futureParts.join();
+ }
+
+ /**
+ * Actions to take when {@link ServletMultiPartFormData.Parts} are completely read.
+ * @param servletRequest A servlet request
+ * @param contentType The contentType, passed as an optimization as it has likely already been retrieved.
+ * @param immediate The action to take if the Parts are available immediately (from within the scope of the call to this method).
+ * @param future The action to take when the Parts are available, if they are not available immediately. The {@link org.eclipse.jetty.util.thread.Invocable.InvocationType}
+ * of this parameter will be used as the type for any implementation calls to {@link Content.Source#demand(Runnable)}.
+ */
+ static void onParts(ServletRequest servletRequest, String contentType, BiConsumer immediate, Invocable.InvocableBiConsumer future)
+ {
+ CompletableFuture futureParts = from(servletRequest, future.getInvocationType(), contentType);
+ if (futureParts.isDone())
+ {
+ Parts parts = null;
+ Throwable error = null;
+ try
+ {
+ parts = futureParts.get();
+ }
+ catch (ExecutionException e)
+ {
+ error = e.getCause();
+ }
+ catch (Throwable t)
+ {
+ error = t;
+ }
+ immediate.accept(parts, error);
+ }
+ else
+ {
+ futureParts.whenComplete(future);
+ }
+ }
+
/**
* Get future {@link ServletMultiPartFormData.Parts} from a servlet request.
* @param servletRequest A servlet request
* @return A future {@link ServletMultiPartFormData.Parts}, which may have already been created and/or completed.
* @see #from(ServletRequest, String)
*/
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture from(ServletRequest servletRequest)
{
return from(servletRequest, Invocable.InvocationType.NON_BLOCKING, servletRequest.getContentType());
@@ -71,7 +119,8 @@ public static CompletableFuture from(ServletRequest servletRequest)
* @return A future {@link ServletMultiPartFormData.Parts}, which may have already been created and/or completed.
* @see #from(ServletRequest, String)
*/
- public static CompletableFuture from(ServletRequest servletRequest, Invocable.InvocationType invocationType)
+ @Deprecated(forRemoval = true, since = "12.0.15")
+ static CompletableFuture from(ServletRequest servletRequest, Invocable.InvocationType invocationType)
{
return from(servletRequest, invocationType, servletRequest.getContentType());
}
@@ -82,6 +131,7 @@ public static CompletableFuture from(ServletRequest servletRequest, Invoc
* @param contentType The contentType, passed as an optimization as it has likely already been retrieved.
* @return A future {@link ServletMultiPartFormData.Parts}, which may have already been created and/or completed.
*/
+ @Deprecated(forRemoval = true, since = "12.0.15")
public static CompletableFuture from(ServletRequest servletRequest, String contentType)
{
return from(servletRequest, Invocable.InvocationType.NON_BLOCKING, contentType);
@@ -94,7 +144,8 @@ public static CompletableFuture from(ServletRequest servletRequest, Strin
* @param contentType The contentType, passed as an optimization as it has likely already been retrieved.
* @return A future {@link ServletMultiPartFormData.Parts}, which may have already been created and/or completed.
*/
- public static CompletableFuture from(ServletRequest servletRequest, Invocable.InvocationType invocationType, String contentType)
+ @Deprecated(forRemoval = true, since = "12.0.15")
+ static CompletableFuture from(ServletRequest servletRequest, Invocable.InvocationType invocationType, String contentType)
{
// Look for an existing future (we use the future here rather than the parts as it can remember any failure).
@SuppressWarnings("unchecked")
From d9a54de8828a17e53197cfa8fb3546a34d9258c9 Mon Sep 17 00:00:00 2001
From: gregw
Date: Wed, 23 Oct 2024 10:43:47 +1100
Subject: [PATCH 22/27] Deprecated the CF APIs and replaced with explicit
getXxx onXxx methods
---
.../org/eclipse/jetty/util/thread/Invocable.java | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java
index 78f281b96bb2..53776d3c13c1 100644
--- a/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java
+++ b/jetty-core/jetty-util/src/main/java/org/eclipse/jetty/util/thread/Invocable.java
@@ -95,11 +95,24 @@ interface Callable extends Invocable
void call() throws Exception;
}
- // TODO javadoc
+ /**
+ * An {@link Invocable} {@link BiConsumer} that provides the
+ * {@link InvocationType} of calls to {@link BiConsumer#accept(Object, Object)}.
+ * @param The first argument
+ * @param The second argument
+ */
interface InvocableBiConsumer extends Invocable, BiConsumer
{
}
+ /**
+ * Create an {@link InvocableBiConsumer}
+ * @param invocationType The {@link InvocationType} of calls to {@link BiConsumer#accept(Object, Object)}
+ * @param biConsumer The consumer on which to delegate calls to {@link BiConsumer#accept(Object, Object)}
+ * @param The first argument
+ * @param The second argument
+ * @return An {@link Invocable} {@link BiConsumer}.
+ */
static InvocableBiConsumer from(InvocationType invocationType, BiConsumer biConsumer)
{
return new InvocableBiConsumer()
From f15e2982ab475a6c4ff24b70cc10df0cb7ba27eb Mon Sep 17 00:00:00 2001
From: gregw
Date: Wed, 23 Oct 2024 16:24:00 +1100
Subject: [PATCH 23/27] removed more CF code
---
.../eclipse/jetty/http/MultiPartFormData.java | 31 ++++++-
.../authentication/FormAuthenticator.java | 2 +-
.../org/eclipse/jetty/server/FormFields.java | 47 ++++++++--
.../jetty/ee10/servlet/EagerFormHandler.java | 4 +-
.../servlet/ServletMultiPartFormData.java | 85 ++++++++++---------
5 files changed, 112 insertions(+), 57 deletions(-)
diff --git a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormData.java b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormData.java
index cbe5af83e618..bee52db65bca 100644
--- a/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormData.java
+++ b/jetty-core/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormData.java
@@ -82,11 +82,31 @@ private MultiPartFormData()
}
/**
- * Returns {@code multipart/form-data} parts using the given {@link Content.Source} and {@link MultiPartConfig},
- * blocking if necessary.
+ * Get {@code multipart/form-data} {@link Parts} from an {@link Attributes}, typically
+ * cached there by calls to {@link #getParts(Content.Source, Attributes, String, MultiPartConfig)}
+ * or {@link #onParts(Content.Source, Attributes, String, MultiPartConfig, BiConsumer, Invocable.InvocableBiConsumer)}
*
+ * @param attributes the attributes where the futureParts are cahced
+ * @return the parts or null
+ */
+ public static Parts getParts(Attributes attributes)
+ {
+ Object attribute = attributes.getAttribute(MultiPartFormData.class.getName());
+ if (attribute instanceof Parts parts)
+ return parts;
+ if (attribute instanceof CompletableFuture> futureParts && futureParts.isDone())
+ return (Parts)futureParts.join();
+ return null;
+ }
+
+ /**
+ * Get {@code multipart/form-data} {@link Parts} from a {@link Content.Source}, caching the results in an
+ * {@link Attributes}. If not already available, the {@code Parts} are read and parsed, blocking if necessary.
+ *
+ * Calls to {@code onParts} and {@code getParts} methods are idempotent, and
+ * can be called multiple times, with subsequent calls returning the results of the first call.
* @param content the source of the multipart content.
- * @param attributes the attributes where the futureParts are tracked.
+ * @param attributes the attributes where the Parts are cached.
* @param contentType the value of the {@link HttpHeader#CONTENT_TYPE} header.
* @param config the multipart configuration.
* @return the parts
@@ -97,6 +117,11 @@ public static MultiPartFormData.Parts getParts(Content.Source content, Attribute
}
/**
+ * Asynchronously get {@code multipart/form-data} {@link Parts} from a {@link Content.Source}, caching the results in an
+ * {@link Attributes}. If not already available, the {@code Parts} are read and parsed.
+ *
+ * Calls to {@code onParts} and {@code getParts} methods are idempotent, and
+ * can be called multiple times, with subsequent calls returning the results of the first call.
* @param content the source of the multipart content.
* @param attributes the attributes where the futureParts are tracked.
* @param contentType the value of the {@link HttpHeader#CONTENT_TYPE} header.
diff --git a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
index 864fa04cc885..8c00769092eb 100644
--- a/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
+++ b/jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java
@@ -202,7 +202,7 @@ public Request prepareRequest(Request request, AuthenticationState authenticatio
Object post = session.removeAttribute(__J_POST);
if (post instanceof Fields futureFields)
- FormFields.set(request, futureFields);
+ FormFields.setFields(request, futureFields);
String method = (String)session.removeAttribute(__J_METHOD);
if (method != null && request.getMethod().equals(method))
diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java
index d24c08a2ed97..63442b1a53a1 100644
--- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java
+++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java
@@ -94,15 +94,24 @@ public static void set(Request request, CompletableFuture fields)
* @param request The request to which to associate the fields with
* @param fields A {@link CompletableFuture} that will provide either the fields or a failure.
*/
- public static void set(Request request, Fields fields)
+ public static void setFields(Request request, Fields fields)
{
request.setAttribute(FormFields.class.getName(), fields);
}
/**
- * Get the Fields from a request, blocking if necessary.
- * @param request The request to get the Fields from
+ * Get the Fields from a request. If the Fields have not been set, then attempt to parse them
+ * from the Request content, blocking if necessary. If the Fields have previously been read asynchronously
+ * by {@link #onFields(Request, BiConsumer, InvocableBiConsumer)} or similar, then those field will return
+ * and this method will not block.
+ *
+ * Calls to {@code onFields} and {@code getFields} methods are idempotent, and
+ * can be called multiple times, with subsequent calls returning the results of the first call.
+ * @param request The request to get or read the Fields from
* @return the Fields
+ * @see #onFields(Request, BiConsumer, InvocableBiConsumer)
+ * @see #onFields(Request, Charset, BiConsumer, InvocableBiConsumer)
+ * @see #getFields(Request, int, int)
*/
public static Fields getFields(Request request)
{
@@ -111,11 +120,20 @@ public static Fields getFields(Request request)
}
/**
- * Get the Fields from a request, blocking if necessary.
- * @param request The request to get the Fields from
+ * Get the Fields from a request. If the Fields have not been set, then attempt to parse them
+ * from the Request content, blocking if necessary. If the Fields have previously been read asynchronously
+ * by {@link #onFields(Request, BiConsumer, InvocableBiConsumer)} or similar, then those field will return
+ * and this method will not block.
+ *
+ * Calls to {@code onFields} and {@code getFields} methods are idempotent, and
+ * can be called multiple times, with subsequent calls returning the results of the first call.
+ * @param request The request to get or read the Fields from
* @param maxFields The maximum number of fields to accept
* @param maxLength The maximum length of fields
* @return the Fields
+ * @see #onFields(Request, BiConsumer, InvocableBiConsumer)
+ * @see #onFields(Request, Charset, BiConsumer, InvocableBiConsumer)
+ * @see #getFields(Request)
*/
public static Fields getFields(Request request, int maxFields, int maxLength)
{
@@ -124,11 +142,17 @@ public static Fields getFields(Request request, int maxFields, int maxLength)
}
/**
- * Actions to take when parsing FormFields asynchronously from a request is complete
- * @param request The request to parse FormFields from
+ * Asynchronously read and parse FormFields from a {@link Request}.
+ *
+ * Calls to {@code onFields} and {@code getFields} methods are idempotent, and
+ * can be called multiple times, with subsequent calls returning the results of the first call.
+ * @param request The request to get or read the Fields from
* @param immediate The action to take if the FormFields are available immediately (from within the scope of the call to this method).
* @param future The action to take when the FormFields are available, if they are not available immediately. The {@link org.eclipse.jetty.util.thread.Invocable.InvocationType}
* of this parameter will be used as the type for any implementation calls to {@link Content.Source#demand(Runnable)}.
+ * @see #onFields(Request, Charset, BiConsumer, InvocableBiConsumer)
+ * @see #getFields(Request)
+ * @see #getFields(Request, int, int)
*/
public static void onFields(Request request, BiConsumer immediate, InvocableBiConsumer future)
{
@@ -137,11 +161,17 @@ public static void onFields(Request request, BiConsumer immed
/**
* Actions to take when parsing FormFields asynchronously from a request is complete
- * @param request The request to parse FormFields from
+ *
+ * Calls to {@code onFields} and {@code getFields} methods are idempotent, and
+ * can be called multiple times, with subsequent calls returning the results of the first call.
+ * @param request The request to get or read the Fields from
* @param charset The charset of the form.
* @param immediate The action to take if the FormFields are available immediately (from within the scope of the call to this method).
* @param future The action to take when the FormFields are available, if they are not available immediately. The {@link org.eclipse.jetty.util.thread.Invocable.InvocationType}
* of this parameter will be used as the type for any implementation calls to {@link Content.Source#demand(Runnable)}.
+ * @see #onFields(Request, BiConsumer, InvocableBiConsumer)
+ * @see #getFields(Request)
+ * @see #getFields(Request, int, int)
*/
public static void onFields(Request request, Charset charset, BiConsumer immediate, InvocableBiConsumer future)
{
@@ -271,7 +301,6 @@ private static CompletableFuture from(Request request, InvocationType in
return from(request, invocationType, request, charset, maxFields, maxLength);
}
- @Deprecated(forRemoval = true, since = "12.0.15")
static CompletableFuture from(Content.Source source, InvocationType invocationType, Attributes attributes, Charset charset, int maxFields, int maxLength)
{
Object attr = attributes.getAttribute(FormFields.class.getName());
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java
index 493988c0eb13..7724b151c73d 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/EagerFormHandler.java
@@ -59,13 +59,13 @@ public boolean handle(Request request, org.eclipse.jetty.server.Response respons
return switch (mimeType)
{
- case FORM_ENCODED -> handleFormFields(request, contentType, response, callback);
+ case FORM_ENCODED -> handleFormFields(request, response, callback);
case MULTIPART_FORM_DATA -> handleMultiPartFormData(request, contentType, response, callback);
default -> super.handle(request, response, callback);
};
}
- protected boolean handleFormFields(Request request, String contentType, org.eclipse.jetty.server.Response response, Callback callback) throws Exception
+ protected boolean handleFormFields(Request request, org.eclipse.jetty.server.Response response, Callback callback) throws Exception
{
BiConsumer onFields = (fields, error) ->
{
diff --git a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java
index 57b82c093bf1..d7f695259a5a 100644
--- a/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java
+++ b/jetty-ee10/jetty-ee10-servlet/src/main/java/org/eclipse/jetty/ee10/servlet/ServletMultiPartFormData.java
@@ -24,7 +24,6 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.BiConsumer;
-import java.util.function.Function;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.ServletRequest;
@@ -56,8 +55,14 @@
public class ServletMultiPartFormData
{
/**
- * Get {@link ServletMultiPartFormData.Parts} from a servlet request, blocking if necessary.
+ * Get {@code multipart/form-data} {@link ServletMultiPartFormData.Parts} from a {@link ServletRequest}, caching the
+ * results in the request {@link ServletRequest#getAttribute(String) Attributes}. If not already available,
+ * the {@code Parts} are read and parsed, blocking if necessary.
+ *
+ * Calls to {@code onParts} and {@code getParts} methods are idempotent, and
+ * can be called multiple times, with subsequent calls returning the results of the first call.
* @param servletRequest A servlet request
+ * @return the parts
*/
static Parts getParts(ServletRequest servletRequest)
{
@@ -66,7 +71,12 @@ static Parts getParts(ServletRequest servletRequest)
}
/**
- * Actions to take when {@link ServletMultiPartFormData.Parts} are completely read.
+ * Asynchronously get {@code multipart/form-data} {@link ServletMultiPartFormData.Parts} from a {@link ServletRequest},
+ * caching the results in the request {@link ServletRequest#getAttribute(String) Attributes}. If not already available,
+ * the {@code Parts} are read and parsed.
+ *
+ * Calls to {@code onParts} and {@code getParts} methods are idempotent, and
+ * can be called multiple times, with subsequent calls returning the results of the first call.
* @param servletRequest A servlet request
* @param contentType The contentType, passed as an optimization as it has likely already been retrieved.
* @param immediate The action to take if the Parts are available immediately (from within the scope of the call to this method).
@@ -75,6 +85,7 @@ static Parts getParts(ServletRequest servletRequest)
*/
static void onParts(ServletRequest servletRequest, String contentType, BiConsumer immediate, Invocable.InvocableBiConsumer future)
{
+ // TODO inline the from method to avoid the CF
CompletableFuture futureParts = from(servletRequest, future.getInvocationType(), contentType);
if (futureParts.isDone())
{
@@ -182,8 +193,12 @@ static CompletableFuture from(ServletRequest servletRequest, Invocable.In
try
{
// Look for an existing future MultiPartFormData.Parts
- CompletableFuture futureFormData = MultiPartFormData.get(servletContextRequest);
- if (futureFormData == null)
+ MultiPartFormData.Parts formParts = MultiPartFormData.getParts(servletContextRequest);
+ if (formParts != null)
+ {
+ futureServletParts = CompletableFuture.completedFuture(new Parts(filesDirectory, formParts));
+ }
+ else
{
// No existing core parts, so we need to configure the parser.
ServletContextHandler contextHandler = servletContextRequest.getServletContext().getServletContextHandler();
@@ -199,9 +214,7 @@ static CompletableFuture from(ServletRequest servletRequest, Invocable.In
else
{
int bufferSize = connection instanceof AbstractConnection c ? c.getInputBufferSize() : 2048;
- InputStreamContentSource iscs = new InputStreamContentSource(servletRequest.getInputStream(), byteBufferPool);
- iscs.setBufferSize(bufferSize);
- source = iscs;
+ source = new InputStreamContentSource(servletRequest.getInputStream(), new ByteBufferPool.Sized(byteBufferPool, false, bufferSize));
}
MultiPartConfig multiPartConfig = Request.getMultiPartConfig(servletContextRequest, filesDirectory)
@@ -212,19 +225,31 @@ static CompletableFuture from(ServletRequest servletRequest, Invocable.In
.maxSize(config.getMaxRequestSize())
.build();
- futureFormData = MultiPartFormData.from(source, invocationType, servletContextRequest, contentType, multiPartConfig);
-
+ futureServletParts = new CompletableFuture<>();
+ CompletableFuture