Merge remote-tracking branch 'origin/jetty-12.0.x' into jetty-12.0.x-8606-scopeListeners
This commit is contained in:
commit
b893186f5e
|
@ -19,10 +19,12 @@ This release process will produce releases:
|
|||
- [ ] Link this issue to the target [GitHub Project(s)](https://github.com/eclipse/jetty.project/projects).
|
||||
- [ ] Assign this issue to a "release manager".
|
||||
- [ ] Review [draft security advisories](https://github.com/eclipse/jetty.project/security/advisories). Ensure that issues are created and assigned to GitHub Projects to capture any advisories that will be announced.
|
||||
- [ ] Create the [GitHub Project(s)](https://github.com/eclipse/jetty.project/projects) for the next releases.
|
||||
- [ ] Review dependabot status. If there has not been a recent run, run [manually](https://github.com/eclipse/jetty.project/network/updates) and review resulting PRs for inclusion.
|
||||
- [ ] Review the issues/PRs assigned to the target [GitHub Project(s)](https://github.com/eclipse/jetty.project/projects). Any PRs that are moved to next releases should be commented on so their authors are informed.
|
||||
- [ ] Freeze the target [GitHub Project(s)](https://github.com/eclipse/jetty.project/projects) by editing their names to "Jetty X.Y.Z FROZEN"
|
||||
- [ ] Update [GitHub Project(s)](https://github.com/eclipse/jetty.project/projects)
|
||||
+ [ ] Create new project for the next releases (not this release).
|
||||
+ [ ] Ensure new project is public (not private)
|
||||
+ [ ] Freeze the target [GitHub Project(s)](https://github.com/eclipse/jetty.project/projects) by editing their names to "Jetty X.Y.Z FROZEN"
|
||||
+ [ ] Review the issues/PRs assigned to the target [GitHub Project(s)](https://github.com/eclipse/jetty.project/projects). Any tasks that are not-yet-started are moved to next releases.
|
||||
- [ ] Review dependabot status. [Manually](https://github.com/eclipse/jetty.project/network/updates) run dependabot if needed and review resulting PRs for inclusion.
|
||||
- [ ] Wait 24 hours from last change to the issues/PRs included in FROZEN GitHub Project(s).
|
||||
- [ ] Verify target [project(s)](https://github.com/eclipse/jetty.project/projects) are complete.
|
||||
- [ ] Verify that branch `jetty-10.0.x` is merged to branch `jetty-11.0.x`.
|
||||
|
@ -35,6 +37,9 @@ This release process will produce releases:
|
|||
+ [ ] Push release branches `release/<ver>` to to https://github.com/eclipse/jetty.project
|
||||
+ [ ] Push release tags `jetty-<ver>` to https://github.com/eclipse/jetty.project
|
||||
+ [ ] Edit a draft release (for each Jetty release) in GitHub (https://github.com/eclipse/jetty.project/releases). Content is generated with the "changelog tool".
|
||||
Be mindful of the order you create multiple release drafts. The first one created will be the "oldest" when published. (eg: Draft is 9, then 10, then 11)
|
||||
The last created "draft" will show up as "latest" in the github UI.
|
||||
If you have to reroll, you'll have to delete the drafts and recreate them (especially so if 9 w/timestamp is in the mix of releases being worked on)
|
||||
- [ ] Assign issue to "test manager", who will oversee the testing of the staged releases.
|
||||
+ [ ] Test [CometD](https://github.com/cometd/cometd).
|
||||
+ [ ] Test [Reactive HttpClient](https://github.com/jetty-project/jetty-reactive-httpclient).
|
||||
|
@ -53,7 +58,7 @@ This release process will produce releases:
|
|||
- [ ] Update Jetty versions on the web sites.
|
||||
+ [ ] Update (or check) [Download](https://www.eclipse.org/jetty/download.php) page is updated.
|
||||
+ [ ] Update (or check) documentation page(s) are updated.
|
||||
- [ ] Publish GitHub Releases.
|
||||
- [ ] Publish GitHub Releases in the order of oldest (eg: 9) to newest (eg: 11) (to ensure that "latest" in github is truly the latest)
|
||||
- [ ] Prepare release announcement for mailing lists.
|
||||
- [ ] Publish any [security advisories](https://github.com/eclipse/jetty.project/security/advisories).
|
||||
+ [ ] Edit `VERSION.txt` to include any actual CVE number next to correspondent issue.
|
||||
|
|
|
@ -81,6 +81,8 @@ public class ContinueProtocolHandler implements ProtocolHandler
|
|||
conversation.updateResponseListeners(null);
|
||||
|
||||
HttpExchange exchange = conversation.getExchanges().peekLast();
|
||||
assert exchange != null;
|
||||
|
||||
if (response.getStatus() == HttpStatus.CONTINUE_100)
|
||||
{
|
||||
// All good, continue.
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 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.client;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* <p>A protocol handler that handles the 103 response code.</p>
|
||||
*/
|
||||
public class EarlyHintsProtocolHandler implements ProtocolHandler
|
||||
{
|
||||
public static final String NAME = "early-hints";
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Request request, Response response)
|
||||
{
|
||||
return response.getStatus() == HttpStatus.EARLY_HINT_103;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response.Listener getResponseListener()
|
||||
{
|
||||
return new EarlyHintsListener();
|
||||
}
|
||||
|
||||
protected void onEarlyHints(Request request, HttpFields responseHeaders)
|
||||
{
|
||||
}
|
||||
|
||||
private class EarlyHintsListener extends BufferingResponseListener
|
||||
{
|
||||
private final ResponseNotifier notifier = new ResponseNotifier();
|
||||
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
Request request = response.getRequest();
|
||||
HttpConversation conversation = ((HttpRequest)request).getConversation();
|
||||
|
||||
// Reset the conversation listeners, since we are going to receive another response code.
|
||||
conversation.updateResponseListeners(null);
|
||||
|
||||
HttpExchange exchange = conversation.getExchanges().peekLast();
|
||||
assert exchange != null;
|
||||
|
||||
HttpFields responseHeaders = HttpFields.build(response.getHeaders());
|
||||
exchange.resetResponse();
|
||||
onEarlyHints(request, responseHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Response response, Throwable failure)
|
||||
{
|
||||
HttpConversation conversation = ((HttpRequest)response.getRequest()).getConversation();
|
||||
// Reset the conversation listeners to allow the conversation to be completed.
|
||||
conversation.updateResponseListeners(null);
|
||||
|
||||
HttpExchange exchange = conversation.getExchanges().peekLast();
|
||||
if (exchange != null)
|
||||
{
|
||||
List<Response.ResponseListener> listeners = exchange.getResponseListeners();
|
||||
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getMediaType(), getEncoding());
|
||||
notifier.forwardFailureComplete(listeners, exchange.getRequest(), exchange.getRequestFailure(), contentResponse, failure);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -221,6 +221,8 @@ public class HttpClient extends ContainerLifeCycle
|
|||
setSocketAddressResolver(new SocketAddressResolver.Async(getExecutor(), getScheduler(), getAddressResolutionTimeout()));
|
||||
|
||||
handlers.put(new ContinueProtocolHandler());
|
||||
handlers.put(new ProcessingProtocolHandler());
|
||||
handlers.put(new EarlyHintsProtocolHandler());
|
||||
handlers.put(new RedirectProtocolHandler(this));
|
||||
handlers.put(new WWWAuthenticationProtocolHandler(this));
|
||||
handlers.put(new ProxyAuthenticationProtocolHandler(this));
|
||||
|
|
|
@ -326,6 +326,10 @@ public abstract class HttpSender
|
|||
|
||||
public void proceed(HttpExchange exchange, Throwable failure)
|
||||
{
|
||||
// Received a 100 Continue, although Expect header was not sent.
|
||||
if (!contentSender.expect100)
|
||||
return;
|
||||
|
||||
contentSender.expect100 = false;
|
||||
if (failure == null)
|
||||
contentSender.iterate();
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 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.client;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* <p>A protocol handler that handles the 102 response code.</p>
|
||||
*/
|
||||
public class ProcessingProtocolHandler implements ProtocolHandler
|
||||
{
|
||||
public static final String NAME = "processing";
|
||||
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(Request request, Response response)
|
||||
{
|
||||
return response.getStatus() == HttpStatus.PROCESSING_102;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response.Listener getResponseListener()
|
||||
{
|
||||
return new ProcessingListener();
|
||||
}
|
||||
|
||||
protected void onProcessing(Request request, HttpFields responseHeaders)
|
||||
{
|
||||
}
|
||||
|
||||
private class ProcessingListener extends BufferingResponseListener
|
||||
{
|
||||
private final ResponseNotifier notifier = new ResponseNotifier();
|
||||
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
Request request = response.getRequest();
|
||||
HttpConversation conversation = ((HttpRequest)request).getConversation();
|
||||
|
||||
// Reset the conversation listeners, since we are going to receive another response code.
|
||||
conversation.updateResponseListeners(null);
|
||||
|
||||
HttpExchange exchange = conversation.getExchanges().peekLast();
|
||||
assert exchange != null;
|
||||
|
||||
HttpFields responseHeaders = HttpFields.build(response.getHeaders());
|
||||
exchange.resetResponse();
|
||||
onProcessing(request, responseHeaders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Response response, Throwable failure)
|
||||
{
|
||||
HttpConversation conversation = ((HttpRequest)response.getRequest()).getConversation();
|
||||
// Reset the conversation listeners to allow the conversation to be completed.
|
||||
conversation.updateResponseListeners(null);
|
||||
|
||||
HttpExchange exchange = conversation.getExchanges().peekLast();
|
||||
if (exchange != null)
|
||||
{
|
||||
List<Response.ResponseListener> listeners = exchange.getResponseListeners();
|
||||
HttpContentResponse contentResponse = new HttpContentResponse(response, getContent(), getMediaType(), getEncoding());
|
||||
notifier.forwardFailureComplete(listeners, exchange.getRequest(), exchange.getRequestFailure(), contentResponse, failure);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,6 +37,7 @@ import org.eclipse.jetty.client.api.Result;
|
|||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -74,7 +75,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
private static final Logger LOG = LoggerFactory.getLogger(InputStreamResponseListener.class);
|
||||
private static final Chunk EOF = new Chunk(BufferUtil.EMPTY_BUFFER, Callback.NOOP);
|
||||
|
||||
private final Object lock = this;
|
||||
private final AutoLock.WithCondition lock = new AutoLock.WithCondition();
|
||||
private final CountDownLatch responseLatch = new CountDownLatch(1);
|
||||
private final CountDownLatch resultLatch = new CountDownLatch(1);
|
||||
private final AtomicReference<InputStream> stream = new AtomicReference<>();
|
||||
|
@ -91,7 +92,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
@Override
|
||||
public void onHeaders(Response response)
|
||||
{
|
||||
synchronized (lock)
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
this.response = response;
|
||||
responseLatch.countDown();
|
||||
|
@ -110,7 +111,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
}
|
||||
|
||||
boolean closed;
|
||||
synchronized (lock)
|
||||
try (AutoLock.WithCondition l = lock.lock())
|
||||
{
|
||||
closed = this.closed;
|
||||
if (!closed)
|
||||
|
@ -118,7 +119,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Queueing content {}", content);
|
||||
chunks.add(new Chunk(content, callback));
|
||||
lock.notifyAll();
|
||||
l.signalAll();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,11 +134,11 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
synchronized (lock)
|
||||
try (AutoLock.WithCondition l = lock.lock())
|
||||
{
|
||||
if (!closed)
|
||||
chunks.add(EOF);
|
||||
lock.notifyAll();
|
||||
l.signalAll();
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -148,13 +149,13 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
public void onFailure(Response response, Throwable failure)
|
||||
{
|
||||
List<Callback> callbacks;
|
||||
synchronized (lock)
|
||||
try (AutoLock.WithCondition l = lock.lock())
|
||||
{
|
||||
if (this.failure != null)
|
||||
return;
|
||||
this.failure = failure;
|
||||
callbacks = drain();
|
||||
lock.notifyAll();
|
||||
l.signalAll();
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -168,7 +169,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
{
|
||||
Throwable failure = result.getFailure();
|
||||
List<Callback> callbacks = Collections.emptyList();
|
||||
synchronized (lock)
|
||||
try (AutoLock.WithCondition l = lock.lock())
|
||||
{
|
||||
this.result = result;
|
||||
if (result.isFailed() && this.failure == null)
|
||||
|
@ -179,7 +180,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
// Notify the response latch in case of request failures.
|
||||
responseLatch.countDown();
|
||||
resultLatch.countDown();
|
||||
lock.notifyAll();
|
||||
l.signalAll();
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -211,7 +212,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
boolean expired = !responseLatch.await(timeout, unit);
|
||||
if (expired)
|
||||
throw new TimeoutException();
|
||||
synchronized (lock)
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
// If the request failed there is no response.
|
||||
if (response == null)
|
||||
|
@ -237,7 +238,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
boolean expired = !resultLatch.await(timeout, unit);
|
||||
if (expired)
|
||||
throw new TimeoutException();
|
||||
synchronized (lock)
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
@ -261,7 +262,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
private List<Callback> drain()
|
||||
{
|
||||
List<Callback> callbacks = new ArrayList<>();
|
||||
synchronized (lock)
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
|
@ -294,7 +295,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
{
|
||||
int result;
|
||||
Callback callback = null;
|
||||
synchronized (lock)
|
||||
try (AutoLock.WithCondition l = lock.lock())
|
||||
{
|
||||
Chunk chunk;
|
||||
while (true)
|
||||
|
@ -312,7 +313,7 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
if (closed)
|
||||
throw new AsynchronousCloseException();
|
||||
|
||||
lock.wait();
|
||||
l.await();
|
||||
}
|
||||
|
||||
ByteBuffer buffer = chunk.buffer;
|
||||
|
@ -346,13 +347,13 @@ public class InputStreamResponseListener extends Listener.Adapter
|
|||
public void close() throws IOException
|
||||
{
|
||||
List<Callback> callbacks;
|
||||
synchronized (lock)
|
||||
try (AutoLock.WithCondition l = lock.lock())
|
||||
{
|
||||
if (closed)
|
||||
return;
|
||||
closed = true;
|
||||
callbacks = drain();
|
||||
lock.notifyAll();
|
||||
l.signalAll();
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
|
|
|
@ -103,7 +103,7 @@ public class HttpTester
|
|||
return new Input(data.slice())
|
||||
{
|
||||
@Override
|
||||
public int fillBuffer() throws IOException
|
||||
public int fillBuffer()
|
||||
{
|
||||
_eof = true;
|
||||
return -1;
|
||||
|
@ -162,10 +162,7 @@ public class HttpTester
|
|||
|
||||
public static Request parseRequest(String request)
|
||||
{
|
||||
Request r = new Request();
|
||||
HttpParser parser = new HttpParser(r);
|
||||
parser.parseNext(BufferUtil.toBuffer(request));
|
||||
return r;
|
||||
return parseRequest(BufferUtil.toBuffer(request));
|
||||
}
|
||||
|
||||
public static Request parseRequest(ByteBuffer request)
|
||||
|
@ -176,6 +173,35 @@ public class HttpTester
|
|||
return r;
|
||||
}
|
||||
|
||||
public static Request parseRequest(InputStream requestStream) throws IOException
|
||||
{
|
||||
Request r = new Request();
|
||||
HttpParser parser = new HttpParser(r);
|
||||
return parseMessage(requestStream, parser) ? r : null;
|
||||
}
|
||||
|
||||
private static boolean parseMessage(InputStream stream, HttpParser parser) throws IOException
|
||||
{
|
||||
// Read and parse a character at a time so we never can read more than we should.
|
||||
byte[] array = new byte[1];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(array);
|
||||
|
||||
while (true)
|
||||
{
|
||||
buffer.position(1);
|
||||
int read = stream.read(array);
|
||||
if (read < 0)
|
||||
parser.atEOF();
|
||||
else
|
||||
buffer.position(0);
|
||||
|
||||
if (parser.parseNext(buffer))
|
||||
return true;
|
||||
else if (read < 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Response parseResponse(String response)
|
||||
{
|
||||
Response r = new Response();
|
||||
|
@ -196,26 +222,7 @@ public class HttpTester
|
|||
{
|
||||
Response r = new Response();
|
||||
HttpParser parser = new HttpParser(r);
|
||||
|
||||
// Read and parse a character at a time so we never can read more than we should.
|
||||
byte[] array = new byte[1];
|
||||
ByteBuffer buffer = ByteBuffer.wrap(array);
|
||||
buffer.limit(1);
|
||||
|
||||
while (true)
|
||||
{
|
||||
buffer.position(1);
|
||||
int l = responseStream.read(array);
|
||||
if (l < 0)
|
||||
parser.atEOF();
|
||||
else
|
||||
buffer.position(0);
|
||||
|
||||
if (parser.parseNext(buffer))
|
||||
return r;
|
||||
else if (l < 0)
|
||||
return null;
|
||||
}
|
||||
return parseMessage(responseStream, parser) ? r : null;
|
||||
}
|
||||
|
||||
public static Response parseResponse(Input in) throws IOException
|
||||
|
@ -406,12 +413,12 @@ public class HttpTester
|
|||
try
|
||||
{
|
||||
_content.write(BufferUtil.toArray(ref));
|
||||
return false;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -244,9 +244,8 @@ public class MultiPartCaptureTest
|
|||
List<MultiPart.Part> charSetParts = allParts.get("_charset_");
|
||||
if (charSetParts != null)
|
||||
{
|
||||
Promise.Completable<String> promise = new Promise.Completable<>();
|
||||
Content.Source.asString(charSetParts.get(0).getContent(), StandardCharsets.US_ASCII, promise);
|
||||
defaultCharset = promise.get();
|
||||
defaultCharset = Promise.Completable.<String>with(p -> Content.Source.asString(charSetParts.get(0).getContent(), StandardCharsets.US_ASCII, p))
|
||||
.get();
|
||||
}
|
||||
|
||||
for (NameValue expected : partContainsContents)
|
||||
|
|
|
@ -383,9 +383,7 @@ public class HTTP2Client extends ContainerLifeCycle
|
|||
|
||||
public CompletableFuture<Session> connect(SslContextFactory sslContextFactory, SocketAddress address, Session.Listener listener)
|
||||
{
|
||||
Promise.Completable<Session> result = new Promise.Completable<>();
|
||||
connect(sslContextFactory, address, listener, result);
|
||||
return result;
|
||||
return Promise.Completable.with(p -> connect(sslContextFactory, address, listener, p));
|
||||
}
|
||||
|
||||
public void connect(SslContextFactory sslContextFactory, SocketAddress address, Session.Listener listener, Promise<Session> promise)
|
||||
|
|
|
@ -60,9 +60,7 @@ public interface Session
|
|||
*/
|
||||
public default CompletableFuture<Stream> newStream(HeadersFrame frame, Stream.Listener listener)
|
||||
{
|
||||
Promise.Completable<Stream> result = new Promise.Completable<>();
|
||||
newStream(frame, result, listener);
|
||||
return result;
|
||||
return Promise.Completable.with(p -> newStream(frame, p, listener));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,6 +85,17 @@ public interface Session
|
|||
*/
|
||||
public int priority(PriorityFrame frame, Callback callback);
|
||||
|
||||
/**
|
||||
* <p>Sends the given SETTINGS {@code frame} to configure the session.</p>
|
||||
*
|
||||
* @param frame the SETTINGS frame to send
|
||||
* @return a CompletableFuture that is notified when the frame has been sent
|
||||
*/
|
||||
public default CompletableFuture<Void> settings(SettingsFrame frame)
|
||||
{
|
||||
return Callback.Completable.with(c -> settings(frame, c));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sends the given SETTINGS {@code frame} to configure the session.</p>
|
||||
*
|
||||
|
|
|
@ -62,9 +62,7 @@ public interface Stream
|
|||
*/
|
||||
public default CompletableFuture<Stream> headers(HeadersFrame frame)
|
||||
{
|
||||
Promise.Completable<Stream> result = new Promise.Completable<>();
|
||||
headers(frame, Callback.from(() -> result.succeeded(this), result::failed));
|
||||
return result;
|
||||
return Promise.Completable.with(p -> headers(frame, Callback.from(() -> p.succeeded(this), p::failed)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -85,9 +83,7 @@ public interface Stream
|
|||
*/
|
||||
public default CompletableFuture<Stream> push(PushPromiseFrame frame, Listener listener)
|
||||
{
|
||||
Promise.Completable<Stream> result = new Promise.Completable<>();
|
||||
push(frame, result, listener);
|
||||
return result;
|
||||
return Promise.Completable.with(p -> push(frame, p, listener));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -139,9 +135,7 @@ public interface Stream
|
|||
*/
|
||||
public default CompletableFuture<Stream> data(DataFrame frame)
|
||||
{
|
||||
Promise.Completable<Stream> result = new Promise.Completable<>();
|
||||
data(frame, Callback.from(() -> result.succeeded(this), result::failed));
|
||||
return result;
|
||||
return Promise.Completable.with(p -> data(frame, Callback.from(() -> p.succeeded(this), p::failed)));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -155,7 +149,18 @@ public interface Stream
|
|||
/**
|
||||
* <p>Sends the given RST_STREAM {@code frame}.</p>
|
||||
*
|
||||
* @param frame the RST_FRAME to send
|
||||
* @param frame the RST_STREAM frame to send
|
||||
* @return the CompletableFuture that gets notified when the frame has been sent
|
||||
*/
|
||||
public default CompletableFuture<Void> reset(ResetFrame frame)
|
||||
{
|
||||
return Callback.Completable.with(c -> reset(frame, c));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Sends the given RST_STREAM {@code frame}.</p>
|
||||
*
|
||||
* @param frame the RST_STREAM frame to send
|
||||
* @param callback the callback that gets notified when the frame has been sent
|
||||
*/
|
||||
public void reset(ResetFrame frame, Callback callback);
|
||||
|
@ -348,7 +353,7 @@ public interface Stream
|
|||
* <p>Callback method invoked when a RST_STREAM frame has been received for this stream.</p>
|
||||
*
|
||||
* @param stream the stream
|
||||
* @param frame the RST_FRAME received
|
||||
* @param frame the RST_STREAM frame received
|
||||
* @param callback the callback to complete when the reset has been handled
|
||||
*/
|
||||
public default void onReset(Stream stream, ResetFrame frame, Callback callback)
|
||||
|
@ -415,18 +420,6 @@ public interface Stream
|
|||
return frame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void retain()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
|
@ -439,6 +432,18 @@ public interface Stream
|
|||
{
|
||||
super(new DataFrame(streamId, BufferUtil.EMPTY_BUFFER, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void retain()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean release()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@ import java.util.function.Supplier;
|
|||
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpGenerator;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
|
@ -53,10 +56,12 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
private final HttpChannel _httpChannel;
|
||||
private final HTTP2Stream _stream;
|
||||
private final long _nanoTime;
|
||||
private MetaData.Request _requestMetaData;
|
||||
private MetaData.Response _responseMetaData;
|
||||
private Content.Chunk _chunk;
|
||||
private MetaData.Response _metaData;
|
||||
private boolean committed;
|
||||
private boolean _demand;
|
||||
private boolean _expects100Continue;
|
||||
|
||||
public HttpStreamOverHTTP2(HTTP2ServerConnection connection, HttpChannel httpChannel, HTTP2Stream stream)
|
||||
{
|
||||
|
@ -82,9 +87,9 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
{
|
||||
try
|
||||
{
|
||||
MetaData.Request request = (MetaData.Request)frame.getMetaData();
|
||||
_requestMetaData = (MetaData.Request)frame.getMetaData();
|
||||
|
||||
Runnable handler = _httpChannel.onRequest(request);
|
||||
Runnable handler = _httpChannel.onRequest(_requestMetaData);
|
||||
|
||||
if (frame.isEndStream())
|
||||
{
|
||||
|
@ -94,16 +99,15 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
}
|
||||
}
|
||||
|
||||
HttpFields fields = request.getFields();
|
||||
HttpFields fields = _requestMetaData.getFields();
|
||||
|
||||
// TODO: handle 100 continue.
|
||||
// _expect100Continue = fields.contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
|
||||
_expects100Continue = fields.contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("HTTP2 request #{}/{}, {} {} {}{}{}",
|
||||
_stream.getId(), Integer.toHexString(_stream.getSession().hashCode()),
|
||||
request.getMethod(), request.getURI(), request.getHttpVersion(),
|
||||
_requestMetaData.getMethod(), _requestMetaData.getURI(), _requestMetaData.getHttpVersion(),
|
||||
System.lineSeparator(), fields);
|
||||
}
|
||||
|
||||
|
@ -147,6 +151,12 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
chunk = createChunk(data);
|
||||
data.release();
|
||||
|
||||
// Some content is read, but the 100 Continue interim
|
||||
// response has not been sent yet, then don't bother
|
||||
// sending it later, as the client already sent the content.
|
||||
if (_expects100Continue && chunk.hasRemaining())
|
||||
_expects100Continue = false;
|
||||
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
_chunk = chunk;
|
||||
|
@ -164,7 +174,6 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
// We may have a non-demanded chunk in case of trailers.
|
||||
if (_chunk != null)
|
||||
notify = true;
|
||||
// Make sure we only demand(1) to make this method is idempotent.
|
||||
else if (!_demand)
|
||||
demand = _demand = true;
|
||||
}
|
||||
|
@ -176,6 +185,11 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
}
|
||||
else if (demand)
|
||||
{
|
||||
if (_expects100Continue)
|
||||
{
|
||||
_expects100Continue = false;
|
||||
send(_requestMetaData, HttpGenerator.CONTINUE_100_INFO, false, null, Callback.NOOP);
|
||||
}
|
||||
_stream.demand();
|
||||
}
|
||||
}
|
||||
|
@ -220,9 +234,12 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
|
||||
private Content.Chunk createChunk(Stream.Data data)
|
||||
{
|
||||
// As we are passing the ByteBuffer to the Chunk we need to retain.
|
||||
data.retain();
|
||||
DataFrame frame = data.frame();
|
||||
if (frame.isEndStream() && frame.remaining() == 0)
|
||||
return Content.Chunk.EOF;
|
||||
|
||||
// We need to retain because we are passing the ByteBuffer to the Chunk.
|
||||
data.retain();
|
||||
return Content.Chunk.from(frame.getData(), frame.isEndStream(), data);
|
||||
}
|
||||
|
||||
|
@ -244,7 +261,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
|
||||
private void sendHeaders(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean last, Callback callback)
|
||||
{
|
||||
_metaData = response;
|
||||
_responseMetaData = response;
|
||||
|
||||
HeadersFrame headersFrame;
|
||||
DataFrame dataFrame = null;
|
||||
|
@ -256,12 +273,17 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
if (HttpStatus.isInterim(response.getStatus()))
|
||||
{
|
||||
// Must not commit interim responses.
|
||||
|
||||
if (hasContent)
|
||||
{
|
||||
callback.failed(new IllegalStateException("Interim response cannot have content"));
|
||||
return;
|
||||
}
|
||||
headersFrame = new HeadersFrame(streamId, _metaData, null, false);
|
||||
|
||||
if (_expects100Continue && response.getStatus() == HttpStatus.CONTINUE_100)
|
||||
_expects100Continue = false;
|
||||
|
||||
headersFrame = new HeadersFrame(streamId, response, null, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -272,7 +294,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
long contentLength = response.getContentLength();
|
||||
if (contentLength < 0)
|
||||
{
|
||||
_metaData = new MetaData.Response(
|
||||
_responseMetaData = new MetaData.Response(
|
||||
response.getHttpVersion(),
|
||||
response.getStatus(),
|
||||
response.getReason(),
|
||||
|
@ -290,7 +312,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
|
||||
if (hasContent)
|
||||
{
|
||||
headersFrame = new HeadersFrame(streamId, _metaData, null, false);
|
||||
headersFrame = new HeadersFrame(streamId, response, null, false);
|
||||
if (last)
|
||||
{
|
||||
HttpFields trailers = retrieveTrailers();
|
||||
|
@ -313,27 +335,27 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
{
|
||||
if (last)
|
||||
{
|
||||
if (isTunnel(request, _metaData))
|
||||
if (isTunnel(request, response))
|
||||
{
|
||||
headersFrame = new HeadersFrame(streamId, _metaData, null, false);
|
||||
headersFrame = new HeadersFrame(streamId, response, null, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpFields trailers = retrieveTrailers();
|
||||
if (trailers == null)
|
||||
{
|
||||
headersFrame = new HeadersFrame(streamId, _metaData, null, true);
|
||||
headersFrame = new HeadersFrame(streamId, response, null, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
headersFrame = new HeadersFrame(streamId, _metaData, null, false);
|
||||
headersFrame = new HeadersFrame(streamId, response, null, false);
|
||||
trailersFrame = new HeadersFrame(streamId, new MetaData(HttpVersion.HTTP_2, trailers), null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
headersFrame = new HeadersFrame(streamId, _metaData, null, false);
|
||||
headersFrame = new HeadersFrame(streamId, response, null, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -342,9 +364,10 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
{
|
||||
LOG.debug("HTTP2 Response #{}/{}:{}{} {}{}{}",
|
||||
_stream.getId(), Integer.toHexString(_stream.getSession().hashCode()),
|
||||
System.lineSeparator(), HttpVersion.HTTP_2, _metaData.getStatus(),
|
||||
System.lineSeparator(), _metaData.getFields());
|
||||
System.lineSeparator(), HttpVersion.HTTP_2, response.getStatus(),
|
||||
System.lineSeparator(), response.getFields());
|
||||
}
|
||||
|
||||
_stream.send(new HTTP2Stream.FrameList(headersFrame, dataFrame, trailersFrame), callback);
|
||||
}
|
||||
|
||||
|
@ -352,7 +375,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
{
|
||||
boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod());
|
||||
boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest;
|
||||
if (hasContent || (last && !isTunnel(request, _metaData)))
|
||||
if (hasContent || (last && !isTunnel(request, _responseMetaData)))
|
||||
{
|
||||
if (last)
|
||||
{
|
||||
|
@ -387,7 +410,7 @@ public class HttpStreamOverHTTP2 implements HttpStream, HTTP2Channel.Server
|
|||
|
||||
private HttpFields retrieveTrailers()
|
||||
{
|
||||
Supplier<HttpFields> supplier = _metaData.getTrailersSupplier();
|
||||
Supplier<HttpFields> supplier = _responseMetaData.getTrailersSupplier();
|
||||
if (supplier == null)
|
||||
return null;
|
||||
HttpFields trailers = supplier.get();
|
||||
|
|
|
@ -289,14 +289,13 @@ public abstract class FlowControlStrategyTest
|
|||
Stream stream = promise.get(5, TimeUnit.SECONDS);
|
||||
|
||||
// Send first chunk that exceeds the window.
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(size * 2), false), completable);
|
||||
CompletableFuture<Stream> completable = stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(size * 2), false));
|
||||
settingsLatch.await(5, TimeUnit.SECONDS);
|
||||
|
||||
completable.thenRun(() ->
|
||||
completable.thenAccept(s ->
|
||||
{
|
||||
// Send the second chunk of data, must not arrive since we're flow control stalled on the client.
|
||||
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(size * 2), true), Callback.NOOP);
|
||||
s.data(new DataFrame(s.getId(), ByteBuffer.allocate(size * 2), true));
|
||||
});
|
||||
|
||||
assertFalse(dataLatch.await(1, TimeUnit.SECONDS));
|
||||
|
@ -341,9 +340,8 @@ public abstract class FlowControlStrategyTest
|
|||
|
||||
Map<Integer, Integer> settings = new HashMap<>();
|
||||
settings.put(SettingsFrame.INITIAL_WINDOW_SIZE, windowSize);
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
session.settings(new SettingsFrame(settings, false), completable);
|
||||
completable.thenRun(settingsLatch::countDown);
|
||||
session.settings(new SettingsFrame(settings, false))
|
||||
.thenRun(settingsLatch::countDown);
|
||||
|
||||
assertTrue(settingsLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
|
@ -641,13 +639,8 @@ public abstract class FlowControlStrategyTest
|
|||
{
|
||||
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
|
||||
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
stream.headers(responseFrame, completable);
|
||||
completable.thenRun(() ->
|
||||
{
|
||||
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.wrap(data), true);
|
||||
stream.data(dataFrame, Callback.NOOP);
|
||||
});
|
||||
stream.headers(responseFrame)
|
||||
.thenAccept(s -> s.data(new DataFrame(s.getId(), ByteBuffer.wrap(data), true)));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
@ -691,8 +684,7 @@ public abstract class FlowControlStrategyTest
|
|||
{
|
||||
MetaData metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
|
||||
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), metaData, null, false);
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
stream.headers(responseFrame, completable);
|
||||
CompletableFuture<Stream> completable = stream.headers(responseFrame);
|
||||
stream.demand();
|
||||
return new Stream.Listener()
|
||||
{
|
||||
|
@ -700,12 +692,13 @@ public abstract class FlowControlStrategyTest
|
|||
public void onDataAvailable(Stream stream)
|
||||
{
|
||||
Stream.Data data = stream.readData();
|
||||
completable.thenRun(() -> stream.data(data.frame(), Callback.from(() ->
|
||||
{
|
||||
data.release();
|
||||
if (!data.frame().isEndStream())
|
||||
stream.demand();
|
||||
})));
|
||||
completable.thenAccept(s -> s.data(data.frame())
|
||||
.whenComplete((r, x) ->
|
||||
{
|
||||
data.release();
|
||||
if (!data.frame().isEndStream())
|
||||
stream.demand();
|
||||
}));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -730,9 +723,8 @@ public abstract class FlowControlStrategyTest
|
|||
ByteBuffer responseContent = ByteBuffer.wrap(responseData);
|
||||
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
|
||||
HeadersFrame requestFrame = new HeadersFrame(metaData, null, false);
|
||||
Promise.Completable<Stream> completable = new Promise.Completable<>();
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
session.newStream(requestFrame, completable, new Stream.Listener()
|
||||
session.newStream(requestFrame, new Stream.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onDataAvailable(Stream stream)
|
||||
|
@ -745,12 +737,11 @@ public abstract class FlowControlStrategyTest
|
|||
else
|
||||
stream.demand();
|
||||
}
|
||||
});
|
||||
completable.thenAccept(stream ->
|
||||
})
|
||||
.thenAccept(s ->
|
||||
{
|
||||
ByteBuffer requestContent = ByteBuffer.wrap(requestData);
|
||||
DataFrame dataFrame = new DataFrame(stream.getId(), requestContent, true);
|
||||
stream.data(dataFrame, Callback.NOOP);
|
||||
s.data(new DataFrame(s.getId(), requestContent, true));
|
||||
});
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
|
|
|
@ -222,8 +222,7 @@ public class HTTP2Test extends AbstractTest
|
|||
CountDownLatch latch = new CountDownLatch(1);
|
||||
MetaData.Request metaData = newRequest("POST", HttpFields.EMPTY);
|
||||
HeadersFrame frame = new HeadersFrame(metaData, null, false);
|
||||
Promise.Completable<Stream> streamCompletable = new Promise.Completable<>();
|
||||
session.newStream(frame, streamCompletable, new Stream.Listener()
|
||||
session.newStream(frame, new Stream.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onDataAvailable(Stream stream)
|
||||
|
@ -235,18 +234,9 @@ public class HTTP2Test extends AbstractTest
|
|||
else
|
||||
stream.demand();
|
||||
}
|
||||
});
|
||||
streamCompletable.thenCompose(stream ->
|
||||
{
|
||||
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(512), false);
|
||||
Callback.Completable dataCompletable = new Callback.Completable();
|
||||
stream.data(dataFrame, dataCompletable);
|
||||
return dataCompletable.thenApply(y -> stream);
|
||||
}).thenAccept(stream ->
|
||||
{
|
||||
DataFrame dataFrame = new DataFrame(stream.getId(), ByteBuffer.allocate(1024), true);
|
||||
stream.data(dataFrame, Callback.NOOP);
|
||||
});
|
||||
})
|
||||
.thenCompose(s -> s.data(new DataFrame(s.getId(), ByteBuffer.allocate(512), false)))
|
||||
.thenAccept(s -> s.data(new DataFrame(s.getId(), ByteBuffer.allocate(1024), true)));
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
@ -546,9 +536,8 @@ public class HTTP2Test extends AbstractTest
|
|||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
|
||||
stream.headers(new HeadersFrame(stream.getId(), response, null, false), completable);
|
||||
CompletableFuture<Stream> completable = stream.headers(new HeadersFrame(stream.getId(), response, null, false));
|
||||
stream.demand();
|
||||
return new Stream.Listener()
|
||||
{
|
||||
|
@ -559,11 +548,8 @@ public class HTTP2Test extends AbstractTest
|
|||
data.release();
|
||||
if (data.frame().isEndStream())
|
||||
{
|
||||
completable.thenRun(() ->
|
||||
{
|
||||
DataFrame endFrame = new DataFrame(stream.getId(), BufferUtil.EMPTY_BUFFER, true);
|
||||
stream.data(endFrame, Callback.NOOP);
|
||||
});
|
||||
completable.thenAccept(s ->
|
||||
s.data(new DataFrame(s.getId(), BufferUtil.EMPTY_BUFFER, true)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -578,9 +564,8 @@ public class HTTP2Test extends AbstractTest
|
|||
|
||||
MetaData.Request metaData = newRequest("GET", HttpFields.EMPTY);
|
||||
HeadersFrame frame = new HeadersFrame(metaData, null, false);
|
||||
Promise.Completable<Stream> completable = new Promise.Completable<>();
|
||||
CountDownLatch completeLatch = new CountDownLatch(2);
|
||||
session.newStream(frame, completable, new Stream.Listener()
|
||||
Stream stream = session.newStream(frame, new Stream.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onDataAvailable(Stream stream)
|
||||
|
@ -592,8 +577,7 @@ public class HTTP2Test extends AbstractTest
|
|||
else
|
||||
stream.demand();
|
||||
}
|
||||
});
|
||||
Stream stream = completable.get(5, TimeUnit.SECONDS);
|
||||
}).get(5, TimeUnit.SECONDS);
|
||||
|
||||
long sleep = 1000;
|
||||
DataFrame data1 = new DataFrame(stream.getId(), ByteBuffer.allocate(1024), false)
|
||||
|
|
|
@ -222,15 +222,14 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
|||
@Override
|
||||
public void process(Request request, org.eclipse.jetty.server.Response response, Callback callback)
|
||||
{
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
response.write(false, ByteBuffer.allocate(1), completable);
|
||||
completable.whenComplete((r, x) ->
|
||||
{
|
||||
if (x != null)
|
||||
callback.failed(x);
|
||||
else
|
||||
response.write(true, ByteBuffer.allocate(2), callback);
|
||||
});
|
||||
Callback.Completable.with(c -> response.write(false, ByteBuffer.allocate(1), c))
|
||||
.whenComplete((r, x) ->
|
||||
{
|
||||
if (x != null)
|
||||
callback.failed(x);
|
||||
else
|
||||
response.write(true, ByteBuffer.allocate(2), callback);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -638,12 +637,10 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
|||
@Override
|
||||
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||
{
|
||||
int streamId = stream.getId();
|
||||
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.NO_CONTENT_204, HttpFields.EMPTY);
|
||||
HeadersFrame responseFrame = new HeadersFrame(streamId, response, null, false);
|
||||
Callback.Completable callback = new Callback.Completable();
|
||||
stream.headers(responseFrame, callback);
|
||||
callback.thenRun(() -> stream.data(new DataFrame(streamId, ByteBuffer.wrap(bytes), true), Callback.NOOP));
|
||||
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, false);
|
||||
stream.headers(responseFrame)
|
||||
.thenAccept(s -> s.data(new DataFrame(s.getId(), ByteBuffer.wrap(bytes), true)));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
@ -671,12 +668,10 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
|||
HttpFields fields = HttpFields.build()
|
||||
.put(":method", "get");
|
||||
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, fields, 0);
|
||||
int streamId = stream.getId();
|
||||
HeadersFrame responseFrame = new HeadersFrame(streamId, response, null, false);
|
||||
Callback.Completable callback = new Callback.Completable();
|
||||
stream.headers(responseFrame, callback);
|
||||
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, false);
|
||||
byte[] bytes = "hello".getBytes(StandardCharsets.US_ASCII);
|
||||
callback.thenRun(() -> stream.data(new DataFrame(streamId, ByteBuffer.wrap(bytes), true), Callback.NOOP));
|
||||
stream.headers(responseFrame)
|
||||
.thenAccept(s -> s.data(new DataFrame(s.getId(), ByteBuffer.wrap(bytes), true)));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
@ -705,9 +700,8 @@ public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
|||
int streamId = stream.getId();
|
||||
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
|
||||
HeadersFrame responseFrame = new HeadersFrame(streamId, response, null, false);
|
||||
Callback.Completable callback = new Callback.Completable();
|
||||
stream.headers(responseFrame, callback);
|
||||
callback.thenRun(() -> stream.data(new DataFrame(streamId, ByteBuffer.wrap(new byte[bytes]), true), Callback.NOOP));
|
||||
stream.headers(responseFrame)
|
||||
.thenAccept(s -> s.data(new DataFrame(s.getId(), ByteBuffer.wrap(new byte[bytes]), true)));
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -559,20 +559,17 @@ public class IdleTimeoutTest extends AbstractTest
|
|||
session.newStream(requestFrame, promise, null);
|
||||
Stream stream = promise.get(5, TimeUnit.SECONDS);
|
||||
|
||||
Callback.Completable completable1 = new Callback.Completable();
|
||||
sleep(idleTimeout / 2);
|
||||
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), false), completable1);
|
||||
completable1.thenCompose(nil ->
|
||||
{
|
||||
Callback.Completable completable2 = new Callback.Completable();
|
||||
sleep(idleTimeout / 2);
|
||||
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), false), completable2);
|
||||
return completable2;
|
||||
}).thenRun(() ->
|
||||
{
|
||||
sleep(idleTimeout / 2);
|
||||
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), true), Callback.NOOP);
|
||||
});
|
||||
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(1), false))
|
||||
.thenCompose(s ->
|
||||
{
|
||||
sleep(idleTimeout / 2);
|
||||
return s.data(new DataFrame(s.getId(), ByteBuffer.allocate(1), false));
|
||||
}).thenAccept(s ->
|
||||
{
|
||||
sleep(idleTimeout / 2);
|
||||
s.data(new DataFrame(s.getId(), ByteBuffer.allocate(1), true));
|
||||
});
|
||||
|
||||
assertFalse(resetLatch.await(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ import org.eclipse.jetty.http2.internal.ErrorCode;
|
|||
import org.eclipse.jetty.http2.internal.HTTP2Session;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.FuturePromise;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
|
@ -74,12 +73,7 @@ public class MaxPushedStreamsTest extends AbstractTest
|
|||
// Push maxPushed resources...
|
||||
IntStream.range(0, maxPushed)
|
||||
.mapToObj(i -> new PushPromiseFrame(stream.getId(), 0, newRequest("GET", "/push_" + i, HttpFields.EMPTY)))
|
||||
.map(pushFrame ->
|
||||
{
|
||||
Promise.Completable<Stream> promise = new Promise.Completable<>();
|
||||
stream.push(pushFrame, promise, null);
|
||||
return promise;
|
||||
})
|
||||
.map(pushFrame -> stream.push(pushFrame, null))
|
||||
// ... wait for the pushed streams...
|
||||
.reduce(result, (cfList, cfStream) -> cfList.thenCombine(cfStream, add),
|
||||
(cfList1, cfList2) -> cfList1.thenCombine(cfList2, addAll))
|
||||
|
@ -87,8 +81,7 @@ public class MaxPushedStreamsTest extends AbstractTest
|
|||
.thenApply(streams ->
|
||||
{
|
||||
PushPromiseFrame extraPushFrame = new PushPromiseFrame(stream.getId(), 0, newRequest("GET", "/push_extra", HttpFields.EMPTY));
|
||||
FuturePromise<Stream> extraPromise = new FuturePromise<>();
|
||||
stream.push(extraPushFrame, extraPromise, new Stream.Listener()
|
||||
stream.push(extraPushFrame, new Stream.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onReset(Stream stream, ResetFrame frame, Callback callback)
|
||||
|
@ -104,13 +97,13 @@ public class MaxPushedStreamsTest extends AbstractTest
|
|||
.thenAccept(streams -> streams.forEach(pushedStream ->
|
||||
{
|
||||
DataFrame data = new DataFrame(pushedStream.getId(), BufferUtil.EMPTY_BUFFER, true);
|
||||
pushedStream.data(data, Callback.NOOP);
|
||||
pushedStream.data(data);
|
||||
}))
|
||||
// ... then send the response.
|
||||
.thenRun(() ->
|
||||
{
|
||||
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
|
||||
stream.headers(new HeadersFrame(stream.getId(), response, null, true), Callback.NOOP);
|
||||
stream.headers(new HeadersFrame(stream.getId(), response, null, true));
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
@ -356,9 +357,8 @@ public class PrefaceTest extends AbstractTest
|
|||
http2Client.start();
|
||||
|
||||
CountDownLatch failureLatch = new CountDownLatch(1);
|
||||
Promise.Completable<Session> promise = new Promise.Completable<>();
|
||||
InetSocketAddress address = new InetSocketAddress("localhost", server.getLocalPort());
|
||||
http2Client.connect(address, new Session.Listener()
|
||||
CompletableFuture<Session> promise = http2Client.connect(address, new Session.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onFailure(Session session, Throwable failure, Callback callback)
|
||||
|
@ -366,7 +366,7 @@ public class PrefaceTest extends AbstractTest
|
|||
failureLatch.countDown();
|
||||
callback.succeeded();
|
||||
}
|
||||
}, promise);
|
||||
});
|
||||
|
||||
try (Socket socket = server.accept())
|
||||
{
|
||||
|
|
|
@ -167,27 +167,24 @@ public class RawHTTP2ProxyTest
|
|||
LOGGER.debug("SERVER2 received {}", data);
|
||||
data.release();
|
||||
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, HttpFields.EMPTY);
|
||||
Callback.Completable completable1 = new Callback.Completable();
|
||||
HeadersFrame reply = new HeadersFrame(stream.getId(), response, null, false);
|
||||
if (LOGGER.isDebugEnabled())
|
||||
LOGGER.debug("SERVER2 sending {}", reply);
|
||||
stream.headers(reply, completable1);
|
||||
completable1.thenCompose(ignored ->
|
||||
{
|
||||
Callback.Completable completable2 = new Callback.Completable();
|
||||
DataFrame dataFrame = new DataFrame(stream.getId(), buffer1.slice(), false);
|
||||
if (LOGGER.isDebugEnabled())
|
||||
LOGGER.debug("SERVER2 sending {}", dataFrame);
|
||||
stream.data(dataFrame, completable2);
|
||||
return completable2;
|
||||
}).thenRun(() ->
|
||||
{
|
||||
MetaData trailer = new MetaData(HttpVersion.HTTP_2, HttpFields.EMPTY);
|
||||
HeadersFrame end = new HeadersFrame(stream.getId(), trailer, null, true);
|
||||
if (LOGGER.isDebugEnabled())
|
||||
LOGGER.debug("SERVER2 sending {}", end);
|
||||
stream.headers(end, Callback.NOOP);
|
||||
});
|
||||
stream.headers(reply)
|
||||
.thenCompose(s ->
|
||||
{
|
||||
DataFrame dataFrame = new DataFrame(s.getId(), buffer1.slice(), false);
|
||||
if (LOGGER.isDebugEnabled())
|
||||
LOGGER.debug("SERVER2 sending {}", dataFrame);
|
||||
return s.data(dataFrame);
|
||||
}).thenAccept(s ->
|
||||
{
|
||||
MetaData trailer = new MetaData(HttpVersion.HTTP_2, HttpFields.EMPTY);
|
||||
HeadersFrame end = new HeadersFrame(s.getId(), trailer, null, true);
|
||||
if (LOGGER.isDebugEnabled())
|
||||
LOGGER.debug("SERVER2 sending {}", end);
|
||||
s.headers(end);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Exchanger;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -168,8 +169,7 @@ public class StreamResetTest extends AbstractTest
|
|||
{
|
||||
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
|
||||
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, false);
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
stream.headers(responseFrame, completable);
|
||||
CompletableFuture<Stream> completable = stream.headers(responseFrame);
|
||||
stream.demand();
|
||||
return new Stream.Listener()
|
||||
{
|
||||
|
@ -178,15 +178,8 @@ public class StreamResetTest extends AbstractTest
|
|||
{
|
||||
Stream.Data data = stream.readData();
|
||||
data.release();
|
||||
completable.thenRun(() ->
|
||||
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(16), true), new Callback()
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
serverDataLatch.countDown();
|
||||
}
|
||||
}));
|
||||
completable.thenCompose(s -> s.data(new DataFrame(s.getId(), ByteBuffer.allocate(16), true)))
|
||||
.thenRun(serverDataLatch::countDown);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -329,9 +322,8 @@ public class StreamResetTest extends AbstractTest
|
|||
try
|
||||
{
|
||||
commitLatch.await(5, TimeUnit.SECONDS);
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), completable);
|
||||
completable.thenRun(resetLatch::countDown);
|
||||
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code))
|
||||
.thenRun(resetLatch::countDown);
|
||||
}
|
||||
catch (InterruptedException x)
|
||||
{
|
||||
|
@ -416,9 +408,8 @@ public class StreamResetTest extends AbstractTest
|
|||
try
|
||||
{
|
||||
commitLatch.await(5, TimeUnit.SECONDS);
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), completable);
|
||||
completable.thenRun(resetLatch::countDown);
|
||||
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code))
|
||||
.thenRun(resetLatch::countDown);
|
||||
}
|
||||
catch (InterruptedException x)
|
||||
{
|
||||
|
@ -1053,8 +1044,7 @@ public class StreamResetTest extends AbstractTest
|
|||
{
|
||||
MetaData.Response response = new MetaData.Response(HttpVersion.HTTP_2, 200, HttpFields.EMPTY);
|
||||
HeadersFrame responseFrame = new HeadersFrame(stream.getId(), response, null, false);
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
stream.headers(responseFrame, completable);
|
||||
stream.headers(responseFrame, Callback.NOOP);
|
||||
return null;
|
||||
}
|
||||
}, http2Factory);
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.io.IOException;
|
|||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
@ -174,19 +175,18 @@ public class TrailersTest extends AbstractTest
|
|||
Stream stream = streamPromise.get(5, TimeUnit.SECONDS);
|
||||
|
||||
// Send some data.
|
||||
Callback.Completable callback = new Callback.Completable();
|
||||
stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(16), false), callback);
|
||||
CompletableFuture<Stream> completable = stream.data(new DataFrame(stream.getId(), ByteBuffer.allocate(16), false));
|
||||
|
||||
assertTrue(trailerLatch.await(555, TimeUnit.SECONDS));
|
||||
assertTrue(trailerLatch.await(5, TimeUnit.SECONDS));
|
||||
|
||||
// Send the trailers.
|
||||
callback.thenRun(() ->
|
||||
completable.thenRun(() ->
|
||||
{
|
||||
HttpFields.Mutable trailerFields = HttpFields.build();
|
||||
trailerFields.put("X-Trailer", "true");
|
||||
MetaData trailers = new MetaData(HttpVersion.HTTP_2, trailerFields);
|
||||
HeadersFrame trailerFrame = new HeadersFrame(stream.getId(), trailers, null, true);
|
||||
stream.headers(trailerFrame, Callback.NOOP);
|
||||
stream.headers(trailerFrame);
|
||||
});
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
|
@ -331,18 +331,21 @@ public class TrailersTest extends AbstractTest
|
|||
session.newStream(requestFrame, promise, null);
|
||||
Stream stream = promise.get(5, TimeUnit.SECONDS);
|
||||
ByteBuffer data = ByteBuffer.wrap(StringUtil.getUtf8Bytes("hello"));
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
stream.data(new DataFrame(stream.getId(), data, false), completable);
|
||||
CountDownLatch failureLatch = new CountDownLatch(1);
|
||||
completable.thenRun(() ->
|
||||
{
|
||||
// Invalid trailer: cannot contain pseudo headers.
|
||||
HttpFields.Mutable trailerFields = HttpFields.build();
|
||||
trailerFields.put(HttpHeader.C_METHOD, "GET");
|
||||
MetaData trailer = new MetaData(HttpVersion.HTTP_2, trailerFields);
|
||||
HeadersFrame trailerFrame = new HeadersFrame(stream.getId(), trailer, null, true);
|
||||
stream.headers(trailerFrame, Callback.from(Callback.NOOP::succeeded, x -> failureLatch.countDown()));
|
||||
});
|
||||
stream.data(new DataFrame(stream.getId(), data, false))
|
||||
.thenAccept(s ->
|
||||
{
|
||||
// Invalid trailer: cannot contain pseudo headers.
|
||||
HttpFields.Mutable trailerFields = HttpFields.build();
|
||||
trailerFields.put(HttpHeader.C_METHOD, "GET");
|
||||
MetaData trailer = new MetaData(HttpVersion.HTTP_2, trailerFields);
|
||||
s.headers(new HeadersFrame(s.getId(), trailer, null, true))
|
||||
.exceptionally(x ->
|
||||
{
|
||||
failureLatch.countDown();
|
||||
return s;
|
||||
});
|
||||
});
|
||||
assertTrue(failureLatch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
|
@ -383,19 +386,17 @@ public class TrailersTest extends AbstractTest
|
|||
});
|
||||
Stream stream = promise.get(5, TimeUnit.SECONDS);
|
||||
ByteBuffer data = ByteBuffer.wrap(StringUtil.getUtf8Bytes("hello"));
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
stream.data(new DataFrame(stream.getId(), data, false), completable);
|
||||
completable.thenRun(() ->
|
||||
{
|
||||
// Disable checks for invalid headers.
|
||||
((HTTP2Session)session).getGenerator().setValidateHpackEncoding(false);
|
||||
// Invalid trailer: cannot contain pseudo headers.
|
||||
HttpFields.Mutable trailerFields = HttpFields.build();
|
||||
trailerFields.put(HttpHeader.C_METHOD, "GET");
|
||||
MetaData trailer = new MetaData(HttpVersion.HTTP_2, trailerFields);
|
||||
HeadersFrame trailerFrame = new HeadersFrame(stream.getId(), trailer, null, true);
|
||||
stream.headers(trailerFrame, Callback.NOOP);
|
||||
});
|
||||
stream.data(new DataFrame(stream.getId(), data, false))
|
||||
.thenAccept(s ->
|
||||
{
|
||||
// Disable checks for invalid headers.
|
||||
((HTTP2Session)session).getGenerator().setValidateHpackEncoding(false);
|
||||
// Invalid trailer: cannot contain pseudo headers.
|
||||
HttpFields.Mutable trailerFields = HttpFields.build();
|
||||
trailerFields.put(HttpHeader.C_METHOD, "GET");
|
||||
MetaData trailer = new MetaData(HttpVersion.HTTP_2, trailerFields);
|
||||
s.headers(new HeadersFrame(s.getId(), trailer, null, true));
|
||||
});
|
||||
|
||||
assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
|
||||
assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
|
||||
|
|
|
@ -26,7 +26,7 @@ import org.eclipse.jetty.quic.common.QuicStreamEndPoint;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class HTTP3StreamClient extends HTTP3Stream implements Stream.Client
|
||||
public class HTTP3StreamClient extends HTTP3Stream implements Stream.Client
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(HTTP3StreamClient.class);
|
||||
|
||||
|
@ -50,13 +50,14 @@ public class HTTP3StreamClient extends HTTP3Stream implements Stream.Client
|
|||
public void onResponse(HeadersFrame frame)
|
||||
{
|
||||
MetaData.Response response = (MetaData.Response)frame.getMetaData();
|
||||
boolean valid;
|
||||
if (response.getStatus() == HttpStatus.CONTINUE_100)
|
||||
valid = validateAndUpdate(EnumSet.of(FrameState.INITIAL), FrameState.INFORMATIONAL);
|
||||
else if (response.getStatus() == HttpStatus.EARLY_HINT_103)
|
||||
valid = validateAndUpdate(EnumSet.of(FrameState.INITIAL, FrameState.HEADER, FrameState.INFORMATIONAL), FrameState.INFORMATIONAL);
|
||||
else
|
||||
valid = validateAndUpdate(EnumSet.of(FrameState.INITIAL, FrameState.INFORMATIONAL), FrameState.HEADER);
|
||||
int status = response.getStatus();
|
||||
boolean valid = switch (status)
|
||||
{
|
||||
case HttpStatus.CONTINUE_100 -> validateAndUpdate(EnumSet.of(FrameState.INITIAL), FrameState.INFORMATIONAL);
|
||||
case HttpStatus.PROCESSING_102,
|
||||
HttpStatus.EARLY_HINT_103 -> validateAndUpdate(EnumSet.of(FrameState.INITIAL, FrameState.INFORMATIONAL), FrameState.INFORMATIONAL);
|
||||
default -> validateAndUpdate(EnumSet.of(FrameState.INITIAL, FrameState.INFORMATIONAL), FrameState.HEADER);
|
||||
};
|
||||
if (valid)
|
||||
{
|
||||
onHeaders(frame);
|
||||
|
|
|
@ -394,9 +394,8 @@ public abstract class HTTP3Stream implements Stream, CyclicTimeouts.Expirable, A
|
|||
public Promise.Completable<Stream> writeFrame(Frame frame)
|
||||
{
|
||||
notIdle();
|
||||
Promise.Completable<Stream> completable = new Promise.Completable<>();
|
||||
session.writeMessageFrame(endPoint.getStreamId(), frame, Callback.from(Invocable.InvocationType.NON_BLOCKING, () -> completable.succeeded(this), completable::failed));
|
||||
return completable;
|
||||
return Promise.Completable.with(p ->
|
||||
session.writeMessageFrame(endPoint.getStreamId(), frame, Callback.from(Invocable.InvocationType.NON_BLOCKING, () -> p.succeeded(this), p::failed)));
|
||||
}
|
||||
|
||||
public boolean isClosed()
|
||||
|
@ -463,9 +462,42 @@ public abstract class HTTP3Stream implements Stream, CyclicTimeouts.Expirable, A
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Defines the state of the stream for received frames,</p>
|
||||
* <p>allowing to verify that a frame sequence is valid for
|
||||
* the HTTP protocol.</p>
|
||||
* <p>For example, for a stream in the {@link #INITIAL} state,
|
||||
* receiving a {@link DataFrame} would move the stream to the
|
||||
* {@link #DATA} state which would be invalid, since for the
|
||||
* HTTP protocol a {@link HeadersFrame} is expected before
|
||||
* any {@link DataFrame}.</p>
|
||||
*/
|
||||
protected enum FrameState
|
||||
{
|
||||
INITIAL, INFORMATIONAL, HEADER, DATA, TRAILER, FAILED
|
||||
/**
|
||||
* The initial state of the stream, before it receives any frame.
|
||||
*/
|
||||
INITIAL,
|
||||
/**
|
||||
* The stream has received an HTTP informational response.
|
||||
*/
|
||||
INFORMATIONAL,
|
||||
/**
|
||||
* The stream has received an HTTP final response.
|
||||
*/
|
||||
HEADER,
|
||||
/**
|
||||
* The stream has received HTTP content.
|
||||
*/
|
||||
DATA,
|
||||
/**
|
||||
* The stream has received an HTTP trailer.
|
||||
*/
|
||||
TRAILER,
|
||||
/**
|
||||
* The stream has encountered a failure.
|
||||
*/
|
||||
FAILED
|
||||
}
|
||||
|
||||
private enum CloseState
|
||||
|
|
|
@ -21,6 +21,9 @@ import java.util.function.Supplier;
|
|||
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpGenerator;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
|
@ -50,9 +53,11 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
private final ServerHTTP3StreamConnection connection;
|
||||
private final HttpChannel httpChannel;
|
||||
private final HTTP3StreamServer stream;
|
||||
private MetaData.Request requestMetaData;
|
||||
private MetaData.Response responseMetaData;
|
||||
private Content.Chunk chunk;
|
||||
private MetaData.Response metaData;
|
||||
private boolean committed;
|
||||
private boolean expects100Continue;
|
||||
|
||||
public HttpStreamOverHTTP3(ServerHTTP3StreamConnection connection, HttpChannel httpChannel, HTTP3StreamServer stream)
|
||||
{
|
||||
|
@ -77,9 +82,9 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
{
|
||||
try
|
||||
{
|
||||
MetaData.Request request = (MetaData.Request)frame.getMetaData();
|
||||
requestMetaData = (MetaData.Request)frame.getMetaData();
|
||||
|
||||
Runnable handler = httpChannel.onRequest(request);
|
||||
Runnable handler = httpChannel.onRequest(requestMetaData);
|
||||
|
||||
if (frame.isLast())
|
||||
{
|
||||
|
@ -89,12 +94,11 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
}
|
||||
}
|
||||
|
||||
HttpFields fields = request.getFields();
|
||||
HttpFields fields = requestMetaData.getFields();
|
||||
|
||||
// TODO: handle 100 continue.
|
||||
// expect100Continue = fields.contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
|
||||
expects100Continue = fields.contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
|
||||
|
||||
boolean connect = request instanceof MetaData.ConnectRequest;
|
||||
boolean connect = requestMetaData instanceof MetaData.ConnectRequest;
|
||||
|
||||
if (!connect)
|
||||
connection.setApplicationMode(true);
|
||||
|
@ -103,7 +107,7 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
{
|
||||
LOG.debug("HTTP3 request #{}/{}, {} {} {}{}{}",
|
||||
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
|
||||
request.getMethod(), request.getURI(), request.getHttpVersion(),
|
||||
requestMetaData.getMethod(), requestMetaData.getURI(), requestMetaData.getHttpVersion(),
|
||||
System.lineSeparator(), fields);
|
||||
}
|
||||
|
||||
|
@ -147,6 +151,12 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
chunk = createChunk(data);
|
||||
data.release();
|
||||
|
||||
// Some content is read, but the 100 Continue interim
|
||||
// response has not been sent yet, then don't bother
|
||||
// sending it later, as the client already sent the content.
|
||||
if (expects100Continue && chunk.hasRemaining())
|
||||
expects100Continue = false;
|
||||
|
||||
try (AutoLock ignored = lock.lock())
|
||||
{
|
||||
this.chunk = chunk;
|
||||
|
@ -171,6 +181,11 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
}
|
||||
else
|
||||
{
|
||||
if (expects100Continue)
|
||||
{
|
||||
expects100Continue = false;
|
||||
send(requestMetaData, HttpGenerator.CONTINUE_100_INFO, false, null, Callback.NOOP);
|
||||
}
|
||||
stream.demand();
|
||||
}
|
||||
}
|
||||
|
@ -245,7 +260,7 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
|
||||
private void sendHeaders(MetaData.Request request, MetaData.Response response, ByteBuffer content, boolean lastContent, Callback callback)
|
||||
{
|
||||
metaData = response;
|
||||
this.responseMetaData = response;
|
||||
|
||||
HeadersFrame headersFrame;
|
||||
DataFrame dataFrame = null;
|
||||
|
@ -256,12 +271,17 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
if (HttpStatus.isInterim(response.getStatus()))
|
||||
{
|
||||
// Must not commit interim responses.
|
||||
|
||||
if (hasContent)
|
||||
{
|
||||
callback.failed(new IllegalStateException("Interim response cannot have content"));
|
||||
return;
|
||||
}
|
||||
headersFrame = new HeadersFrame(metaData, false);
|
||||
|
||||
if (expects100Continue && response.getStatus() == HttpStatus.CONTINUE_100)
|
||||
expects100Continue = false;
|
||||
|
||||
headersFrame = new HeadersFrame(response, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -272,7 +292,7 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
long contentLength = response.getContentLength();
|
||||
if (contentLength < 0)
|
||||
{
|
||||
metaData = new MetaData.Response(
|
||||
this.responseMetaData = new MetaData.Response(
|
||||
response.getHttpVersion(),
|
||||
response.getStatus(),
|
||||
response.getReason(),
|
||||
|
@ -290,7 +310,7 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
|
||||
if (hasContent)
|
||||
{
|
||||
headersFrame = new HeadersFrame(metaData, false);
|
||||
headersFrame = new HeadersFrame(response, false);
|
||||
if (lastContent)
|
||||
{
|
||||
HttpFields trailers = retrieveTrailers();
|
||||
|
@ -313,27 +333,27 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
{
|
||||
if (lastContent)
|
||||
{
|
||||
if (isTunnel(request, metaData))
|
||||
if (isTunnel(request, response))
|
||||
{
|
||||
headersFrame = new HeadersFrame(metaData, false);
|
||||
headersFrame = new HeadersFrame(response, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpFields trailers = retrieveTrailers();
|
||||
if (trailers == null)
|
||||
{
|
||||
headersFrame = new HeadersFrame(metaData, true);
|
||||
headersFrame = new HeadersFrame(response, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
headersFrame = new HeadersFrame(metaData, false);
|
||||
headersFrame = new HeadersFrame(response, false);
|
||||
trailersFrame = new HeadersFrame(new MetaData(HttpVersion.HTTP_3, trailers), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
headersFrame = new HeadersFrame(metaData, false);
|
||||
headersFrame = new HeadersFrame(response, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -342,8 +362,8 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
{
|
||||
LOG.debug("HTTP3 Response #{}/{}:{}{} {}{}{}",
|
||||
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
|
||||
System.lineSeparator(), HttpVersion.HTTP_3, metaData.getStatus(),
|
||||
System.lineSeparator(), metaData.getFields());
|
||||
System.lineSeparator(), HttpVersion.HTTP_3, response.getStatus(),
|
||||
System.lineSeparator(), response.getFields());
|
||||
}
|
||||
|
||||
CompletableFuture<Stream> cf = stream.respond(headersFrame);
|
||||
|
@ -363,7 +383,7 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
{
|
||||
boolean isHeadRequest = HttpMethod.HEAD.is(request.getMethod());
|
||||
boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest;
|
||||
if (hasContent || (lastContent && !isTunnel(request, metaData)))
|
||||
if (hasContent || (lastContent && !isTunnel(request, responseMetaData)))
|
||||
{
|
||||
if (lastContent)
|
||||
{
|
||||
|
@ -398,7 +418,7 @@ public class HttpStreamOverHTTP3 implements HttpStream
|
|||
|
||||
private HttpFields retrieveTrailers()
|
||||
{
|
||||
Supplier<HttpFields> supplier = metaData.getTrailersSupplier();
|
||||
Supplier<HttpFields> supplier = responseMetaData.getTrailersSupplier();
|
||||
if (supplier == null)
|
||||
return null;
|
||||
HttpFields trailers = supplier.get();
|
||||
|
|
|
@ -26,9 +26,14 @@ import java.util.function.Function;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jetty.client.ContinueProtocolHandler;
|
||||
import org.eclipse.jetty.client.EarlyHintsProtocolHandler;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.ProcessingProtocolHandler;
|
||||
import org.eclipse.jetty.client.ProtocolHandlers;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
||||
import org.eclipse.jetty.http.HttpField;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
@ -67,6 +72,8 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ProxyHandler.class);
|
||||
private static final String CLIENT_TO_PROXY_REQUEST_ATTRIBUTE = ProxyHandler.class.getName() + ".clientToProxyRequest";
|
||||
private static final String PROXY_TO_CLIENT_RESPONSE_ATTRIBUTE = ProxyHandler.class.getName() + ".proxyToClientResponse";
|
||||
private static final String PROXY_TO_SERVER_CONTINUE_ATTRIBUTE = ProxyHandler.class.getName() + ".proxyToServerContinue";
|
||||
private static final EnumSet<HttpHeader> HOP_HEADERS = EnumSet.of(
|
||||
HttpHeader.CONNECTION,
|
||||
HttpHeader.KEEP_ALIVE,
|
||||
|
@ -162,7 +169,11 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
configureHttpClient(httpClient);
|
||||
LifeCycle.start(httpClient);
|
||||
httpClient.getContentDecoderFactories().clear();
|
||||
httpClient.getProtocolHandlers().clear();
|
||||
ProtocolHandlers protocolHandlers = httpClient.getProtocolHandlers();
|
||||
protocolHandlers.clear();
|
||||
protocolHandlers.put(new ProxyContinueProtocolHandler());
|
||||
protocolHandlers.put(new ProxyProcessingProtocolHandler());
|
||||
protocolHandlers.put(new ProxyEarlyHintsProtocolHandler());
|
||||
return httpClient;
|
||||
}
|
||||
|
||||
|
@ -204,7 +215,7 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("""
|
||||
{} received C2P request
|
||||
{} C2P received request
|
||||
{}
|
||||
{}""",
|
||||
requestId(clientToProxyRequest),
|
||||
|
@ -216,6 +227,8 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
LOG.debug("{} URI rewrite {} => {}", requestId(clientToProxyRequest), clientToProxyRequest.getHttpURI(), rewritten);
|
||||
|
||||
var proxyToServerRequest = newProxyToServerRequest(clientToProxyRequest, rewritten);
|
||||
proxyToServerRequest.attribute(CLIENT_TO_PROXY_REQUEST_ATTRIBUTE, clientToProxyRequest)
|
||||
.attribute(PROXY_TO_CLIENT_RESPONSE_ATTRIBUTE, proxyToClientResponse);
|
||||
|
||||
copyRequestHeaders(clientToProxyRequest, proxyToServerRequest);
|
||||
|
||||
|
@ -225,7 +238,18 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
{
|
||||
if (expects100Continue(clientToProxyRequest))
|
||||
{
|
||||
// TODO
|
||||
// Delay reading the content until the server sends 100 Continue.
|
||||
AsyncRequestContent delayedProxyToServerRequestContent = new AsyncRequestContent();
|
||||
proxyToServerRequest.body(delayedProxyToServerRequestContent);
|
||||
Runnable action = () ->
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} P2S continuing request", requestId(clientToProxyRequest));
|
||||
var proxyToServerRequestContent = newProxyToServerRequestContent(clientToProxyRequest, proxyToClientResponse, proxyToServerRequest);
|
||||
Content.copy(proxyToServerRequestContent, delayedProxyToServerRequestContent,
|
||||
Callback.from(delayedProxyToServerRequestContent::close, x -> delayedProxyToServerRequestContent.write(Content.Chunk.from(x), Callback.NOOP)));
|
||||
};
|
||||
proxyToServerRequest.attribute(PROXY_TO_SERVER_CONTINUE_ATTRIBUTE, action);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -248,8 +272,7 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
protected org.eclipse.jetty.client.api.Request newProxyToServerRequest(Request clientToProxyRequest, HttpURI newHttpURI)
|
||||
{
|
||||
return getHttpClient().newRequest(newHttpURI.toURI())
|
||||
.method(clientToProxyRequest.getMethod())
|
||||
.attribute(CLIENT_TO_PROXY_REQUEST_ATTRIBUTE, clientToProxyRequest);
|
||||
.method(clientToProxyRequest.getMethod());
|
||||
}
|
||||
|
||||
protected void copyRequestHeaders(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest)
|
||||
|
@ -388,7 +411,7 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("""
|
||||
{} sending P2S request
|
||||
{} P2S sending request
|
||||
{}
|
||||
{}""",
|
||||
requestId(clientToProxyRequest),
|
||||
|
@ -417,6 +440,28 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
Response.writeError(clientToProxyRequest, proxyToClientResponse, callback, status);
|
||||
}
|
||||
|
||||
protected void onServerToProxyResponse100Continue(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} P2C 100 continue response", requestId(clientToProxyRequest));
|
||||
Runnable action = (Runnable)proxyToServerRequest.getAttributes().get(PROXY_TO_SERVER_CONTINUE_ATTRIBUTE);
|
||||
action.run();
|
||||
}
|
||||
|
||||
protected void onServerToProxyResponse102Processing(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, HttpFields serverToProxyResponseHeaders, Response proxyToClientResponse)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} P2C 102 interim response {}", requestId(clientToProxyRequest), serverToProxyResponseHeaders);
|
||||
proxyToClientResponse.writeInterim(HttpStatus.PROCESSING_102, serverToProxyResponseHeaders);
|
||||
}
|
||||
|
||||
protected void onServerToProxyResponse103EarlyHints(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, HttpFields serverToProxyResponseHeaders, Response proxyToClientResponse)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} P2C 103 interim response {}", requestId(clientToProxyRequest), serverToProxyResponseHeaders);
|
||||
proxyToClientResponse.writeInterim(HttpStatus.EARLY_HINT_103, serverToProxyResponseHeaders);
|
||||
}
|
||||
|
||||
protected void onProxyToClientResponseComplete(Request clientToProxyRequest, org.eclipse.jetty.client.api.Request proxyToServerRequest, org.eclipse.jetty.client.api.Response serverToProxyResponse, Response proxyToClientResponse, Callback proxyToClientCallback)
|
||||
{
|
||||
proxyToClientCallback.succeeded();
|
||||
|
@ -519,7 +564,7 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
{
|
||||
Content.Chunk chunk = clientToProxyRequest.read();
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} read C2P content {}", requestId(clientToProxyRequest), chunk);
|
||||
LOG.debug("{} C2P read content {}", requestId(clientToProxyRequest), chunk);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
|
@ -544,8 +589,7 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
@Override
|
||||
public boolean rewind()
|
||||
{
|
||||
// TODO: can this be delegated to the clientToProxyRequest?
|
||||
return false;
|
||||
return clientToProxyRequest.rewind();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -576,7 +620,7 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("""
|
||||
{} received S2P response
|
||||
{} S2P received response
|
||||
{}
|
||||
{}""",
|
||||
requestId(clientToProxyRequest),
|
||||
|
@ -595,7 +639,7 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
if (LOG.isDebugEnabled())
|
||||
{
|
||||
LOG.debug("""
|
||||
{} sending P2C response
|
||||
{} P2C sending response
|
||||
{}
|
||||
{}""",
|
||||
requestId(clientToProxyRequest),
|
||||
|
@ -608,14 +652,14 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
public void onContent(org.eclipse.jetty.client.api.Response serverToProxyResponse, ByteBuffer serverToProxyContent, Callback serverToProxyContentCallback)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} received S2P content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
|
||||
LOG.debug("{} S2P received content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
|
||||
Callback callback = new Callback()
|
||||
{
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} succeeded to write P2C content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
|
||||
LOG.debug("{} P2C succeeded to write content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent));
|
||||
serverToProxyContentCallback.succeeded();
|
||||
}
|
||||
|
||||
|
@ -623,7 +667,7 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
public void failed(Throwable failure)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} failed to write P2C content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent), failure);
|
||||
LOG.debug("{} P2C failed to write content {}", requestId(clientToProxyRequest), BufferUtil.toDetailString(serverToProxyContent), failure);
|
||||
serverToProxyContentCallback.failed(failure);
|
||||
// Cannot write towards the client, abort towards the server.
|
||||
serverToProxyResponse.abort(failure);
|
||||
|
@ -714,4 +758,45 @@ public abstract class ProxyHandler extends Handler.Processor
|
|||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
}
|
||||
|
||||
private class ProxyContinueProtocolHandler extends ContinueProtocolHandler
|
||||
{
|
||||
@Override
|
||||
protected void onContinue(org.eclipse.jetty.client.api.Request proxyToServerRequest)
|
||||
{
|
||||
super.onContinue(proxyToServerRequest);
|
||||
var clientToProxyRequest = (Request)proxyToServerRequest.getAttributes().get(CLIENT_TO_PROXY_REQUEST_ATTRIBUTE);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} S2P received 100 Continue", requestId(clientToProxyRequest));
|
||||
onServerToProxyResponse100Continue(clientToProxyRequest, proxyToServerRequest);
|
||||
}
|
||||
}
|
||||
|
||||
private class ProxyProcessingProtocolHandler extends ProcessingProtocolHandler
|
||||
{
|
||||
@Override
|
||||
protected void onProcessing(org.eclipse.jetty.client.api.Request proxyToServerRequest, HttpFields serverToProxyResponseHeaders)
|
||||
{
|
||||
super.onProcessing(proxyToServerRequest, serverToProxyResponseHeaders);
|
||||
var clientToProxyRequest = (Request)proxyToServerRequest.getAttributes().get(CLIENT_TO_PROXY_REQUEST_ATTRIBUTE);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} S2P received 102 Processing", requestId(clientToProxyRequest));
|
||||
var proxyToClientResponse = (Response)proxyToServerRequest.getAttributes().get(PROXY_TO_CLIENT_RESPONSE_ATTRIBUTE);
|
||||
onServerToProxyResponse102Processing(clientToProxyRequest, proxyToServerRequest, serverToProxyResponseHeaders, proxyToClientResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private class ProxyEarlyHintsProtocolHandler extends EarlyHintsProtocolHandler
|
||||
{
|
||||
@Override
|
||||
protected void onEarlyHints(org.eclipse.jetty.client.api.Request proxyToServerRequest, HttpFields serverToProxyResponseHeaders)
|
||||
{
|
||||
super.onEarlyHints(proxyToServerRequest, serverToProxyResponseHeaders);
|
||||
var clientToProxyRequest = (Request)proxyToServerRequest.getAttributes().get(CLIENT_TO_PROXY_REQUEST_ATTRIBUTE);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} S2P received 103 Early Hints", requestId(clientToProxyRequest));
|
||||
var proxyToClientResponse = (Response)proxyToServerRequest.getAttributes().get(PROXY_TO_CLIENT_RESPONSE_ATTRIBUTE);
|
||||
onServerToProxyResponse103EarlyHints(clientToProxyRequest, proxyToServerRequest, serverToProxyResponseHeaders, proxyToClientResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 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.proxy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||
import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
|
||||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||
import org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
|
||||
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
|
||||
public class AbstractProxyTest
|
||||
{
|
||||
public static List<HttpVersion> httpVersions()
|
||||
{
|
||||
return List.of(HttpVersion.HTTP_1_1, HttpVersion.HTTP_2);
|
||||
}
|
||||
|
||||
protected Server server;
|
||||
protected ServerConnector serverConnector;
|
||||
protected Server proxy;
|
||||
protected ServerConnector proxyConnector;
|
||||
protected HttpClient client;
|
||||
|
||||
protected void startClient() throws Exception
|
||||
{
|
||||
ClientConnector clientConnector = new ClientConnector();
|
||||
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||
clientThreads.setName("client");
|
||||
clientConnector.setExecutor(clientThreads);
|
||||
HTTP2Client http2Client = new HTTP2Client(clientConnector);
|
||||
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, HttpClientConnectionFactory.HTTP11, new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client)));
|
||||
client.start();
|
||||
}
|
||||
|
||||
protected void startProxy(ProxyHandler handler) throws Exception
|
||||
{
|
||||
QueuedThreadPool proxyPool = new QueuedThreadPool();
|
||||
proxyPool.setName("proxy");
|
||||
proxy = new Server(proxyPool);
|
||||
HttpConfiguration configuration = new HttpConfiguration();
|
||||
configuration.setSendDateHeader(false);
|
||||
configuration.setSendServerVersion(false);
|
||||
proxyConnector = new ServerConnector(proxy, 1, 1, new HttpConnectionFactory(configuration), new HTTP2CServerConnectionFactory(configuration));
|
||||
proxy.addConnector(proxyConnector);
|
||||
proxy.setHandler(handler);
|
||||
proxy.start();
|
||||
}
|
||||
|
||||
protected void startServer(Handler handler) throws Exception
|
||||
{
|
||||
QueuedThreadPool serverPool = new QueuedThreadPool();
|
||||
serverPool.setName("server");
|
||||
server = new Server(serverPool);
|
||||
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
serverConnector = new ServerConnector(server, 1, 1, new HttpConnectionFactory(httpConfig), new HTTP2CServerConnectionFactory(httpConfig));
|
||||
server.addConnector(serverConnector);
|
||||
server.setHandler(handler);
|
||||
server.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dispose()
|
||||
{
|
||||
LifeCycle.stop(client);
|
||||
LifeCycle.stop(proxy);
|
||||
LifeCycle.stop(server);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 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.proxy;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.util.StringRequestContent;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.Promise;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class InterimResponseProxyTest extends AbstractProxyTest
|
||||
{
|
||||
@Test
|
||||
public void testInterimResponses() throws Exception
|
||||
{
|
||||
startServer(new Handler.Processor()
|
||||
{
|
||||
@Override
|
||||
public void process(Request request, Response response, Callback callback)
|
||||
{
|
||||
CompletableFuture<Void> completable = response.writeInterim(HttpStatus.CONTINUE_100, HttpFields.EMPTY)
|
||||
.thenCompose(ignored -> Promise.Completable.<String>with(p -> Content.Source.asString(request, StandardCharsets.UTF_8, p)))
|
||||
.thenCompose(content -> response.writeInterim(HttpStatus.PROCESSING_102, HttpFields.EMPTY).thenApply(ignored -> content))
|
||||
.thenCompose(content -> response.writeInterim(HttpStatus.EARLY_HINT_103, HttpFields.EMPTY).thenApply(ignored -> content))
|
||||
.thenCompose(content -> Callback.Completable.with(c -> Content.Sink.write(response, true, content, c)));
|
||||
callback.completeWith(completable);
|
||||
}
|
||||
});
|
||||
|
||||
startProxy(new ProxyHandler.Reverse(clientToProxyRequest ->
|
||||
HttpURI.build(clientToProxyRequest.getHttpURI()).port(serverConnector.getLocalPort())));
|
||||
|
||||
startClient();
|
||||
|
||||
String content = "hello world";
|
||||
ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort())
|
||||
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
|
||||
.body(new StringRequestContent(content))
|
||||
.send();
|
||||
assertEquals(content, response.getContentAsString());
|
||||
}
|
||||
}
|
|
@ -13,8 +13,6 @@
|
|||
|
||||
package org.eclipse.jetty.proxy;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
|
||||
|
@ -24,83 +22,20 @@ import org.eclipse.jetty.http.HttpURI;
|
|||
import org.eclipse.jetty.http.HttpVersion;
|
||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||
import org.eclipse.jetty.http2.client.transport.ClientConnectionFactoryOverHTTP2;
|
||||
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
|
||||
import org.eclipse.jetty.io.ClientConnector;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class ReverseProxyTest
|
||||
public class ReverseProxyTest extends AbstractProxyTest
|
||||
{
|
||||
private Server server;
|
||||
private ServerConnector serverConnector;
|
||||
private Server proxy;
|
||||
private ServerConnector proxyConnector;
|
||||
private HttpClient client;
|
||||
|
||||
private void startServer(Handler handler) throws Exception
|
||||
{
|
||||
QueuedThreadPool serverPool = new QueuedThreadPool();
|
||||
serverPool.setName("server");
|
||||
server = new Server(serverPool);
|
||||
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
serverConnector = new ServerConnector(server, 1, 1, new HttpConnectionFactory(httpConfig), new HTTP2CServerConnectionFactory(httpConfig));
|
||||
server.addConnector(serverConnector);
|
||||
server.setHandler(handler);
|
||||
server.start();
|
||||
}
|
||||
|
||||
private void startProxy(Handler handler) throws Exception
|
||||
{
|
||||
QueuedThreadPool proxyPool = new QueuedThreadPool();
|
||||
proxyPool.setName("proxy");
|
||||
proxy = new Server(proxyPool);
|
||||
HttpConfiguration configuration = new HttpConfiguration();
|
||||
configuration.setSendDateHeader(false);
|
||||
configuration.setSendServerVersion(false);
|
||||
proxyConnector = new ServerConnector(proxy, 1, 1, new HttpConnectionFactory(configuration), new HTTP2CServerConnectionFactory(configuration));
|
||||
proxy.addConnector(proxyConnector);
|
||||
proxy.setHandler(handler);
|
||||
proxy.start();
|
||||
}
|
||||
|
||||
private void startClient() throws Exception
|
||||
{
|
||||
ClientConnector clientConnector = new ClientConnector();
|
||||
QueuedThreadPool clientThreads = new QueuedThreadPool();
|
||||
clientThreads.setName("client");
|
||||
clientConnector.setExecutor(clientThreads);
|
||||
HTTP2Client http2Client = new HTTP2Client(clientConnector);
|
||||
client = new HttpClient(new HttpClientTransportDynamic(clientConnector, HttpClientConnectionFactory.HTTP11, new ClientConnectionFactoryOverHTTP2.HTTP2(http2Client)));
|
||||
client.start();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void dispose()
|
||||
{
|
||||
LifeCycle.stop(client);
|
||||
LifeCycle.stop(proxy);
|
||||
LifeCycle.stop(server);
|
||||
}
|
||||
|
||||
private static List<HttpVersion> httpVersions()
|
||||
{
|
||||
return List.of(HttpVersion.HTTP_1_1, HttpVersion.HTTP_2);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("httpVersions")
|
||||
public void testSimple(HttpVersion httpVersion) throws Exception
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<profile>
|
||||
<id>jdk17</id>
|
||||
<activation>
|
||||
<jdk>[17,)</jdk>
|
||||
<jdk>17</jdk>
|
||||
</activation>
|
||||
<modules>
|
||||
<module>jetty-quic-quiche-foreign-incubator</module>
|
||||
|
|
|
@ -56,6 +56,11 @@
|
|||
<artifactId>jetty-test-helper</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-xhtml-schemas</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-http-tools</artifactId>
|
||||
|
|
|
@ -534,9 +534,7 @@ public interface Handler extends LifeCycle, Destroyable, Invocable
|
|||
public List<Handler> getHandlers()
|
||||
{
|
||||
Handler next = getHandler();
|
||||
if (next == null)
|
||||
return Collections.emptyList();
|
||||
return List.of(next);
|
||||
return (next == null) ? Collections.emptyList() : Collections.singletonList(next);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -112,9 +112,8 @@ public class OptionalSslConnectionFactory extends DetectorConnectionFactory
|
|||
"Connection: close\r\n" +
|
||||
"\r\n" +
|
||||
body;
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
endPoint.write(completable, ByteBuffer.wrap(response.getBytes(StandardCharsets.US_ASCII)));
|
||||
completable.whenComplete((r, x) -> endPoint.close());
|
||||
Callback.Completable.with(c -> endPoint.write(c, ByteBuffer.wrap(response.getBytes(StandardCharsets.US_ASCII))))
|
||||
.whenComplete((r, x) -> endPoint.close());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -44,7 +44,7 @@ public class ResourceListing
|
|||
public static final Logger LOG = LoggerFactory.getLogger(ResourceListing.class);
|
||||
|
||||
/**
|
||||
* Convert the Resource directory into an HTML directory listing.
|
||||
* Convert the Resource directory into an XHTML directory listing.
|
||||
*
|
||||
* @param resource the resource to build the listing from
|
||||
* @param base The base URL
|
||||
|
@ -52,7 +52,7 @@ public class ResourceListing
|
|||
* @param query query params
|
||||
* @return the HTML as String
|
||||
*/
|
||||
public static String getAsHTML(Resource resource, String base, boolean parent, String query)
|
||||
public static String getAsXHTML(Resource resource, String base, boolean parent, String query)
|
||||
{
|
||||
// This method doesn't check aliases, so it is OK to canonicalize here.
|
||||
base = URIUtil.normalizePath(base);
|
||||
|
@ -105,13 +105,15 @@ public class ResourceListing
|
|||
|
||||
StringBuilder buf = new StringBuilder(4096);
|
||||
|
||||
// Doctype Declaration (HTML5)
|
||||
buf.append("<!DOCTYPE html>\n");
|
||||
buf.append("<html lang=\"en\">\n");
|
||||
// Doctype Declaration + XHTML
|
||||
buf.append("""
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
|
||||
""");
|
||||
|
||||
// HTML Header
|
||||
buf.append("<head>\n");
|
||||
buf.append("<meta charset=\"utf-8\">\n");
|
||||
buf.append("<link href=\"jetty-dir.css\" rel=\"stylesheet\" />\n");
|
||||
buf.append("<title>");
|
||||
buf.append(title);
|
||||
|
@ -145,7 +147,7 @@ public class ResourceListing
|
|||
}
|
||||
}
|
||||
|
||||
buf.append("<tr><th class=\"name\"><a href=\"?C=N&O=").append(order).append("\">");
|
||||
buf.append("<tr><th class=\"name\"><a href=\"?C=N&O=").append(order).append("\">");
|
||||
buf.append("Name").append(arrow);
|
||||
buf.append("</a></th>");
|
||||
|
||||
|
@ -165,7 +167,7 @@ public class ResourceListing
|
|||
}
|
||||
}
|
||||
|
||||
buf.append("<th class=\"lastmodified\"><a href=\"?C=M&O=").append(order).append("\">");
|
||||
buf.append("<th class=\"lastmodified\"><a href=\"?C=M&O=").append(order).append("\">");
|
||||
buf.append("Last Modified").append(arrow);
|
||||
buf.append("</a></th>");
|
||||
|
||||
|
@ -184,7 +186,7 @@ public class ResourceListing
|
|||
arrow = ARROW_DOWN;
|
||||
}
|
||||
}
|
||||
buf.append("<th class=\"size\"><a href=\"?C=S&O=").append(order).append("\">");
|
||||
buf.append("<th class=\"size\"><a href=\"?C=S&O=").append(order).append("\">");
|
||||
buf.append("Size").append(arrow);
|
||||
buf.append("</a></th></tr>\n");
|
||||
buf.append("</thead>\n");
|
||||
|
@ -197,6 +199,7 @@ public class ResourceListing
|
|||
{
|
||||
// Name
|
||||
buf.append("<tr><td class=\"name\"><a href=\"");
|
||||
// TODO This produces an absolute link from the /context/<listing-dir> path, investigate if we can use relative links reliably now
|
||||
buf.append(URIUtil.addPaths(encodedBase, "../"));
|
||||
buf.append("\">Parent Directory</a></td>");
|
||||
// Last Modified
|
||||
|
@ -228,8 +231,7 @@ public class ResourceListing
|
|||
buf.append(path);
|
||||
buf.append("\">");
|
||||
buf.append(deTag(name));
|
||||
buf.append(" ");
|
||||
buf.append("</a></td>");
|
||||
buf.append(" </a></td>");
|
||||
|
||||
// Last Modified
|
||||
buf.append("<td class=\"lastmodified\">");
|
||||
|
@ -262,11 +264,18 @@ public class ResourceListing
|
|||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Encode any characters that could break the URI string in an HREF.
|
||||
* Such as <a href="/path/to;<script>Window.alert("XSS"+'%20'+"here");</script>">Link</a>
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* Such as:
|
||||
* {@code <a href="/path/to;<script>Window.alert('XSS'+'%20'+'here');</script>">Link</a>}
|
||||
* </p>
|
||||
* <p>
|
||||
* The above example would parse incorrectly on various browsers as the "<" or '"' characters
|
||||
* would end the href attribute value string prematurely.
|
||||
* </p>
|
||||
*
|
||||
* @param raw the raw text to encode.
|
||||
* @return the defanged text.
|
||||
|
|
|
@ -559,7 +559,7 @@ public class ResourceService
|
|||
}
|
||||
|
||||
String base = URIUtil.addEncodedPaths(request.getHttpURI().getPath(), URIUtil.SLASH);
|
||||
String listing = ResourceListing.getAsHTML(httpContent.getResource(), base, pathInContext.length() > 1, request.getHttpURI().getQuery());
|
||||
String listing = ResourceListing.getAsXHTML(httpContent.getResource(), base, pathInContext.length() > 1, request.getHttpURI().getQuery());
|
||||
if (listing == null)
|
||||
{
|
||||
writeHttpError(request, response, callback, HttpStatus.FORBIDDEN_403);
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.eclipse.jetty.server;
|
|||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ListIterator;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -62,6 +63,9 @@ public interface Response extends Content.Sink
|
|||
|
||||
void reset();
|
||||
|
||||
CompletableFuture<Void> writeInterim(int status, HttpFields headers);
|
||||
|
||||
// TODO: make it static, otherwise we must override it in Wrapper.
|
||||
default boolean writeTrailers(Content.Chunk chunk, Callback ignored)
|
||||
{
|
||||
if (chunk instanceof Trailers trailers)
|
||||
|
@ -156,7 +160,6 @@ public interface Response extends Content.Sink
|
|||
if (field.getHeader() == HttpHeader.SET_COOKIE)
|
||||
{
|
||||
CookieCompliance compliance = httpConfiguration.getResponseCookieCompliance();
|
||||
HttpCookie oldCookie;
|
||||
if (field instanceof HttpCookie.SetCookieHttpField)
|
||||
{
|
||||
if (!HttpCookie.match(((HttpCookie.SetCookieHttpField)field).getHttpCookie(), cookie.getName(), cookie.getDomain(), cookie.getPath()))
|
||||
|
@ -203,8 +206,6 @@ public interface Response extends Content.Sink
|
|||
|
||||
static void writeError(Request request, Response response, Callback callback, int status, String message, Throwable cause)
|
||||
{
|
||||
// TODO what about 102 Processing?
|
||||
|
||||
// Retrieve the Logger instance here, rather than having a
|
||||
// public field that will force a transitive dependency on SLF4J.
|
||||
Logger logger = LoggerFactory.getLogger(Response.class);
|
||||
|
@ -420,5 +421,11 @@ public interface Response extends Content.Sink
|
|||
{
|
||||
getWrapped().reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> writeInterim(int status, HttpFields headers)
|
||||
{
|
||||
return getWrapped().writeInterim(status, headers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -855,7 +855,7 @@ public class ContextHandler extends Handler.Wrapper implements Attributes, Grace
|
|||
if (resource.isAlias())
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Aliased resource: {}~={}", resource, resource.getAlias());
|
||||
LOG.debug("Aliased resource: {} -> {}", resource, resource.getTargetURI());
|
||||
|
||||
// alias checks
|
||||
for (AliasCheck check : _aliasChecks)
|
||||
|
|
|
@ -13,13 +13,113 @@
|
|||
|
||||
package org.eclipse.jetty.server.handler;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.thread.Invocable;
|
||||
|
||||
/**
|
||||
* A <code>HandlerContainer</code> that allows a hot swap of a wrapped handler.
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public class HotSwapHandler extends Handler.Wrapper
|
||||
public class HotSwapHandler extends Handler.AbstractContainer implements Handler.Nested
|
||||
{
|
||||
// TODO unit tests
|
||||
|
||||
private volatile Handler _handler;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public HotSwapHandler()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the handlers.
|
||||
*/
|
||||
public Handler getHandler()
|
||||
{
|
||||
return _handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the handlers.
|
||||
*/
|
||||
@Override
|
||||
public List<Handler> getHandlers()
|
||||
{
|
||||
Handler next = _handler;
|
||||
return (next == null) ? Collections.emptyList() : Collections.singletonList(next);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param handler Set the {@link Handler} which should be wrapped.
|
||||
*/
|
||||
public void setHandler(Handler handler)
|
||||
{
|
||||
// check state
|
||||
Server server1 = ((Nested)this).getServer();
|
||||
if (server1 != null && server1.isStarted() && handler != null &&
|
||||
server1.getInvocationType() != Invocable.combine(server1.getInvocationType(), handler.getInvocationType()))
|
||||
throw new IllegalArgumentException("Cannot change invocation type of started server");
|
||||
|
||||
// Check for loops.
|
||||
if (handler == this || (handler instanceof Container container &&
|
||||
container.getDescendants().contains(this)))
|
||||
throw new IllegalStateException("setHandler loop");
|
||||
|
||||
try
|
||||
{
|
||||
Server server = getServer();
|
||||
if (handler == _handler)
|
||||
return;
|
||||
|
||||
Handler oldHandler = _handler;
|
||||
if (handler != null)
|
||||
{
|
||||
handler.setServer(server);
|
||||
addBean(handler, true);
|
||||
if (oldHandler != null && oldHandler.isStarted())
|
||||
handler.start();
|
||||
}
|
||||
_handler = handler;
|
||||
if (oldHandler != null)
|
||||
removeBean(oldHandler);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request.Processor handle(Request request) throws Exception
|
||||
{
|
||||
Handler next = _handler;
|
||||
return next == null ? null : next.handle(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
Handler next = getHandler();
|
||||
return next == null ? InvocationType.NON_BLOCKING : next.getInvocationType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy()
|
||||
{
|
||||
if (!isStopped())
|
||||
throw new IllegalStateException("!STOPPED");
|
||||
Handler child = getHandler();
|
||||
if (child != null)
|
||||
{
|
||||
setHandler((Handler)null);
|
||||
child.destroy();
|
||||
}
|
||||
super.destroy();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.LongAdder;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
@ -1263,6 +1264,24 @@ public class HttpChannelState implements HttpChannel, Components
|
|||
_request.getHttpChannel().resetResponse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> writeInterim(int status, HttpFields headers)
|
||||
{
|
||||
Completable completable = new Completable();
|
||||
if (HttpStatus.isInterim(status))
|
||||
{
|
||||
HttpChannelState channel = _request.getHttpChannel();
|
||||
HttpVersion version = channel.getConnectionMetaData().getHttpVersion();
|
||||
MetaData.Response response = new MetaData.Response(version, status, headers);
|
||||
channel._stream.send(_request._metaData, response, false, null, completable);
|
||||
}
|
||||
else
|
||||
{
|
||||
completable.failed(new IllegalArgumentException("Invalid interim status code: " + status));
|
||||
}
|
||||
return completable;
|
||||
}
|
||||
|
||||
private MetaData.Response lockedPrepareResponse(HttpChannelState httpChannel, boolean last)
|
||||
{
|
||||
// Assume 200 unless told otherwise.
|
||||
|
|
|
@ -1157,14 +1157,12 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
|
|||
private MetaData.Request _request;
|
||||
private HttpField _upgrade = null;
|
||||
private Connection _upgradeConnection;
|
||||
|
||||
Content.Chunk _chunk;
|
||||
private Content.Chunk _chunk;
|
||||
private boolean _connectionClose = false;
|
||||
private boolean _connectionKeepAlive = false;
|
||||
private boolean _connectionUpgrade = false;
|
||||
private boolean _unknownExpectation = false;
|
||||
private boolean _expect100Continue = false;
|
||||
private boolean _expect102Processing = false;
|
||||
private boolean _expects100Continue = false;
|
||||
private List<String> _complianceViolations;
|
||||
|
||||
protected HttpStreamOverHTTP1(String method, String uri, HttpVersion version)
|
||||
|
@ -1206,22 +1204,16 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
|
|||
{
|
||||
if (!HttpHeaderValue.parseCsvIndex(value, t ->
|
||||
{
|
||||
switch (t)
|
||||
if (t == HttpHeaderValue.CONTINUE)
|
||||
{
|
||||
case CONTINUE:
|
||||
_expect100Continue = true;
|
||||
return true;
|
||||
case PROCESSING:
|
||||
_expect102Processing = true;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
_expects100Continue = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, s -> false))
|
||||
{
|
||||
_unknownExpectation = true;
|
||||
_expect100Continue = false;
|
||||
_expect102Processing = false;
|
||||
_expects100Continue = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -1398,8 +1390,12 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
|
|||
|
||||
Content.Chunk content = _chunk;
|
||||
_chunk = Content.Chunk.next(content);
|
||||
if (content != null && _expect100Continue && content.hasRemaining())
|
||||
_expect100Continue = false;
|
||||
|
||||
// Some content is read, but the 100 Continue interim
|
||||
// response has not been sent yet, then don't bother
|
||||
// sending it later, as the client already sent the content.
|
||||
if (content != null && _expects100Continue && content.hasRemaining())
|
||||
_expects100Continue = false;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
@ -1423,9 +1419,9 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
|
|||
return;
|
||||
}
|
||||
|
||||
if (_expect100Continue)
|
||||
if (_expects100Continue)
|
||||
{
|
||||
_expect100Continue = false;
|
||||
_expects100Continue = false;
|
||||
send(_request, HttpGenerator.CONTINUE_100_INFO, false, null, Callback.NOOP);
|
||||
}
|
||||
|
||||
|
@ -1454,15 +1450,18 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
|
|||
{
|
||||
callback.failed(new IllegalStateException("Committed"));
|
||||
}
|
||||
else if (response.getStatus() == 102 && !_expect102Processing)
|
||||
else if (_expects100Continue)
|
||||
{
|
||||
// silently discard
|
||||
callback.succeeded();
|
||||
}
|
||||
else if (response.getStatus() != 100 && _expect100Continue)
|
||||
{
|
||||
// If we are still expecting a 100 continues when we commit then we can't be persistent
|
||||
_generator.setPersistent(false);
|
||||
if (response.getStatus() == HttpStatus.CONTINUE_100)
|
||||
{
|
||||
_expects100Continue = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Expecting to send a 100 Continue response, but it's a different response,
|
||||
// then cannot be persistent because likely the client did not send the content.
|
||||
_generator.setPersistent(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (_sendCallback.reset(_request, response, content, last, callback))
|
||||
|
@ -1586,11 +1585,11 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
|
|||
if (HttpConnection.this.upgrade(stream))
|
||||
return;
|
||||
|
||||
// Finish consuming the request
|
||||
// If we are still expecting
|
||||
if (_expect100Continue)
|
||||
if (_expects100Continue)
|
||||
{
|
||||
// close to seek EOF
|
||||
// No content was read, and no 100 Continue response was sent.
|
||||
// Close the parser so that below it seeks EOF, not the next request.
|
||||
_expects100Continue = false;
|
||||
_parser.close();
|
||||
}
|
||||
|
||||
|
@ -1613,8 +1612,6 @@ public class HttpConnection extends AbstractConnection implements Runnable, Writ
|
|||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("non-current completion {}", this);
|
||||
|
||||
// TODO what about upgrade????
|
||||
|
||||
// If we are looking for the next request
|
||||
if (_parser.isStart())
|
||||
{
|
||||
|
|
|
@ -369,9 +369,8 @@ public class DetectorConnectionTest
|
|||
// omitting this will leak the buffer
|
||||
connector.getByteBufferPool().release(buffer);
|
||||
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
endPoint.write(completable, ByteBuffer.wrap("No upgrade for you".getBytes(StandardCharsets.US_ASCII)));
|
||||
completable.whenComplete((r, x) -> endPoint.close());
|
||||
Callback.Completable.with(c -> endPoint.write(c, ByteBuffer.wrap("No upgrade for you".getBytes(StandardCharsets.US_ASCII))))
|
||||
.whenComplete((r, x) -> endPoint.close());
|
||||
}
|
||||
};
|
||||
HttpConnectionFactory http = new HttpConnectionFactory();
|
||||
|
|
|
@ -279,7 +279,7 @@ public class LocalConnectorTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testExpectContinuesAvailable() throws Exception
|
||||
public void testExpect100ContinueContentAvailable() throws Exception
|
||||
{
|
||||
LocalConnector.LocalEndPoint endp = _connector.connect();
|
||||
endp.addInput(
|
||||
|
@ -299,7 +299,7 @@ public class LocalConnectorTest
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testExpectContinues() throws Exception
|
||||
public void testExpect100Continue() throws Exception
|
||||
{
|
||||
LocalConnector.LocalEndPoint endp = _connector.executeRequest(
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,497 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 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.server;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.xml.catalog.Catalog;
|
||||
import javax.xml.catalog.CatalogManager;
|
||||
import javax.xml.catalog.CatalogResolver;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.FS;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||
import org.eclipse.jetty.toolchain.xhtml.CatalogXHTML;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.ErrorHandler;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.SAXParseException;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
public class ResourceListingTest
|
||||
{
|
||||
@Test
|
||||
public void testBasicResourceXHtmlListingRoot(WorkDir workDir) throws IOException
|
||||
{
|
||||
Path root = workDir.getEmptyPathDir();
|
||||
|
||||
FS.touch(root.resolve("entry1.txt"));
|
||||
FS.touch(root.resolve("entry2.dat"));
|
||||
Files.createDirectory(root.resolve("dirFoo"));
|
||||
Files.createDirectory(root.resolve("dirBar"));
|
||||
Files.createDirectory(root.resolve("dirZed"));
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource resource = resourceFactory.newResource(root);
|
||||
String content = ResourceListing.getAsXHTML(resource, "/", false, null);
|
||||
assertTrue(isValidXHtml(content));
|
||||
|
||||
assertThat(content, containsString("entry1.txt"));
|
||||
assertThat(content, containsString("<a href=\"/entry1.txt\">"));
|
||||
assertThat(content, containsString("entry2.dat"));
|
||||
assertThat(content, containsString("<a href=\"/entry2.dat\">"));
|
||||
assertThat(content, containsString("dirFoo/"));
|
||||
assertThat(content, containsString("<a href=\"/dirFoo/\">"));
|
||||
assertThat(content, containsString("dirBar/"));
|
||||
assertThat(content, containsString("<a href=\"/dirBar/\">"));
|
||||
assertThat(content, containsString("dirZed/"));
|
||||
assertThat(content, containsString("<a href=\"/dirZed/\">"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicResourceXHtmlListingDeep(WorkDir workDir) throws IOException
|
||||
{
|
||||
Path root = workDir.getEmptyPathDir();
|
||||
|
||||
FS.touch(root.resolve("entry1.txt"));
|
||||
FS.touch(root.resolve("entry2.dat"));
|
||||
Files.createDirectory(root.resolve("dirFoo"));
|
||||
Files.createDirectory(root.resolve("dirBar"));
|
||||
Files.createDirectory(root.resolve("dirZed"));
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource resource = resourceFactory.newResource(root);
|
||||
String content = ResourceListing.getAsXHTML(resource, "/deep/", false, null);
|
||||
assertTrue(isValidXHtml(content));
|
||||
|
||||
assertThat(content, containsString("entry1.txt"));
|
||||
assertThat(content, containsString("<a href=\"/deep/entry1.txt\">"));
|
||||
assertThat(content, containsString("entry2.dat"));
|
||||
assertThat(content, containsString("<a href=\"/deep/entry2.dat\">"));
|
||||
assertThat(content, containsString("dirFoo/"));
|
||||
assertThat(content, containsString("<a href=\"/deep/dirFoo/\">"));
|
||||
assertThat(content, containsString("dirBar/"));
|
||||
assertThat(content, containsString("<a href=\"/deep/dirBar/\">"));
|
||||
assertThat(content, containsString("dirZed/"));
|
||||
assertThat(content, containsString("<a href=\"/deep/dirZed/\">"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceCollectionXHtmlListingContext(WorkDir workDir) throws IOException
|
||||
{
|
||||
Path root = workDir.getEmptyPathDir();
|
||||
|
||||
Path docrootA = root.resolve("docrootA");
|
||||
Files.createDirectory(docrootA);
|
||||
FS.touch(docrootA.resolve("entry1.txt"));
|
||||
FS.touch(docrootA.resolve("entry2.dat"));
|
||||
FS.touch(docrootA.resolve("similar.txt"));
|
||||
Files.createDirectory(docrootA.resolve("dirSame"));
|
||||
Files.createDirectory(docrootA.resolve("dirFoo"));
|
||||
Files.createDirectory(docrootA.resolve("dirBar"));
|
||||
|
||||
Path docrootB = root.resolve("docrootB");
|
||||
Files.createDirectory(docrootB);
|
||||
FS.touch(docrootB.resolve("entry3.png"));
|
||||
FS.touch(docrootB.resolve("entry4.tar.gz"));
|
||||
FS.touch(docrootB.resolve("similar.txt")); // same filename as in docrootA
|
||||
Files.createDirectory(docrootB.resolve("dirSame")); // same directory name as in docrootA
|
||||
Files.createDirectory(docrootB.resolve("dirCid"));
|
||||
Files.createDirectory(docrootB.resolve("dirZed"));
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
List<URI> uriRootList = List.of(docrootA.toUri(), docrootB.toUri());
|
||||
Resource resource = resourceFactory.newResource(uriRootList);
|
||||
String content = ResourceListing.getAsXHTML(resource, "/context/", false, null);
|
||||
assertTrue(isValidXHtml(content));
|
||||
|
||||
assertThat(content, containsString("entry1.txt"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry1.txt\">"));
|
||||
assertThat(content, containsString("entry2.dat"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry2.dat\">"));
|
||||
assertThat(content, containsString("entry3.png"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry3.png\">"));
|
||||
assertThat(content, containsString("entry4.tar.gz"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry4.tar.gz\">"));
|
||||
assertThat(content, containsString("dirFoo/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirFoo/\">"));
|
||||
assertThat(content, containsString("dirBar/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirBar/\">"));
|
||||
assertThat(content, containsString("dirCid/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirCid/\">"));
|
||||
assertThat(content, containsString("dirZed/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirZed/\">"));
|
||||
|
||||
int count;
|
||||
|
||||
// how many dirSame links do we have?
|
||||
count = content.split(Pattern.quote("<a href=\"/context/dirSame/\">"), -1).length - 1;
|
||||
assertThat(count, is(1));
|
||||
|
||||
// how many similar.txt do we have?
|
||||
count = content.split(Pattern.quote("<a href=\"/context/similar.txt\">"), -1).length - 1;
|
||||
assertThat(count, is(1));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResourceCollectionMixedTypesXHtmlListingContext(WorkDir workDir) throws IOException
|
||||
{
|
||||
Path root = workDir.getEmptyPathDir();
|
||||
|
||||
Path docrootA = root.resolve("docrootA");
|
||||
Files.createDirectory(docrootA);
|
||||
FS.touch(docrootA.resolve("entry1.txt"));
|
||||
FS.touch(docrootA.resolve("entry2.dat"));
|
||||
Files.createDirectory(docrootA.resolve("dirFoo"));
|
||||
Files.createDirectory(docrootA.resolve("dirBar"));
|
||||
|
||||
Path docrootB = root.resolve("docrootB");
|
||||
Files.createDirectory(docrootB);
|
||||
FS.touch(docrootB.resolve("entry3.png"));
|
||||
FS.touch(docrootB.resolve("entry4.tar.gz"));
|
||||
Files.createDirectory(docrootB.resolve("dirCid"));
|
||||
Files.createDirectory(docrootB.resolve("dirZed"));
|
||||
|
||||
// Introduce a non-directory entry
|
||||
Path docNonRootC = root.resolve("non-root.dat");
|
||||
FS.touch(docNonRootC);
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
// Collection consisting of file, dir, dir
|
||||
List<URI> uriRootList = List.of(docNonRootC.toUri(), docrootA.toUri(), docrootB.toUri());
|
||||
Resource resource = resourceFactory.newResource(uriRootList);
|
||||
String content = ResourceListing.getAsXHTML(resource, "/context/", false, null);
|
||||
assertTrue(isValidXHtml(content));
|
||||
|
||||
assertThat(content, containsString("entry1.txt"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry1.txt\">"));
|
||||
assertThat(content, containsString("entry2.dat"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry2.dat\">"));
|
||||
assertThat(content, containsString("entry3.png"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry3.png\">"));
|
||||
assertThat(content, containsString("entry4.tar.gz"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry4.tar.gz\">"));
|
||||
assertThat(content, containsString("dirFoo/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirFoo/\">"));
|
||||
assertThat(content, containsString("dirBar/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirBar/\">"));
|
||||
assertThat(content, containsString("dirCid/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirCid/\">"));
|
||||
assertThat(content, containsString("dirZed/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirZed/\">"));
|
||||
assertThat(content, containsString("<a href=\"/context/non-root.dat\">"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a ResourceCollection that is constructed by nested ResourceCollection.
|
||||
*/
|
||||
@Test
|
||||
public void testResourceCollectionNestedXHtmlListingContext(WorkDir workDir) throws IOException
|
||||
{
|
||||
Path root = workDir.getEmptyPathDir();
|
||||
|
||||
Path docrootA = root.resolve("docrootA");
|
||||
Files.createDirectory(docrootA);
|
||||
FS.touch(docrootA.resolve("entry1.txt"));
|
||||
FS.touch(docrootA.resolve("entry2.dat"));
|
||||
Files.createDirectory(docrootA.resolve("dirFoo"));
|
||||
Files.createDirectory(docrootA.resolve("dirBar"));
|
||||
|
||||
Path docrootB = root.resolve("docrootB");
|
||||
Files.createDirectory(docrootB);
|
||||
FS.touch(docrootB.resolve("entry3.png"));
|
||||
FS.touch(docrootB.resolve("entry4.tar.gz"));
|
||||
Files.createDirectory(docrootB.resolve("dirCid"));
|
||||
Files.createDirectory(docrootB.resolve("dirZed"));
|
||||
|
||||
// Introduce a non-directory entry
|
||||
Path docNonRootC = root.resolve("non-root.dat");
|
||||
FS.touch(docNonRootC);
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
// Collection consisting of inputs [file, dir]
|
||||
Resource resourceCollectionA = resourceFactory.newResource(List.of(docNonRootC.toUri(), docrootB.toUri()));
|
||||
// Basic resource, just a dir
|
||||
Resource basicResource = resourceFactory.newResource(docrootA);
|
||||
// New Collection consisting of inputs [collection, dir] - resulting in [file, dir, dir]
|
||||
Resource resourceCollectionB = Resource.combine(resourceCollectionA, basicResource);
|
||||
|
||||
// Use collection in generating the output
|
||||
String content = ResourceListing.getAsXHTML(resourceCollectionB, "/context/", false, null);
|
||||
assertTrue(isValidXHtml(content));
|
||||
|
||||
assertThat(content, containsString("entry1.txt"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry1.txt\">"));
|
||||
assertThat(content, containsString("entry2.dat"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry2.dat\">"));
|
||||
assertThat(content, containsString("entry3.png"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry3.png\">"));
|
||||
assertThat(content, containsString("entry4.tar.gz"));
|
||||
assertThat(content, containsString("<a href=\"/context/entry4.tar.gz\">"));
|
||||
assertThat(content, containsString("dirFoo/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirFoo/\">"));
|
||||
assertThat(content, containsString("dirBar/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirBar/\">"));
|
||||
assertThat(content, containsString("dirCid/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirCid/\">"));
|
||||
assertThat(content, containsString("dirZed/"));
|
||||
assertThat(content, containsString("<a href=\"/context/dirZed/\">"));
|
||||
assertThat(content, containsString("<a href=\"/context/non-root.dat\">"));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A regression on windows allowed the directory listing show
|
||||
* the fully qualified paths within the directory listing.
|
||||
* This test ensures that this behavior will not arise again.
|
||||
*/
|
||||
@Test
|
||||
public void testListingFilenamesOnly(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path docRoot = workDir.getEmptyPathDir();
|
||||
|
||||
/* create some content in the docroot */
|
||||
FS.ensureDirExists(docRoot);
|
||||
Path one = docRoot.resolve("one");
|
||||
FS.ensureDirExists(one);
|
||||
Path deep = one.resolve("deep");
|
||||
FS.ensureDirExists(deep);
|
||||
FS.touch(deep.resolve("foo"));
|
||||
FS.ensureDirExists(docRoot.resolve("two"));
|
||||
FS.ensureDirExists(docRoot.resolve("three"));
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource resourceBase = resourceFactory.newResource(docRoot);
|
||||
Resource resource = resourceBase.resolve("one/deep/");
|
||||
|
||||
String content = ResourceListing.getAsXHTML(resource, "/context/", false, null);
|
||||
assertTrue(isValidXHtml(content));
|
||||
|
||||
assertThat(content, containsString("/foo"));
|
||||
|
||||
String resBasePath = docRoot.toAbsolutePath().toString();
|
||||
assertThat(content, not(containsString(resBasePath)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListingProperUrlEncoding(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path docRoot = workDir.getEmptyPathDir();
|
||||
/* create some content in the docroot */
|
||||
|
||||
Path wackyDir = docRoot.resolve("dir;"); // this should not be double-encoded.
|
||||
FS.ensureDirExists(wackyDir);
|
||||
|
||||
FS.ensureDirExists(wackyDir.resolve("four"));
|
||||
FS.ensureDirExists(wackyDir.resolve("five"));
|
||||
FS.ensureDirExists(wackyDir.resolve("six"));
|
||||
|
||||
/* At this point we have the following
|
||||
* testListingProperUrlEncoding/
|
||||
* `-- docroot
|
||||
* `-- dir;
|
||||
* |-- five
|
||||
* |-- four
|
||||
* `-- six
|
||||
*/
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource resourceBase = resourceFactory.newResource(docRoot);
|
||||
|
||||
// Resolve directory
|
||||
Resource resource = resourceBase.resolve("dir%3B");
|
||||
|
||||
// Context
|
||||
String content = ResourceListing.getAsXHTML(resource, "/context/dir%3B/", false, null);
|
||||
assertTrue(isValidXHtml(content));
|
||||
|
||||
// Should not see double-encoded ";"
|
||||
// First encoding: ";" -> "%3B"
|
||||
// Second encoding: "%3B" -> "%253B" (BAD!)
|
||||
assertThat(content, not(containsString("%253B")));
|
||||
|
||||
assertThat(content, containsString("/dir%3B/"));
|
||||
assertThat(content, containsString("/dir%3B/four/"));
|
||||
assertThat(content, containsString("/dir%3B/five/"));
|
||||
assertThat(content, containsString("/dir%3B/six/"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListingWithQuestionMarks(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path docRoot = workDir.getEmptyPathDir();
|
||||
|
||||
/* create some content in the docroot */
|
||||
FS.ensureDirExists(docRoot.resolve("one"));
|
||||
FS.ensureDirExists(docRoot.resolve("two"));
|
||||
FS.ensureDirExists(docRoot.resolve("three"));
|
||||
|
||||
// Creating dir 'f??r' (Might not work in Windows)
|
||||
assumeMkDirSupported(docRoot, "f??r");
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource resource = resourceFactory.newResource(docRoot);
|
||||
|
||||
String content = ResourceListing.getAsXHTML(resource, "/context/", false, null);
|
||||
assertTrue(isValidXHtml(content));
|
||||
|
||||
assertThat(content, containsString("f??r"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListingEncoding(WorkDir workDir) throws Exception
|
||||
{
|
||||
Path docRoot = workDir.getEmptyPathDir();
|
||||
|
||||
/* create some content in the docroot */
|
||||
Path one = docRoot.resolve("one");
|
||||
FS.ensureDirExists(one);
|
||||
|
||||
// example of content on disk that could cause problems when taken to the HTML space.
|
||||
Path alert = one.resolve("onmouseclick='alert(oops)'");
|
||||
FS.touch(alert);
|
||||
|
||||
try (ResourceFactory.Closeable resourceFactory = ResourceFactory.closeable())
|
||||
{
|
||||
Resource resourceBase = resourceFactory.newResource(docRoot);
|
||||
Resource resource = resourceBase.resolve("one");
|
||||
|
||||
String content = ResourceListing.getAsXHTML(resource, "/context/one", false, null);
|
||||
assertTrue(isValidXHtml(content));
|
||||
|
||||
// Entry should be properly encoded
|
||||
assertThat(content, containsString("<a href=\"/context/one/onmouseclick=%27alert(oops)%27\">"));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValidXHtml(String content)
|
||||
{
|
||||
// we expect that our generated output conforms to text/xhtml is well-formed
|
||||
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8)))
|
||||
{
|
||||
Catalog catalog = CatalogXHTML.getCatalog();
|
||||
CatalogResolver resolver = CatalogManager.catalogResolver(catalog);
|
||||
|
||||
DocumentBuilderFactory xmlDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
xmlDocumentBuilderFactory.setValidating(true);
|
||||
DocumentBuilder db = xmlDocumentBuilderFactory.newDocumentBuilder();
|
||||
db.setEntityResolver(resolver);
|
||||
List<SAXParseException> errors = new ArrayList<>();
|
||||
db.setErrorHandler(new ErrorHandler()
|
||||
{
|
||||
@Override
|
||||
public void warning(SAXParseException exception)
|
||||
{
|
||||
exception.printStackTrace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(SAXParseException exception)
|
||||
{
|
||||
errors.add(exception);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fatalError(SAXParseException exception)
|
||||
{
|
||||
errors.add(exception);
|
||||
}
|
||||
});
|
||||
|
||||
// We consider this content to be XML well-formed if these 2 lines do not throw an Exception
|
||||
Document doc = db.parse(inputStream);
|
||||
doc.getDocumentElement().normalize();
|
||||
|
||||
if (errors.size() > 0)
|
||||
{
|
||||
IOException ioException = new IOException("Failed to validate XHTML");
|
||||
for (SAXException saxException : errors)
|
||||
{
|
||||
ioException.addSuppressed(saxException);
|
||||
}
|
||||
fail(ioException);
|
||||
}
|
||||
|
||||
return true; // it's well-formed
|
||||
}
|
||||
catch (IOException | ParserConfigurationException | SAXException e)
|
||||
{
|
||||
e.printStackTrace(System.err);
|
||||
return false; // XHTML has got issues
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to create the directory, skip testcase if not supported on OS.
|
||||
*/
|
||||
private static Path assumeMkDirSupported(Path path, String subpath)
|
||||
{
|
||||
Path ret = null;
|
||||
|
||||
try
|
||||
{
|
||||
ret = path.resolve(subpath);
|
||||
|
||||
if (Files.exists(ret))
|
||||
return ret;
|
||||
|
||||
Files.createDirectories(ret);
|
||||
}
|
||||
catch (InvalidPathException | IOException ignore)
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
assumeTrue(ret != null, "Directory creation not supported on OS: " + path + File.separator + subpath);
|
||||
assumeTrue(Files.exists(ret), "Directory creation not supported on OS: " + ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
|
@ -344,7 +344,7 @@ public class ContextHandlerGetResourceTest
|
|||
|
||||
Resource resource = context.getResource(path);
|
||||
assertNotNull(resource);
|
||||
assertEquals(context.getResource("/subdir/TextFile.Long.txt").getURI(), resource.getAlias());
|
||||
assertEquals(context.getResource("/subdir/TextFile.Long.txt").getURI(), resource.getTargetURI());
|
||||
|
||||
URL url = context.getServletContext().getResource(path);
|
||||
assertNotNull(url);
|
||||
|
|
|
@ -42,6 +42,7 @@ import org.eclipse.jetty.http.HttpField;
|
|||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.http.UriCompliance;
|
||||
import org.eclipse.jetty.logging.StacklessLogging;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
|
@ -2626,6 +2627,11 @@ public class ResourceHandlerTest
|
|||
assertThat(response.get(LOCATION), endsWith("/context/"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests to attempt to break out of the Context restrictions by
|
||||
* abusing encoding (or lack thereof), listing output,
|
||||
* welcome file behaviors, and more.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource("contextBreakoutScenarios")
|
||||
public void testListingContextBreakout(ResourceHandlerTest.Scenario scenario) throws Exception
|
||||
|
@ -2784,9 +2790,27 @@ public class ResourceHandlerTest
|
|||
assertThat(body, containsString("f??r"));
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Tests to ensure that when requesting a legit directory listing, you
|
||||
* cannot arbitrarily include XSS in the output via careful manipulation
|
||||
* of the request path.
|
||||
* </p>
|
||||
* <p>
|
||||
* This is mainly a test of how the raw request details evolve over time, and
|
||||
* migrate through the ResourceHandler before it hits the
|
||||
* ResourceListing.getAsXHTML for output production
|
||||
* </p>
|
||||
*/
|
||||
@Test
|
||||
public void testListingXSS() throws Exception
|
||||
{
|
||||
// Allow unsafe URI requests for this test case specifically
|
||||
// The requests below abuse the path-param features of URI, and the default UriCompliance mode
|
||||
// will prevent the use those requests as a 400 Bad Request: Ambiguous URI empty segment
|
||||
HttpConfiguration httpConfiguration = _local.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration();
|
||||
httpConfiguration.setUriCompliance(UriCompliance.UNSAFE);
|
||||
|
||||
/* create some content in the docroot */
|
||||
Path one = docRoot.resolve("one");
|
||||
FS.ensureDirExists(one);
|
||||
|
@ -2798,7 +2822,9 @@ public class ResourceHandlerTest
|
|||
|
||||
/*
|
||||
* Intentionally bad request URI. Sending a non-encoded URI with typically
|
||||
* encoded characters '<', '>', and '"'.
|
||||
* encoded characters '<', '>', and '"', using the path-param feature of the
|
||||
* URI spec to still produce a listing. This path-param value should not make it
|
||||
* down to the ResourceListing.getAsXHTML() method.
|
||||
*/
|
||||
String req1 = """
|
||||
GET /context/;<script>window.alert("hi");</script> HTTP/1.1\r
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# Jetty Logging using jetty-slf4j-impl
|
||||
#org.eclipse.jetty.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.util.LEVEL=DEBUG
|
||||
#org.eclipse.jetty.io.LEVEL=DEBUG
|
||||
|
|
|
@ -16,22 +16,6 @@
|
|||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<org.eclipse.jetty.test.client.transport.H3.enable>${h3.test.enabled}</org.eclipse.jetty.test.client.transport.H3.enable>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
|
|
|
@ -16,6 +16,7 @@ package org.eclipse.jetty.test.client.transport;
|
|||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -67,30 +68,24 @@ public class AbstractTest
|
|||
protected HttpClient client;
|
||||
protected Path unixDomainPath;
|
||||
|
||||
private static EnumSet<Transport> allTransports()
|
||||
public static Collection<Transport> transports()
|
||||
{
|
||||
EnumSet<Transport> transports = EnumSet.allOf(Transport.class);
|
||||
// Disable H3 tests unless explicitly enabled with a system property.
|
||||
if (!Boolean.getBoolean("org.eclipse.jetty.test.client.transport.H3.enable"))
|
||||
if ("ci".equals(System.getProperty("env")))
|
||||
transports.remove(Transport.H3);
|
||||
return transports;
|
||||
}
|
||||
|
||||
public static List<Transport> transports()
|
||||
public static Collection<Transport> transportsNoFCGI()
|
||||
{
|
||||
return List.copyOf(allTransports());
|
||||
}
|
||||
|
||||
public static List<Transport> transportsNoFCGI()
|
||||
{
|
||||
EnumSet<Transport> transports = allTransports();
|
||||
Collection<Transport> transports = transports();
|
||||
transports.remove(Transport.FCGI);
|
||||
return List.copyOf(transports);
|
||||
return transports;
|
||||
}
|
||||
|
||||
public static List<Transport> transportsNoUnixDomain()
|
||||
public static Collection<Transport> transportsNoUnixDomain()
|
||||
{
|
||||
EnumSet<Transport> transports = allTransports();
|
||||
Collection<Transport> transports = transports();
|
||||
transports.remove(Transport.UNIX_DOMAIN);
|
||||
return List.copyOf(transports);
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 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.test.client.transport;
|
||||
|
||||
// TODO: write 100 Continue tests for Handler (not Servlet) functionality.
|
||||
public class Continue100Test
|
||||
{
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2022 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.test.client.transport;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.util.StringRequestContent;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.io.Content;
|
||||
import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.Request;
|
||||
import org.eclipse.jetty.server.Response;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class HttpInterimResponseTest extends AbstractTest
|
||||
{
|
||||
@ParameterizedTest
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testImplicit100Continue(Transport transport) throws Exception
|
||||
{
|
||||
start(transport, new Handler.Processor()
|
||||
{
|
||||
@Override
|
||||
public void process(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
// Reading the request content immediately
|
||||
// issues an implicit 100 Continue response.
|
||||
Content.Source.consumeAll(request, callback);
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest(newURI(transport))
|
||||
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
|
||||
.body(new StringRequestContent("request-content"))
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testMultipleDifferentInterimResponses(Transport transport) throws Exception
|
||||
{
|
||||
start(transport, new Handler.Processor()
|
||||
{
|
||||
@Override
|
||||
public void process(Request request, Response response, Callback callback) throws Exception
|
||||
{
|
||||
CompletableFuture<Void> completable = response.writeInterim(HttpStatus.CONTINUE_100, HttpFields.EMPTY)
|
||||
.thenCompose(ignored -> Callback.Completable.with(c -> Content.Source.consumeAll(request, c)))
|
||||
.thenCompose(ignored ->
|
||||
{
|
||||
HttpFields.Mutable fields1 = HttpFields.build();
|
||||
fields1.put("Progress", "25%");
|
||||
return response.writeInterim(HttpStatus.PROCESSING_102, fields1);
|
||||
})
|
||||
.thenCompose(ignored ->
|
||||
{
|
||||
HttpFields.Mutable fields2 = HttpFields.build();
|
||||
fields2.put("Progress", "100%");
|
||||
return response.writeInterim(HttpStatus.PROCESSING_102, fields2);
|
||||
})
|
||||
.thenRun(() ->
|
||||
{
|
||||
response.setStatus(200);
|
||||
response.getHeaders().put("X-Header", "X-Value");
|
||||
})
|
||||
.thenCompose(ignored ->
|
||||
{
|
||||
HttpFields.Mutable hints = HttpFields.build();
|
||||
hints.put(HttpHeader.LINK, "</main.css>; rel=preload");
|
||||
return response.writeInterim(HttpStatus.EARLY_HINT_103, hints)
|
||||
.thenApply(i -> hints);
|
||||
})
|
||||
.thenCompose(hints1 ->
|
||||
{
|
||||
HttpFields.Mutable hints = HttpFields.build();
|
||||
hints.put(HttpHeader.LINK, "</style.css>; rel=preload");
|
||||
return response.writeInterim(HttpStatus.EARLY_HINT_103, hints)
|
||||
.thenApply(i -> HttpFields.build(hints1).add(hints));
|
||||
})
|
||||
.thenCompose(hints ->
|
||||
{
|
||||
response.getHeaders().put("X-Header-Foo", "Foo");
|
||||
response.getHeaders().add(hints);
|
||||
return Callback.Completable.with(c -> Content.Sink.write(response, true, "response-content", c));
|
||||
});
|
||||
callback.completeWith(completable);
|
||||
}
|
||||
});
|
||||
|
||||
ContentResponse response = client.newRequest(newURI(transport))
|
||||
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE))
|
||||
.body(new StringRequestContent("request-content"))
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertEquals("X-Value", response.getHeaders().get("X-Header"));
|
||||
assertEquals("Foo", response.getHeaders().get("X-Header-Foo"));
|
||||
assertThat(response.getHeaders().getValuesList(HttpHeader.LINK), contains("</main.css>; rel=preload", "</style.css>; rel=preload"));
|
||||
assertEquals("response-content", response.getContentAsString());
|
||||
}
|
||||
}
|
|
@ -477,6 +477,20 @@ public interface Callback extends Invocable
|
|||
*/
|
||||
class Completable extends CompletableFuture<Void> implements Callback
|
||||
{
|
||||
/**
|
||||
* <p>Creates a new {@code Completable} to be consumed by the given
|
||||
* {@code consumer}, then returns the newly created {@code Completable}.</p>
|
||||
*
|
||||
* @param consumer the code that consumes the newly created {@code Completable}
|
||||
* @return the newly created {@code Completable}
|
||||
*/
|
||||
public static Completable with(Consumer<Completable> consumer)
|
||||
{
|
||||
Completable completable = new Completable();
|
||||
consumer.accept(completable);
|
||||
return completable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a completable future given a callback.
|
||||
*
|
||||
|
|
|
@ -26,17 +26,6 @@ import org.slf4j.LoggerFactory;
|
|||
*/
|
||||
public interface Promise<C>
|
||||
{
|
||||
default void completeWith(CompletableFuture<C> cf)
|
||||
{
|
||||
cf.whenComplete((c, x) ->
|
||||
{
|
||||
if (x == null)
|
||||
succeeded(c);
|
||||
else
|
||||
failed(x);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Callback invoked when the operation completes.</p>
|
||||
*
|
||||
|
@ -135,6 +124,21 @@ public interface Promise<C>
|
|||
*/
|
||||
class Completable<S> extends CompletableFuture<S> implements Promise<S>
|
||||
{
|
||||
/**
|
||||
* <p>Creates a new {@code Completable} to be consumed by the given
|
||||
* {@code consumer}, then returns the newly created {@code Completable}.</p>
|
||||
*
|
||||
* @param consumer the code that consumes the newly created {@code Completable}
|
||||
* @return the newly created {@code Completable}
|
||||
* @param <R> the type of the result
|
||||
*/
|
||||
public static <R> Completable<R> with(Consumer<Promise<R>> consumer)
|
||||
{
|
||||
Completable<R> completable = new Completable<>();
|
||||
consumer.accept(completable);
|
||||
return completable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded(S result)
|
||||
{
|
||||
|
|
|
@ -48,79 +48,9 @@ public class PathResource extends Resource
|
|||
.build();
|
||||
|
||||
private final Path path;
|
||||
private final Path alias;
|
||||
private final URI uri;
|
||||
|
||||
private Path checkAliasPath()
|
||||
{
|
||||
Path abs = path;
|
||||
|
||||
// TODO: is this a valid shortcut?
|
||||
// If the path doesn't exist, then there's no alias to reference
|
||||
if (!Files.exists(path))
|
||||
return null;
|
||||
|
||||
/* Catch situation where the Path class has already normalized
|
||||
* the URI eg. input path "aa./foo.txt"
|
||||
* from an #resolve(String) is normalized away during
|
||||
* the creation of a Path object reference.
|
||||
* If the URI is different from the Path.toUri() then
|
||||
* we will just use the original URI to construct the
|
||||
* alias reference Path.
|
||||
*
|
||||
* We use the method `toUri(Path)` here, instead of
|
||||
* Path.toUri() to ensure that the path contains
|
||||
* a trailing slash if it's a directory, (something
|
||||
* not all FileSystems seem to support)
|
||||
*/
|
||||
if (!URIUtil.equalsIgnoreEncodings(uri, toUri(path)))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Use normalized path to get past navigational references like "/bar/../foo/test.txt"
|
||||
Path ref = Paths.get(uri.normalize());
|
||||
return ref.toRealPath();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// If the toRealPath() call fails, then let
|
||||
// the alias checking routines continue on
|
||||
// to other techniques.
|
||||
LOG.trace("IGNORED", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
if (!abs.isAbsolute())
|
||||
abs = path.toAbsolutePath();
|
||||
|
||||
// Any normalization difference means it's an alias,
|
||||
// and we don't want to bother further to follow
|
||||
// symlinks as it's an alias anyway.
|
||||
Path normal = path.normalize();
|
||||
if (!isSameName(abs, normal))
|
||||
return normal;
|
||||
|
||||
try
|
||||
{
|
||||
if (Files.isSymbolicLink(path))
|
||||
return path.getParent().resolve(Files.readSymbolicLink(path));
|
||||
if (Files.exists(path))
|
||||
{
|
||||
Path real = abs.toRealPath();
|
||||
if (!isSameName(abs, real))
|
||||
return real;
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.trace("IGNORED", e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("bad alias ({} {}) for {}", e.getClass().getName(), e.getMessage(), path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private boolean targetResolved = false;
|
||||
private Path targetPath;
|
||||
|
||||
/**
|
||||
* Test if the paths are the same name.
|
||||
|
@ -235,13 +165,12 @@ public class PathResource extends Resource
|
|||
|
||||
this.path = path;
|
||||
this.uri = uri;
|
||||
this.alias = checkAliasPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists()
|
||||
{
|
||||
return Files.exists(alias != null ? alias : path);
|
||||
return Files.exists(targetPath != null ? targetPath : path);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -325,9 +254,87 @@ public class PathResource extends Resource
|
|||
}
|
||||
|
||||
@Override
|
||||
public URI getAlias()
|
||||
public URI getTargetURI()
|
||||
{
|
||||
return this.alias == null ? null : this.alias.toUri();
|
||||
if (!targetResolved)
|
||||
{
|
||||
targetPath = resolveTargetPath();
|
||||
targetResolved = true;
|
||||
}
|
||||
if (targetPath == null)
|
||||
return null;
|
||||
return targetPath.toUri();
|
||||
}
|
||||
|
||||
private Path resolveTargetPath()
|
||||
{
|
||||
Path abs = path;
|
||||
|
||||
// TODO: is this a valid shortcut?
|
||||
// If the path doesn't exist, then there's no alias to reference
|
||||
if (!Files.exists(path))
|
||||
return null;
|
||||
|
||||
/* Catch situation where the Path class has already normalized
|
||||
* the URI eg. input path "aa./foo.txt"
|
||||
* from an #resolve(String) is normalized away during
|
||||
* the creation of a Path object reference.
|
||||
* If the URI is different from the Path.toUri() then
|
||||
* we will just use the original URI to construct the
|
||||
* alias reference Path.
|
||||
*
|
||||
* We use the method `toUri(Path)` here, instead of
|
||||
* Path.toUri() to ensure that the path contains
|
||||
* a trailing slash if it's a directory, (something
|
||||
* not all FileSystems seem to support)
|
||||
*/
|
||||
if (!URIUtil.equalsIgnoreEncodings(uri, toUri(path)))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Use normalized path to get past navigational references like "/bar/../foo/test.txt"
|
||||
Path ref = Paths.get(uri.normalize());
|
||||
return ref.toRealPath();
|
||||
}
|
||||
catch (IOException ioe)
|
||||
{
|
||||
// If the toRealPath() call fails, then let
|
||||
// the alias checking routines continue on
|
||||
// to other techniques.
|
||||
LOG.trace("IGNORED", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
if (!abs.isAbsolute())
|
||||
abs = path.toAbsolutePath();
|
||||
|
||||
// Any normalization difference means it's an alias,
|
||||
// and we don't want to bother further to follow
|
||||
// symlinks as it's an alias anyway.
|
||||
Path normal = path.normalize();
|
||||
if (!isSameName(abs, normal))
|
||||
return normal;
|
||||
|
||||
try
|
||||
{
|
||||
if (Files.isSymbolicLink(path))
|
||||
return path.getParent().resolve(Files.readSymbolicLink(path));
|
||||
if (Files.exists(path))
|
||||
{
|
||||
Path real = abs.toRealPath();
|
||||
if (!isSameName(abs, real))
|
||||
return real;
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
LOG.trace("IGNORED", e);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOG.warn("bad alias ({} {}) for {}", e.getClass().getName(), e.getMessage(), path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -325,15 +325,17 @@ public abstract class Resource implements Iterable<Resource>
|
|||
*/
|
||||
public boolean isAlias()
|
||||
{
|
||||
return getAlias() != null;
|
||||
return getTargetURI() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The canonical Alias for the Resource as a URI.
|
||||
* If this Resource is an alias pointing to a different location,
|
||||
* return the target location as URI.
|
||||
*
|
||||
* @return The canonical Alias of this resource or null if none.
|
||||
* @return The target URI location of this resource,
|
||||
* or null if there is no target URI location (eg: not an alias, or a symlink)
|
||||
*/
|
||||
public URI getAlias()
|
||||
public URI getTargetURI()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -285,7 +285,10 @@ public class ResourceCollection extends Resource
|
|||
List<Resource> result = new ArrayList<>();
|
||||
for (Resource r : _resources)
|
||||
{
|
||||
result.addAll(r.list());
|
||||
if (r.isDirectory())
|
||||
result.addAll(r.list());
|
||||
else
|
||||
result.add(r);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
package org.eclipse.jetty.util.ssl;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
@ -53,11 +54,10 @@ public class KeyStoreScanner extends ContainerLifeCycle implements Scanner.Discr
|
|||
if (Files.isDirectory(monitoredFile))
|
||||
throw new IllegalArgumentException("expected keystore file not directory");
|
||||
|
||||
if (keystoreResource.getAlias() != null)
|
||||
{
|
||||
// this resource has an alias, use the alias, as that's what's returned in the Scanner
|
||||
monitoredFile = Paths.get(keystoreResource.getAlias());
|
||||
}
|
||||
// Use real location of keystore (if different), so that change monitoring can work properly
|
||||
URI targetURI = keystoreResource.getTargetURI();
|
||||
if (targetURI != null)
|
||||
monitoredFile = Paths.get(targetURI);
|
||||
|
||||
keystoreFile = monitoredFile;
|
||||
if (LOG.isDebugEnabled())
|
||||
|
|
|
@ -84,7 +84,7 @@ public class FileSystemResourceTest
|
|||
assertThat(FileSystemPool.INSTANCE.mounts(), empty());
|
||||
}
|
||||
|
||||
private Matcher<Resource> hasNoAlias()
|
||||
private Matcher<Resource> hasNoTargetURI()
|
||||
{
|
||||
return new BaseMatcher<>()
|
||||
{
|
||||
|
@ -92,24 +92,24 @@ public class FileSystemResourceTest
|
|||
public boolean matches(Object item)
|
||||
{
|
||||
final Resource res = (Resource)item;
|
||||
return !res.isAlias();
|
||||
return res.getTargetURI() == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description)
|
||||
{
|
||||
description.appendText("getAlias should return null");
|
||||
description.appendText("getTargetURI should return null");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeMismatch(Object item, Description description)
|
||||
{
|
||||
description.appendText("was ").appendValue(((Resource)item).getAlias());
|
||||
description.appendText("was ").appendValue(((Resource)item).getTargetURI());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Matcher<Resource> isAliasFor(final Resource resource)
|
||||
private Matcher<Resource> isTargetFor(final Resource resource)
|
||||
{
|
||||
return new BaseMatcher<>()
|
||||
{
|
||||
|
@ -117,10 +117,10 @@ public class FileSystemResourceTest
|
|||
public boolean matches(Object item)
|
||||
{
|
||||
final Resource ritem = (Resource)item;
|
||||
final URI alias = ritem.getAlias();
|
||||
final URI alias = ritem.getTargetURI();
|
||||
if (alias == null)
|
||||
{
|
||||
return resource.getAlias() == null;
|
||||
return resource.getTargetURI() == null;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -131,13 +131,13 @@ public class FileSystemResourceTest
|
|||
@Override
|
||||
public void describeTo(Description description)
|
||||
{
|
||||
description.appendText("getAlias should return ").appendValue(resource.getURI());
|
||||
description.appendText("getTargetURI should return ").appendValue(resource.getURI());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeMismatch(Object item, Description description)
|
||||
{
|
||||
description.appendText("was ").appendValue(((Resource)item).getAlias());
|
||||
description.appendText("was ").appendValue(((Resource)item).getTargetURI());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -583,13 +583,13 @@ public class FileSystemResourceTest
|
|||
// Access to the same resource, but via a symlink means that they are not equivalent
|
||||
assertThat("foo.equals(bar)", resFoo.equals(resBar), is(false));
|
||||
|
||||
assertThat("resource.alias", resFoo, hasNoAlias());
|
||||
assertThat("resource.uri.alias", ResourceFactory.root().newResource(resFoo.getURI()), hasNoAlias());
|
||||
assertThat("resource.file.alias", ResourceFactory.root().newResource(resFoo.getPath()), hasNoAlias());
|
||||
assertThat("resource.targetURI", resFoo, hasNoTargetURI());
|
||||
assertThat("resource.uri.targetURI", ResourceFactory.root().newResource(resFoo.getURI()), hasNoTargetURI());
|
||||
assertThat("resource.file.targetURI", ResourceFactory.root().newResource(resFoo.getPath()), hasNoTargetURI());
|
||||
|
||||
assertThat("alias", resBar, isAliasFor(resFoo));
|
||||
assertThat("uri.alias", ResourceFactory.root().newResource(resBar.getURI()), isAliasFor(resFoo));
|
||||
assertThat("file.alias", ResourceFactory.root().newResource(resBar.getPath()), isAliasFor(resFoo));
|
||||
assertThat("targetURI", resBar, isTargetFor(resFoo));
|
||||
assertThat("uri.targetURI", ResourceFactory.root().newResource(resBar.getURI()), isTargetFor(resFoo));
|
||||
assertThat("file.targetURI", ResourceFactory.root().newResource(resBar.getPath()), isTargetFor(resFoo));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -623,13 +623,13 @@ public class FileSystemResourceTest
|
|||
// Access to the same resource, but via a symlink means that they are not equivalent
|
||||
assertThat("foo.equals(bar)", resFoo.equals(resBar), is(false));
|
||||
|
||||
assertThat("resource.alias", resFoo, hasNoAlias());
|
||||
assertThat("resource.uri.alias", ResourceFactory.root().newResource(resFoo.getURI()), hasNoAlias());
|
||||
assertThat("resource.file.alias", ResourceFactory.root().newResource(resFoo.getPath()), hasNoAlias());
|
||||
assertThat("resource.targetURI", resFoo, hasNoTargetURI());
|
||||
assertThat("resource.uri.targetURI", ResourceFactory.root().newResource(resFoo.getURI()), hasNoTargetURI());
|
||||
assertThat("resource.file.targetURI", ResourceFactory.root().newResource(resFoo.getPath()), hasNoTargetURI());
|
||||
|
||||
assertThat("alias", resBar, isAliasFor(resFoo));
|
||||
assertThat("uri.alias", ResourceFactory.root().newResource(resBar.getURI()), isAliasFor(resFoo));
|
||||
assertThat("file.alias", ResourceFactory.root().newResource(resBar.getPath()), isAliasFor(resFoo));
|
||||
assertThat("targetURI", resBar, isTargetFor(resFoo));
|
||||
assertThat("uri.targetURI", ResourceFactory.root().newResource(resBar.getURI()), isTargetFor(resFoo));
|
||||
assertThat("file.targetURI", ResourceFactory.root().newResource(resBar.getPath()), isTargetFor(resFoo));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -644,9 +644,9 @@ public class FileSystemResourceTest
|
|||
// Reference to actual resource that exists
|
||||
Resource resource = base.resolve("file");
|
||||
|
||||
assertThat("resource.alias", resource, hasNoAlias());
|
||||
assertThat("resource.uri.alias", ResourceFactory.root().newResource(resource.getURI()), hasNoAlias());
|
||||
assertThat("resource.file.alias", ResourceFactory.root().newResource(resource.getPath()), hasNoAlias());
|
||||
assertThat("resource.targetURI", resource, hasNoTargetURI());
|
||||
assertThat("resource.uri.targetURI", ResourceFactory.root().newResource(resource.getURI()), hasNoTargetURI());
|
||||
assertThat("resource.file.targetURI", ResourceFactory.root().newResource(resource.getPath()), hasNoTargetURI());
|
||||
|
||||
// On some case insensitive file systems, lets see if an alternate
|
||||
// case for the filename results in an alias reference
|
||||
|
@ -654,9 +654,9 @@ public class FileSystemResourceTest
|
|||
if (alias.exists())
|
||||
{
|
||||
// If it exists, it must be an alias
|
||||
assertThat("alias", alias, isAliasFor(resource));
|
||||
assertThat("alias.uri", ResourceFactory.root().newResource(alias.getURI()), isAliasFor(resource));
|
||||
assertThat("alias.file", ResourceFactory.root().newResource(alias.getPath()), isAliasFor(resource));
|
||||
assertThat("targetURI", alias, isTargetFor(resource));
|
||||
assertThat("targetURI.uri", ResourceFactory.root().newResource(alias.getURI()), isTargetFor(resource));
|
||||
assertThat("targetURI.file", ResourceFactory.root().newResource(alias.getPath()), isTargetFor(resource));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -682,9 +682,9 @@ public class FileSystemResourceTest
|
|||
// Long filename
|
||||
Resource resource = base.resolve("TextFile.Long.txt");
|
||||
|
||||
assertThat("resource.alias", resource, hasNoAlias());
|
||||
assertThat("resource.uri.alias", ResourceFactory.root().newResource(resource.getURI()), hasNoAlias());
|
||||
assertThat("resource.file.alias", ResourceFactory.root().newResource(resource.getPath()), hasNoAlias());
|
||||
assertThat("resource.targetURI", resource, hasNoTargetURI());
|
||||
assertThat("resource.uri.targetURI", ResourceFactory.root().newResource(resource.getURI()), hasNoTargetURI());
|
||||
assertThat("resource.file.targetURI", ResourceFactory.root().newResource(resource.getPath()), hasNoTargetURI());
|
||||
|
||||
// On some versions of Windows, the long filename can be referenced
|
||||
// via a short 8.3 equivalent filename.
|
||||
|
@ -692,9 +692,9 @@ public class FileSystemResourceTest
|
|||
if (alias.exists())
|
||||
{
|
||||
// If it exists, it must be an alias
|
||||
assertThat("alias", alias, isAliasFor(resource));
|
||||
assertThat("alias.uri", ResourceFactory.root().newResource(alias.getURI()), isAliasFor(resource));
|
||||
assertThat("alias.file", ResourceFactory.root().newResource(alias.getPath()), isAliasFor(resource));
|
||||
assertThat("targetURI", alias, isTargetFor(resource));
|
||||
assertThat("targetURI.uri", ResourceFactory.root().newResource(alias.getURI()), isTargetFor(resource));
|
||||
assertThat("targetURI.file", ResourceFactory.root().newResource(alias.getPath()), isTargetFor(resource));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -718,9 +718,9 @@ public class FileSystemResourceTest
|
|||
Resource base = ResourceFactory.root().newResource(dir);
|
||||
Resource resource = base.resolve("testfile");
|
||||
|
||||
assertThat("resource.alias", resource, hasNoAlias());
|
||||
assertThat("resource.uri.alias", ResourceFactory.root().newResource(resource.getURI()), hasNoAlias());
|
||||
assertThat("resource.file.alias", ResourceFactory.root().newResource(resource.getPath()), hasNoAlias());
|
||||
assertThat("resource.targetURI", resource, hasNoTargetURI());
|
||||
assertThat("resource.uri.targetURI", ResourceFactory.root().newResource(resource.getURI()), hasNoTargetURI());
|
||||
assertThat("resource.file.targetURI", ResourceFactory.root().newResource(resource.getPath()), hasNoTargetURI());
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -729,9 +729,9 @@ public class FileSystemResourceTest
|
|||
if (alias.exists())
|
||||
{
|
||||
// If it exists, it must be an alias
|
||||
assertThat("resource.alias", alias, isAliasFor(resource));
|
||||
assertThat("resource.uri.alias", ResourceFactory.root().newResource(alias.getURI()), isAliasFor(resource));
|
||||
assertThat("resource.file.alias", ResourceFactory.root().newResource(alias.getPath()), isAliasFor(resource));
|
||||
assertThat("resource.targetURI", alias, isTargetFor(resource));
|
||||
assertThat("resource.uri.targetURI", ResourceFactory.root().newResource(alias.getURI()), isTargetFor(resource));
|
||||
assertThat("resource.file.targetURI", ResourceFactory.root().newResource(alias.getPath()), isTargetFor(resource));
|
||||
}
|
||||
}
|
||||
catch (InvalidPathException e)
|
||||
|
@ -760,9 +760,9 @@ public class FileSystemResourceTest
|
|||
Resource base = ResourceFactory.root().newResource(dir);
|
||||
Resource resource = base.resolve("testfile");
|
||||
|
||||
assertThat("resource.alias", resource, hasNoAlias());
|
||||
assertThat("resource.uri.alias", ResourceFactory.root().newResource(resource.getURI()), hasNoAlias());
|
||||
assertThat("resource.file.alias", ResourceFactory.root().newResource(resource.getPath()), hasNoAlias());
|
||||
assertThat("resource.targetURI", resource, hasNoTargetURI());
|
||||
assertThat("resource.uri.targetURI", ResourceFactory.root().newResource(resource.getURI()), hasNoTargetURI());
|
||||
assertThat("resource.file.targetURI", ResourceFactory.root().newResource(resource.getPath()), hasNoTargetURI());
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -773,9 +773,9 @@ public class FileSystemResourceTest
|
|||
assumeTrue(alias.getURI().getScheme().equals("file"));
|
||||
|
||||
// If it exists, it must be an alias
|
||||
assertThat("resource.alias", alias, isAliasFor(resource));
|
||||
assertThat("resource.uri.alias", ResourceFactory.root().newResource(alias.getURI()), isAliasFor(resource));
|
||||
assertThat("resource.file.alias", ResourceFactory.root().newResource(alias.getPath()), isAliasFor(resource));
|
||||
assertThat("resource.targetURI", alias, isTargetFor(resource));
|
||||
assertThat("resource.uri.targetURI", ResourceFactory.root().newResource(alias.getURI()), isTargetFor(resource));
|
||||
assertThat("resource.file.targetURI", ResourceFactory.root().newResource(alias.getPath()), isTargetFor(resource));
|
||||
}
|
||||
}
|
||||
catch (InvalidPathException e)
|
||||
|
@ -804,9 +804,9 @@ public class FileSystemResourceTest
|
|||
Resource base = ResourceFactory.root().newResource(dir);
|
||||
Resource resource = base.resolve("testfile");
|
||||
|
||||
assertThat("resource.alias", resource, hasNoAlias());
|
||||
assertThat("resource.uri.alias", ResourceFactory.root().newResource(resource.getURI()), hasNoAlias());
|
||||
assertThat("resource.file.alias", ResourceFactory.root().newResource(resource.getPath()), hasNoAlias());
|
||||
assertThat("resource.targetURI", resource, hasNoTargetURI());
|
||||
assertThat("resource.uri.targetURI", ResourceFactory.root().newResource(resource.getURI()), hasNoTargetURI());
|
||||
assertThat("resource.file.targetURI", ResourceFactory.root().newResource(resource.getPath()), hasNoTargetURI());
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -815,9 +815,9 @@ public class FileSystemResourceTest
|
|||
if (alias.exists())
|
||||
{
|
||||
// If it exists, it must be an alias
|
||||
assertThat("resource.alias", alias, isAliasFor(resource));
|
||||
assertThat("resource.uri.alias", ResourceFactory.root().newResource(alias.getURI()), isAliasFor(resource));
|
||||
assertThat("resource.file.alias", ResourceFactory.root().newResource(alias.getPath()), isAliasFor(resource));
|
||||
assertThat("resource.targetURI", alias, isTargetFor(resource));
|
||||
assertThat("resource.uri.targetURI", ResourceFactory.root().newResource(alias.getURI()), isTargetFor(resource));
|
||||
assertThat("resource.file.targetURI", ResourceFactory.root().newResource(alias.getPath()), isTargetFor(resource));
|
||||
}
|
||||
}
|
||||
catch (InvalidPathException e)
|
||||
|
@ -844,7 +844,7 @@ public class FileSystemResourceTest
|
|||
|
||||
Resource base = ResourceFactory.root().newResource(dir);
|
||||
Resource res = base.resolve("foo;");
|
||||
assertThat("Alias: " + res, res, hasNoAlias());
|
||||
assertThat("Target URI: " + res, res, hasNoTargetURI());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1039,25 +1039,25 @@ public class FileSystemResourceTest
|
|||
|
||||
Resource fileres = ResourceFactory.root().newResource(refQuoted);
|
||||
assertThat("Exists: " + refQuoted, fileres.exists(), is(true));
|
||||
assertThat("Alias: " + refQuoted, fileres, hasNoAlias());
|
||||
assertThat("Target URI: " + refQuoted, fileres, hasNoTargetURI());
|
||||
|
||||
URI refEncoded = dir.toUri().resolve("foo%27s.txt");
|
||||
|
||||
fileres = ResourceFactory.root().newResource(refEncoded);
|
||||
assertThat("Exists: " + refEncoded, fileres.exists(), is(true));
|
||||
assertThat("Alias: " + refEncoded, fileres, hasNoAlias());
|
||||
assertThat("Target URI: " + refEncoded, fileres, hasNoTargetURI());
|
||||
|
||||
URI refQuoteSpace = dir.toUri().resolve("f%20o's.txt");
|
||||
|
||||
fileres = ResourceFactory.root().newResource(refQuoteSpace);
|
||||
assertThat("Exists: " + refQuoteSpace, fileres.exists(), is(true));
|
||||
assertThat("Alias: " + refQuoteSpace, fileres, hasNoAlias());
|
||||
assertThat("Target URI: " + refQuoteSpace, fileres, hasNoTargetURI());
|
||||
|
||||
URI refEncodedSpace = dir.toUri().resolve("f%20o%27s.txt");
|
||||
|
||||
fileres = ResourceFactory.root().newResource(refEncodedSpace);
|
||||
assertThat("Exists: " + refEncodedSpace, fileres.exists(), is(true));
|
||||
assertThat("Alias: " + refEncodedSpace, fileres, hasNoAlias());
|
||||
assertThat("Target URI: " + refEncodedSpace, fileres, hasNoTargetURI());
|
||||
|
||||
URI refA = dir.toUri().resolve("foo's.txt");
|
||||
URI refB = dir.toUri().resolve("foo%27s.txt");
|
||||
|
@ -1143,7 +1143,7 @@ public class FileSystemResourceTest
|
|||
try
|
||||
{
|
||||
assertThat("Exists: " + basePath, base.exists(), is(true));
|
||||
assertThat("Alias: " + basePath, base, hasNoAlias());
|
||||
assertThat("Target URI: " + basePath, base, hasNoTargetURI());
|
||||
|
||||
Resource r = base.resolve("aa%5C/foo.txt");
|
||||
assertThat("getURI()", r.getPath().toString(), containsString("aa\\/foo.txt"));
|
||||
|
@ -1152,8 +1152,8 @@ public class FileSystemResourceTest
|
|||
if (org.junit.jupiter.api.condition.OS.WINDOWS.isCurrentOs())
|
||||
{
|
||||
assertThat("isAlias()", r.isAlias(), is(true));
|
||||
assertThat("getAlias()", r.getAlias(), notNullValue());
|
||||
assertThat("getAlias()", r.getAlias().toASCIIString(), containsString("aa/foo.txt"));
|
||||
assertThat("getTargetURI()", r.getTargetURI(), notNullValue());
|
||||
assertThat("getTargetURI()", r.getTargetURI().toASCIIString(), containsString("aa/foo.txt"));
|
||||
assertThat("Exists: " + r, r.exists(), is(true));
|
||||
}
|
||||
else
|
||||
|
@ -1186,7 +1186,7 @@ public class FileSystemResourceTest
|
|||
try
|
||||
{
|
||||
assertThat("Exists: " + basePath, base.exists(), is(true));
|
||||
assertThat("Alias: " + basePath, base, hasNoAlias());
|
||||
assertThat("Target URI: " + basePath, base, hasNoTargetURI());
|
||||
|
||||
Resource r = base.resolve("aa./foo.txt");
|
||||
assertThat("getURI()", r.getURI().toASCIIString(), containsString("aa./foo.txt"));
|
||||
|
@ -1194,8 +1194,8 @@ public class FileSystemResourceTest
|
|||
if (OS.WINDOWS.isCurrentOs())
|
||||
{
|
||||
assertThat("isAlias()", r.isAlias(), is(true));
|
||||
assertThat("getAlias()", r.getAlias(), notNullValue());
|
||||
assertThat("getAlias()", r.getAlias().toASCIIString(), containsString("aa/foo.txt"));
|
||||
assertThat("getTargetURI()", r.getTargetURI(), notNullValue());
|
||||
assertThat("getTargetURI()", r.getTargetURI().toASCIIString(), containsString("aa/foo.txt"));
|
||||
assertThat("Exists: " + r, r.exists(), is(true));
|
||||
}
|
||||
else
|
||||
|
@ -1226,7 +1226,7 @@ public class FileSystemResourceTest
|
|||
try
|
||||
{
|
||||
assertThat("Exists: " + basePath, base.exists(), is(true));
|
||||
assertThat("Alias: " + basePath, base, hasNoAlias());
|
||||
assertThat("Target URI: " + basePath, base, hasNoTargetURI());
|
||||
|
||||
Resource r = base.resolve("/foo.txt");
|
||||
assertThat("getURI()", r.getURI().toASCIIString(), containsString("/foo.txt"));
|
||||
|
@ -1256,13 +1256,13 @@ public class FileSystemResourceTest
|
|||
try
|
||||
{
|
||||
assertThat("Exists: " + basePath, base.exists(), is(true));
|
||||
assertThat("Alias: " + basePath, base, hasNoAlias());
|
||||
assertThat("Target URI: " + basePath, base, hasNoTargetURI());
|
||||
|
||||
Resource r = base.resolve("//foo.txt");
|
||||
assertThat("getURI()", r.getURI().toASCIIString(), containsString("/foo.txt"));
|
||||
|
||||
assertThat("isAlias()", r.isAlias(), is(false));
|
||||
assertThat("getAlias()", r.getAlias(), nullValue());
|
||||
assertThat("getTargetURI()", r.getTargetURI(), nullValue());
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
|
@ -1288,13 +1288,13 @@ public class FileSystemResourceTest
|
|||
try
|
||||
{
|
||||
assertThat("Exists: " + basePath, base.exists(), is(true));
|
||||
assertThat("Alias: " + basePath, base, hasNoAlias());
|
||||
assertThat("Target URI: " + basePath, base, hasNoTargetURI());
|
||||
|
||||
Resource r = base.resolve("aa//foo.txt");
|
||||
assertThat("getURI()", r.getURI().toASCIIString(), containsString("aa/foo.txt"));
|
||||
|
||||
assertThat("isAlias()", r.isAlias(), is(false));
|
||||
assertThat("getAlias()", r.getAlias(), nullValue());
|
||||
assertThat("getTargetURI()", r.getTargetURI(), nullValue());
|
||||
}
|
||||
catch (IllegalArgumentException e)
|
||||
{
|
||||
|
@ -1342,11 +1342,11 @@ public class FileSystemResourceTest
|
|||
|
||||
Resource base = ResourceFactory.root().newResource(utf8Dir);
|
||||
assertThat("Exists: " + utf8Dir, base.exists(), is(true));
|
||||
assertThat("Alias: " + utf8Dir, base, hasNoAlias());
|
||||
assertThat("Target URI: " + utf8Dir, base, hasNoTargetURI());
|
||||
|
||||
Resource r = base.resolve("file.txt");
|
||||
assertThat("Exists: " + r, r.exists(), is(true));
|
||||
assertThat("Alias: " + r, r, hasNoAlias());
|
||||
assertThat("Target URI: " + r, r, hasNoTargetURI());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1357,7 +1357,7 @@ public class FileSystemResourceTest
|
|||
Resource resource = base.resolve("WEB-INF/");
|
||||
assertThat("getURI()", resource.getURI().toASCIIString(), containsString("path/WEB-INF/"));
|
||||
assertThat("isAlias()", resource.isAlias(), is(false));
|
||||
assertThat("getAlias()", resource.getAlias(), nullValue());
|
||||
assertThat("getTargetURI()", resource.getTargetURI(), nullValue());
|
||||
}
|
||||
|
||||
private String toString(Resource resource) throws IOException
|
||||
|
|
|
@ -303,37 +303,37 @@ public class PathResourceTest
|
|||
// Test not alias paths
|
||||
Resource resource = resourceFactory.newResource(file);
|
||||
assertTrue(resource.exists());
|
||||
assertNull(resource.getAlias());
|
||||
assertNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file.toAbsolutePath());
|
||||
assertTrue(resource.exists());
|
||||
assertNull(resource.getAlias());
|
||||
assertNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file.toUri());
|
||||
assertTrue(resource.exists());
|
||||
assertNull(resource.getAlias());
|
||||
assertNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file.toUri().toString());
|
||||
assertTrue(resource.exists());
|
||||
assertNull(resource.getAlias());
|
||||
assertNull(resource.getTargetURI());
|
||||
resource = archiveResource.resolve("test.txt");
|
||||
assertTrue(resource.exists());
|
||||
assertNull(resource.getAlias());
|
||||
assertNull(resource.getTargetURI());
|
||||
|
||||
// Test alias paths
|
||||
resource = resourceFactory.newResource(file0);
|
||||
assertTrue(resource.exists());
|
||||
assertNotNull(resource.getAlias());
|
||||
assertNotNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file0.toAbsolutePath());
|
||||
assertTrue(resource.exists());
|
||||
assertNotNull(resource.getAlias());
|
||||
assertNotNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file0.toUri());
|
||||
assertTrue(resource.exists());
|
||||
assertNotNull(resource.getAlias());
|
||||
assertNotNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file0.toUri().toString());
|
||||
assertTrue(resource.exists());
|
||||
assertNotNull(resource.getAlias());
|
||||
assertNotNull(resource.getTargetURI());
|
||||
|
||||
resource = archiveResource.resolve("test.txt\0");
|
||||
assertTrue(resource.exists());
|
||||
assertNotNull(resource.getAlias());
|
||||
assertNotNull(resource.getTargetURI());
|
||||
}
|
||||
catch (InvalidPathException e)
|
||||
{
|
||||
|
@ -402,9 +402,9 @@ public class PathResourceTest
|
|||
assertThat("resource.uri.alias", resourceFactory.newResource(resFoo.getURI()).isAlias(), is(false));
|
||||
assertThat("resource.file.alias", resourceFactory.newResource(resFoo.getPath()).isAlias(), is(false));
|
||||
|
||||
assertThat("alias", resBar.getAlias(), is(resFoo.getURI()));
|
||||
assertThat("uri.alias", resourceFactory.newResource(resBar.getURI()).getAlias(), is(resFoo.getURI()));
|
||||
assertThat("file.alias", resourceFactory.newResource(resBar.getPath()).getAlias(), is(resFoo.getURI()));
|
||||
assertThat("targetURI", resBar.getTargetURI(), is(resFoo.getURI()));
|
||||
assertThat("uri.targetURI", resourceFactory.newResource(resBar.getURI()).getTargetURI(), is(resFoo.getURI()));
|
||||
assertThat("file.targetURI", resourceFactory.newResource(resBar.getPath()).getTargetURI(), is(resFoo.getURI()));
|
||||
}
|
||||
catch (InvalidPathException e)
|
||||
{
|
||||
|
|
|
@ -132,37 +132,37 @@ public class ResourceAliasTest
|
|||
// Test not alias paths
|
||||
Resource resource = resourceFactory.newResource(file);
|
||||
assertTrue(resource.exists());
|
||||
assertNull(resource.getAlias());
|
||||
assertNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file.toAbsolutePath());
|
||||
assertTrue(resource.exists());
|
||||
assertNull(resource.getAlias());
|
||||
assertNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file.toUri());
|
||||
assertTrue(resource.exists());
|
||||
assertNull(resource.getAlias());
|
||||
assertNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file.toUri().toString());
|
||||
assertTrue(resource.exists());
|
||||
assertNull(resource.getAlias());
|
||||
assertNull(resource.getTargetURI());
|
||||
resource = dir.resolve("test.txt");
|
||||
assertTrue(resource.exists());
|
||||
assertNull(resource.getAlias());
|
||||
assertNull(resource.getTargetURI());
|
||||
|
||||
// Test alias paths
|
||||
resource = resourceFactory.newResource(file0);
|
||||
assertTrue(resource.exists());
|
||||
assertNotNull(resource.getAlias());
|
||||
assertNotNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file0.toAbsolutePath());
|
||||
assertTrue(resource.exists());
|
||||
assertNotNull(resource.getAlias());
|
||||
assertNotNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file0.toUri());
|
||||
assertTrue(resource.exists());
|
||||
assertNotNull(resource.getAlias());
|
||||
assertNotNull(resource.getTargetURI());
|
||||
resource = resourceFactory.newResource(file0.toUri().toString());
|
||||
assertTrue(resource.exists());
|
||||
assertNotNull(resource.getAlias());
|
||||
assertNotNull(resource.getTargetURI());
|
||||
|
||||
resource = dir.resolve("test.txt\0");
|
||||
assertTrue(resource.exists());
|
||||
assertNotNull(resource.getAlias());
|
||||
assertNotNull(resource.getTargetURI());
|
||||
}
|
||||
catch (InvalidPathException e)
|
||||
{
|
||||
|
|
|
@ -386,7 +386,7 @@ public class ResourceTest
|
|||
assertNotNull(dot);
|
||||
assertTrue(dot.exists());
|
||||
assertTrue(dot.isAlias(), "Reference to '.' is an alias to itself");
|
||||
assertTrue(Files.isSameFile(dot.getPath(), Paths.get(dot.getAlias())));
|
||||
assertTrue(Files.isSameFile(dot.getPath(), Paths.get(dot.getTargetURI())));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -413,7 +413,7 @@ public class ResourceTest
|
|||
assertNotNull(dot);
|
||||
assertTrue(dot.exists());
|
||||
assertTrue(dot.isAlias(), "Reference to '.' is an alias to itself");
|
||||
assertTrue(Files.isSameFile(dot.getPath(), Paths.get(dot.getAlias())));
|
||||
assertTrue(Files.isSameFile(dot.getPath(), Paths.get(dot.getTargetURI())));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -23,7 +23,6 @@ import org.eclipse.jetty.ee10.servlet.BaseHolder;
|
|||
import org.eclipse.jetty.ee10.servlet.Source.Origin;
|
||||
import org.eclipse.jetty.ee10.webapp.WebAppContext;
|
||||
import org.eclipse.jetty.ee10.webapp.WebDescriptor;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.eclipse.jetty.util.thread.AutoLock;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -169,7 +168,7 @@ public class AnnotationIntrospector
|
|||
String descriptorLocation = holder.getSource().getResource();
|
||||
if (descriptorLocation == null)
|
||||
return true; //no descriptor, can't be metadata-complete
|
||||
return !WebDescriptor.isMetaDataComplete(_context.getMetaData().getFragmentDescriptor(ResourceFactory.of(_context).newResource(descriptorLocation)));
|
||||
return !WebDescriptor.isMetaDataComplete(_context.getMetaData().getFragmentDescriptor(_context.getResourceFactory().newResource(descriptorLocation)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import org.eclipse.jetty.ee10.plus.webapp.EnvConfiguration;
|
|||
import org.eclipse.jetty.ee10.plus.webapp.PlusConfiguration;
|
||||
import org.eclipse.jetty.ee10.webapp.WebAppContext;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
||||
/**
|
||||
* ServerWithJNDI
|
||||
|
@ -37,7 +36,7 @@ public class ServerWithJNDI
|
|||
WebAppContext webapp = new WebAppContext();
|
||||
webapp.setContextPath("/");
|
||||
Path testJndiWar = JettyDemos.find("jetty-ee10-demo-jndi-webapp/target/jetty-ee10-demo-jndi-webapp-@VER@.war");
|
||||
webapp.setWarResource(ResourceFactory.of(webapp).newResource(testJndiWar));
|
||||
webapp.setWarResource(webapp.getResourceFactory().newResource(testJndiWar));
|
||||
server.setHandler(webapp);
|
||||
|
||||
// Enable parsing of jndi-related parts of web.xml and jetty-env.xml
|
||||
|
|
|
@ -115,7 +115,7 @@ public class TestServer
|
|||
Path webappBase = webappProjectRoot.resolve("jetty-ee10/jetty-ee10-demos/jetty-ee10-demo-jetty-webapp/src/main/webapp");
|
||||
if (!Files.exists(webappBase))
|
||||
throw new FileNotFoundException(webappBase.toString());
|
||||
webapp.setBaseResource(ResourceFactory.of(server).newResource(webappBase));
|
||||
webapp.setBaseResource(webapp.getResourceFactory().newResource(webappBase));
|
||||
webapp.setAttribute(MetaInfConfiguration.CONTAINER_JAR_PATTERN,
|
||||
".*/test-jetty-webapp/target/classes.*$|" +
|
||||
".*/jakarta.servlet.api-[^/]*\\.jar$|.*/jakarta.servlet.jsp.jstl-.*\\.jar$|.*/org.apache.taglibs.taglibs-standard.*\\.jar$"
|
||||
|
|
|
@ -21,10 +21,11 @@ import java.util.List;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.maven.artifact.Artifact;
|
||||
import org.apache.maven.plugin.AbstractMojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoFailureException;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
||||
/**
|
||||
* Base class for all goals that operate on unassembled webapps.
|
||||
|
@ -104,18 +105,25 @@ public abstract class AbstractUnassembledWebAppMojo extends AbstractWebAppMojo
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void configureWebApp() throws Exception
|
||||
protected void configureWebApp() throws AbstractMojoExecutionException
|
||||
{
|
||||
super.configureWebApp();
|
||||
configureUnassembledWebApp();
|
||||
try
|
||||
{
|
||||
configureUnassembledWebApp();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new MojoFailureException("Unable to configure unassembled webapp", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a webapp that has not been assembled into a war.
|
||||
*
|
||||
* @throws Exception
|
||||
* @throws IOException
|
||||
*/
|
||||
protected void configureUnassembledWebApp() throws Exception
|
||||
protected void configureUnassembledWebApp() throws IOException
|
||||
{
|
||||
//Set up the location of the webapp.
|
||||
//There are 2 parts to this: setWar() and setBaseResource(). On standalone jetty,
|
||||
|
@ -131,7 +139,7 @@ public abstract class AbstractUnassembledWebAppMojo extends AbstractWebAppMojo
|
|||
//Use the default static resource location
|
||||
if (!webAppSourceDirectory.exists())
|
||||
webAppSourceDirectory.mkdirs();
|
||||
originalBaseResource = ResourceFactory.of(webApp).newResource(webAppSourceDirectory.getCanonicalPath());
|
||||
originalBaseResource = webApp.getResourceFactory().newResource(webAppSourceDirectory.getCanonicalPath());
|
||||
}
|
||||
else
|
||||
originalBaseResource = webApp.getBaseResource();
|
||||
|
@ -167,7 +175,7 @@ public abstract class AbstractUnassembledWebAppMojo extends AbstractWebAppMojo
|
|||
//Has an explicit web.xml file been configured to use?
|
||||
if (webXml != null)
|
||||
{
|
||||
Resource r = ResourceFactory.of(webApp).newResource(webXml.toPath());
|
||||
Resource r = webApp.getResourceFactory().newResource(webXml.toPath());
|
||||
if (r.exists() && !r.isDirectory())
|
||||
{
|
||||
webApp.setDescriptor(r.toString());
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.apache.maven.artifact.repository.ArtifactRepository;
|
|||
import org.apache.maven.execution.MavenSession;
|
||||
import org.apache.maven.model.Dependency;
|
||||
import org.apache.maven.plugin.AbstractMojo;
|
||||
import org.apache.maven.plugin.AbstractMojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoFailureException;
|
||||
import org.apache.maven.plugin.descriptor.PluginDescriptor;
|
||||
|
@ -781,7 +782,7 @@ public abstract class AbstractWebAppMojo extends AbstractMojo
|
|||
* @throws Exception
|
||||
*/
|
||||
protected void configureWebApp()
|
||||
throws Exception
|
||||
throws AbstractMojoExecutionException
|
||||
{
|
||||
if (webApp == null)
|
||||
webApp = new MavenWebAppContext();
|
||||
|
|
|
@ -14,8 +14,11 @@
|
|||
package org.eclipse.jetty.ee10.maven.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.apache.maven.plugin.AbstractMojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoFailureException;
|
||||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
import org.apache.maven.plugins.annotations.ResolutionScope;
|
||||
|
@ -36,21 +39,30 @@ public class JettyEffectiveWebXml extends AbstractUnassembledWebAppMojo
|
|||
protected File effectiveWebXml;
|
||||
|
||||
@Override
|
||||
public void configureWebApp() throws Exception
|
||||
public void configureWebApp() throws AbstractMojoExecutionException
|
||||
{
|
||||
//Use a nominated war file for which to generate the effective web.xml, or
|
||||
//if that is not set, try to use the details of the current project's
|
||||
//unassembled webapp
|
||||
super.configureWebApp();
|
||||
if (StringUtil.isBlank(webApp.getWar()))
|
||||
try
|
||||
{
|
||||
if (StringUtil.isBlank(webApp.getWar()))
|
||||
{
|
||||
super.configureUnassembledWebApp();
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new MojoFailureException("Unable to configure unassembled webapp", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Override so we can call the parent's method in a different order.
|
||||
*/
|
||||
@Override
|
||||
protected void configureUnassembledWebApp() throws Exception
|
||||
protected void configureUnassembledWebApp()
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -76,7 +88,7 @@ public class JettyEffectiveWebXml extends AbstractUnassembledWebAppMojo
|
|||
{
|
||||
try
|
||||
{
|
||||
QuickStartGenerator generator = new QuickStartGenerator(effectiveWebXml, webApp);
|
||||
QuickStartGenerator generator = new QuickStartGenerator(effectiveWebXml.toPath(), webApp);
|
||||
generator.generate();
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
|
@ -29,7 +29,6 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.server.ShutdownMonitor;
|
||||
import org.eclipse.jetty.server.handler.ContextHandler;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
|
||||
/**
|
||||
* JettyEmbedded
|
||||
|
@ -185,7 +184,7 @@ public class JettyEmbedder extends AbstractLifeCycle
|
|||
this.stopKey = stopKey;
|
||||
}
|
||||
|
||||
public void setWebApp(MavenWebAppContext app) throws Exception
|
||||
public void setWebApp(MavenWebAppContext app)
|
||||
{
|
||||
webApp = app;
|
||||
}
|
||||
|
@ -280,7 +279,7 @@ public class JettyEmbedder extends AbstractLifeCycle
|
|||
Path qs = webApp.getTempDirectory().toPath().resolve("quickstart-web.xml");
|
||||
if (Files.exists(qs) && Files.isRegularFile(qs))
|
||||
{
|
||||
webApp.setAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML, ResourceFactory.of(webApp).newResource(qs));
|
||||
webApp.setAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML, qs);
|
||||
webApp.addConfiguration(new MavenQuickStartConfiguration());
|
||||
webApp.setAttribute(QuickStartConfiguration.MODE, Mode.QUICKSTART);
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ package org.eclipse.jetty.ee10.maven.plugin;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
|
@ -51,10 +50,10 @@ public class JettyForkedChild extends ContainerLifeCycle
|
|||
|
||||
/**
|
||||
* @param args arguments that were passed to main
|
||||
* @throws Exception
|
||||
* @throws IOException if unable to configure
|
||||
*/
|
||||
public JettyForkedChild(String[] args)
|
||||
throws Exception
|
||||
throws IOException
|
||||
{
|
||||
jetty = new JettyEmbedder();
|
||||
configure(args);
|
||||
|
@ -64,10 +63,10 @@ public class JettyForkedChild extends ContainerLifeCycle
|
|||
* Based on the args passed to the program, configure jetty.
|
||||
*
|
||||
* @param args args that were passed to the program.
|
||||
* @throws Exception
|
||||
* @throws IOException if unable to load webprops
|
||||
*/
|
||||
public void configure(String[] args)
|
||||
throws Exception
|
||||
throws IOException
|
||||
{
|
||||
Map<String, String> jettyProperties = new HashMap<>();
|
||||
|
||||
|
@ -171,10 +170,9 @@ public class JettyForkedChild extends ContainerLifeCycle
|
|||
* present.
|
||||
*
|
||||
* @return file contents as properties
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
private Properties loadWebAppProps() throws FileNotFoundException, IOException
|
||||
private Properties loadWebAppProps() throws IOException
|
||||
{
|
||||
Properties props = new Properties();
|
||||
if (Objects.nonNull(webAppPropsFile))
|
||||
|
|
|
@ -112,9 +112,9 @@ public class JettyForker extends AbstractForker
|
|||
throws Exception
|
||||
{
|
||||
//Run the webapp to create the quickstart file and properties file
|
||||
generator = new QuickStartGenerator(forkWebXml, webApp);
|
||||
generator = new QuickStartGenerator(forkWebXml.toPath(), webApp);
|
||||
generator.setContextXml(contextXml);
|
||||
generator.setWebAppPropsFile(webAppPropsFile);
|
||||
generator.setWebAppProps(webAppPropsFile.toPath());
|
||||
generator.setServer(server);
|
||||
generator.generate();
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ import org.eclipse.jetty.util.IncludeExcludeSet;
|
|||
import org.eclipse.jetty.util.Scanner;
|
||||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.eclipse.jetty.util.thread.Scheduler;
|
||||
|
||||
/**
|
||||
|
@ -220,7 +219,7 @@ public class JettyRunMojo extends AbstractUnassembledWebAppMojo
|
|||
{
|
||||
if (webApp.getDescriptor() != null)
|
||||
{
|
||||
Resource r = ResourceFactory.of(webApp).newResource(webApp.getDescriptor());
|
||||
Resource r = webApp.getResourceFactory().newResource(webApp.getDescriptor());
|
||||
scanner.addFile(r.getPath());
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import java.nio.file.Paths;
|
|||
import java.util.Date;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.maven.plugin.AbstractMojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugins.annotations.Execute;
|
||||
import org.apache.maven.plugins.annotations.LifecyclePhase;
|
||||
|
@ -67,7 +68,7 @@ public class JettyRunWarMojo extends AbstractWebAppMojo
|
|||
protected Path war;
|
||||
|
||||
@Override
|
||||
public void configureWebApp() throws Exception
|
||||
public void configureWebApp() throws AbstractMojoExecutionException
|
||||
{
|
||||
super.configureWebApp();
|
||||
//if no war has been explicitly configured, use the one from the webapp project
|
||||
|
|
|
@ -16,6 +16,7 @@ package org.eclipse.jetty.ee10.maven.plugin;
|
|||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.apache.maven.plugin.AbstractMojoExecutionException;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
import org.apache.maven.plugins.annotations.Mojo;
|
||||
import org.apache.maven.plugins.annotations.Parameter;
|
||||
|
@ -59,7 +60,7 @@ public class JettyStartWarMojo extends AbstractWebAppMojo
|
|||
protected JettyHomeForker homeForker;
|
||||
|
||||
@Override
|
||||
public void configureWebApp() throws Exception
|
||||
public void configureWebApp() throws AbstractMojoExecutionException
|
||||
{
|
||||
super.configureWebApp();
|
||||
//if a war has not been explicitly configured, use the one from the project
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.eclipse.jetty.ee10.webapp.MetaInfConfiguration;
|
|||
import org.eclipse.jetty.ee10.webapp.WebAppContext;
|
||||
import org.eclipse.jetty.util.FileID;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -68,7 +67,7 @@ public class MavenMetaInfConfiguration extends MetaInfConfiguration
|
|||
try
|
||||
{
|
||||
LOG.debug(" add resource to resources to examine {}", file);
|
||||
list.add(ResourceFactory.of(context).newResource(file.toURI()));
|
||||
list.add(context.getResourceFactory().newResource(file.toURI()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -102,7 +101,7 @@ public class MavenMetaInfConfiguration extends MetaInfConfiguration
|
|||
{
|
||||
try
|
||||
{
|
||||
list.add(ResourceFactory.of(context).newResource(file.toURI()));
|
||||
list.add(context.getResourceFactory().newResource(file.toURI()));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
@ -41,7 +41,6 @@ import org.eclipse.jetty.util.StringUtil;
|
|||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -100,7 +99,7 @@ public class MavenWebAppContext extends WebAppContext
|
|||
*/
|
||||
private boolean _baseAppFirst = true;
|
||||
|
||||
public MavenWebAppContext() throws Exception
|
||||
public MavenWebAppContext()
|
||||
{
|
||||
super();
|
||||
// Turn off copyWebInf option as it is not applicable for plugin.
|
||||
|
@ -232,7 +231,7 @@ public class MavenWebAppContext extends WebAppContext
|
|||
.map(URI::create)
|
||||
.toList();
|
||||
|
||||
setBaseResource(ResourceFactory.of(this).newResource(uris));
|
||||
setBaseResource(this.getResourceFactory().newResource(uris));
|
||||
}
|
||||
catch (Throwable t)
|
||||
{
|
||||
|
@ -319,7 +318,7 @@ public class MavenWebAppContext extends WebAppContext
|
|||
for (Configuration c : configurations)
|
||||
{
|
||||
if (c instanceof EnvConfiguration)
|
||||
((EnvConfiguration)c).setJettyEnvResource(ResourceFactory.of(this).newResource(getJettyEnvXml()));
|
||||
((EnvConfiguration)c).setJettyEnvResource(this.getResourceFactory().newResource(getJettyEnvXml()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -383,9 +382,9 @@ public class MavenWebAppContext extends WebAppContext
|
|||
// return the resource matching the web-inf classes
|
||||
// rather than the test classes
|
||||
if (_classes != null)
|
||||
return ResourceFactory.of(this).newResource(_classes.toPath());
|
||||
return this.getResourceFactory().newResource(_classes.toPath());
|
||||
else if (_testClasses != null)
|
||||
return ResourceFactory.of(this).newResource(_testClasses.toPath());
|
||||
return this.getResourceFactory().newResource(_testClasses.toPath());
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -395,7 +394,7 @@ public class MavenWebAppContext extends WebAppContext
|
|||
while (res == null && (i < _webInfClasses.size()))
|
||||
{
|
||||
String newPath = StringUtil.replace(uri, WEB_INF_CLASSES_PREFIX, _webInfClasses.get(i).getPath());
|
||||
res = ResourceFactory.of(this).newResource(newPath);
|
||||
res = this.getResourceFactory().newResource(newPath);
|
||||
if (!res.exists())
|
||||
{
|
||||
res = null;
|
||||
|
@ -416,7 +415,7 @@ public class MavenWebAppContext extends WebAppContext
|
|||
return null;
|
||||
File jarFile = _webInfJarMap.get(jarName);
|
||||
if (jarFile != null)
|
||||
return ResourceFactory.of(this).newResource(jarFile.getPath());
|
||||
return this.getResourceFactory().newResource(jarFile.getPath());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -39,8 +39,7 @@ public class OverlayManager
|
|||
this.warPlugin = warPlugin;
|
||||
}
|
||||
|
||||
public void applyOverlays(MavenWebAppContext webApp)
|
||||
throws Exception
|
||||
public void applyOverlays(MavenWebAppContext webApp) throws IOException
|
||||
{
|
||||
List<Resource> resourceBases = new ArrayList<Resource>();
|
||||
|
||||
|
@ -71,8 +70,7 @@ public class OverlayManager
|
|||
* Generate an ordered list of overlays
|
||||
*/
|
||||
protected List<Overlay> getOverlays()
|
||||
throws Exception
|
||||
{
|
||||
{
|
||||
Set<Artifact> matchedWarArtifacts = new HashSet<Artifact>();
|
||||
List<Overlay> overlays = new ArrayList<Overlay>();
|
||||
|
||||
|
@ -127,7 +125,7 @@ public class OverlayManager
|
|||
*/
|
||||
protected Resource unpackOverlay(Overlay overlay)
|
||||
throws IOException
|
||||
{
|
||||
{
|
||||
if (overlay.getResource() == null)
|
||||
return null; //nothing to unpack
|
||||
|
||||
|
|
|
@ -13,13 +13,12 @@
|
|||
|
||||
package org.eclipse.jetty.ee10.maven.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.eclipse.jetty.ee10.annotations.AnnotationConfiguration;
|
||||
import org.eclipse.jetty.ee10.quickstart.QuickStartConfiguration;
|
||||
import org.eclipse.jetty.ee10.quickstart.QuickStartConfiguration.Mode;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
|
||||
/**
|
||||
|
@ -31,9 +30,9 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
|||
*/
|
||||
public class QuickStartGenerator
|
||||
{
|
||||
private File quickstartXml;
|
||||
private MavenWebAppContext webApp;
|
||||
private File webAppPropsFile;
|
||||
private final Path quickstartXml;
|
||||
private final MavenWebAppContext webApp;
|
||||
private Path webAppProps;
|
||||
private String contextXml;
|
||||
private boolean prepared = false;
|
||||
private Server server;
|
||||
|
@ -43,10 +42,10 @@ public class QuickStartGenerator
|
|||
* @param quickstartXml the file to generate quickstart into
|
||||
* @param webApp the webapp for which to generate quickstart
|
||||
*/
|
||||
public QuickStartGenerator(File quickstartXml, MavenWebAppContext webApp)
|
||||
public QuickStartGenerator(Path quickstartXml, MavenWebAppContext webApp)
|
||||
{
|
||||
this.quickstartXml = quickstartXml;
|
||||
this.webApp = webApp;
|
||||
this.webApp = webApp == null ? new MavenWebAppContext() : webApp;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,7 +59,7 @@ public class QuickStartGenerator
|
|||
/**
|
||||
* @return the quickstartXml
|
||||
*/
|
||||
public File getQuickstartXml()
|
||||
public Path getQuickstartXml()
|
||||
{
|
||||
return quickstartXml;
|
||||
}
|
||||
|
@ -81,17 +80,17 @@ public class QuickStartGenerator
|
|||
this.server = server;
|
||||
}
|
||||
|
||||
public File getWebAppPropsFile()
|
||||
public Path getWebAppProps()
|
||||
{
|
||||
return webAppPropsFile;
|
||||
return webAppProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param webAppPropsFile properties file describing the webapp
|
||||
* @param webAppProps properties file describing the webapp
|
||||
*/
|
||||
public void setWebAppPropsFile(File webAppPropsFile)
|
||||
public void setWebAppProps(Path webAppProps)
|
||||
{
|
||||
this.webAppPropsFile = webAppPropsFile;
|
||||
this.webAppProps = webAppProps;
|
||||
}
|
||||
|
||||
public String getContextXml()
|
||||
|
@ -109,19 +108,13 @@ public class QuickStartGenerator
|
|||
|
||||
/**
|
||||
* Configure the webapp in preparation for quickstart generation.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private void prepareWebApp()
|
||||
throws Exception
|
||||
{
|
||||
if (webApp == null)
|
||||
webApp = new MavenWebAppContext();
|
||||
|
||||
//set the webapp up to do very little other than generate the quickstart-web.xml
|
||||
webApp.addConfiguration(new MavenQuickStartConfiguration());
|
||||
webApp.setAttribute(QuickStartConfiguration.MODE, Mode.GENERATE);
|
||||
webApp.setAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML, ResourceFactory.of(webApp).newResource(quickstartXml.toPath()));
|
||||
webApp.setAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML, quickstartXml);
|
||||
webApp.setAttribute(QuickStartConfiguration.ORIGIN_ATTRIBUTE, "o");
|
||||
webApp.setCopyWebDir(false);
|
||||
webApp.setCopyWebInf(false);
|
||||
|
@ -133,8 +126,7 @@ public class QuickStartGenerator
|
|||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public void generate()
|
||||
throws Exception
|
||||
public void generate() throws Exception
|
||||
{
|
||||
if (quickstartXml == null)
|
||||
throw new IllegalStateException("No quickstart xml output file");
|
||||
|
@ -174,8 +166,8 @@ public class QuickStartGenerator
|
|||
webApp.start(); //just enough to generate the quickstart
|
||||
|
||||
//save config of the webapp BEFORE we stop
|
||||
if (webAppPropsFile != null)
|
||||
WebAppPropertyConverter.toProperties(webApp, webAppPropsFile, contextXml);
|
||||
if (webAppProps != null)
|
||||
WebAppPropertyConverter.toProperties(webApp, webAppProps.toFile(), contextXml);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
|
@ -54,9 +54,8 @@ public class ServerSupport
|
|||
* @param server the server to use
|
||||
* @param contextHandlers the context handlers to include
|
||||
* @param requestLog a request log to use
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void configureHandlers(Server server, List<ContextHandler> contextHandlers, RequestLog requestLog) throws Exception
|
||||
public static void configureHandlers(Server server, List<ContextHandler> contextHandlers, RequestLog requestLog)
|
||||
{
|
||||
if (server == null)
|
||||
throw new IllegalArgumentException("Server is null");
|
||||
|
@ -148,9 +147,8 @@ public class ServerSupport
|
|||
* Add a WebAppContext to a Server
|
||||
* @param server the server to use
|
||||
* @param webapp the webapp to add
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void addWebApplication(Server server, WebAppContext webapp) throws Exception
|
||||
public static void addWebApplication(Server server, WebAppContext webapp)
|
||||
{
|
||||
if (server == null)
|
||||
throw new IllegalArgumentException("Server is null");
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.eclipse.jetty.ee10.maven.plugin;
|
|||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
|
@ -31,7 +32,6 @@ import org.eclipse.jetty.util.StringUtil;
|
|||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceCollection;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.eclipse.jetty.xml.XmlConfiguration;
|
||||
|
||||
/**
|
||||
|
@ -64,10 +64,10 @@ public class WebAppPropertyConverter
|
|||
* @param webApp the webapp to convert
|
||||
* @param propsFile the file to put the properties into
|
||||
* @param contextXml the optional context xml file related to the webApp
|
||||
* @throws Exception if any I/O exception occurs
|
||||
* @throws IOException if any I/O exception occurs
|
||||
*/
|
||||
public static void toProperties(MavenWebAppContext webApp, File propsFile, String contextXml)
|
||||
throws Exception
|
||||
throws IOException
|
||||
{
|
||||
if (webApp == null)
|
||||
throw new IllegalArgumentException("No webapp");
|
||||
|
@ -174,7 +174,7 @@ public class WebAppPropertyConverter
|
|||
if (resource == null)
|
||||
throw new IllegalStateException("No resource");
|
||||
|
||||
fromProperties(webApp, ResourceFactory.of(webApp).newResource(resource).getPath(), server, jettyProperties);
|
||||
fromProperties(webApp, webApp.getResourceFactory().newResource(resource).getPath(), server, jettyProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -209,7 +209,7 @@ public class WebAppPropertyConverter
|
|||
str = webAppProperties.getProperty(QUICKSTART_WEB_XML);
|
||||
if (!StringUtil.isBlank(str))
|
||||
{
|
||||
webApp.setAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML, ResourceFactory.of(webApp).newResource(str));
|
||||
webApp.setAttribute(QuickStartConfiguration.QUICKSTART_WEB_XML, webApp.getResourceFactory().newResource(str));
|
||||
}
|
||||
|
||||
// - the tmp directory
|
||||
|
@ -228,7 +228,7 @@ public class WebAppPropertyConverter
|
|||
// This is a use provided list of overlays, which could have mountable entries.
|
||||
List<URI> uris = URIUtil.split(str);
|
||||
webApp.setWar(null);
|
||||
webApp.setBaseResource(ResourceFactory.of(webApp).newResource(uris));
|
||||
webApp.setBaseResource(webApp.getResourceFactory().newResource(uris));
|
||||
}
|
||||
|
||||
str = webAppProperties.getProperty(WAR_FILE);
|
||||
|
@ -290,7 +290,7 @@ public class WebAppPropertyConverter
|
|||
str = (String)webAppProperties.getProperty(CONTEXT_XML);
|
||||
if (!StringUtil.isBlank(str))
|
||||
{
|
||||
XmlConfiguration xmlConfiguration = new XmlConfiguration(ResourceFactory.of(webApp).newResource(str));
|
||||
XmlConfiguration xmlConfiguration = new XmlConfiguration(webApp.getResourceFactory().newResource(str));
|
||||
xmlConfiguration.getIdMap().put("Server", server);
|
||||
//add in any properties
|
||||
if (jettyProperties != null)
|
||||
|
|
|
@ -13,12 +13,17 @@
|
|||
|
||||
package org.eclipse.jetty.ee10.maven.plugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.jupiter.WorkDir;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
|
@ -27,24 +32,32 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
*/
|
||||
public class TestQuickStartGenerator
|
||||
{
|
||||
public WorkDir workDir;
|
||||
|
||||
@Test
|
||||
public void testGenerator() throws Exception
|
||||
{
|
||||
Path tmpDir = workDir.getEmptyPathDir();
|
||||
|
||||
MavenWebAppContext webApp = new MavenWebAppContext();
|
||||
webApp.setContextPath("/shouldbeoverridden");
|
||||
webApp.setBaseResource(MavenTestingUtils.getTestResourcePathDir("root"));
|
||||
File quickstartFile = new File(MavenTestingUtils.getTargetTestingDir(), "quickstart-web.xml");
|
||||
Path rootDir = MavenTestingUtils.getTargetPath("test-classes/root");
|
||||
assertTrue(Files.exists(rootDir));
|
||||
assertTrue(Files.isDirectory(rootDir));
|
||||
webApp.setBaseResource(ResourceFactory.root().newResource(rootDir));
|
||||
|
||||
Path quickstartFile = tmpDir.resolve("quickstart-web.xml");
|
||||
QuickStartGenerator generator = new QuickStartGenerator(quickstartFile, webApp);
|
||||
generator.setContextXml(MavenTestingUtils.getTestResourceFile("embedder-context.xml").getAbsolutePath());
|
||||
generator.setContextXml(MavenTestingUtils.getTargetFile("test-classes/embedder-context.xml").getAbsolutePath());
|
||||
generator.setServer(new Server());
|
||||
MavenTestingUtils.getTargetTestingDir().mkdirs();
|
||||
File propsFile = new File(MavenTestingUtils.getTargetTestingDir(), "webapp.props");
|
||||
propsFile.createNewFile();
|
||||
generator.setWebAppPropsFile(propsFile);
|
||||
|
||||
Path propsFile = tmpDir.resolve("webapp.props");
|
||||
Files.createFile(propsFile);
|
||||
generator.setWebAppProps(propsFile);
|
||||
generator.generate();
|
||||
assertTrue(propsFile.exists());
|
||||
assertTrue(propsFile.length() > 0);
|
||||
assertTrue(quickstartFile.exists());
|
||||
assertTrue(quickstartFile.length() > 0);
|
||||
assertTrue(Files.exists(propsFile));
|
||||
assertThat(Files.size(propsFile), greaterThan(0L));
|
||||
assertTrue(Files.exists(quickstartFile));
|
||||
assertThat(Files.size(quickstartFile), greaterThan(0L));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,20 +154,20 @@ public class PlusDescriptorProcessorTest
|
|||
doEnvConfiguration(envCtx, vacuumStringEnvEntry);
|
||||
|
||||
URL webXml = Thread.currentThread().getContextClassLoader().getResource("web.xml");
|
||||
webDescriptor = new WebDescriptor(org.eclipse.jetty.util.resource.ResourceFactory.of(context).newResource(webXml));
|
||||
webDescriptor = new WebDescriptor(context.getResourceFactory().newResource(webXml));
|
||||
webDescriptor.parse(WebDescriptor.getParser(false));
|
||||
|
||||
URL frag1Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-1.xml");
|
||||
fragDescriptor1 = new FragmentDescriptor(org.eclipse.jetty.util.resource.ResourceFactory.of(context).newResource(frag1Xml));
|
||||
fragDescriptor1 = new FragmentDescriptor(context.getResourceFactory().newResource(frag1Xml));
|
||||
fragDescriptor1.parse(WebDescriptor.getParser(false));
|
||||
URL frag2Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-2.xml");
|
||||
fragDescriptor2 = new FragmentDescriptor(org.eclipse.jetty.util.resource.ResourceFactory.of(context).newResource(frag2Xml));
|
||||
fragDescriptor2 = new FragmentDescriptor(context.getResourceFactory().newResource(frag2Xml));
|
||||
fragDescriptor2.parse(WebDescriptor.getParser(false));
|
||||
URL frag3Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-3.xml");
|
||||
fragDescriptor3 = new FragmentDescriptor(org.eclipse.jetty.util.resource.ResourceFactory.of(context).newResource(frag3Xml));
|
||||
fragDescriptor3 = new FragmentDescriptor(context.getResourceFactory().newResource(frag3Xml));
|
||||
fragDescriptor3.parse(WebDescriptor.getParser(false));
|
||||
URL frag4Xml = Thread.currentThread().getContextClassLoader().getResource("web-fragment-4.xml");
|
||||
fragDescriptor4 = new FragmentDescriptor(org.eclipse.jetty.util.resource.ResourceFactory.of(context).newResource(frag4Xml));
|
||||
fragDescriptor4 = new FragmentDescriptor(context.getResourceFactory().newResource(frag4Xml));
|
||||
fragDescriptor4.parse(WebDescriptor.getParser(false));
|
||||
Thread.currentThread().setContextClassLoader(oldLoader);
|
||||
}
|
||||
|
|
|
@ -181,15 +181,14 @@ public class ConnectHandlerSSLTest extends AbstractConnectHandlerTest
|
|||
else
|
||||
{
|
||||
builder.append("\r\n");
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
Content.Sink.write(response, false, builder.toString(), completable);
|
||||
completable.whenComplete((r, x) ->
|
||||
{
|
||||
if (x != null)
|
||||
callback.failed(x);
|
||||
else
|
||||
response.write(true, ByteBuffer.wrap(bytes), callback);
|
||||
});
|
||||
Callback.Completable.with(c -> Content.Sink.write(response, false, builder.toString(), c))
|
||||
.whenComplete((r, x) ->
|
||||
{
|
||||
if (x != null)
|
||||
callback.failed(x);
|
||||
else
|
||||
response.write(true, ByteBuffer.wrap(bytes), callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -872,15 +872,14 @@ public class ConnectHandlerTest extends AbstractConnectHandlerTest
|
|||
else
|
||||
{
|
||||
builder.append("\r\n");
|
||||
Callback.Completable completable = new Callback.Completable();
|
||||
Content.Sink.write(response, false, builder.toString(), completable);
|
||||
completable.whenComplete((r, x) ->
|
||||
{
|
||||
if (x != null)
|
||||
callback.failed(x);
|
||||
else
|
||||
response.write(true, ByteBuffer.wrap(bytes), callback);
|
||||
});
|
||||
Callback.Completable.with(c -> Content.Sink.write(response, false, builder.toString(), c))
|
||||
.whenComplete((r, x) ->
|
||||
{
|
||||
if (x != null)
|
||||
callback.failed(x);
|
||||
else
|
||||
response.write(true, ByteBuffer.wrap(bytes), callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
case "/close" ->
|
||||
|
|
|
@ -15,6 +15,7 @@ package org.eclipse.jetty.ee10.quickstart;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -99,8 +100,9 @@ public class QuickStartConfiguration extends AbstractConfiguration
|
|||
throw new IllegalStateException("Bad Quickstart location");
|
||||
|
||||
//look for quickstart-web.xml in WEB-INF of webapp
|
||||
Resource quickStartWebXml = getQuickStartWebXml(context);
|
||||
LOG.debug("quickStartWebXml={} exists={}", quickStartWebXml, quickStartWebXml.exists());
|
||||
Path quickStartWebXml = getQuickStartWebXml(context);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("quickStartWebXml={} exists={}", quickStartWebXml, Files.exists(quickStartWebXml));
|
||||
|
||||
//Get the mode
|
||||
Object o = context.getAttribute(MODE);
|
||||
|
@ -114,7 +116,7 @@ public class QuickStartConfiguration extends AbstractConfiguration
|
|||
{
|
||||
case GENERATE:
|
||||
{
|
||||
if (quickStartWebXml.exists())
|
||||
if (Files.exists(quickStartWebXml))
|
||||
LOG.info("Regenerating {}", quickStartWebXml);
|
||||
else
|
||||
LOG.info("Generating {}", quickStartWebXml);
|
||||
|
@ -128,7 +130,7 @@ public class QuickStartConfiguration extends AbstractConfiguration
|
|||
}
|
||||
case AUTO:
|
||||
{
|
||||
if (quickStartWebXml.exists())
|
||||
if (Files.exists(quickStartWebXml))
|
||||
{
|
||||
quickStart(context);
|
||||
}
|
||||
|
@ -141,7 +143,7 @@ public class QuickStartConfiguration extends AbstractConfiguration
|
|||
break;
|
||||
}
|
||||
case QUICKSTART:
|
||||
if (quickStartWebXml.exists())
|
||||
if (Files.exists(quickStartWebXml))
|
||||
quickStart(context);
|
||||
else
|
||||
throw new IllegalStateException("No " + quickStartWebXml);
|
||||
|
@ -159,7 +161,8 @@ public class QuickStartConfiguration extends AbstractConfiguration
|
|||
if (attr != null)
|
||||
generator.setOriginAttribute(attr.toString());
|
||||
|
||||
generator.setQuickStartWebXml((Resource)context.getAttribute(QUICKSTART_WEB_XML));
|
||||
Path quickStartWebXml = getQuickStartWebXml(context);
|
||||
generator.setQuickStartWebXml(quickStartWebXml);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -181,7 +184,8 @@ public class QuickStartConfiguration extends AbstractConfiguration
|
|||
//add a decorator that will find introspectable annotations
|
||||
context.getObjectFactory().addDecorator(new AnnotationDecorator(context)); //this must be the last Decorator because they are run in reverse order!
|
||||
|
||||
LOG.debug("configured {}", this);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("configured {}", this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,57 +211,97 @@ public class QuickStartConfiguration extends AbstractConfiguration
|
|||
protected void quickStart(WebAppContext context)
|
||||
throws Exception
|
||||
{
|
||||
LOG.info("Quickstarting {}", context);
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.info("Quickstarting {}", context);
|
||||
_quickStart = true;
|
||||
context.setConfigurations(context.getConfigurations().stream()
|
||||
.filter(c -> !__replacedConfigurations.contains(c.replaces()) && !__replacedConfigurations.contains(c.getClass())).toList().toArray(new Configuration[]{}));
|
||||
context.getMetaData().setWebDescriptor(new WebDescriptor((Resource)context.getAttribute(QUICKSTART_WEB_XML)));
|
||||
.filter(c -> !__replacedConfigurations.contains(c.replaces()))
|
||||
.filter(c -> !__replacedConfigurations.contains(c.getClass()))
|
||||
.toArray(Configuration[]::new));
|
||||
Path quickStartWebXml = getQuickStartWebXml(context);
|
||||
if (!Files.exists(quickStartWebXml))
|
||||
throw new IllegalStateException("Quickstart doesn't exist: " + quickStartWebXml);
|
||||
Resource quickStartWebResource = context.getResourceFactory().newResource(quickStartWebXml);
|
||||
context.getMetaData().setWebDescriptor(new WebDescriptor(quickStartWebResource));
|
||||
context.getContext().getServletContext().setEffectiveMajorVersion(context.getMetaData().getWebDescriptor().getMajorVersion());
|
||||
context.getContext().getServletContext().setEffectiveMinorVersion(context.getMetaData().getWebDescriptor().getMinorVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the quickstart-web.xml file as a Resource.
|
||||
* Get the quickstart-web.xml Path from the webapp (from attributes if present, or built from the context's {@link WebAppContext#getWebInf()}).
|
||||
*
|
||||
* @param context the web app context
|
||||
* @return the Resource for the quickstart-web.xml
|
||||
* @throws Exception if unable to find the quickstart xml
|
||||
* @return the Path for the quickstart-web.xml
|
||||
* @throws IOException if unable to build the quickstart xml
|
||||
*/
|
||||
public Resource getQuickStartWebXml(WebAppContext context) throws Exception
|
||||
public static Path getQuickStartWebXml(WebAppContext context) throws IOException
|
||||
{
|
||||
Object attr = context.getAttribute(QUICKSTART_WEB_XML);
|
||||
if (attr instanceof Resource)
|
||||
return (Resource)attr;
|
||||
if (attr instanceof Path)
|
||||
return (Path)attr;
|
||||
|
||||
Resource webInf = context.getWebInf();
|
||||
if (webInf == null || !webInf.exists())
|
||||
{
|
||||
Files.createDirectories(context.getBaseResource().getPath().resolve("WEB-INF"));
|
||||
webInf = context.getWebInf();
|
||||
}
|
||||
Path webInfDir = getWebInfPath(context);
|
||||
Path qstartPath = webInfDir.resolve("quickstart-web.xml");
|
||||
|
||||
Resource qstart;
|
||||
if (attr == null || StringUtil.isBlank(attr.toString()))
|
||||
{
|
||||
// TODO: should never return from WEB-INF/lib/foo.jar!/WEB-INF/quickstart-web.xml
|
||||
// TODO: should also never return from a META-INF/versions/#/WEB-INF/quickstart-web.xml location
|
||||
qstart = webInf.resolve("quickstart-web.xml");
|
||||
}
|
||||
else
|
||||
if (attr != null && StringUtil.isNotBlank(attr.toString()))
|
||||
{
|
||||
Resource resource;
|
||||
String attrValue = attr.toString();
|
||||
try
|
||||
{
|
||||
// Try a relative resolution
|
||||
qstart = _resourceFactory.newResource(webInf.getPath().resolve(attr.toString()));
|
||||
resource = context.getResourceFactory().newResource(webInfDir.resolve(attrValue));
|
||||
}
|
||||
catch (Throwable th)
|
||||
{
|
||||
// try as a resource
|
||||
qstart = _resourceFactory.newResource(attr.toString());
|
||||
resource = context.getResourceFactory().newResource(attrValue);
|
||||
}
|
||||
if (resource != null)
|
||||
{
|
||||
Path attrPath = resource.getPath();
|
||||
if (attrPath != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Using quickstart attribute {} value of {}", attr, attrValue);
|
||||
qstartPath = attrPath;
|
||||
}
|
||||
}
|
||||
context.setAttribute(QUICKSTART_WEB_XML, qstart);
|
||||
}
|
||||
context.setAttribute(QUICKSTART_WEB_XML, qstart);
|
||||
return qstart;
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Using quickstart location: {}", qstartPath);
|
||||
context.setAttribute(QUICKSTART_WEB_XML, qstartPath);
|
||||
return qstartPath;
|
||||
}
|
||||
|
||||
private static Path getWebInfPath(WebAppContext context) throws IOException
|
||||
{
|
||||
Path webInfDir = null;
|
||||
Resource webInf = context.getWebInf();
|
||||
if (webInf != null)
|
||||
{
|
||||
webInfDir = webInf.getPath();
|
||||
}
|
||||
|
||||
if (webInfDir == null)
|
||||
{
|
||||
Path baseResourcePath = findFirstWritablePath(context);
|
||||
webInfDir = baseResourcePath.resolve("WEB-INF");
|
||||
if (!Files.exists(webInfDir))
|
||||
Files.createDirectories(webInfDir);
|
||||
}
|
||||
return webInfDir;
|
||||
}
|
||||
|
||||
private static Path findFirstWritablePath(WebAppContext context) throws IOException
|
||||
{
|
||||
for (Resource resource: context.getBaseResource())
|
||||
{
|
||||
Path path = resource.getPath();
|
||||
if (path == null || !Files.isDirectory(path) || !Files.isWritable(path))
|
||||
continue; // skip
|
||||
return path;
|
||||
}
|
||||
throw new IOException("Unable to find writable path in Base Resources");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.io.FileNotFoundException;
|
|||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -56,7 +57,6 @@ import org.eclipse.jetty.ee10.webapp.WebInfConfiguration;
|
|||
import org.eclipse.jetty.http.MimeTypes;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
import org.eclipse.jetty.xml.XmlAppendable;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -81,7 +81,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
|
|||
protected final boolean _abort;
|
||||
protected String _originAttribute;
|
||||
protected int _count;
|
||||
protected Resource _quickStartWebXml;
|
||||
protected Path _quickStartWebXml;
|
||||
|
||||
public QuickStartGeneratorConfiguration()
|
||||
{
|
||||
|
@ -114,12 +114,12 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
|
|||
return _originAttribute;
|
||||
}
|
||||
|
||||
public Resource getQuickStartWebXml()
|
||||
public Path getQuickStartWebXml()
|
||||
{
|
||||
return _quickStartWebXml;
|
||||
}
|
||||
|
||||
public void setQuickStartWebXml(Resource quickStartWebXml)
|
||||
public void setQuickStartWebXml(Path quickStartWebXml)
|
||||
{
|
||||
_quickStartWebXml = quickStartWebXml;
|
||||
}
|
||||
|
@ -812,7 +812,7 @@ public class QuickStartGeneratorConfiguration extends AbstractConfiguration
|
|||
{
|
||||
MetaData metadata = context.getMetaData();
|
||||
metadata.resolve(context);
|
||||
try (OutputStream os = Files.newOutputStream(_quickStartWebXml.getPath()))
|
||||
try (OutputStream os = Files.newOutputStream(_quickStartWebXml))
|
||||
{
|
||||
generateQuickStartWebXml(context, os);
|
||||
LOG.info("Generated {}", _quickStartWebXml);
|
||||
|
|
|
@ -28,6 +28,7 @@ import java.util.Objects;
|
|||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
@ -921,6 +922,12 @@ public class DefaultServlet extends HttpServlet
|
|||
{
|
||||
_response.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> writeInterim(int status, HttpFields headers)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class ServletResourceService extends ResourceService implements ResourceService.WelcomeFactory
|
||||
|
|
|
@ -28,6 +28,8 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||
import org.eclipse.jetty.ee10.servlet.ServletRequestState.Action;
|
||||
import org.eclipse.jetty.http.BadMessageException;
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpURI;
|
||||
import org.eclipse.jetty.io.Connection;
|
||||
|
@ -42,6 +44,7 @@ import org.eclipse.jetty.server.handler.ContextHandler;
|
|||
import org.eclipse.jetty.util.Blocker;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.HostPort;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.eclipse.jetty.util.URIUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -66,19 +69,12 @@ public class ServletChannel implements Runnable
|
|||
private ServletContextRequest _request;
|
||||
private long _oldIdleTimeout;
|
||||
private Callback _callback;
|
||||
|
||||
private boolean _expects100Continue;
|
||||
// TODO:
|
||||
private final Listener _combinedListener = NOOP_LISTENER;
|
||||
|
||||
/**
|
||||
* Bytes written after interception (eg after compression)
|
||||
*/
|
||||
// Bytes written after interception (e.g. after compression).
|
||||
private long _written;
|
||||
|
||||
public ServletChannel()
|
||||
{
|
||||
}
|
||||
|
||||
public void setCallback(Callback callback)
|
||||
{
|
||||
if (_callback != null)
|
||||
|
@ -94,10 +90,9 @@ public class ServletChannel implements Runnable
|
|||
_state = new ServletRequestState(this); // TODO can this be recycled?
|
||||
_endPoint = request.getConnectionMetaData().getConnection().getEndPoint();
|
||||
_connector = request.getConnectionMetaData().getConnector();
|
||||
|
||||
// TODO: can we do this?
|
||||
_configuration = request.getConnectionMetaData().getHttpConfiguration();
|
||||
|
||||
_expects100Continue = request.getHeaders().contains(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString());
|
||||
request.getHttpInput().init();
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -326,7 +321,23 @@ public class ServletChannel implements Runnable
|
|||
*/
|
||||
public void continue100(int available) throws IOException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
if (isExpecting100Continue())
|
||||
{
|
||||
_expects100Continue = false;
|
||||
if (available == 0)
|
||||
{
|
||||
if (isCommitted())
|
||||
throw new IOException("Committed before 100 Continue");
|
||||
try
|
||||
{
|
||||
getResponse().writeInterim(HttpStatus.CONTINUE_100, HttpFields.EMPTY).get();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
throw IO.rethrow(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void recycle()
|
||||
|
@ -712,7 +723,7 @@ public class ServletChannel implements Runnable
|
|||
|
||||
public boolean isExpecting100Continue()
|
||||
{
|
||||
return false;
|
||||
return _expects100Continue;
|
||||
}
|
||||
|
||||
public boolean isExpecting102Processing()
|
||||
|
|
|
@ -1079,7 +1079,7 @@ public class ServletContextHandler extends ContextHandler implements Graceful
|
|||
Resource baseResource = getBaseResource();
|
||||
if (baseResource != null && baseResource.isAlias())
|
||||
LOG.warn("BaseResource {} is aliased to {} in {}. May not be supported in future releases.",
|
||||
baseResource, baseResource.getAlias(), this);
|
||||
baseResource, baseResource.getTargetURI(), this);
|
||||
|
||||
if (_logger == null)
|
||||
_logger = LoggerFactory.getLogger(ContextHandler.class.getName() + getLogNameSuffix());
|
||||
|
|
|
@ -21,6 +21,7 @@ import java.util.EnumSet;
|
|||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jakarta.servlet.ServletContext;
|
||||
|
@ -646,47 +647,22 @@ public class ServletContextResponse extends ContextResponse
|
|||
@Override
|
||||
public void sendError(int sc, String msg) throws IOException
|
||||
{
|
||||
// TODO
|
||||
switch (sc)
|
||||
{
|
||||
case -1:
|
||||
{
|
||||
_servletChannel.abort(new IOException(msg));
|
||||
break;
|
||||
}
|
||||
case HttpStatus.PROCESSING_102:
|
||||
{
|
||||
// TODO: should we check whether an Expect: 102 header is present?
|
||||
if (!isCommitted())
|
||||
{
|
||||
try (Blocker.Callback blocker = Blocker.callback())
|
||||
{
|
||||
_response.setStatus(HttpStatus.PROCESSING_102);
|
||||
_response.write(true, null, blocker);
|
||||
blocker.block();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HttpStatus.EARLY_HINT_103:
|
||||
case -1 -> _servletChannel.abort(new IOException(msg));
|
||||
case HttpStatus.PROCESSING_102, HttpStatus.EARLY_HINT_103 ->
|
||||
{
|
||||
if (!isCommitted())
|
||||
{
|
||||
try (Blocker.Callback blocker = Blocker.callback())
|
||||
{
|
||||
_response.setStatus(HttpStatus.EARLY_HINT_103);
|
||||
_response.write(true, null, blocker);
|
||||
CompletableFuture<Void> completable = _response.writeInterim(sc, _response.getHeaders().asImmutable());
|
||||
blocker.completeWith(completable);
|
||||
blocker.block();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// This is just a state change
|
||||
getState().sendError(sc, msg);
|
||||
break;
|
||||
}
|
||||
default -> getState().sendError(sc, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
package org.eclipse.jetty.ee10.servlet.security.authentication;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.eclipse.jetty.ee10.servlet.security.Authentication;
|
||||
|
@ -194,5 +195,11 @@ public class DeferredAuthentication implements Authentication.Deferred
|
|||
public void reset()
|
||||
{
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> writeInterim(int status, HttpFields headers)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -17,19 +17,6 @@
|
|||
<maven.deploy.skip>true</maven.deploy.skip>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<org.eclipse.jetty.test.client.transport.H3.enable>${h3.test.enabled}</org.eclipse.jetty.test.client.transport.H3.enable>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>enable-incubator-foreign</id>
|
||||
|
|
|
@ -16,6 +16,7 @@ package org.eclipse.jetty.ee10.test.client.transport;
|
|||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -69,25 +70,19 @@ public class AbstractTest
|
|||
protected HttpClient client;
|
||||
protected Path unixDomainPath;
|
||||
|
||||
private static EnumSet<Transport> allTransports()
|
||||
public static Collection<Transport> transports()
|
||||
{
|
||||
EnumSet<Transport> transports = EnumSet.allOf(Transport.class);
|
||||
// Disable H3 tests unless explicitly enabled with a system property.
|
||||
if (!Boolean.getBoolean("org.eclipse.jetty.test.client.transport.H3.enable"))
|
||||
if ("ci".equals(System.getProperty("env")))
|
||||
transports.remove(Transport.H3);
|
||||
return transports;
|
||||
}
|
||||
|
||||
public static List<Transport> transports()
|
||||
public static Collection<Transport> transportsNoFCGI()
|
||||
{
|
||||
return List.copyOf(allTransports());
|
||||
}
|
||||
|
||||
public static List<Transport> transportsNoFCGI()
|
||||
{
|
||||
EnumSet<Transport> transports = allTransports();
|
||||
Collection<Transport> transports = transports();
|
||||
transports.remove(Transport.FCGI);
|
||||
return List.copyOf(transports);
|
||||
return transports;
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
|
|
@ -36,19 +36,21 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.client.ContinueProtocolHandler;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.AsyncRequestContent;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.client.util.BytesRequestContent;
|
||||
import org.eclipse.jetty.ee10.servlet.ServletContextRequest;
|
||||
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.eclipse.jetty.http.HttpTester;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
|
@ -59,21 +61,18 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assumptions.assumeTrue;
|
||||
|
||||
// TODO: re-enable this test when 100 continue is implemented.
|
||||
@Disabled
|
||||
public class HttpClientContinueTest extends AbstractTest
|
||||
{
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithOneContentRespond100Continue(Transport transport) throws Exception
|
||||
{
|
||||
testExpect100ContinueRespond100Continue(transport, "data1".getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithMultipleContentsRespond100Continue(Transport transport) throws Exception
|
||||
{
|
||||
testExpect100ContinueRespond100Continue(transport, "data1".getBytes(StandardCharsets.UTF_8), "data2".getBytes(StandardCharsets.UTF_8), "data3".getBytes(StandardCharsets.UTF_8));
|
||||
|
@ -112,7 +111,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithChunkedContentRespond100Continue(Transport transport) throws Exception
|
||||
{
|
||||
start(transport, new HttpServlet()
|
||||
|
@ -159,14 +158,14 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithContentRespond417ExpectationFailed(Transport transport) throws Exception
|
||||
{
|
||||
testExpect100ContinueWithContentRespondError(transport, 417);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithContentRespond413RequestEntityTooLarge(Transport transport) throws Exception
|
||||
{
|
||||
testExpect100ContinueWithContentRespondError(transport, 413);
|
||||
|
@ -209,7 +208,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithContentWithRedirect(Transport transport) throws Exception
|
||||
{
|
||||
String data = "success";
|
||||
|
@ -255,7 +254,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testRedirectWithExpect100ContinueWithContent(Transport transport) throws Exception
|
||||
{
|
||||
// A request with Expect: 100-Continue cannot receive non-final responses like 3xx
|
||||
|
@ -304,7 +303,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithContentWithResponseFailureBefore100Continue(Transport transport) throws Exception
|
||||
{
|
||||
AtomicReference<org.eclipse.jetty.client.api.Request> clientRequestRef = new AtomicReference<>();
|
||||
|
@ -353,7 +352,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithContentWithResponseFailureAfter100Continue(Transport transport) throws Exception
|
||||
{
|
||||
AtomicReference<org.eclipse.jetty.client.api.Request> clientRequestRef = new AtomicReference<>();
|
||||
|
@ -403,7 +402,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithContentWithResponseFailureDuring100Continue(Transport transport) throws Exception
|
||||
{
|
||||
start(transport, new HttpServlet()
|
||||
|
@ -461,7 +460,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithDeferredContentRespond100Continue(Transport transport) throws Exception
|
||||
{
|
||||
byte[] chunk1 = new byte[]{0, 1, 2, 3};
|
||||
|
@ -531,7 +530,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithInitialAndDeferredContentRespond100Continue(Transport transport) throws Exception
|
||||
{
|
||||
AtomicReference<Thread> handlerThread = new AtomicReference<>();
|
||||
|
@ -581,7 +580,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithConcurrentDeferredContentRespond100Continue(Transport transport) throws Exception
|
||||
{
|
||||
start(transport, new HttpServlet()
|
||||
|
@ -620,7 +619,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
@MethodSource("transportsNoFCGI")
|
||||
public void testExpect100ContinueWithInitialAndConcurrentDeferredContentRespond100Continue(Transport transport) throws Exception
|
||||
{
|
||||
start(transport, new HttpServlet()
|
||||
|
@ -676,17 +675,14 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
public void testExpect100ContinueWithTwoResponsesInOneRead(Transport transport) throws Exception
|
||||
@Test
|
||||
public void testExpect100ContinueWithTwoResponsesInOneRead() throws Exception
|
||||
{
|
||||
assumeTrue(transport == Transport.HTTP || transport == Transport.HTTPS);
|
||||
|
||||
// There is a chance that the server replies with the 100 Continue response
|
||||
// and immediately after with the "normal" response, say a 200 OK.
|
||||
// These may be read by the client in a single read, and must be handled correctly.
|
||||
|
||||
startClient(transport);
|
||||
startClient(Transport.HTTP);
|
||||
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
|
@ -705,7 +701,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
|
||||
try (Socket socket = server.accept())
|
||||
{
|
||||
// Read the request headers.
|
||||
// Read only the request headers.
|
||||
readRequestHeaders(socket.getInputStream());
|
||||
|
||||
OutputStream output = socket.getOutputStream();
|
||||
|
@ -737,49 +733,62 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
// TODO: figure out a way to force the 100 continue response.
|
||||
@Disabled
|
||||
public void testNoExpectRespond100Continue(Transport transport) throws Exception
|
||||
@Test
|
||||
public void testNoExpectRespond100Continue() throws Exception
|
||||
{
|
||||
start(transport, new HttpServlet()
|
||||
{
|
||||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
ServletContextRequest jettyRequest = (ServletContextRequest)request;
|
||||
// Force a 100 Continue response.
|
||||
// TODO: figure out a way to force the 100 continue response.
|
||||
// jettyRequest.getHttpChannel().sendResponse(HttpGenerator.CONTINUE_100_INFO, null, false);
|
||||
// Echo the content.
|
||||
IO.copy(request.getInputStream(), response.getOutputStream());
|
||||
}
|
||||
});
|
||||
|
||||
byte[] bytes = new byte[1024];
|
||||
new Random().nextBytes(bytes);
|
||||
ContentResponse response = client.newRequest(newURI(transport))
|
||||
.body(new BytesRequestContent(bytes))
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.send();
|
||||
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertArrayEquals(bytes, response.getContent());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("transports")
|
||||
public void testNoExpect100ContinueThenRedirectThen100ContinueThenResponse(Transport transport) throws Exception
|
||||
{
|
||||
assumeTrue(transport == Transport.HTTP || transport == Transport.HTTPS);
|
||||
|
||||
startClient(transport);
|
||||
startClient(Transport.HTTP);
|
||||
client.setMaxConnectionsPerDestination(1);
|
||||
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
server.bind(new InetSocketAddress("localhost", 0));
|
||||
System.err.println("server listening on localhost:" + server.getLocalPort());
|
||||
|
||||
byte[] bytes = new byte[1024];
|
||||
new Random().nextBytes(bytes);
|
||||
Request clientRequest = client.newRequest("localhost", server.getLocalPort())
|
||||
.body(new BytesRequestContent(bytes))
|
||||
.timeout(5, TimeUnit.SECONDS);
|
||||
FutureResponseListener listener = new FutureResponseListener(clientRequest);
|
||||
clientRequest.send(listener);
|
||||
|
||||
try (Socket socket = server.accept())
|
||||
{
|
||||
InputStream input = socket.getInputStream();
|
||||
OutputStream output = socket.getOutputStream();
|
||||
|
||||
HttpTester.Request serverRequest = HttpTester.parseRequest(input);
|
||||
assertNotNull(serverRequest);
|
||||
byte[] content = serverRequest.getContentBytes();
|
||||
|
||||
String serverResponse = """
|
||||
HTTP/1.1 100 Continue\r
|
||||
\r
|
||||
HTTP/1.1 200 OK\r
|
||||
Content-Length: $L\r
|
||||
\r
|
||||
""".replace("$L", String.valueOf(content.length));
|
||||
output.write(serverResponse.getBytes(StandardCharsets.UTF_8));
|
||||
output.write(content);
|
||||
output.flush();
|
||||
}
|
||||
|
||||
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
|
||||
assertEquals(HttpStatus.OK_200, response.getStatus());
|
||||
assertArrayEquals(bytes, response.getContent());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoExpect100ContinueThen100ContinueThenRedirectThen100ContinueThenResponse() throws Exception
|
||||
{
|
||||
startClient(Transport.HTTP);
|
||||
client.setMaxConnectionsPerDestination(1);
|
||||
|
||||
try (ServerSocket server = new ServerSocket())
|
||||
{
|
||||
server.bind(new InetSocketAddress("localhost", 0));
|
||||
System.err.println("server listening on localhost:" + server.getLocalPort());
|
||||
|
||||
// No Expect header, no content.
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
@ -795,7 +804,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
InputStream input = socket.getInputStream();
|
||||
OutputStream output = socket.getOutputStream();
|
||||
|
||||
readRequestHeaders(input);
|
||||
HttpTester.parseRequest(input);
|
||||
String response1 = """
|
||||
HTTP/1.1 100 Continue\r
|
||||
\r
|
||||
|
@ -807,7 +816,7 @@ public class HttpClientContinueTest extends AbstractTest
|
|||
output.write(response1.getBytes(StandardCharsets.UTF_8));
|
||||
output.flush();
|
||||
|
||||
readRequestHeaders(input);
|
||||
HttpTester.parseRequest(input);
|
||||
String response2 = """
|
||||
HTTP/1.1 100 Continue\r
|
||||
\r
|
||||
|
|
|
@ -14,8 +14,6 @@
|
|||
package org.eclipse.jetty.ee10.test.client.transport;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
@ -23,17 +21,10 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
import jakarta.servlet.http.HttpServlet;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.eclipse.jetty.client.HttpConversation;
|
||||
import org.eclipse.jetty.client.HttpExchange;
|
||||
import org.eclipse.jetty.client.HttpRequest;
|
||||
import org.eclipse.jetty.client.ProtocolHandler;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.client.api.Result;
|
||||
import org.eclipse.jetty.client.util.BufferingResponseListener;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpHeaderValue;
|
||||
import org.eclipse.jetty.http.HttpStatus;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
|
@ -42,9 +33,6 @@ import static org.hamcrest.Matchers.contains;
|
|||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
// TODO: these tests fail with "last already written" since in core Jetty
|
||||
// we don't handle well writing 2 HTTP responses one after the other.
|
||||
@Disabled
|
||||
public class InformationalResponseTest extends AbstractTest
|
||||
{
|
||||
@ParameterizedTest
|
||||
|
@ -65,50 +53,6 @@ public class InformationalResponseTest extends AbstractTest
|
|||
long idleTimeout = 10000;
|
||||
setStreamIdleTimeout(idleTimeout);
|
||||
|
||||
CountDownLatch processingLatch = new CountDownLatch(1);
|
||||
client.getProtocolHandlers().put(new ProtocolHandler()
|
||||
{
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "Processing";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(org.eclipse.jetty.client.api.Request request, Response response)
|
||||
{
|
||||
return response.getStatus() == HttpStatus.PROCESSING_102;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response.Listener getResponseListener()
|
||||
{
|
||||
return new Response.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
processingLatch.countDown();
|
||||
var request = response.getRequest();
|
||||
HttpConversation conversation = ((HttpRequest)request).getConversation();
|
||||
// Reset the conversation listeners, since we are going to receive another response code
|
||||
conversation.updateResponseListeners(null);
|
||||
|
||||
HttpExchange exchange = conversation.getExchanges().peekLast();
|
||||
if (exchange != null && response.getStatus() == HttpStatus.PROCESSING_102)
|
||||
{
|
||||
// All good, continue.
|
||||
exchange.resetResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
response.abort(new IllegalStateException("should not have accepted"));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
CountDownLatch completeLatch = new CountDownLatch(1);
|
||||
AtomicReference<Response> response = new AtomicReference<>();
|
||||
BufferingResponseListener listener = new BufferingResponseListener()
|
||||
|
@ -122,11 +66,9 @@ public class InformationalResponseTest extends AbstractTest
|
|||
};
|
||||
client.newRequest(newURI(transport))
|
||||
.method("GET")
|
||||
.headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.PROCESSING))
|
||||
.timeout(10, TimeUnit.SECONDS)
|
||||
.send(listener);
|
||||
|
||||
assertTrue(processingLatch.await(10, TimeUnit.SECONDS));
|
||||
assertTrue(completeLatch.await(10, TimeUnit.SECONDS));
|
||||
assertThat(response.get().getStatus(), is(200));
|
||||
assertThat(listener.getContentAsString(), is("OK"));
|
||||
|
@ -141,11 +83,11 @@ public class InformationalResponseTest extends AbstractTest
|
|||
@Override
|
||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException
|
||||
{
|
||||
response.setHeader("Hint", "one");
|
||||
response.addHeader("Hint", "one");
|
||||
response.sendError(HttpStatus.EARLY_HINT_103);
|
||||
response.setHeader("Hint", "two");
|
||||
response.addHeader("Hint", "two");
|
||||
response.sendError(HttpStatus.EARLY_HINT_103);
|
||||
response.setHeader("Hint", "three");
|
||||
response.addHeader("Hint", "three");
|
||||
response.setStatus(200);
|
||||
response.getOutputStream().print("OK");
|
||||
}
|
||||
|
@ -153,50 +95,6 @@ public class InformationalResponseTest extends AbstractTest
|
|||
long idleTimeout = 10000;
|
||||
setStreamIdleTimeout(idleTimeout);
|
||||
|
||||
List<String> hints = new CopyOnWriteArrayList<>();
|
||||
client.getProtocolHandlers().put(new ProtocolHandler()
|
||||
{
|
||||
@Override
|
||||
public String getName()
|
||||
{
|
||||
return "EarlyHint";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean accept(org.eclipse.jetty.client.api.Request request, Response response)
|
||||
{
|
||||
return response.getStatus() == HttpStatus.EARLY_HINT_103;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response.Listener getResponseListener()
|
||||
{
|
||||
return new Response.Listener()
|
||||
{
|
||||
@Override
|
||||
public void onSuccess(Response response)
|
||||
{
|
||||
var request = response.getRequest();
|
||||
HttpConversation conversation = ((HttpRequest)request).getConversation();
|
||||
// Reset the conversation listeners, since we are going to receive another response code
|
||||
conversation.updateResponseListeners(null);
|
||||
|
||||
HttpExchange exchange = conversation.getExchanges().peekLast();
|
||||
if (exchange != null && response.getStatus() == HttpStatus.EARLY_HINT_103)
|
||||
{
|
||||
// All good, continue.
|
||||
hints.add(response.getHeaders().get("Hint"));
|
||||
exchange.resetResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
response.abort(new IllegalStateException("should not have accepted"));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
CountDownLatch complete = new CountDownLatch(1);
|
||||
AtomicReference<Response> response = new AtomicReference<>();
|
||||
BufferingResponseListener listener = new BufferingResponseListener()
|
||||
|
@ -204,7 +102,6 @@ public class InformationalResponseTest extends AbstractTest
|
|||
@Override
|
||||
public void onComplete(Result result)
|
||||
{
|
||||
hints.add(result.getResponse().getHeaders().get("Hint"));
|
||||
response.set(result.getResponse());
|
||||
complete.countDown();
|
||||
}
|
||||
|
@ -217,6 +114,6 @@ public class InformationalResponseTest extends AbstractTest
|
|||
assertTrue(complete.await(5, TimeUnit.SECONDS));
|
||||
assertThat(response.get().getStatus(), is(200));
|
||||
assertThat(listener.getContentAsString(), is("OK"));
|
||||
assertThat(hints, contains("one", "two", "three"));
|
||||
assertThat(response.get().getHeaders().getValuesList("Hint"), contains("one", "two", "three"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@ import org.eclipse.jetty.ee10.plus.webapp.PlusConfiguration;
|
|||
import org.eclipse.jetty.ee10.webapp.WebAppContext;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.eclipse.jetty.xml.XmlConfiguration;
|
||||
|
||||
public class Quickstart
|
||||
|
@ -37,7 +36,7 @@ public class Quickstart
|
|||
WebAppContext webapp = new WebAppContext();
|
||||
Resource contextXml = null;
|
||||
if (args.length > 1)
|
||||
contextXml = ResourceFactory.of(webapp).newResource(args[1]);
|
||||
contextXml = webapp.getResourceFactory().newResource(args[1]);
|
||||
|
||||
Server server = new Server(8080);
|
||||
|
||||
|
|
|
@ -1253,7 +1253,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
|
|||
public void setExtraClasspath(String extraClasspath)
|
||||
{
|
||||
List<URI> uris = URIUtil.split(extraClasspath);
|
||||
setExtraClasspath(ResourceFactory.of(this).newResource(uris));
|
||||
setExtraClasspath(this.getResourceFactory().newResource(uris));
|
||||
}
|
||||
|
||||
public void setExtraClasspath(ResourceCollection extraClasspath)
|
||||
|
|
|
@ -41,7 +41,6 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
public static final String TEMPORARY_RESOURCE_BASE = "org.eclipse.jetty.webapp.tmpResourceBase";
|
||||
|
||||
protected Resource _preUnpackBaseResource;
|
||||
private ResourceFactory.Closeable _resourceFactory;
|
||||
|
||||
public WebInfConfiguration()
|
||||
{
|
||||
|
@ -93,9 +92,6 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
if (tmpdirConfigured != null && !tmpdirConfigured)
|
||||
context.setTempDirectory(null);
|
||||
|
||||
IO.close(_resourceFactory);
|
||||
_resourceFactory = null;
|
||||
|
||||
//reset the base resource back to what it was before we did any unpacking of resources
|
||||
//TODO there is something wrong with the config of the resource base as this should never be null
|
||||
context.setBaseResource(_preUnpackBaseResource == null ? null : _preUnpackBaseResource);
|
||||
|
@ -282,7 +278,6 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
public void unpack(WebAppContext context) throws IOException
|
||||
{
|
||||
Resource webApp = context.getBaseResource();
|
||||
_resourceFactory = ResourceFactory.closeable();
|
||||
_preUnpackBaseResource = context.getBaseResource();
|
||||
|
||||
if (webApp == null)
|
||||
|
@ -296,12 +291,13 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
if (webApp == null)
|
||||
throw new IllegalStateException("No resourceBase or war set for context");
|
||||
|
||||
// Accept aliases for WAR files
|
||||
if (webApp.isAlias())
|
||||
// Use real location (if different) for WAR file, so that change/modification monitoring can work.
|
||||
URI targetURI = webApp.getTargetURI();
|
||||
if (targetURI != null)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("{} anti-aliased to {}", webApp, webApp.getAlias());
|
||||
webApp = context.newResource(webApp.getAlias());
|
||||
LOG.debug("{} anti-aliased to {}", webApp, targetURI);
|
||||
webApp = context.newResource(targetURI);
|
||||
}
|
||||
|
||||
if (LOG.isDebugEnabled())
|
||||
|
@ -317,7 +313,7 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
if (webApp.exists() && !webApp.isDirectory() && !webApp.toString().startsWith("jar:"))
|
||||
{
|
||||
// No - then lets see if it can be turned into a jar URL.
|
||||
webApp = _resourceFactory.newJarFileResource(webApp.getURI());
|
||||
webApp = context.getResourceFactory().newJarFileResource(webApp.getURI());
|
||||
}
|
||||
|
||||
// If we should extract or the URL is still not usable
|
||||
|
@ -334,7 +330,7 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
if (war != null)
|
||||
{
|
||||
// look for a sibling like "foo/" to a "foo.war"
|
||||
Path warfile = _resourceFactory.newResource(war).getPath();
|
||||
Path warfile = context.getResourceFactory().newResource(war).getPath();
|
||||
if (warfile != null && warfile.getFileName().toString().toLowerCase(Locale.ENGLISH).endsWith(".war"))
|
||||
{
|
||||
Path sibling = warfile.getParent().resolve(warfile.getFileName().toString().substring(0, warfile.getFileName().toString().length() - 4));
|
||||
|
@ -397,7 +393,7 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
}
|
||||
}
|
||||
}
|
||||
webApp = _resourceFactory.newResource(extractedWebAppDir.normalize());
|
||||
webApp = context.getResourceFactory().newResource(extractedWebAppDir.normalize());
|
||||
}
|
||||
|
||||
// Now do we have something usable?
|
||||
|
@ -450,7 +446,7 @@ public class WebInfConfiguration extends AbstractConfiguration
|
|||
webInfClasses.copyTo(webInfClassesDir.toPath());
|
||||
}
|
||||
|
||||
webInf = _resourceFactory.newResource(extractedWebInfDir.getCanonicalPath());
|
||||
webInf = context.getResourceFactory().newResource(extractedWebInfDir.getCanonicalPath());
|
||||
|
||||
Resource rc = Resource.combine(webInf, webApp);
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ public class WebXmlConfiguration extends AbstractConfiguration
|
|||
String defaultsDescriptor = context.getDefaultsDescriptor();
|
||||
if (defaultsDescriptor != null && defaultsDescriptor.length() > 0)
|
||||
{
|
||||
Resource dftResource = ResourceFactory.of(context).newSystemResource(defaultsDescriptor);
|
||||
Resource dftResource = context.getResourceFactory().newSystemResource(defaultsDescriptor);
|
||||
if (dftResource == null)
|
||||
{
|
||||
String pkg = WebXmlConfiguration.class.getPackageName().replace(".", "/") + "/";
|
||||
|
@ -83,7 +83,7 @@ public class WebXmlConfiguration extends AbstractConfiguration
|
|||
{
|
||||
if (overrideDescriptor != null && overrideDescriptor.length() > 0)
|
||||
{
|
||||
Resource orideResource = ResourceFactory.of(context).newSystemResource(overrideDescriptor);
|
||||
Resource orideResource = context.getResourceFactory().newSystemResource(overrideDescriptor);
|
||||
if (orideResource == null)
|
||||
orideResource = context.newResource(overrideDescriptor);
|
||||
context.getMetaData().addOverrideDescriptor(new OverrideDescriptor(orideResource));
|
||||
|
|
|
@ -30,7 +30,6 @@ import org.eclipse.jetty.util.IO;
|
|||
import org.eclipse.jetty.util.component.LifeCycle;
|
||||
import org.eclipse.jetty.util.resource.FileSystemPool;
|
||||
import org.eclipse.jetty.util.resource.Resource;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
@ -63,7 +62,7 @@ public class WebAppClassLoaderTest
|
|||
|
||||
_testWebappDir = MavenTestingUtils.getProjectDirPath("src/test/webapp");
|
||||
_context = new WebAppContext();
|
||||
Resource webapp = ResourceFactory.of(_context).newResource(_testWebappDir);
|
||||
Resource webapp = _context.getResourceFactory().newResource(_testWebappDir);
|
||||
_context.setBaseResource(webapp);
|
||||
_context.setContextPath("/test");
|
||||
_context.setExtraClasspath("src/test/resources/ext/*");
|
||||
|
|
|
@ -44,7 +44,6 @@ import org.eclipse.jetty.toolchain.test.JAR;
|
|||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||
import org.eclipse.jetty.util.resource.ResourceFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -154,7 +153,7 @@ public class WebAppTester extends ContainerLifeCycle
|
|||
// Configure the WebAppContext.
|
||||
_context = new WebAppContext();
|
||||
_context.setContextPath(contextPath);
|
||||
_context.setBaseResource(ResourceFactory.of(_context).newResource(_contextDir).getPath());
|
||||
_context.setBaseResource(_context.getResourceFactory().newResource(_contextDir).getPath());
|
||||
|
||||
_context.setConfigurations(new Configuration[]
|
||||
{
|
||||
|
|
|
@ -128,13 +128,6 @@
|
|||
<groupId>org.infinispan</groupId>
|
||||
<artifactId>infinispan-client-hotrod</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<!-- this is depending on an old log4j version which have this issue https://issues.apache.org/jira/browse/LOG4J2-3241 -->
|
||||
<exclusion>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.infinispan</groupId>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue