Added documentation to migrate from Servlet to Handler.
Introduced CompletableTask to simplify Content.Source reads. Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
parent
b126407c4e
commit
8f6a38aca8
|
@ -97,8 +97,52 @@
|
|||
[[pg-migration-11-to-12-servlet-to-handler]]
|
||||
==== Migrate Servlets to Jetty Handlers
|
||||
|
||||
Web applications written using the Servlet APIs may be re-written using the Jetty `Handler` APIs.
|
||||
The sections below outline the Jetty `Handler` APIs that correspond to the Servlet APIs.
|
||||
|
||||
To replace the functionalities of Servlet Filters, refer to xref:pg-server-http-handler[this section].
|
||||
|
||||
===== Handler Request APIs
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=request]
|
||||
----
|
||||
|
||||
===== Handler Request Content APIs
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=requestContent-string]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=requestContent-buffer]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=requestContent-stream]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=requestContent-source]
|
||||
----
|
||||
|
||||
===== Handler Response APIs
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=response]
|
||||
----
|
||||
|
||||
===== Handler Response Content APIs
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-implicit]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-implicit-status]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-explicit]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-content]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-string]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-echo]
|
||||
|
||||
include::../{doc_code}/org/eclipse/jetty/docs/programming/migration/ServletToHandlerDocs.java[tags=responseContent-trailers]
|
||||
----
|
||||
|
||||
[[pg-migration-11-to-12-api-changes]]
|
||||
==== APIs Changes
|
||||
|
|
|
@ -0,0 +1,643 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.docs.programming.migration;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jetty.http.HttpCookie;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.Trailers;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.server.Context;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.Session;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.CompletableTask;
|
||||
import org.eclipse.jetty.util.Fields;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ServletToHandlerDocs
|
||||
{
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::request[]
|
||||
public class RequestAPIs extends Handler.Abstract
|
||||
{
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Gets the request method.
|
||||
// Replaces:
|
||||
// - servletRequest.getMethod();
|
||||
String method = request.getMethod();
|
||||
|
||||
// Gets the request protocol name and version.
|
||||
// Replaces:
|
||||
// - servletRequest.getProtocol();
|
||||
String protocol = request.getConnectionMetaData().getProtocol();
|
||||
|
||||
// Gets the full request URI.
|
||||
// Replaces:
|
||||
// - servletRequest.getRequestURL();
|
||||
String fullRequestURI = request.getHttpURI().asString();
|
||||
|
||||
// Gets the request context.
|
||||
// Replaces:
|
||||
// - servletRequest.getServletContext()
|
||||
Context context = request.getContext();
|
||||
|
||||
// Gets the context path.
|
||||
// Replaces:
|
||||
// - servletRequest.getContextPath()
|
||||
String contextPath = context.getContextPath();
|
||||
|
||||
// Gets the request path.
|
||||
// Replaces:
|
||||
// - servletRequest.getRequestURI();
|
||||
String requestPath = request.getHttpURI().getPath();
|
||||
|
||||
// Gets the request path after the context path.
|
||||
// Replaces:
|
||||
// - servletRequest.getServletPath() + servletRequest.getPathInfo()
|
||||
String pathInContext = Request.getPathInContext(request);
|
||||
|
||||
// Gets the request query.
|
||||
// Replaces:
|
||||
// - servletRequest.getQueryString()
|
||||
String queryString = request.getHttpURI().getQuery();
|
||||
|
||||
// Gets request parameters.
|
||||
// Replaces:
|
||||
// - servletRequest.getParameterNames();
|
||||
// - servletRequest.getParameter(name);
|
||||
// - servletRequest.getParameterValues(name);
|
||||
// - servletRequest.getParameterMap();
|
||||
Fields queryParameters = Request.extractQueryParameters(request, UTF_8);
|
||||
Fields allParameters = Request.getParameters(request);
|
||||
|
||||
// Gets cookies.
|
||||
// Replaces:
|
||||
// - servletRequest.getCookies();
|
||||
List<HttpCookie> cookies = Request.getCookies(request);
|
||||
|
||||
// Gets request HTTP headers.
|
||||
// Replaces:
|
||||
// - servletRequest.getHeaderNames()
|
||||
// - servletRequest.getHeader(name)
|
||||
// - servletRequest.getHeaders(name)
|
||||
// - servletRequest.getDateHeader(name)
|
||||
// - servletRequest.getIntHeader(name)
|
||||
HttpFields requestHeaders = request.getHeaders();
|
||||
|
||||
// Gets the request Content-Type.
|
||||
// Replaces:
|
||||
// - servletRequest.getContentType()
|
||||
String contentType = request.getHeaders().get(HttpHeader.CONTENT_TYPE);
|
||||
|
||||
// Gets the request Content-Length.
|
||||
// Replaces:
|
||||
// - servletRequest.getContentLength()
|
||||
// - servletRequest.getContentLengthLong()
|
||||
long contentLength = request.getHeaders().getLongField(HttpHeader.CONTENT_LENGTH);
|
||||
|
||||
// Gets the request locales.
|
||||
// Replaces:
|
||||
// - servletRequest.getLocale()
|
||||
// - servletRequest.getLocales()
|
||||
List<Locale> locales = Request.getLocales(request);
|
||||
|
||||
// Gets the request scheme.
|
||||
// Replaces:
|
||||
// - servletRequest.getScheme()
|
||||
String scheme = request.getHttpURI().getScheme();
|
||||
|
||||
// Gets the server name.
|
||||
// Replaces:
|
||||
// - servletRequest.getServerName()
|
||||
String serverName = Request.getServerName(request);
|
||||
|
||||
// Gets the server port.
|
||||
// Replaces:
|
||||
// - servletRequest.getServerPort()
|
||||
int serverPort = Request.getServerPort(request);
|
||||
|
||||
// Gets the remote host/address.
|
||||
// Replaces:
|
||||
// - servletRequest.getRemoteAddr()
|
||||
// - servletRequest.getRemoteHost()
|
||||
String remoteAddress = Request.getRemoteAddr(request);
|
||||
|
||||
// Gets the remote port.
|
||||
// Replaces:
|
||||
// - servletRequest.getRemotePort()
|
||||
int remotePort = Request.getRemotePort(request);
|
||||
|
||||
// Gets the local host/address.
|
||||
// Replaces:
|
||||
// - servletRequest.getLocalAddr()
|
||||
// - servletRequest.getLocalHost()
|
||||
String localAddress = Request.getLocalAddr(request);
|
||||
|
||||
// Gets the local port.
|
||||
// Replaces:
|
||||
// - servletRequest.getLocalPort()
|
||||
int localPort = Request.getLocalPort(request);
|
||||
|
||||
// Gets the request attributes.
|
||||
// Replaces:
|
||||
// - servletRequest.getAttributeNames()
|
||||
// - servletRequest.getAttribute(name)
|
||||
// - servletRequest.setAttribute(name, value)
|
||||
// - servletRequest.removeAttribute(name)
|
||||
String name = "name";
|
||||
Object value = "value";
|
||||
Set<String> names = request.getAttributeNameSet();
|
||||
Object attribute = request.getAttribute(name);
|
||||
request.setAttribute(name, value);
|
||||
request.removeAttribute(name);
|
||||
request.clearAttributes();
|
||||
Map<String, Object> map = request.asAttributeMap();
|
||||
|
||||
// Gets the request trailers.
|
||||
// Replaces:
|
||||
// - servletRequest.getTrailerFields()
|
||||
HttpFields trailers = request.getTrailers();
|
||||
|
||||
// Gets the HTTP session.
|
||||
// Replaces:
|
||||
// - servletRequest.getSession()
|
||||
// - servletRequest.getSession(create)
|
||||
boolean create = true;
|
||||
Session session = request.getSession(create);
|
||||
|
||||
callback.succeeded();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// end::request[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class RequestContentAPIsString extends Handler.Abstract
|
||||
{
|
||||
// tag::requestContent-string[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Non-blocking read the request content as a String.
|
||||
// Use with caution as the request content may be large.
|
||||
Promise.Completable<String> completable = new Promise.Completable<>();
|
||||
Content.Source.asString(request, UTF_8, completable);
|
||||
|
||||
completable.whenComplete((requestContent, failure) ->
|
||||
{
|
||||
if (failure == null)
|
||||
{
|
||||
// Process the request content here.
|
||||
|
||||
// Implicitly respond with status code 200 and no content.
|
||||
callback.succeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Implicitly respond with status code 500.
|
||||
callback.failed(failure);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::requestContent-string[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class RequestContentAPIsByteBuffer extends Handler.Abstract
|
||||
{
|
||||
// tag::requestContent-buffer[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Non-blocking read the request content as a ByteBuffer.
|
||||
// Use with caution as the request content may be large.
|
||||
Promise.Completable<ByteBuffer> completable = new Promise.Completable<>();
|
||||
Content.Source.asByteBuffer(request, completable);
|
||||
|
||||
completable.whenComplete((requestContent, failure) ->
|
||||
{
|
||||
if (failure == null)
|
||||
{
|
||||
// Process the request content here.
|
||||
|
||||
// Implicitly respond with status code 200 and no content.
|
||||
callback.succeeded();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Implicitly respond with status code 500.
|
||||
callback.failed(failure);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::requestContent-buffer[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class RequestContentAPIsInputStream extends Handler.Abstract
|
||||
{
|
||||
// tag::requestContent-stream[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Read the request content as an InputStream.
|
||||
// Note that InputStream.read() may block.
|
||||
try (InputStream inputStream = Content.Source.asInputStream(request))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
int read = inputStream.read();
|
||||
|
||||
// EOF was reached, stop reading.
|
||||
if (read < 0)
|
||||
break;
|
||||
|
||||
// Process the read byte here.
|
||||
}
|
||||
}
|
||||
|
||||
// Implicitly respond with status code 200 and no content.
|
||||
callback.succeeded();
|
||||
return true;
|
||||
}
|
||||
// end::requestContent-stream[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class RequestContentAPIsSource extends Handler.Abstract
|
||||
{
|
||||
// tag::requestContent-source[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
CompletableTask<Void> reader = new CompletableTask<>()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// Read in a loop.
|
||||
while (true)
|
||||
{
|
||||
// Read a chunk of content.
|
||||
Content.Chunk chunk = request.read();
|
||||
|
||||
// If there is no content, demand to be
|
||||
// called back when more content is available.
|
||||
if (chunk == null)
|
||||
{
|
||||
request.demand(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// If a failure is read, complete with a failure.
|
||||
if (Content.Chunk.isFailure(chunk))
|
||||
{
|
||||
Throwable failure = chunk.getFailure();
|
||||
completeExceptionally(failure);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunk instanceof Trailers trailers)
|
||||
{
|
||||
// Possibly process the request trailers here.
|
||||
// Trailers have an empty ByteBuffer and are a last chunk.
|
||||
}
|
||||
|
||||
// Process the request content chunk here.
|
||||
// After the processing, the chunk MUST be released.
|
||||
chunk.release();
|
||||
|
||||
// If the last chunk is read, complete normally.
|
||||
if (chunk.isLast())
|
||||
{
|
||||
complete(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Not the last chunk of content, loop around to read more.
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Initiate the read of the request content.
|
||||
reader.start();
|
||||
|
||||
// When the read is complete, complete the Handler callback.
|
||||
callback.completeWith(reader);
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::requestContent-source[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
// tag::response[]
|
||||
public class ResponseAPIs extends Handler.Abstract
|
||||
{
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Sets/Gets the response HTTP status.
|
||||
// Replaces:
|
||||
// - servletResponse.setStatus(code);
|
||||
// - servletResponse.getStatus();
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
int status = response.getStatus();
|
||||
|
||||
// Gets the response HTTP headers.
|
||||
// Replaces:
|
||||
// - servletResponse.setHeader(name, value);
|
||||
// - servletResponse.addHeader(name, value);
|
||||
// - servletResponse.setDateHeader(name, date);
|
||||
// - servletResponse.addDateHeader(name, date);
|
||||
// - servletResponse.setIntHeader(name, value);
|
||||
// - servletResponse.addIntHeader(name, value);
|
||||
// - servletResponse.getHeaderNames()
|
||||
// - servletResponse.getHeader(name)
|
||||
// - servletResponse.getHeaders(name)
|
||||
// - servletResponse.containsHeader(name)
|
||||
HttpFields.Mutable responseHeaders = response.getHeaders();
|
||||
|
||||
// Sets an HTTP cookie.
|
||||
// Replaces:
|
||||
// - servletResponse.addCookie(cookie);
|
||||
HttpCookie cookie = HttpCookie.build("name", "value")
|
||||
.domain("example.org")
|
||||
.path("/path")
|
||||
.maxAge(Duration.ofDays(1).toSeconds())
|
||||
.build();
|
||||
Response.addCookie(response, cookie);
|
||||
|
||||
// Sets the response Content-Type.
|
||||
// Replaces:
|
||||
// - servletResponse.setContentType(type)
|
||||
responseHeaders.put(HttpHeader.CONTENT_TYPE, "text/plain; charset=UTF-8");
|
||||
|
||||
// Sets the response Content-Length.
|
||||
// Replaces:
|
||||
// - servletResponse.setContentLength(length)
|
||||
// - servletResponse.setContentLengthLong(length)
|
||||
responseHeaders.put(HttpHeader.CONTENT_LENGTH, 1024L);
|
||||
|
||||
// Sets/Gets the response trailers.
|
||||
// Replaces:
|
||||
// - servletResponse.setTrailerFields(() -> trailers)
|
||||
// - servletResponse.getTrailerFields()
|
||||
HttpFields trailers = HttpFields.build().put("checksum", 0xCAFE);
|
||||
response.setTrailersSupplier(trailers);
|
||||
Supplier<HttpFields> trailersSupplier = response.getTrailersSupplier();
|
||||
|
||||
// Gets whether the response is committed.
|
||||
// Replaces:
|
||||
// - servletResponse.isCommitted()
|
||||
boolean committed = response.isCommitted();
|
||||
|
||||
// Resets the response.
|
||||
// Replaces:
|
||||
// - servletResponse.reset();
|
||||
response.reset();
|
||||
|
||||
// Sends a redirect response.
|
||||
// Replaces:
|
||||
// - servletResponse.encodeRedirectURL(location)
|
||||
// - servletResponse.sendRedirect(location)
|
||||
String location = Request.toRedirectURI(request, "/redirect");
|
||||
Response.sendRedirect(request, response, callback, location);
|
||||
|
||||
// Sends an error response.
|
||||
// Replaces:
|
||||
// - servletResponse.sendError(code);
|
||||
// - servletResponse.sendError(code, message);
|
||||
Response.writeError(request, response, callback, HttpStatus.SERVICE_UNAVAILABLE_503, "Request Cannot be Processed");
|
||||
|
||||
callback.succeeded();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// end::response[]
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIsImplicit extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-implicit[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Produces an implicit response with status code 200
|
||||
// with no content when returning from this method.
|
||||
|
||||
// The Handler callback must be completed when returning true.
|
||||
callback.succeeded();
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-implicit[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIsImplicitWithStatus extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-implicit-status[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Produces an implicit response with status 204
|
||||
// with no content when returning from this method.
|
||||
response.setStatus(HttpStatus.NO_CONTENT_204);
|
||||
|
||||
// The Handler callback must be completed when returning true.
|
||||
callback.succeeded();
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-implicit-status[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIsExplicit extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-explicit[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Produces an explicit response with status 204 with no content.
|
||||
response.setStatus(HttpStatus.NO_CONTENT_204);
|
||||
|
||||
// This explicit first write() writes the response status code and headers.
|
||||
// It is also the last write (as specified by the first parameter)
|
||||
// and writes an empty content (the second parameter, a null ByteBuffer).
|
||||
// When this write completes, the Handler callback is completed.
|
||||
response.write(true, null, callback);
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-explicit[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPISimpleContent extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-content[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
|
||||
ByteBuffer content = UTF_8.encode("Hello World");
|
||||
|
||||
// Explicit first write that writes the response status code, headers and content.
|
||||
// When this write completes, the Handler callback is completed.
|
||||
response.write(true, content, callback);
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-content[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIFlush extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-content[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
|
||||
ByteBuffer content = UTF_8.encode("Hello World");
|
||||
response.getHeaders().put(HttpHeader.CONTENT_LENGTH, content.remaining());
|
||||
|
||||
// Flush the response status code and the headers (no content).
|
||||
// This is the fist but non-last write.
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
response.write(false, null, completable);
|
||||
|
||||
// When the first write completes, perform the second (and last) write.
|
||||
completable.whenComplete((ignored, failure) ->
|
||||
{
|
||||
if (failure == null)
|
||||
{
|
||||
// Now explicitly write the content as the last write.
|
||||
// When this write completes, the Handler callback is completed.
|
||||
response.write(true, content, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Implicitly respond with status code 500.
|
||||
callback.failed(failure);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-content[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIString extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-string[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
|
||||
// Utility method to write UTF-8 string content.
|
||||
// When this write completes, the Handler callback is completed.
|
||||
Content.Sink.write(response, true, "Hello World", callback);
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-string[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPIEcho extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-echo[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
|
||||
// Utility method to echo the content from the request to the response.
|
||||
// When the echo completes, the Handler callback is completed.
|
||||
Content.copy(request, response, callback);
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-echo[]
|
||||
}
|
||||
|
||||
@SuppressWarnings("InnerClassMayBeStatic")
|
||||
public class ResponseContentAPITrailers extends Handler.Abstract
|
||||
{
|
||||
// tag::responseContent-trailers[]
|
||||
@Override
|
||||
public boolean handle(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
response.setStatus(HttpStatus.OK_200);
|
||||
|
||||
// The trailers must be set on the response before the first write.
|
||||
HttpFields.Mutable trailers = HttpFields.build();
|
||||
response.setTrailersSupplier(trailers);
|
||||
|
||||
// Explicit first write that writes the response status code, headers and content.
|
||||
// The trailers have not been written yet; they will be written with the last write.
|
||||
ByteBuffer content = UTF_8.encode("Hello World");
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
response.write(false, content, completable);
|
||||
|
||||
completable.whenComplete((ignored, failure) ->
|
||||
{
|
||||
if (failure == null)
|
||||
{
|
||||
// Update the trailers
|
||||
trailers.put("Content-Checksum", 0xCAFE);
|
||||
|
||||
// Explicit last write to write the trailers
|
||||
// and complete the Handler callback.
|
||||
response.write(true, null, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Implicitly respond with status code 500.
|
||||
callback.failed(failure);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
// end::responseContent-trailers[]
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@ import org.eclipse.jetty.io.content.PathContentSource;
|
|||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.CompletableTask;
|
||||
import org.eclipse.jetty.util.FutureCallback;
|
||||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
@ -104,7 +105,9 @@ public class ContentSourceTest
|
|||
return List.of(asyncSource, byteBufferSource, transformerSource, pathSource, inputSource, inputSource2);
|
||||
}
|
||||
|
||||
/** Get the next chunk, blocking if necessary
|
||||
/**
|
||||
* Get the next chunk, blocking if necessary
|
||||
*
|
||||
* @param source The source to get the next chunk from
|
||||
* @return A non null chunk
|
||||
*/
|
||||
|
@ -113,8 +116,7 @@ public class ContentSourceTest
|
|||
Content.Chunk chunk = source.read();
|
||||
if (chunk != null)
|
||||
return chunk;
|
||||
FuturePromise<Content.Chunk> next = new FuturePromise<>();
|
||||
Runnable getNext = new Runnable()
|
||||
CompletableTask<Content.Chunk> task = new CompletableTask<>()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
|
@ -122,18 +124,12 @@ public class ContentSourceTest
|
|||
Content.Chunk chunk = source.read();
|
||||
if (chunk == null)
|
||||
source.demand(this);
|
||||
next.succeeded(chunk);
|
||||
else
|
||||
complete(chunk);
|
||||
}
|
||||
};
|
||||
source.demand(getNext);
|
||||
try
|
||||
{
|
||||
return next.get();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
source.demand(task);
|
||||
return task.join();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
|
@ -141,8 +137,7 @@ public class ContentSourceTest
|
|||
public void testRead(Content.Source source) throws Exception
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
CountDownLatch eof = new CountDownLatch(1);
|
||||
source.demand(new Runnable()
|
||||
var task = new CompletableTask<>()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
|
@ -162,14 +157,14 @@ public class ContentSourceTest
|
|||
|
||||
if (chunk.isLast())
|
||||
{
|
||||
eof.countDown();
|
||||
complete(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(eof.await(10, TimeUnit.SECONDS));
|
||||
};
|
||||
source.demand(task);
|
||||
task.get(10, TimeUnit.SECONDS);
|
||||
assertThat(builder.toString(), is("onetwo"));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
|
||||
//
|
||||
// This program and the accompanying materials are made available under the
|
||||
// terms of the Eclipse Public License v. 2.0 which is available at
|
||||
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
|
||||
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
|
||||
//
|
||||
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
|
||||
// ========================================================================
|
||||
//
|
||||
|
||||
package org.eclipse.jetty.util;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* <p>A {@link CompletableFuture} that implements {@link Runnable} to perform
|
||||
* a one-shot task that eventually completes this {@link CompletableFuture}.</p>
|
||||
* <p>Subclasses override {@link #run()} to implement the task.</p>
|
||||
* <p>Users of this class start the task execution via {@link #start()}.</p>
|
||||
* <p>Typical usage:</p>
|
||||
* <pre>{@code
|
||||
* CompletableTask<T> task = new CompletableTask<>()
|
||||
* {
|
||||
* @Override
|
||||
* public void run()
|
||||
* {
|
||||
* try
|
||||
* {
|
||||
* // Perform some task.
|
||||
* T result = performTask();
|
||||
*
|
||||
* // Eventually complete this CompletableFuture.
|
||||
* complete(result);
|
||||
* }
|
||||
* catch (Throwable x)
|
||||
* {
|
||||
* completeExceptionally(x);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // Start the task and then process the
|
||||
* // result of the task when it is complete.
|
||||
* task.start()
|
||||
* .whenComplete((result, failure) ->
|
||||
* {
|
||||
* if (failure == null)
|
||||
* {
|
||||
* // The task completed successfully.
|
||||
* }
|
||||
* else
|
||||
* {
|
||||
* // The task failed.
|
||||
* }
|
||||
* });
|
||||
* }</pre>
|
||||
*
|
||||
* @param <T> the type of the result of the task
|
||||
*/
|
||||
public abstract class CompletableTask<T> extends CompletableFuture<T> implements Runnable
|
||||
{
|
||||
/**
|
||||
* <p>Starts the task by calling {@link #run()}
|
||||
* and returns this {@link CompletableTask}.</p>
|
||||
*
|
||||
* @return this {@link CompletableTask}
|
||||
*/
|
||||
public CompletableTask<T> start()
|
||||
{
|
||||
run();
|
||||
return this;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue