diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Context.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Context.java index 196d7892798..54cd6331536 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Context.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Context.java @@ -18,21 +18,22 @@ import java.util.List; import java.util.concurrent.Executor; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.Decorator; import org.eclipse.jetty.util.resource.Resource; /** - * A Context for handling a request. - * Every request will always have a non-null context, which may initially be the Server context, or - * a context provided by a ContextHandler. - *

- * A Context is also an {@link Executor}, which allows tasks to be run by a thread pool, but scoped - * to the classloader and any other aspects of the context. Method {@link #run(Runnable)} - * is also provided to allow the current Thread to be scoped to the context. - *

- * A Context is also a {@link Decorator}, allowing objects to be decorated in a context scope. - * + *

The context for handling an HTTP request.

+ *

Every request has a non-{@code null} context, which may initially + * be the {@link Server#getContext() server context}, or + * a context provided by a {@link ContextHandler}.

+ *

A context is also an {@link Executor}, which allows tasks to be run by a + * thread pool, but scoped to the ClassLoader and any other context property.

+ *

Method {@link #run(Runnable)} is also provided to allow the current thread + * to be scoped to the context for the execution of the task.

+ *

A Context is also a {@link Decorator}, allowing objects to be decorated + * in a context scope.

*/ public interface Context extends Attributes, Decorator, Executor { @@ -41,39 +42,70 @@ public interface Context extends Attributes, Decorator, Executor */ String getContextPath(); + /** + * @return the {@link ClassLoader} associated with this Context + */ ClassLoader getClassLoader(); + /** + * @return the base resource used to lookup other resources + * specified by the request URI path + */ Resource getBaseResource(); + /** + * @return the error {@link Request.Handler} associated with this Context + */ Request.Handler getErrorHandler(); + /** + * @return a list of virtual host names associated with this Context + */ List getVirtualHosts(); + /** + * @return the mime types associated with this Context + */ MimeTypes getMimeTypes(); - /** execute runnable in container thread scoped to context */ + /** + *

Executes the given task in a thread scoped to this Context.

+ * + * @param task the task to run + * @see #run(Runnable) + */ @Override - void execute(Runnable runnable); + void execute(Runnable task); - /** scope the calling thread to the context and run the runnable. */ - void run(Runnable runnable); + /** + *

Runs the given task in the current thread scoped to this Context.

+ * + * @param task the task to run + * @see #run(Runnable, Request) + */ + void run(Runnable task); - /** scope the calling thread to the context and request and run the runnable. */ - void run(Runnable runnable, Request request); + /** + *

Runs the given task in the current thread scoped to this Context and the given Request.

+ * + * @param task the task to run + * @param request the HTTP request to use in the scope + */ + void run(Runnable task, Request request); /** *

Returns a URI path scoped to this Context.

*

For example, if the context path is {@code /ctx} then a * full path of {@code /ctx/foo/bar} will return {@code /foo/bar}.

* - * @param fullPath A full URI path - * @return The URI path scoped to this Context, or {@code null} if the full path does not match this Context. - * The empty string is returned if the full path is exactly the context path. + * @param fullPath a full URI path + * @return the URI path scoped to this Context, or {@code null} if the full path does not match this Context. + * The empty string is returned if the full path is exactly the context path. */ String getPathInContext(String fullPath); /** - * @return A temporary directory, configured either for the context, the server or the JVM. Never null. + * @return a non-{@code null} temporary directory, configured either for the context, the server or the JVM */ File getTempDirectory(); } diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index 9a189533d2b..47108db886e 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -504,7 +504,7 @@ public interface Request extends Attributes, Content.Source * completing the passed callback. The handling may be asynchronous, i.e. this method may return true and * complete the given callback later, possibly from a different thread. If this method returns false, * then the callback must not be invoked and any mutation on the response reversed.

- *

Exceptions thrown by this method may be subsequently handled by an {@link ErrorHandler}, + *

Exceptions thrown by this method may be subsequently handled by an error {@link Request.Handler}, * if present, otherwise a default HTTP 500 error is generated and the * callback completed while writing the error response.

*

The simplest implementation is:

diff --git a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index 5a19d53c07e..6d243a7af6e 100644 --- a/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -38,44 +38,98 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * An asynchronous HTTP response. - * TODO Javadoc + *

The representation of an HTTP response, for any protocol version (HTTP/1.1, HTTP/2, HTTP/3).

*/ public interface Response extends Content.Sink { - // This is needed so that response methods can access the wrapped Request#getContext method + /** + * @return the {@link Request} associated with this {@code Response} + */ Request getRequest(); + /** + * @return the response HTTP status code + */ int getStatus(); + /** + * @param code the response HTTP status code + */ void setStatus(int code); + /** + * @return the response HTTP headers + */ HttpFields.Mutable getHeaders(); + /** + * @return a supplier for the HTTP trailers + */ Supplier getTrailersSupplier(); + /** + * @param trailers a supplier for the HTTP trailers + */ void setTrailersSupplier(Supplier trailers); + /** + *

Returns whether this response has already been committed.

+ *

Committing a response means that the HTTP status code and HTTP headers + * cannot be modified anymore, typically because they have already been + * serialized and sent over the network.

+ * + * @return whether this response has already been committed + */ boolean isCommitted(); + /** + *

Returns whether the response completed successfully.

+ *

The response HTTP status code, HTTP headers and content + * have been successfully serialized and sent over the network + * without errors.

+ * + * @return whether the response completed successfully + */ boolean isCompletedSuccessfully(); + /** + *

Resets this response, clearing the HTTP status code, HTTP headers + * and HTTP trailers.

+ * + * @throws IllegalStateException if the response is already + * {@link #isCommitted() committed} + */ void reset(); + /** + *

Writes an {@link HttpStatus#isInterim(int) HTTP interim response}, + * with the given HTTP status code and HTTP headers.

+ *

It is possible to write more than one interim response, for example + * in case of {@link HttpStatus#EARLY_HINT_103}.

+ *

The returned {@link CompletableFuture} is notified of the result + * of this write, whether it succeeded or failed.

+ * + * @param status the interim HTTP status code + * @param headers the HTTP headers + * @return a {@link CompletableFuture} with the result of the write + */ CompletableFuture writeInterim(int status, HttpFields headers); /** * {@inheritDoc} - *

Invocations of the passed {@code Callback} are serialized and a callback for a completed {@code write} call is - * not invoked until any previous {@code write} callback has returned. - * Thus the {@code Callback} should not block waiting for a callback of a future write call.

+ *

The invocation of the passed {@code Callback} is serialized + * with previous calls of this method, so that it is not invoked until + * any invocation of the callback of a previous call to this method + * has returned.

+ *

Thus a {@code Callback} should not block waiting for a callback + * of a future call to this method.

+ *

Furthermore, the invocation of the passed callback is serialized + * with invocations of the {@link Runnable} demand callback passed to + * {@link Request#demand(Runnable)}.

+ * * @param last whether the ByteBuffer is the last to write * @param byteBuffer the ByteBuffer to write * @param callback the callback to notify when the write operation is complete - * In addition to the invocation guarantees of {@link Content.Sink#write(boolean, ByteBuffer, Callback)}, - * this implementation serializes the invocation of the {@code Callback} with - * invocations of any {@link Request#demand(Runnable)} {@code Runnable} invocations. - * @see Content.Sink#write(boolean, ByteBuffer, Callback) */ @Override void write(boolean last, ByteBuffer byteBuffer, Callback callback); @@ -125,6 +179,16 @@ public interface Response extends Content.Sink }; } + /** + *

Unwraps the given response, recursively, until the wrapped instance + * is an instance of the given type, otherwise returns {@code null}.

+ * + * @param response the response to unwrap + * @param type the response type to find + * @return the response as the given type, or {@code null} + * @param the response type + * @see Wrapper + */ @SuppressWarnings("unchecked") static T as(Response response, Class type) { @@ -137,11 +201,34 @@ public interface Response extends Content.Sink return null; } + /** + *

Sends a {@code 302} HTTP redirect status code to the given location, + * without consuming the available request content.

+ * + * @param request the HTTP request + * @param response the HTTP response + * @param callback the callback to complete + * @param location the redirect location + * @see #sendRedirect(Request, Response, Callback, int, String, boolean) + */ static void sendRedirect(Request request, Response response, Callback callback, String location) { sendRedirect(request, response, callback, HttpStatus.MOVED_TEMPORARILY_302, location, false); } + /** + *

Sends a {@code 302} HTTP redirect status code to the given location.

+ * + * @param request the HTTP request + * @param response the HTTP response + * @param callback the callback to complete + * @param code the redirect HTTP status code + * @param location the redirect location + * @param consumeAvailable whether to consumer the available request content + * @see Request#toRedirectURI(Request, String) + * @throws IllegalArgumentException if the status code is not a redirect, or the location is {@code null} + * @throws IllegalStateException if the response is already {@link #isCommitted() committed} + */ static void sendRedirect(Request request, Response response, Callback callback, int code, String location, boolean consumeAvailable) { if (!HttpStatus.isRedirection(code)) @@ -174,6 +261,12 @@ public interface Response extends Content.Sink response.write(true, null, callback); } + /** + *

Adds an HTTP cookie to the response.

+ * + * @param response the HTTP response + * @param cookie the HTTP cookie to add + */ static void addCookie(Response response, HttpCookie cookie) { if (StringUtil.isBlank(cookie.getName())) @@ -187,6 +280,12 @@ public interface Response extends Content.Sink response.getHeaders().put(HttpFields.EXPIRES_01JAN1970); } + /** + *

Replaces (if already exists, otherwise adds) an HTTP cookie to the response.

+ * + * @param response the HTTP response + * @param cookie the HTTP cookie to replace or add + */ static void replaceCookie(Response response, HttpCookie cookie) { if (StringUtil.isBlank(cookie.getName())) @@ -222,6 +321,16 @@ public interface Response extends Content.Sink addCookie(response, cookie); } + /** + *

Writes an error response with HTTP status code {@code 500}.

+ *

The error {@link Request.Handler} returned by {@link Context#getErrorHandler()}, + * if any, is invoked.

+ * + * @param request the HTTP request + * @param response the HTTP response + * @param callback the callback to complete + * @param cause the cause of the error + */ static void writeError(Request request, Response response, Callback callback, Throwable cause) { if (cause == null) @@ -236,16 +345,51 @@ public interface Response extends Content.Sink writeError(request, response, callback, status, message, cause); } + /** + *

Writes an error response with the given HTTP status code.

+ *

The error {@link Request.Handler} returned by {@link Context#getErrorHandler()}, + * if any, is invoked.

+ * + * @param request the HTTP request + * @param response the HTTP response + * @param callback the callback to complete + * @param status the error HTTP status code + */ static void writeError(Request request, Response response, Callback callback, int status) { writeError(request, response, callback, status, null, null); } + /** + *

Writes an error response with the given HTTP status code, + * and the given message in the response content.

+ *

The error {@link Request.Handler} returned by {@link Context#getErrorHandler()}, + * if any, is invoked.

+ * + * @param request the HTTP request + * @param response the HTTP response + * @param callback the callback to complete + * @param status the error HTTP status code + * @param message the error message to write in the response content + */ static void writeError(Request request, Response response, Callback callback, int status, String message) { writeError(request, response, callback, status, message, null); } + /** + *

Writes an error response with the given HTTP status code, + * and the given message in the response content.

+ *

The error {@link Request.Handler} returned by {@link Context#getErrorHandler()}, + * if any, is invoked.

+ * + * @param request the HTTP request + * @param response the HTTP response + * @param callback the callback to complete + * @param status the error HTTP status code + * @param message the error message to write in the response content + * @param cause the cause of the error + */ static void writeError(Request request, Response response, Callback callback, int status, String message, Throwable cause) { // Retrieve the Logger instance here, rather than having a @@ -298,6 +442,13 @@ public interface Response extends Content.Sink response.write(true, null, callback); } + /** + *

Unwraps the given response until the innermost wrapped response instance.

+ * + * @param response the response to unwrap + * @return the innermost wrapped response instance + * @see Wrapper + */ static Response getOriginalResponse(Response response) { while (response instanceof Response.Wrapper wrapped) @@ -307,6 +458,11 @@ public interface Response extends Content.Sink return response; } + /** + * @param response the HTTP response + * @return the number of response content bytes written so far, + * or {@code -1} if the number is unknown + */ static long getContentBytesWritten(Response response) { Response originalResponse = getOriginalResponse(response); @@ -326,17 +482,17 @@ public interface Response extends Content.Sink _wrapped = wrapped; } + public Response getWrapped() + { + return _wrapped; + } + @Override public Request getRequest() { return _request; } - public Response getWrapped() - { - return _wrapped; - } - @Override public int getStatus() { @@ -367,12 +523,6 @@ public interface Response extends Content.Sink getWrapped().setTrailersSupplier(trailers); } - @Override - public void write(boolean last, ByteBuffer byteBuffer, Callback callback) - { - getWrapped().write(last, byteBuffer, callback); - } - @Override public boolean isCommitted() { @@ -396,5 +546,11 @@ public interface Response extends Content.Sink { return getWrapped().writeInterim(status, headers); } + + @Override + public void write(boolean last, ByteBuffer byteBuffer, Callback callback) + { + getWrapped().write(last, byteBuffer, callback); + } } }