Skip to content

Commit

Permalink
Merge branch 'jetty-12.0.x' into jetty-12.0.x-9396-websocket-jpms-review
Browse files Browse the repository at this point in the history
  • Loading branch information
gregw committed Jun 30, 2023
2 parents 9036777 + ec2dbe7 commit e0133d7
Show file tree
Hide file tree
Showing 300 changed files with 5,789 additions and 4,669 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ There is also an link:#error-handler[Error Handler] that services errors related

_____
[NOTE]
The `DefaultHandler` will also handle serving out the `flav.ico` file should a request make it through all of the other handlers without being resolved.
The `DefaultHandler` will also handle serving out the `favicon.ico` file should a request make it through all of the other handlers without being resolved.
_____

[source, java, subs="{sub-order}"]
----
Server server = new Server(8080);
HandlerList handlers = new HandlerList();
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setBaseResource(Resource.newResource("."));
handlers.setHandlers(new Handler[]
{ resourceHandler, new DefaultHandler() });
resourceHandler.setBaseResource(ResourceFactory.of(resourceHandler).newResource("."));
Handler.Sequence handlers = new Handler.Sequence(
resourceHandler, new DefaultHandler()
);
server.setHandler(handlers);
server.start();
----
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ The Jetty Server Libraries provides a number of out-of-the-box __Handler__s that
====== ContextHandler

`ContextHandler` is a `Handler` that represents a _context_ for a web application.
It is a `HandlerWrapper` that performs some action before and after delegating to the nested `Handler`.
It is a `Handler.Wrapper` that performs some action before and after delegating to the nested `Handler`.
// TODO: expand on what the ContextHandler does, e.g. ServletContext.

The simplest use of `ContextHandler` is the following:
Expand Down Expand Up @@ -140,7 +140,7 @@ See also xref:pg-server-http-handler-use-util-default-handler[how to use] `Defau

`GzipHandler` provides supports for automatic decompression of compressed request content and automatic compression of response content.

`GzipHandler` is a `HandlerWrapper` that inspects the request and, if the request matches the `GzipHandler` configuration, just installs the required components to eventually perform decompression of the request content or compression of the response content.
`GzipHandler` is a `Handler.Wrapper` that inspects the request and, if the request matches the `GzipHandler` configuration, just installs the required components to eventually perform decompression of the request content or compression of the response content.
The decompression/compression is not performed until the web application reads request content or writes response content.

`GzipHandler` can be configured at the server level in this way:
Expand Down Expand Up @@ -291,7 +291,7 @@ include::../../{doc_code}/org/eclipse/jetty/docs/programming/server/http/HTTPSer
* Sends a HTTP `404` response for any other request
* The HTTP `404` response content nicely shows a HTML table with all the contexts deployed on the `Server` instance

`DefaultHandler` is best used as the last `Handler` of a `HandlerList`, for example:
`DefaultHandler` is best used directly set on the server, for example:

[source,java,indent=0]
----
Expand All @@ -310,7 +310,7 @@ Server
└── DefaultHandler
----

In the example above, `ContextHandlerCollection` will try to match a request to one of the contexts; if the match fails, `HandlerList` will call the next `Handler` which is `DefaultHandler` that will return a HTTP `404` with an HTML page showing the existing contexts deployed on the `Server`.
In the example above, `ContextHandlerCollection` will try to match a request to one of the contexts; if the match fails, `Server` will call the `DefaultHandler` that will return a HTTP `404` with an HTML page showing the existing contexts deployed on the `Server`.

NOTE: `DefaultHandler` just sends a nicer HTTP `404` response in case of wrong requests from clients.
Jetty will send an HTTP `404` response anyway if `DefaultHandler` is not used.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
//
// ========================================================================
// 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;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;

import org.eclipse.jetty.io.Content;
import org.eclipse.jetty.io.content.AsyncContent;
import org.eclipse.jetty.io.content.ContentSourceCompletableFuture;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.CharsetStringBuilder;
import org.eclipse.jetty.util.FutureCallback;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressWarnings("unused")
public class ContentDocs
{
private static final Logger LOG = LoggerFactory.getLogger(ContentDocs.class);

// tag::echo[]
public void echo(Content.Source source, Content.Sink sink, Callback callback)
{
Callback echo = new Callback()
{
private Content.Chunk chunk;

public void succeeded()
{
// release any previous chunk
if (chunk != null)
{
chunk.release();
// complete if it was the last
if (chunk.isLast())
{
callback.succeeded();
return;
}
}

while (true)
{
// read the next chunk
chunk = source.read();

if (chunk == null)
{
// if no chunk, demand more and call succeeded when demand is met.
source.demand(this::succeeded);
return;
}

if (Content.Chunk.isFailure(chunk, true))
{
// if it is a persistent failure, then fail the callback
callback.failed(chunk.getFailure());
return;
}

if (chunk.hasRemaining() || chunk.isLast())
{
// if chunk has content or is last, write it to the sink and resume this loop in callback
sink.write(chunk.isLast(), chunk.getByteBuffer(), this);
return;
}

chunk.release();
}
}

public void failed(Throwable x)
{
source.fail(x);
callback.failed(x);
}
};
source.demand(echo::succeeded);
}
// tag::echo[]

public static void testEcho() throws Exception
{
AsyncContent source = new AsyncContent();
AsyncContent sink = new AsyncContent();

Callback.Completable echoCallback = new Callback.Completable();
new ContentDocs().echo(source, sink, echoCallback);

Content.Chunk chunk = sink.read();
if (chunk != null)
throw new IllegalStateException("No chunk expected yet");

FutureCallback onContentAvailable = new FutureCallback();
sink.demand(onContentAvailable::succeeded);
if (onContentAvailable.isDone())
throw new IllegalStateException("No demand expected yet");

Callback.Completable writeCallback = new Callback.Completable();
Content.Sink.write(source, false, "One", writeCallback);
if (writeCallback.isDone())
throw new IllegalStateException("Should wait until first chunk is consumed");

onContentAvailable.get();
chunk = sink.read();
if (!"One".equals(BufferUtil.toString(chunk.getByteBuffer())))
throw new IllegalStateException("first chunk is expected");

if (writeCallback.isDone())
throw new IllegalStateException("Should wait until first chunk is consumed");
chunk.release();
writeCallback.get();


writeCallback = new Callback.Completable();
Content.Sink.write(source, true, "Two", writeCallback);
if (writeCallback.isDone())
throw new IllegalStateException("Should wait until second chunk is consumed");

onContentAvailable = new FutureCallback();
sink.demand(onContentAvailable::succeeded);
if (!onContentAvailable.isDone())
throw new IllegalStateException("Demand expected for second chunk");

chunk = sink.read();
if (!"Two".equals(BufferUtil.toString(chunk.getByteBuffer())))
throw new IllegalStateException("second chunk is expected");
chunk.release();
writeCallback.get();

onContentAvailable = new FutureCallback();
sink.demand(onContentAvailable::succeeded);
if (!onContentAvailable.isDone())
throw new IllegalStateException("Demand expected for EOF");

chunk = sink.read();
if (!chunk.isLast())
throw new IllegalStateException("EOF expected");
}

public static class FutureString extends CompletableFuture<String>
{
private final CharsetStringBuilder text;
private final Content.Source source;

public FutureString(Content.Source source, Charset charset)
{
this.source = source;
this.text = CharsetStringBuilder.forCharset(charset);
source.demand(this::onContentAvailable);
}

private void onContentAvailable()
{
while (true)
{
Content.Chunk chunk = source.read();
if (chunk == null)
{
source.demand(this::onContentAvailable);
return;
}

try
{
if (Content.Chunk.isFailure(chunk))
throw chunk.getFailure();

if (chunk.hasRemaining())
text.append(chunk.getByteBuffer());

if (chunk.isLast() && complete(text.build()))
return;
}
catch (Throwable e)
{
completeExceptionally(e);
}
finally
{
chunk.release();
}
}
}
}

public static void testFutureString() throws Exception
{
AsyncContent source = new AsyncContent();
FutureString future = new FutureString(source, StandardCharsets.UTF_8);
if (future.isDone())
throw new IllegalStateException();

Callback.Completable writeCallback = new Callback.Completable();
Content.Sink.write(source, false, "One", writeCallback);
if (!writeCallback.isDone() || future.isDone())
throw new IllegalStateException("Should be consumed");
Content.Sink.write(source, false, "Two", writeCallback);
if (!writeCallback.isDone() || future.isDone())
throw new IllegalStateException("Should be consumed");
Content.Sink.write(source, true, "Three", writeCallback);
if (!writeCallback.isDone() || !future.isDone())
throw new IllegalStateException("Should be consumed");
}

public static class FutureUtf8String extends ContentSourceCompletableFuture<String>
{
private final Utf8StringBuilder builder = new Utf8StringBuilder();

public FutureUtf8String(Content.Source content)
{
super(content);
}

@Override
protected String parse(Content.Chunk chunk) throws Throwable
{
if (chunk.hasRemaining())
builder.append(chunk.getByteBuffer());
return chunk.isLast() ? builder.takeCompleteString(IllegalStateException::new) : null;
}
}

public static void main(String... args) throws Exception
{
testEcho();
testFutureString();
}
}
Loading

0 comments on commit e0133d7

Please sign in to comment.