diff --git a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerTest.java b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerTest.java
index 56d9e32007b..c1ef6421593 100644
--- a/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerTest.java
+++ b/examples/embedded/src/test/java/org/eclipse/jetty/embedded/ExampleServerTest.java
@@ -21,7 +21,7 @@ package org.eclipse.jetty.embedded;
import java.net.URI;
import org.eclipse.jetty.client.api.ContentResponse;
-import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.client.util.StringRequestContent;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Server;
@@ -75,7 +75,7 @@ public class ExampleServerTest extends AbstractEmbeddedTest
String postBody = "Greetings from " + ExampleServerTest.class;
ContentResponse response = client.newRequest(uri)
.method(HttpMethod.POST)
- .content(new StringContentProvider(postBody))
+ .body(new StringRequestContent(postBody))
.send();
// Check the response status code
diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java
index 3b46fbf013e..295242a908f 100644
--- a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java
+++ b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnection.java
@@ -44,9 +44,6 @@ public class ALPNClientConnection extends NegotiatingClientConnection
public void selected(String protocol)
{
- if (protocol == null || !protocols.contains(protocol))
- close();
- else
- completed(protocol);
+ completed(protocol);
}
}
diff --git a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java
index 900013fa1f4..63c2ef63231 100644
--- a/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java
+++ b/jetty-alpn/jetty-alpn-client/src/main/java/org/eclipse/jetty/alpn/client/ALPNClientConnectionFactory.java
@@ -110,12 +110,4 @@ public class ALPNClientConnectionFactory extends NegotiatingClientConnectionFact
}
throw new IllegalStateException("No ALPNProcessor for " + engine);
}
-
- public static class ALPN extends Info
- {
- public ALPN(Executor executor, ClientConnectionFactory factory, List protocols)
- {
- super(List.of("alpn"), new ALPNClientConnectionFactory(executor, factory, protocols));
- }
- }
}
diff --git a/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java b/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java
index 3a455ffb024..c1c392242fa 100644
--- a/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java
+++ b/jetty-alpn/jetty-alpn-java-client/src/main/java/org/eclipse/jetty/alpn/java/client/JDK9ClientALPNProcessor.java
@@ -75,8 +75,11 @@ public class JDK9ClientALPNProcessor implements ALPNProcessor.Client
{
String protocol = alpnConnection.getSSLEngine().getApplicationProtocol();
if (LOG.isDebugEnabled())
- LOG.debug("selected protocol {}", protocol);
- alpnConnection.selected(protocol);
+ LOG.debug("selected protocol '{}'", protocol);
+ if (protocol != null && !protocol.isEmpty())
+ alpnConnection.selected(protocol);
+ else
+ alpnConnection.selected(null);
}
}
}
diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java
index 0f8be1166a9..c47a0cff430 100644
--- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java
+++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/RunAsAnnotationHandler.java
@@ -21,7 +21,6 @@ package org.eclipse.jetty.annotations;
import javax.servlet.Servlet;
import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler;
-import org.eclipse.jetty.plus.annotation.RunAsCollection;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.webapp.Descriptor;
import org.eclipse.jetty.webapp.MetaData;
@@ -61,14 +60,7 @@ public class RunAsAnnotationHandler extends AbstractIntrospectableAnnotationHand
if (d == null)
{
metaData.setOrigin(holder.getName() + ".servlet.run-as", runAs, clazz);
- org.eclipse.jetty.plus.annotation.RunAs ra = new org.eclipse.jetty.plus.annotation.RunAs(clazz.getName(), role);
- RunAsCollection raCollection = (RunAsCollection)_context.getAttribute(RunAsCollection.RUNAS_COLLECTION);
- if (raCollection == null)
- {
- raCollection = new RunAsCollection();
- _context.setAttribute(RunAsCollection.RUNAS_COLLECTION, raCollection);
- }
- raCollection.add(ra);
+ holder.setRunAsRole(role);
}
}
}
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java
index 13753551f7c..2e169e62266 100644
--- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestAnnotationConfiguration.java
@@ -118,25 +118,25 @@ public class TestAnnotationConfiguration
@Test
public void testAnnotationScanControl() throws Exception
{
- //check that a 2.5 webapp won't discover annotations
+ //check that a 2.5 webapp with configurationDiscovered will discover annotations
TestableAnnotationConfiguration config25 = new TestableAnnotationConfiguration();
WebAppContext context25 = new WebAppContext();
context25.setClassLoader(Thread.currentThread().getContextClassLoader());
context25.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE);
context25.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0);
+ context25.setConfigurationDiscovered(false);
context25.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context25.getServletContext().setEffectiveMajorVersion(2);
context25.getServletContext().setEffectiveMinorVersion(5);
config25.configure(context25);
config25.assertAnnotationDiscovery(false);
- //check that a 2.5 webapp with configurationDiscovered will discover annotations
+ //check that a 2.5 webapp discover annotations
TestableAnnotationConfiguration config25b = new TestableAnnotationConfiguration();
WebAppContext context25b = new WebAppContext();
context25b.setClassLoader(Thread.currentThread().getContextClassLoader());
context25b.setAttribute(AnnotationConfiguration.MULTI_THREADED, Boolean.FALSE);
context25b.setAttribute(AnnotationConfiguration.MAX_SCAN_WAIT, 0);
- context25b.setConfigurationDiscovered(true);
context25b.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context25b.getServletContext().setEffectiveMajorVersion(2);
context25b.getServletContext().setEffectiveMinorVersion(5);
@@ -293,6 +293,7 @@ public class TestAnnotationConfiguration
AnnotationConfiguration config = new AnnotationConfiguration();
WebAppContext context = new WebAppContext();
List scis;
+ context.setConfigurationDiscovered(false);
context.setClassLoader(webAppLoader);
context.getMetaData().setWebDescriptor(new WebDescriptor(Resource.newResource(web25)));
context.getMetaData().setWebInfClassesResources(classes);
diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestRunAsAnnotation.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestRunAsAnnotation.java
new file mode 100644
index 00000000000..bac5020a164
--- /dev/null
+++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestRunAsAnnotation.java
@@ -0,0 +1,63 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.annotations;
+
+import java.io.File;
+
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.webapp.WebAppContext;
+import org.eclipse.jetty.webapp.WebDescriptor;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TestRunAsAnnotation
+{
+ @Test
+ public void testRunAsAnnotation() throws Exception
+ {
+ WebAppContext wac = new WebAppContext();
+
+ //pre-add a servlet but not by descriptor
+ ServletHolder holder = new ServletHolder();
+ holder.setName("foo1");
+ holder.setHeldClass(ServletC.class);
+ holder.setInitOrder(1); //load on startup
+ wac.getServletHandler().addServletWithMapping(holder, "/foo/*");
+
+ //add another servlet of the same class, but as if by descriptor
+ ServletHolder holder2 = new ServletHolder();
+ holder2.setName("foo2");
+ holder2.setHeldClass(ServletC.class);
+ holder2.setInitOrder(1);
+ wac.getServletHandler().addServletWithMapping(holder2, "/foo2/*");
+ Resource fakeXml = Resource.newResource(new File(MavenTestingUtils.getTargetTestingDir("run-as"), "fake.xml"));
+ wac.getMetaData().setOrigin(holder2.getName() + ".servlet.run-as", new WebDescriptor(fakeXml));
+
+ AnnotationIntrospector parser = new AnnotationIntrospector(wac);
+ RunAsAnnotationHandler handler = new RunAsAnnotationHandler(wac);
+ parser.registerHandler(handler);
+ parser.introspect(new ServletC(), null);
+
+ assertEquals("admin", holder.getRunAsRole());
+ assertEquals(null, holder2.getRunAsRole());
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
index 129cdcb3748..d0b41f16f88 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractConnectorHttpClientTransport.java
@@ -72,7 +72,7 @@ public abstract class AbstractConnectorHttpClientTransport extends AbstractHttpC
context.put(ClientConnector.CLIENT_CONNECTION_FACTORY_CONTEXT_KEY, destination.getClientConnectionFactory());
@SuppressWarnings("unchecked")
Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY);
- context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, promise);
+ context.put(ClientConnector.CONNECTION_PROMISE_CONTEXT_KEY, Promise.from(ioConnection -> {}, promise::failed));
connector.connect(address, context);
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java
index 417d27d2e5c..ace7d76a9cc 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AsyncContentProvider.java
@@ -21,10 +21,14 @@ package org.eclipse.jetty.client;
import java.util.EventListener;
import org.eclipse.jetty.client.api.ContentProvider;
+import org.eclipse.jetty.client.api.Request;
/**
* A {@link ContentProvider} that notifies listeners that content is available.
+ *
+ * @deprecated no replacement, use {@link Request.Content} instead.
*/
+@Deprecated
public interface AsyncContentProvider extends ContentProvider
{
/**
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
index 4053bb89c54..0e6f070a439 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AuthenticationProtocolHandler.java
@@ -30,7 +30,6 @@ import java.util.regex.Pattern;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.Authentication.HeaderInfo;
import org.eclipse.jetty.client.api.Connection;
-import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
@@ -187,8 +186,8 @@ public abstract class AuthenticationProtocolHandler implements ProtocolHandler
return;
}
- ContentProvider requestContent = request.getContent();
- if (requestContent != null && !requestContent.isReproducible())
+ Request.Content requestContent = request.getBody();
+ if (!requestContent.isReproducible())
{
if (LOG.isDebugEnabled())
LOG.debug("Request content not reproducible for {}", request);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
index 6ae5b0eb1cd..f5270c93dc0 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java
@@ -53,7 +53,7 @@ import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
-import org.eclipse.jetty.client.util.FormContentProvider;
+import org.eclipse.jetty.client.util.FormRequestContent;
import org.eclipse.jetty.http.HttpCompliance;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
@@ -380,7 +380,7 @@ public class HttpClient extends ContainerLifeCycle
*/
public ContentResponse FORM(URI uri, Fields fields) throws InterruptedException, ExecutionException, TimeoutException
{
- return POST(uri).content(new FormContentProvider(fields)).send();
+ return POST(uri).body(new FormRequestContent(fields)).send();
}
/**
@@ -447,7 +447,7 @@ public class HttpClient extends ContainerLifeCycle
Request newRequest = newHttpRequest(oldRequest.getConversation(), newURI);
newRequest.method(oldRequest.getMethod())
.version(oldRequest.getVersion())
- .content(oldRequest.getContent())
+ .body(oldRequest.getBody())
.idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS)
.timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS)
.followRedirects(oldRequest.isFollowRedirects());
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
index cb6cc8f844c..a20662f1827 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConnection.java
@@ -27,9 +27,9 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.client.api.Authentication;
-import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
@@ -129,11 +129,6 @@ public abstract class HttpConnection implements IConnection
if (normalized)
return;
- HttpVersion version = request.getVersion();
- HttpFields headers = request.getHeaders();
- ContentProvider content = request.getContent();
- ProxyConfiguration.Proxy proxy = destination.getProxy();
-
// Make sure the path is there
String path = request.getPath();
if (path.trim().length() == 0)
@@ -144,6 +139,7 @@ public abstract class HttpConnection implements IConnection
URI uri = request.getURI();
+ ProxyConfiguration.Proxy proxy = destination.getProxy();
if (proxy instanceof HttpProxy && !HttpClient.isSchemeSecure(request.getScheme()) && uri != null)
{
path = uri.toString();
@@ -151,6 +147,8 @@ public abstract class HttpConnection implements IConnection
}
// If we are HTTP 1.1, add the Host header
+ HttpVersion version = request.getVersion();
+ HttpFields headers = request.getHeaders();
if (version.getVersion() <= 11)
{
if (!headers.containsKey(HttpHeader.HOST.asString()))
@@ -158,13 +156,16 @@ public abstract class HttpConnection implements IConnection
}
// Add content headers
- if (content != null)
+ Request.Content content = request.getBody();
+ if (content == null)
+ {
+ request.body(new BytesRequestContent());
+ }
+ else
{
if (!headers.containsKey(HttpHeader.CONTENT_TYPE.asString()))
{
- String contentType = null;
- if (content instanceof ContentProvider.Typed)
- contentType = ((ContentProvider.Typed)content).getContentType();
+ String contentType = content.getContentType();
if (contentType != null)
{
headers.put(HttpHeader.CONTENT_TYPE, contentType);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java
deleted file mode 100644
index dc9bf42b7e9..00000000000
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpContent.java
+++ /dev/null
@@ -1,237 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under
-// the terms of the Eclipse Public License 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0
-//
-// This Source Code may also be made available under the following
-// Secondary Licenses when the conditions for such availability set
-// forth in the Eclipse Public License, v. 2.0 are satisfied:
-// the Apache License v2.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.io.Closeable;
-import java.nio.ByteBuffer;
-import java.util.Collections;
-import java.util.Iterator;
-
-import org.eclipse.jetty.client.api.ContentProvider;
-import org.eclipse.jetty.util.BufferUtil;
-import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.IO;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * {@link HttpContent} is a stateful, linear representation of the request content provided
- * by a {@link ContentProvider} that can be traversed one-way to obtain content buffers to
- * send to an HTTP server.
- *
- * {@link HttpContent} offers the notion of a one-way cursor to traverse the content.
- * The cursor starts in a virtual "before" position and can be advanced using {@link #advance()}
- * until it reaches a virtual "after" position where the content is fully consumed.
- *
- * At each valid (non-before and non-after) cursor position, {@link HttpContent} provides the following state:
- *
- *
the buffer containing the content to send, via {@link #getByteBuffer()}
- *
a copy of the content buffer that can be used for notifications, via {@link #getContent()}
- *
whether the buffer to write is the last one, via {@link #isLast()}
- *
- * {@link HttpContent} may not have content, if the related {@link ContentProvider} is {@code null}, and this
- * is reflected by {@link #hasContent()}.
- *
- * {@link HttpContent} may have {@link AsyncContentProvider deferred content}, in which case {@link #advance()}
- * moves the cursor to a position that provides {@code null} {@link #getByteBuffer() buffer} and
- * {@link #getContent() content}. When the deferred content is available, a further call to {@link #advance()}
- * will move the cursor to a position that provides non {@code null} buffer and content.
- */
-public class HttpContent implements Callback, Closeable
-{
- private static final Logger LOG = LoggerFactory.getLogger(HttpContent.class);
- private static final ByteBuffer AFTER = ByteBuffer.allocate(0);
- private static final ByteBuffer CLOSE = ByteBuffer.allocate(0);
-
- private final ContentProvider provider;
- private final Iterator iterator;
- private ByteBuffer buffer;
- private ByteBuffer content;
- private boolean last;
-
- public HttpContent(ContentProvider provider)
- {
- this.provider = provider;
- this.iterator = provider == null ? Collections.emptyIterator() : provider.iterator();
- }
-
- /**
- * @return true if the buffer is the sentinel instance {@link CLOSE}
- */
- private static boolean isTheCloseBuffer(ByteBuffer buffer)
- {
- @SuppressWarnings("ReferenceEquality")
- boolean isTheCloseBuffer = (buffer == CLOSE);
- return isTheCloseBuffer;
- }
-
- /**
- * @return whether there is any content at all
- */
- public boolean hasContent()
- {
- return provider != null;
- }
-
- /**
- * @return whether the cursor points to the last content
- */
- public boolean isLast()
- {
- return last;
- }
-
- /**
- * @return the {@link ByteBuffer} containing the content at the cursor's position
- */
- public ByteBuffer getByteBuffer()
- {
- return buffer;
- }
-
- /**
- * @return a {@link ByteBuffer#slice()} of {@link #getByteBuffer()} at the cursor's position
- */
- public ByteBuffer getContent()
- {
- return content;
- }
-
- /**
- * Advances the cursor to the next block of content.
- *
- * The next block of content may be valid (which yields a non-null buffer
- * returned by {@link #getByteBuffer()}), but may also be deferred
- * (which yields a null buffer returned by {@link #getByteBuffer()}).
- *
- * If the block of content pointed by the new cursor position is valid, this method returns true.
- *
- * @return true if there is content at the new cursor's position, false otherwise.
- */
- public boolean advance()
- {
- if (iterator instanceof Synchronizable)
- {
- synchronized (((Synchronizable)iterator).getLock())
- {
- return advance(iterator);
- }
- }
- else
- {
- return advance(iterator);
- }
- }
-
- private boolean advance(Iterator iterator)
- {
- boolean hasNext = iterator.hasNext();
- ByteBuffer bytes = hasNext ? iterator.next() : null;
- boolean hasMore = hasNext && iterator.hasNext();
- boolean wasLast = last;
- last = !hasMore;
-
- if (hasNext)
- {
- buffer = bytes;
- content = bytes == null ? null : bytes.slice();
- if (LOG.isDebugEnabled())
- LOG.debug("Advanced content to {} chunk {}", hasMore ? "next" : "last", String.valueOf(bytes));
- return bytes != null;
- }
- else
- {
- // No more content, but distinguish between last and consumed.
- if (wasLast)
- {
- buffer = content = AFTER;
- if (LOG.isDebugEnabled())
- LOG.debug("Advanced content past last chunk");
- }
- else
- {
- buffer = content = CLOSE;
- if (LOG.isDebugEnabled())
- LOG.debug("Advanced content to last chunk");
- }
- return false;
- }
- }
-
- /**
- * @return whether the cursor has been advanced past the {@link #isLast() last} position.
- */
- @SuppressWarnings("ReferenceEquality")
- public boolean isConsumed()
- {
- return buffer == AFTER;
- }
-
- @Override
- public void succeeded()
- {
- if (isConsumed())
- return;
- if (isTheCloseBuffer(buffer))
- return;
- if (iterator instanceof Callback)
- ((Callback)iterator).succeeded();
- }
-
- @Override
- public void failed(Throwable x)
- {
- if (isConsumed())
- return;
- if (isTheCloseBuffer(buffer))
- return;
- if (iterator instanceof Callback)
- ((Callback)iterator).failed(x);
- }
-
- @Override
- public void close()
- {
- if (iterator instanceof Closeable)
- IO.close((Closeable)iterator);
- }
-
- @Override
- public String toString()
- {
- return String.format("%s@%x - has=%b,last=%b,consumed=%b,buffer=%s",
- getClass().getSimpleName(),
- hashCode(),
- hasContent(),
- isLast(),
- isConsumed(),
- BufferUtil.toDetailString(getContent()));
- }
-}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
index 1a743f496f9..75a89dcefe4 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpExchange.java
@@ -20,6 +20,7 @@ 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.slf4j.Logger;
@@ -237,6 +238,12 @@ public class HttpExchange
// We failed this exchange, deal with it.
+ // Applications could be blocked providing
+ // request content, notify them of the failure.
+ Request.Content body = request.getBody();
+ if (abortRequest && body != null)
+ body.fail(failure);
+
// Case #1: exchange was in the destination queue.
if (destination.remove(this))
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
index 15a4047c4e7..e8fba07cb92 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java
@@ -528,7 +528,7 @@ public abstract class HttpReceiver
HttpResponse response = exchange.getResponse();
if (LOG.isDebugEnabled())
- LOG.debug("Response complete {}", response);
+ LOG.debug("Response complete {}, result: {}", response, result);
if (result != null)
{
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
index 0cc192f6cdd..92ec7e7ae8b 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java
@@ -49,8 +49,9 @@ 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.internal.RequestContentAdapter;
import org.eclipse.jetty.client.util.FutureResponseListener;
-import org.eclipse.jetty.client.util.PathContentProvider;
+import org.eclipse.jetty.client.util.PathRequestContent;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
@@ -81,7 +82,7 @@ public class HttpRequest implements Request
private long idleTimeout = -1;
private long timeout;
private long timeoutAt;
- private ContentProvider content;
+ private Content content;
private boolean followRedirects;
private List cookies;
private Map attributes;
@@ -647,7 +648,9 @@ public class HttpRequest implements Request
@Override
public ContentProvider getContent()
{
- return content;
+ if (content instanceof RequestContentAdapter)
+ return ((RequestContentAdapter)content).getContentProvider();
+ return null;
}
@Override
@@ -661,6 +664,18 @@ public class HttpRequest implements Request
{
if (contentType != null)
header(HttpHeader.CONTENT_TYPE, contentType);
+ return body(ContentProvider.toRequestContent(content));
+ }
+
+ @Override
+ public Content getBody()
+ {
+ return content;
+ }
+
+ @Override
+ public Request body(Content content)
+ {
this.content = content;
return this;
}
@@ -674,7 +689,7 @@ public class HttpRequest implements Request
@Override
public Request file(Path file, String contentType) throws IOException
{
- return content(new PathContentProvider(contentType, file));
+ return body(new PathRequestContent(contentType, file));
}
@Override
@@ -809,11 +824,7 @@ public class HttpRequest implements Request
public boolean abort(Throwable cause)
{
if (aborted.compareAndSet(null, Objects.requireNonNull(cause)))
- {
- if (content instanceof Callback)
- ((Callback)content).failed(cause);
return conversation.abort(cause);
- }
return false;
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
index beedfb22a53..0fe281890cd 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java
@@ -23,52 +23,38 @@ import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicReference;
-import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
-import org.eclipse.jetty.util.IteratingCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
- * {@link HttpSender} abstracts the algorithm to send HTTP requests, so that subclasses only implement
- * the transport-specific code to send requests over the wire, implementing
- * {@link #sendHeaders(HttpExchange, HttpContent, Callback)} and
- * {@link #sendContent(HttpExchange, HttpContent, Callback)}.
- *
- * {@link HttpSender} governs two state machines.
- *
- * The request state machine is updated by {@link HttpSender} as the various steps of sending a request
- * are executed, see {@code RequestState}.
- * At any point in time, a user thread may abort the request, which may (if the request has not been
- * completely sent yet) move the request state machine to {@code RequestState#FAILURE}.
- * The request state machine guarantees that the request steps are executed (by I/O threads) only if
- * the request has not been failed already.
- *
- * The sender state machine is updated by {@link HttpSender} from three sources: deferred content notifications
- * (via {@link #onContent()}), 100-continue notifications (via {@link #proceed(HttpExchange, Throwable)})
- * and normal request send (via {@link #sendContent(HttpExchange, HttpContent, Callback)}).
- * This state machine must guarantee that the request sending is never executed concurrently: only one of
- * those sources may trigger the call to {@link #sendContent(HttpExchange, HttpContent, Callback)}.
+ *
HttpSender abstracts the algorithm to send HTTP requests, so that subclasses only
+ * implement the transport-specific code to send requests over the wire, implementing
+ * {@link #sendHeaders(HttpExchange, ByteBuffer, boolean, Callback)} and
+ * {@link #sendContent(HttpExchange, ByteBuffer, boolean, Callback)}.
+ *
HttpSender governs the request state machines, which is updated as the various
+ * steps of sending a request are executed, see {@code RequestState}.
+ * At any point in time, a user thread may abort the request, which may (if the request
+ * has not been completely sent yet) move the request state machine to {@code RequestState#FAILURE}.
+ * The request state machine guarantees that the request steps are executed (by I/O threads)
+ * only if the request has not been failed already.
*
* @see HttpReceiver
*/
-public abstract class HttpSender implements AsyncContentProvider.Listener
+public abstract class HttpSender
{
private static final Logger LOG = LoggerFactory.getLogger(HttpSender.class);
+ private final ContentConsumer consumer = new ContentConsumer();
private final AtomicReference requestState = new AtomicReference<>(RequestState.QUEUED);
- private final AtomicReference senderState = new AtomicReference<>(SenderState.IDLE);
- private final Callback commitCallback = new CommitCallback();
- private final IteratingCallback contentCallback = new ContentCallback();
- private final Callback lastCallback = new LastCallback();
+ private final AtomicReference failure = new AtomicReference<>();
private final HttpChannel channel;
- private HttpContent content;
- private Throwable failure;
+ private Request.Content.Subscription subscription;
protected HttpSender(HttpChannel channel)
{
@@ -90,126 +76,15 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
return requestState.get() == RequestState.FAILURE;
}
- @Override
- public void onContent()
- {
- HttpExchange exchange = getHttpExchange();
- if (exchange == null)
- return;
-
- while (true)
- {
- SenderState current = senderState.get();
- switch (current)
- {
- case IDLE:
- {
- SenderState newSenderState = SenderState.SENDING;
- if (updateSenderState(current, newSenderState))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
- contentCallback.iterate();
- return;
- }
- break;
- }
- case SENDING:
- {
- SenderState newSenderState = SenderState.SENDING_WITH_CONTENT;
- if (updateSenderState(current, newSenderState))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
- return;
- }
- break;
- }
- case EXPECTING:
- {
- SenderState newSenderState = SenderState.EXPECTING_WITH_CONTENT;
- if (updateSenderState(current, newSenderState))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
- return;
- }
- break;
- }
- case PROCEEDING:
- {
- SenderState newSenderState = SenderState.PROCEEDING_WITH_CONTENT;
- if (updateSenderState(current, newSenderState))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Deferred content available, {} -> {}", current, newSenderState);
- return;
- }
- break;
- }
- case SENDING_WITH_CONTENT:
- case EXPECTING_WITH_CONTENT:
- case PROCEEDING_WITH_CONTENT:
- case WAITING:
- case COMPLETED:
- case FAILED:
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Deferred content available, {}", current);
- return;
- }
- default:
- {
- illegalSenderState(current);
- return;
- }
- }
- }
- }
-
public void send(HttpExchange exchange)
{
if (!queuedToBegin(exchange))
return;
- Request request = exchange.getRequest();
- ContentProvider contentProvider = request.getContent();
- HttpContent content = this.content = new HttpContent(contentProvider);
-
- SenderState newSenderState = SenderState.SENDING;
- if (expects100Continue(request))
- newSenderState = content.hasContent() ? SenderState.EXPECTING_WITH_CONTENT : SenderState.EXPECTING;
-
- out:
- while (true)
- {
- SenderState current = senderState.get();
- switch (current)
- {
- case IDLE:
- case COMPLETED:
- {
- if (updateSenderState(current, newSenderState))
- break out;
- break;
- }
- default:
- {
- illegalSenderState(current);
- return;
- }
- }
- }
-
- // Setting the listener may trigger calls to onContent() by other
- // threads so we must set it only after the sender state has been updated
- if (contentProvider instanceof AsyncContentProvider)
- ((AsyncContentProvider)contentProvider).setListener(this);
-
if (!beginToHeaders(exchange))
return;
- sendHeaders(exchange, content, commitCallback);
+ demand();
}
protected boolean expects100Continue(Request request)
@@ -228,10 +103,16 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
RequestNotifier notifier = getHttpChannel().getHttpDestination().getRequestNotifier();
notifier.notifyBegin(request);
+ Request.Content body = request.getBody();
+
+ consumer.exchange = exchange;
+ consumer.expect100 = expects100Continue(request);
+ subscription = body.subscribe(consumer, !consumer.expect100);
+
if (updateRequestState(RequestState.TRANSIENT, RequestState.BEGIN))
return true;
- terminateRequest(exchange);
+ abortRequest(exchange);
return false;
}
@@ -249,7 +130,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
if (updateRequestState(RequestState.TRANSIENT, RequestState.HEADERS))
return true;
- terminateRequest(exchange);
+ abortRequest(exchange);
return false;
}
@@ -267,7 +148,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
if (updateRequestState(RequestState.TRANSIENT, RequestState.COMMIT))
return true;
- terminateRequest(exchange);
+ abortRequest(exchange);
return false;
}
@@ -291,7 +172,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
if (updateRequestState(RequestState.TRANSIENT, RequestState.CONTENT))
return true;
- terminateRequest(exchange);
+ abortRequest(exchange);
return false;
}
default:
@@ -353,6 +234,20 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
executeAbort(exchange, failure);
}
+ private void demand()
+ {
+ try
+ {
+ subscription.demand();
+ }
+ catch (Throwable x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Failure invoking demand()", x);
+ anyToFailure(x);
+ }
+ }
+
private void executeAbort(HttpExchange exchange, Throwable failure)
{
try
@@ -368,13 +263,23 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
}
}
- private void terminateRequest(HttpExchange exchange)
+ private void abortRequest(HttpExchange exchange)
{
- // In abort(), the state is updated before the failure is recorded
- // to avoid to overwrite it, so here we may read a null failure.
- Throwable failure = this.failure;
- if (failure == null)
- failure = new HttpRequestException("Concurrent failure", exchange.getRequest());
+ Throwable failure = this.failure.get();
+
+ if (subscription != null)
+ subscription.fail(failure);
+
+ dispose();
+
+ Request request = exchange.getRequest();
+ if (LOG.isDebugEnabled())
+ LOG.debug("Request abort {} {} on {}: {}", request, exchange, getHttpChannel(), failure);
+ HttpDestination destination = getHttpChannel().getHttpDestination();
+ destination.getRequestNotifier().notifyFailure(request, failure);
+
+ // Mark atomically the request as terminated, with
+ // respect to concurrency between request and response.
Result result = exchange.terminateRequest();
terminateRequest(exchange, failure, result);
}
@@ -415,163 +320,73 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
}
/**
- * Implementations should send the HTTP headers over the wire, possibly with some content,
- * in a single write, and notify the given {@code callback} of the result of this operation.
- *
- * If there is more content to send, then {@link #sendContent(HttpExchange, HttpContent, Callback)}
- * will be invoked.
+ *
Implementations should send the HTTP headers over the wire, possibly with some content,
+ * in a single write, and notify the given {@code callback} of the result of this operation.
+ *
If there is more content to send, then {@link #sendContent(HttpExchange, ByteBuffer, boolean, Callback)}
+ * will be invoked.
*
- * @param exchange the exchange to send
- * @param content the content to send
+ * @param exchange the exchange
+ * @param contentBuffer the content to send
+ * @param lastContent whether the content is the last content to send
* @param callback the callback to notify
*/
- protected abstract void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback);
+ protected abstract void sendHeaders(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback);
/**
- * Implementations should send the content at the {@link HttpContent} cursor position over the wire.
- *
- * The {@link HttpContent} cursor is advanced by HttpSender at the right time, and if more
- * content needs to be sent, this method is invoked again; subclasses need only to send the content
- * at the {@link HttpContent} cursor position.
- *
- * This method is invoked one last time when {@link HttpContent#isConsumed()} is true and therefore
- * there is no actual content to send.
- * This is done to allow subclasses to write "terminal" bytes (such as the terminal chunk when the
- * transfer encoding is chunked) if their protocol needs to.
+ *
Implementations should send the given HTTP content over the wire.
*
- * @param exchange the exchange to send
- * @param content the content to send
+ * @param exchange the exchange
+ * @param contentBuffer the content to send
+ * @param lastContent whether the content is the last content to send
* @param callback the callback to notify
*/
- protected abstract void sendContent(HttpExchange exchange, HttpContent content, Callback callback);
+ protected abstract void sendContent(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback);
protected void reset()
{
- HttpContent content = this.content;
- this.content = null;
- content.close();
- senderState.set(SenderState.COMPLETED);
+ consumer.reset();
}
protected void dispose()
{
- HttpContent content = this.content;
- this.content = null;
- if (content != null)
- content.close();
- senderState.set(SenderState.FAILED);
}
public void proceed(HttpExchange exchange, Throwable failure)
{
- if (!expects100Continue(exchange.getRequest()))
- return;
-
- if (failure != null)
- {
+ consumer.expect100 = false;
+ if (failure == null)
+ demand();
+ else
anyToFailure(failure);
- return;
- }
-
- while (true)
- {
- SenderState current = senderState.get();
- switch (current)
- {
- case EXPECTING:
- {
- // We are still sending the headers, but we already got the 100 Continue.
- if (updateSenderState(current, SenderState.PROCEEDING))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Proceeding while expecting");
- return;
- }
- break;
- }
- case EXPECTING_WITH_CONTENT:
- {
- // More deferred content was submitted to onContent(), we already
- // got the 100 Continue, but we may be still sending the headers
- // (for example, with SSL we may have sent the encrypted data,
- // received the 100 Continue but not yet updated the decrypted
- // WriteFlusher so sending more content now may result in a
- // WritePendingException).
- if (updateSenderState(current, SenderState.PROCEEDING_WITH_CONTENT))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Proceeding while scheduled");
- return;
- }
- break;
- }
- case WAITING:
- {
- // We received the 100 Continue, now send the content if any.
- if (updateSenderState(current, SenderState.SENDING))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Proceeding while waiting");
- contentCallback.iterate();
- return;
- }
- break;
- }
- case FAILED:
- {
- return;
- }
- default:
- {
- illegalSenderState(current);
- return;
- }
- }
- }
}
public boolean abort(HttpExchange exchange, Throwable failure)
{
+ // Store only the first failure.
+ this.failure.compareAndSet(null, failure);
+
// Update the state to avoid more request processing.
- boolean terminate;
- out:
+ boolean abort;
while (true)
{
RequestState current = requestState.get();
- switch (current)
+ if (current == RequestState.FAILURE)
{
- case FAILURE:
+ return false;
+ }
+ else
+ {
+ if (updateRequestState(current, RequestState.FAILURE))
{
- return false;
- }
- default:
- {
- if (updateRequestState(current, RequestState.FAILURE))
- {
- terminate = current != RequestState.TRANSIENT;
- break out;
- }
+ abort = current != RequestState.TRANSIENT;
break;
}
}
}
- this.failure = failure;
-
- dispose();
-
- Request request = exchange.getRequest();
- if (LOG.isDebugEnabled())
- LOG.debug("Request abort {} {} on {}: {}", request, exchange, getHttpChannel(), failure);
- HttpDestination destination = getHttpChannel().getHttpDestination();
- destination.getRequestNotifier().notifyFailure(request, failure);
-
- if (terminate)
+ if (abort)
{
- // Mark atomically the request as terminated, with
- // respect to concurrency between request and response.
- Result result = exchange.terminateRequest();
- terminateRequest(exchange, failure, result);
+ abortRequest(exchange);
return true;
}
else
@@ -590,27 +405,13 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
return updated;
}
- private boolean updateSenderState(SenderState from, SenderState to)
- {
- boolean updated = senderState.compareAndSet(from, to);
- if (!updated && LOG.isDebugEnabled())
- LOG.debug("SenderState update failed: {} -> {}: {}", from, to, senderState.get());
- return updated;
- }
-
- private void illegalSenderState(SenderState current)
- {
- anyToFailure(new IllegalStateException("Expected " + current + " found " + senderState.get() + " instead"));
- }
-
@Override
public String toString()
{
- return String.format("%s@%x(req=%s,snd=%s,failure=%s)",
+ return String.format("%s@%x(req=%s,failure=%s)",
getClass().getSimpleName(),
hashCode(),
requestState,
- senderState,
failure);
}
@@ -649,286 +450,98 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
FAILURE
}
- /**
- * The sender states {@link HttpSender} goes through when sending a request.
- */
- private enum SenderState
+ private class ContentConsumer implements Request.Content.Consumer, Callback
{
- /**
- * {@link HttpSender} is not sending request headers nor request content
- */
- IDLE,
- /**
- * {@link HttpSender} is sending the request header or request content
- */
- SENDING,
- /**
- * {@link HttpSender} is currently sending the request, and deferred content is available to be sent
- */
- SENDING_WITH_CONTENT,
- /**
- * {@link HttpSender} is sending the headers but will wait for 100 Continue before sending the content
- */
- EXPECTING,
- /**
- * {@link HttpSender} is currently sending the headers, will wait for 100 Continue, and deferred content is available to be sent
- */
- EXPECTING_WITH_CONTENT,
- /**
- * {@link HttpSender} has sent the headers and is waiting for 100 Continue
- */
- WAITING,
- /**
- * {@link HttpSender} is sending the headers, while 100 Continue has arrived
- */
- PROCEEDING,
- /**
- * {@link HttpSender} is sending the headers, while 100 Continue has arrived, and deferred content is available to be sent
- */
- PROCEEDING_WITH_CONTENT,
- /**
- * {@link HttpSender} has finished to send the request
- */
- COMPLETED,
- /**
- * {@link HttpSender} has failed to send the request
- */
- FAILED
- }
+ private HttpExchange exchange;
+ private boolean expect100;
+ private ByteBuffer contentBuffer;
+ private boolean lastContent;
+ private Callback callback;
+ private boolean committed;
+
+ private void reset()
+ {
+ exchange = null;
+ contentBuffer = null;
+ lastContent = false;
+ callback = null;
+ committed = false;
+ }
+
+ @Override
+ public void onContent(ByteBuffer buffer, boolean last, Callback callback)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Content {} last={} for {}", BufferUtil.toDetailString(buffer), last, exchange.getRequest());
+ this.contentBuffer = buffer.slice();
+ this.lastContent = last;
+ this.callback = callback;
+ if (committed)
+ sendContent(exchange, buffer, last, this);
+ else
+ sendHeaders(exchange, buffer, last, this);
+ }
+
+ @Override
+ public void onFailure(Throwable failure)
+ {
+ failed(failure);
+ }
- private class CommitCallback implements Callback
- {
@Override
public void succeeded()
{
- try
+ boolean proceed = false;
+ if (committed)
{
- HttpContent content = HttpSender.this.content;
- if (content == null)
- return;
- content.succeeded();
- process();
- }
- catch (Throwable x)
- {
- anyToFailure(x);
- }
- }
-
- @Override
- public void failed(Throwable failure)
- {
- HttpContent content = HttpSender.this.content;
- if (content == null)
- return;
- content.failed(failure);
- anyToFailure(failure);
- }
-
- private void process() throws Exception
- {
- HttpExchange exchange = getHttpExchange();
- if (exchange == null)
- return;
-
- if (!headersToCommit(exchange))
- return;
-
- HttpContent content = HttpSender.this.content;
- if (content == null)
- return;
-
- if (!content.hasContent())
- {
- // No content to send, we are done.
- someToSuccess(exchange);
+ proceed = someToContent(exchange, contentBuffer);
}
else
{
- // Was any content sent while committing?
- ByteBuffer contentBuffer = content.getContent();
- if (contentBuffer != null)
+ committed = true;
+ if (headersToCommit(exchange))
{
- if (!someToContent(exchange, contentBuffer))
- return;
- }
-
- while (true)
- {
- SenderState current = senderState.get();
- switch (current)
- {
- case SENDING:
- {
- contentCallback.iterate();
- return;
- }
- case SENDING_WITH_CONTENT:
- {
- // We have deferred content to send.
- updateSenderState(current, SenderState.SENDING);
- break;
- }
- case EXPECTING:
- {
- // We sent the headers, wait for the 100 Continue response.
- if (updateSenderState(current, SenderState.WAITING))
- return;
- break;
- }
- case EXPECTING_WITH_CONTENT:
- {
- // We sent the headers, we have deferred content to send,
- // wait for the 100 Continue response.
- if (updateSenderState(current, SenderState.WAITING))
- return;
- break;
- }
- case PROCEEDING:
- {
- // We sent the headers, we have the 100 Continue response,
- // we have no content to send.
- if (updateSenderState(current, SenderState.IDLE))
- return;
- break;
- }
- case PROCEEDING_WITH_CONTENT:
- {
- // We sent the headers, we have the 100 Continue response,
- // we have deferred content to send.
- updateSenderState(current, SenderState.SENDING);
- break;
- }
- case FAILED:
- {
- return;
- }
- default:
- {
- illegalSenderState(current);
- return;
- }
- }
+ proceed = true;
+ // Was any content sent while committing?
+ if (contentBuffer.hasRemaining())
+ proceed = someToContent(exchange, contentBuffer);
}
}
- }
- }
- private class ContentCallback extends IteratingCallback
- {
- @Override
- protected Action process() throws Exception
- {
- HttpExchange exchange = getHttpExchange();
- if (exchange == null)
- return Action.IDLE;
+ // Succeed the content callback only after emitting the request content event.
+ callback.succeeded();
- HttpContent content = HttpSender.this.content;
- if (content == null)
- return Action.IDLE;
+ // There was some concurrent error?
+ if (!proceed)
+ return;
- while (true)
+ if (lastContent)
+ {
+ someToSuccess(exchange);
+ }
+ else if (expect100)
{
- boolean advanced = content.advance();
- boolean lastContent = content.isLast();
if (LOG.isDebugEnabled())
- LOG.debug("Content present {}, last {}, consumed {} for {}", advanced, lastContent, content.isConsumed(), exchange.getRequest());
-
- if (advanced)
- {
- sendContent(exchange, content, this);
- return Action.SCHEDULED;
- }
-
- if (lastContent)
- {
- sendContent(exchange, content, lastCallback);
- return Action.IDLE;
- }
-
- SenderState current = senderState.get();
- switch (current)
- {
- case SENDING:
- {
- if (updateSenderState(current, SenderState.IDLE))
- {
- if (LOG.isDebugEnabled())
- LOG.debug("Content is deferred for {}", exchange.getRequest());
- return Action.IDLE;
- }
- break;
- }
- case SENDING_WITH_CONTENT:
- {
- updateSenderState(current, SenderState.SENDING);
- break;
- }
- default:
- {
- illegalSenderState(current);
- return Action.IDLE;
- }
- }
+ LOG.debug("Expecting 100 Continue for {}", exchange.getRequest());
+ }
+ else
+ {
+ demand();
}
}
@Override
- public void succeeded()
+ public void failed(Throwable x)
{
- HttpExchange exchange = getHttpExchange();
- if (exchange == null)
- return;
- HttpContent content = HttpSender.this.content;
- if (content == null)
- return;
- content.succeeded();
- ByteBuffer buffer = content.getContent();
- someToContent(exchange, buffer);
- super.succeeded();
+ if (callback != null)
+ callback.failed(x);
+ anyToFailure(x);
}
@Override
- public void onCompleteFailure(Throwable failure)
+ public InvocationType getInvocationType()
{
- HttpContent content = HttpSender.this.content;
- if (content == null)
- return;
- content.failed(failure);
- anyToFailure(failure);
- }
-
- @Override
- protected void onCompleteSuccess()
- {
- // Nothing to do, since we always return IDLE from process().
- // Termination is obtained via LastCallback.
- }
- }
-
- private class LastCallback implements Callback
- {
- @Override
- public void succeeded()
- {
- HttpExchange exchange = getHttpExchange();
- if (exchange == null)
- return;
- HttpContent content = HttpSender.this.content;
- if (content == null)
- return;
- content.succeeded();
- someToSuccess(exchange);
- }
-
- @Override
- public void failed(Throwable failure)
- {
- HttpContent content = HttpSender.this.content;
- if (content == null)
- return;
- content.failed(failure);
- anyToFailure(failure);
+ return InvocationType.NON_BLOCKING;
}
}
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java
index a4886519114..805ca6b5b39 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/RequestNotifier.java
@@ -158,10 +158,10 @@ public class RequestNotifier
public void notifyContent(Request request, ByteBuffer content)
{
- // Slice the buffer to avoid that listeners peek into data they should not look at.
- content = content.slice();
if (!content.hasRemaining())
return;
+ // Slice the buffer to avoid that listeners peek into data they should not look at.
+ content = content.slice();
// Optimized to avoid allocations of iterator instances.
List requestListeners = request.getRequestListeners(null);
for (int i = 0; i < requestListeners.size(); ++i)
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java
index f9a705af082..3d43110fdc3 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/ContentProvider.java
@@ -23,6 +23,7 @@ import java.nio.ByteBuffer;
import java.util.Iterator;
import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.internal.RequestContentAdapter;
import org.eclipse.jetty.client.util.ByteBufferContentProvider;
import org.eclipse.jetty.client.util.PathContentProvider;
@@ -41,9 +42,24 @@ import org.eclipse.jetty.client.util.PathContentProvider;
* header set by applications; if the length is negative, it typically removes
* any {@code Content-Length} header set by applications, resulting in chunked
* content (i.e. {@code Transfer-Encoding: chunked}) being sent to the server.
+ *
+ * @deprecated use {@link Request.Content} instead, or {@link #toRequestContent(ContentProvider)}
+ * to convert ContentProvider to {@link Request.Content}.
*/
+@Deprecated
public interface ContentProvider extends Iterable
{
+ /**
+ *
Converts a ContentProvider to a {@link Request.Content}.
+ *
+ * @param provider the ContentProvider to convert
+ * @return a {@link Request.Content} that wraps the ContentProvider
+ */
+ public static Request.Content toRequestContent(ContentProvider provider)
+ {
+ return new RequestContentAdapter(provider);
+ }
+
/**
* @return the content length, if known, or -1 if the content length is unknown
*/
@@ -68,7 +84,10 @@ public interface ContentProvider extends Iterable
/**
* An extension of {@link ContentProvider} that provides a content type string
* to be used as a {@code Content-Type} HTTP header in requests.
+ *
+ * @deprecated use {@link Request.Content} instead
*/
+ @Deprecated
public interface Typed extends ContentProvider
{
/**
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
index 8b7d97d1248..53e67aab4f4 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java
@@ -19,6 +19,7 @@
package org.eclipse.jetty.client.api;
import java.io.IOException;
+import java.io.InputStream;
import java.net.HttpCookie;
import java.net.URI;
import java.net.URLEncoder;
@@ -37,6 +38,7 @@ import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
+import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
/**
@@ -216,22 +218,39 @@ public interface Request
/**
* @return the content provider of this request
+ * @deprecated use {@link #getBody()} instead
*/
+ @Deprecated
ContentProvider getContent();
/**
* @param content the content provider of this request
* @return this request object
+ * @deprecated use {@link #body(Content)} instead
*/
+ @Deprecated
Request content(ContentProvider content);
/**
* @param content the content provider of this request
* @param contentType the content type
* @return this request object
+ * @deprecated use {@link #body(Content)} instead
*/
+ @Deprecated
Request content(ContentProvider content, String contentType);
+ /**
+ * @return the request content of this request
+ */
+ Content getBody();
+
+ /**
+ * @param content the request content of this request
+ * @return this request object
+ */
+ Request body(Content content);
+
/**
* Shortcut method to specify a file as a content for this request, with the default content type of
* "application/octect-stream".
@@ -615,4 +634,158 @@ public interface Request
{
}
}
+
+ /**
+ *
A reactive model to produce request content, similar to {@link java.util.concurrent.Flow.Publisher}.
+ *
Implementations receive the content consumer via {@link #subscribe(Consumer, boolean)},
+ * and return a {@link Subscription} as the link between producer and consumer.
+ *
Content producers must notify content to the consumer only if there is demand.
+ *
Content consumers can generate demand for content by invoking {@link Subscription#demand()}.
+ *
Content production must follow this algorithm:
+ *
+ *
the first time content is demanded
+ *
+ *
when the content is not available => produce an empty content
+ *
when the content is available:
+ *
+ *
when {@code emitInitialContent == false} => produce an empty content
+ *
when {@code emitInitialContent == true} => produce the content
+ *
+ *
+ *
+ *
+ *
the second and subsequent times content is demanded
+ *
+ *
when the content is not available => do not produce content
+ *
when the content is available => produce the content
+ *
+ *
+ *
+ *
+ * @see #subscribe(Consumer, boolean)
+ */
+ public interface Content
+ {
+ /**
+ * @return the content type string such as "application/octet-stream" or
+ * "application/json;charset=UTF8", or null if no content type must be set
+ */
+ public default String getContentType()
+ {
+ return "application/octet-stream";
+ }
+
+ /**
+ * @return the content length, if known, or -1 if the content length is unknown
+ */
+ public default long getLength()
+ {
+ return -1;
+ }
+
+ /**
+ *
Whether this content producer can produce exactly the same content more
+ * than once.
+ *
Implementations should return {@code true} only if the content can be
+ * produced more than once, which means that {@link #subscribe(Consumer, boolean)}
+ * may be called again.
+ *
The {@link HttpClient} implementation may use this method in particular
+ * cases where it detects that it is safe to retry a request that failed.
+ *
+ * @return whether the content can be produced more than once
+ */
+ public default boolean isReproducible()
+ {
+ return false;
+ }
+
+ /**
+ *
Initializes this content producer with the content consumer, and with
+ * the indication of whether initial content, if present, must be emitted
+ * upon the initial demand of content (to support delaying the send of the
+ * request content in case of {@code Expect: 100-Continue} when
+ * {@code emitInitialContent} is {@code false}).
+ *
+ * @param consumer the content consumer to invoke when there is demand for content
+ * @param emitInitialContent whether to emit initial content, if present
+ * @return the Subscription that links this producer to the consumer
+ */
+ public Subscription subscribe(Consumer consumer, boolean emitInitialContent);
+
+ /**
+ *
Fails this request content, possibly failing and discarding accumulated
+ * content that was not demanded.
+ *
The failure may be notified to the consumer at a later time, when the
+ * consumer demands for content.
+ *
Typical failure: the request being aborted by user code, or idle timeouts.
+ *
+ * @param failure the reason of the failure
+ */
+ public default void fail(Throwable failure)
+ {
+ }
+
+ /**
+ *
A reactive model to consume request content, similar to {@link java.util.concurrent.Flow.Subscriber}.
+ *
Callback methods {@link #onContent(ByteBuffer, boolean, Callback)} and {@link #onFailure(Throwable)}
+ * are invoked in strict sequential order and never concurrently, although possibly by different threads.
+ */
+ public interface Consumer
+ {
+ /**
+ *
Callback method invoked by the producer when there is content available
+ * and there is demand for content.
+ *
The {@code callback} is associated with the {@code buffer} to
+ * signal when the content buffer has been consumed.
+ *
Failing the {@code callback} does not have any effect on content
+ * production. To stop the content production, the consumer must call
+ * {@link Subscription#fail(Throwable)}.
+ *
In case an exception is thrown by this method, it is equivalent to
+ * a call to {@link Subscription#fail(Throwable)}.
+ *
+ * @param buffer the content buffer to consume
+ * @param last whether it's the last content
+ * @param callback a callback to invoke when the content buffer is consumed
+ */
+ public void onContent(ByteBuffer buffer, boolean last, Callback callback);
+
+ /**
+ *
Callback method invoked by the producer when it failed to produce content.
+ *
Typical failure: a producer getting an exception while reading from an
+ * {@link InputStream} to produce content.
+ *
+ * @param failure the reason of the failure
+ */
+ public default void onFailure(Throwable failure)
+ {
+ }
+ }
+
+ /**
+ *
The link between a content producer and a content consumer.
+ *
Content consumers can demand more content via {@link #demand()},
+ * or ask the content producer to stop producing content via
+ * {@link #fail(Throwable)}.
Implements the algorithm described in {@link Request.Content}.
+ */
+ public abstract class AbstractSubscription implements Subscription
+ {
+ private final Consumer consumer;
+ private final boolean emitInitialContent;
+ private Throwable failure;
+ private int demand;
+ // Whether content production was stalled because there was no demand.
+ private boolean stalled;
+ // Whether the first content has been produced.
+ private boolean committed;
+
+ public AbstractSubscription(Consumer consumer, boolean emitInitialContent)
+ {
+ this.consumer = consumer;
+ this.emitInitialContent = emitInitialContent;
+ this.stalled = true;
+ }
+
+ @Override
+ public void demand()
+ {
+ boolean produce;
+ try (AutoLock ignored = lock.lock())
+ {
+ ++demand;
+ produce = stalled;
+ if (stalled)
+ stalled = false;
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Content demand, producing {} for {}", produce, this);
+ if (produce)
+ produce();
+ }
+
+ private void produce()
+ {
+ while (true)
+ {
+ Throwable failure;
+ boolean committed;
+ try (AutoLock ignored = lock.lock())
+ {
+ failure = this.failure;
+ committed = this.committed;
+ }
+ if (failure != null)
+ {
+ notifyFailure(failure);
+ return;
+ }
+
+ if (committed || emitInitialContent)
+ {
+ try
+ {
+ if (!produceContent(this::processContent))
+ return;
+ }
+ catch (Throwable x)
+ {
+ // Fail and loop around to notify the failure.
+ fail(x);
+ }
+ }
+ else
+ {
+ if (!processContent(BufferUtil.EMPTY_BUFFER, false, Callback.NOOP))
+ return;
+ }
+ }
+ }
+
+ /**
+ *
Subclasses implement this method to produce content,
+ * without worrying about demand or exception handling.
+ *
Typical implementation (pseudo code):
+ *
+ * protected boolean produceContent(Producer producer) throws Exception
+ * {
+ * // Step 1: try to produce content, exceptions may be thrown during production
+ * // (for example, producing content reading from an InputStream may throw).
+ *
+ * // Step 2A: content could be produced.
+ * ByteBuffer buffer = ...;
+ * boolean last = ...;
+ * Callback callback = ...;
+ * return producer.produce(buffer, last, callback);
+ *
+ * // Step 2B: content could not be produced.
+ * // (for example it is not available yet)
+ * return false;
+ * }
+ *
+ *
+ * @param producer the producer to notify when content can be produced
+ * @return whether content production should continue
+ * @throws Exception when content production fails
+ */
+ protected abstract boolean produceContent(Producer producer) throws Exception;
+
+ @Override
+ public void fail(Throwable failure)
+ {
+ try (AutoLock ignored = lock.lock())
+ {
+ if (this.failure == null)
+ this.failure = failure;
+ }
+ }
+
+ private boolean processContent(ByteBuffer content, boolean last, Callback callback)
+ {
+ try (AutoLock ignored = lock.lock())
+ {
+ committed = true;
+ --demand;
+ }
+
+ if (content != null)
+ notifyContent(content, last, callback);
+ else
+ callback.succeeded();
+
+ boolean noDemand;
+ try (AutoLock ignored = lock.lock())
+ {
+ noDemand = demand == 0;
+ if (noDemand)
+ stalled = true;
+ }
+ if (noDemand)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("No demand, processing stalled for {}", this);
+ return false;
+ }
+ return true;
+ }
+
+ protected void notifyContent(ByteBuffer buffer, boolean last, Callback callback)
+ {
+ try
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Notifying content last={} {} for {}", last, BufferUtil.toDetailString(buffer), this);
+ consumer.onContent(buffer, last, callback);
+ }
+ catch (Throwable x)
+ {
+ callback.failed(x);
+ fail(x);
+ }
+ }
+
+ private void notifyFailure(Throwable failure)
+ {
+ try
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Notifying failure for {}", this, failure);
+ consumer.onFailure(failure);
+ }
+ catch (Exception x)
+ {
+ LOG.trace("Failure while notifying content failure {}", failure, x);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ int demand;
+ boolean stalled;
+ boolean committed;
+ try (AutoLock ignored = lock.lock())
+ {
+ demand = this.demand;
+ stalled = this.stalled;
+ committed = this.committed;
+ }
+ return String.format("%s.%s@%x[demand=%d,stalled=%b,committed=%b,emitInitial=%b]",
+ getClass().getEnclosingClass().getSimpleName(),
+ getClass().getSimpleName(), hashCode(), demand, stalled, committed, emitInitialContent);
+ }
+ }
+
+ public interface Producer
+ {
+ boolean produce(ByteBuffer content, boolean lastContent, Callback callback);
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java
index 679de25be1b..6779bd5028a 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AbstractTypedContentProvider.java
@@ -20,6 +20,10 @@ package org.eclipse.jetty.client.util;
import org.eclipse.jetty.client.api.ContentProvider;
+/**
+ * @deprecated use {@link AbstractRequestContent} instead.
+ */
+@Deprecated
public abstract class AbstractTypedContentProvider implements ContentProvider.Typed
{
private final String contentType;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/AsyncRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AsyncRequestContent.java
new file mode 100644
index 00000000000..2e084786d0c
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/AsyncRequestContent.java
@@ -0,0 +1,385 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.util;
+
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.locks.Condition;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.thread.AutoLock;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AsyncRequestContent implements Request.Content, Request.Content.Subscription, Closeable
+{
+ private static final Logger LOG = LoggerFactory.getLogger(AsyncRequestContent.class);
+
+ private final AutoLock lock = new AutoLock();
+ private final Condition flush = lock.newCondition();
+ private final Deque chunks = new ArrayDeque<>();
+ private final String contentType;
+ private long length = -1;
+ private Consumer consumer;
+ private boolean emitInitialContent;
+ private int demand;
+ private boolean stalled;
+ private boolean committed;
+ private boolean closed;
+ private boolean terminated;
+ private Throwable failure;
+
+ public AsyncRequestContent(ByteBuffer... buffers)
+ {
+ this("application/octet-stream", buffers);
+ }
+
+ public AsyncRequestContent(String contentType, ByteBuffer... buffers)
+ {
+ this.contentType = contentType;
+ Stream.of(buffers).forEach(this::offer);
+ }
+
+ @Override
+ public String getContentType()
+ {
+ return contentType;
+ }
+
+ @Override
+ public long getLength()
+ {
+ return length;
+ }
+
+ @Override
+ public Subscription subscribe(Consumer consumer, boolean emitInitialContent)
+ {
+ try (AutoLock ignored = lock.lock())
+ {
+ if (this.consumer != null)
+ throw new IllegalStateException("Multiple subscriptions not supported on " + this);
+ this.consumer = consumer;
+ this.emitInitialContent = emitInitialContent;
+ this.stalled = true;
+ if (closed)
+ length = chunks.stream().mapToLong(chunk -> chunk.buffer.remaining()).sum();
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Content subscription for {}: {}", this, consumer);
+ return this;
+ }
+
+ @Override
+ public void demand()
+ {
+ boolean produce;
+ try (AutoLock ignored = lock.lock())
+ {
+ ++demand;
+ produce = stalled;
+ if (stalled)
+ stalled = false;
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Content demand, producing {} for {}", produce, this);
+ if (produce)
+ produce();
+ }
+
+ @Override
+ public void fail(Throwable failure)
+ {
+ List toFail = List.of();
+ try (AutoLock ignored = lock.lock())
+ {
+ if (this.failure == null)
+ {
+ this.failure = failure;
+ // Transfer all chunks to fail them all.
+ toFail = chunks.stream()
+ .map(chunk -> chunk.callback)
+ .collect(Collectors.toList());
+ chunks.clear();
+ flush.signal();
+ }
+ }
+ toFail.forEach(c -> c.failed(failure));
+ }
+
+ public boolean offer(ByteBuffer buffer)
+ {
+ return offer(buffer, Callback.NOOP);
+ }
+
+ public boolean offer(ByteBuffer buffer, Callback callback)
+ {
+ return offer(new Chunk(buffer, callback));
+ }
+
+ private boolean offer(Chunk chunk)
+ {
+ boolean produce = false;
+ Throwable failure;
+ try (AutoLock ignored = lock.lock())
+ {
+ failure = this.failure;
+ if (failure == null)
+ {
+ if (closed)
+ {
+ failure = new IOException("closed");
+ }
+ else
+ {
+ chunks.offer(chunk);
+ if (demand > 0)
+ {
+ if (stalled)
+ {
+ stalled = false;
+ produce = true;
+ }
+ }
+ }
+ }
+ }
+ if (LOG.isDebugEnabled())
+ LOG.debug("Content offer {}, producing {} for {}", failure == null ? "succeeded" : "failed", produce, this, failure);
+ if (failure != null)
+ {
+ chunk.callback.failed(failure);
+ return false;
+ }
+ else if (produce)
+ {
+ produce();
+ }
+ return true;
+ }
+
+ private void produce()
+ {
+ while (true)
+ {
+ Throwable failure;
+ try (AutoLock ignored = lock.lock())
+ {
+ failure = this.failure;
+ }
+ if (failure != null)
+ {
+ notifyFailure(consumer, failure);
+ return;
+ }
+
+ try
+ {
+ Consumer consumer;
+ Chunk chunk = Chunk.EMPTY;
+ boolean lastContent = false;
+ try (AutoLock ignored = lock.lock())
+ {
+ if (terminated)
+ throw new EOFException("Demand after last content");
+ consumer = this.consumer;
+ if (committed || emitInitialContent)
+ {
+ chunk = chunks.poll();
+ lastContent = closed && chunks.isEmpty();
+ if (lastContent)
+ terminated = true;
+ }
+ if (chunk == null && (lastContent || !committed))
+ chunk = Chunk.EMPTY;
+ if (chunk == null)
+ {
+ stalled = true;
+ }
+ else
+ {
+ --demand;
+ committed = true;
+ }
+ }
+ if (chunk == null)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("No content, processing stalled for {}", this);
+ return;
+ }
+
+ notifyContent(consumer, chunk.buffer, lastContent, Callback.from(this::notifyFlush, chunk.callback));
+
+ boolean noDemand;
+ try (AutoLock ignored = lock.lock())
+ {
+ noDemand = demand == 0;
+ if (noDemand)
+ stalled = true;
+ }
+ if (noDemand)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("No demand, processing stalled for {}", this);
+ return;
+ }
+ }
+ catch (Throwable x)
+ {
+ // Fail and loop around to notify the failure.
+ fail(x);
+ }
+ }
+ }
+
+ private void notifyContent(Consumer consumer, ByteBuffer buffer, boolean last, Callback callback)
+ {
+ try
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Notifying content last={} {} for {}", last, BufferUtil.toDetailString(buffer), this);
+ consumer.onContent(buffer, last, callback);
+ }
+ catch (Throwable x)
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Failure while notifying content", x);
+ callback.failed(x);
+ fail(x);
+ }
+ }
+
+ private void notifyFailure(Consumer consumer, Throwable failure)
+ {
+ try
+ {
+ if (LOG.isDebugEnabled())
+ LOG.debug("Notifying failure for {}", this, failure);
+ consumer.onFailure(failure);
+ }
+ catch (Throwable x)
+ {
+ LOG.trace("Failure while notifying content failure {}", failure, x);
+ }
+ }
+
+ private void notifyFlush()
+ {
+ try (AutoLock ignored = lock.lock())
+ {
+ flush.signal();
+ }
+ }
+
+ public void flush() throws IOException
+ {
+ try (AutoLock ignored = lock.lock())
+ {
+ try
+ {
+ while (true)
+ {
+ // Always wrap the exception to make sure
+ // the stack trace comes from flush().
+ if (failure != null)
+ throw new IOException(failure);
+ if (chunks.isEmpty())
+ return;
+ flush.await();
+ }
+ }
+ catch (InterruptedException x)
+ {
+ throw new InterruptedIOException();
+ }
+ }
+ }
+
+ @Override
+ public void close()
+ {
+ boolean produce = false;
+ try (AutoLock ignored = lock.lock())
+ {
+ if (closed)
+ return;
+ closed = true;
+ if (demand > 0)
+ {
+ if (stalled)
+ {
+ stalled = false;
+ produce = true;
+ }
+ }
+ flush.signal();
+ }
+ if (produce)
+ produce();
+ }
+
+ public boolean isClosed()
+ {
+ try (AutoLock ignored = lock.lock())
+ {
+ return closed;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ int demand;
+ boolean stalled;
+ int chunks;
+ try (AutoLock ignored = lock.lock())
+ {
+ demand = this.demand;
+ stalled = this.stalled;
+ chunks = this.chunks.size();
+ }
+ return String.format("%s@%x[demand=%d,stalled=%b,chunks=%d]", getClass().getSimpleName(), hashCode(), demand, stalled, chunks);
+ }
+
+ private static class Chunk
+ {
+ private static final Chunk EMPTY = new Chunk(BufferUtil.EMPTY_BUFFER, Callback.NOOP);
+
+ private final ByteBuffer buffer;
+ private final Callback callback;
+
+ private Chunk(ByteBuffer buffer, Callback callback)
+ {
+ this.buffer = Objects.requireNonNull(buffer);
+ this.callback = Objects.requireNonNull(callback);
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java
index 1342dc6cc0c..e1c94d3595c 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferContentProvider.java
@@ -30,7 +30,10 @@ import org.eclipse.jetty.client.api.ContentProvider;
* The position and limit of the {@link ByteBuffer}s passed to the constructor are not modified,
* and each invocation of the {@link #iterator()} method returns a {@link ByteBuffer#slice() slice}
* of the original {@link ByteBuffer}.
+ *
+ * @deprecated use {@link ByteBufferRequestContent} instead.
*/
+@Deprecated
public class ByteBufferContentProvider extends AbstractTypedContentProvider
{
private final ByteBuffer[] buffers;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferRequestContent.java
new file mode 100644
index 00000000000..36362acfc3f
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/ByteBufferRequestContent.java
@@ -0,0 +1,94 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.util;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+
+/**
+ *
A {@link Request.Content} for {@link ByteBuffer}s.
+ *
The position and limit of the {@link ByteBuffer}s passed to the constructor are not modified;
+ * content production returns a {@link ByteBuffer#slice() slice} of the original {@link ByteBuffer}.
+ */
+public class ByteBufferRequestContent extends AbstractRequestContent
+{
+ private final ByteBuffer[] buffers;
+ private final long length;
+
+ public ByteBufferRequestContent(ByteBuffer... buffers)
+ {
+ this("application/octet-stream", buffers);
+ }
+
+ public ByteBufferRequestContent(String contentType, ByteBuffer... buffers)
+ {
+ super(contentType);
+ this.buffers = buffers;
+ this.length = Arrays.stream(buffers).mapToLong(Buffer::remaining).sum();
+ }
+
+ @Override
+ public long getLength()
+ {
+ return length;
+ }
+
+ @Override
+ public boolean isReproducible()
+ {
+ return true;
+ }
+
+ @Override
+ protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent)
+ {
+ return new SubscriptionImpl(consumer, emitInitialContent);
+ }
+
+ private class SubscriptionImpl extends AbstractSubscription
+ {
+ private int index;
+
+ private SubscriptionImpl(Consumer consumer, boolean emitInitialContent)
+ {
+ super(consumer, emitInitialContent);
+ }
+
+ @Override
+ protected boolean produceContent(Producer producer) throws IOException
+ {
+ if (index < 0)
+ throw new EOFException("Demand after last content");
+ ByteBuffer buffer = BufferUtil.EMPTY_BUFFER;
+ if (index < buffers.length)
+ buffer = buffers[index++];
+ boolean lastContent = index == buffers.length;
+ if (lastContent)
+ index = -1;
+ return producer.produce(buffer.slice(), lastContent, Callback.NOOP);
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java
index 631311fdb37..3f4d3f1f2f8 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesContentProvider.java
@@ -26,7 +26,10 @@ import org.eclipse.jetty.client.api.ContentProvider;
/**
* A {@link ContentProvider} for byte arrays.
+ *
+ * @deprecated use {@link BytesRequestContent} instead.
*/
+@Deprecated
public class BytesContentProvider extends AbstractTypedContentProvider
{
private final byte[][] bytes;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesRequestContent.java
new file mode 100644
index 00000000000..43001d607ce
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BytesRequestContent.java
@@ -0,0 +1,91 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.util;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+
+/**
+ * A {@link Request.Content} for byte arrays.
+ */
+public class BytesRequestContent extends AbstractRequestContent
+{
+ private final byte[][] bytes;
+ private final long length;
+
+ public BytesRequestContent(byte[]... bytes)
+ {
+ this("application/octet-stream", bytes);
+ }
+
+ public BytesRequestContent(String contentType, byte[]... bytes)
+ {
+ super(contentType);
+ this.bytes = bytes;
+ this.length = Arrays.stream(bytes).mapToLong(a -> a.length).sum();
+ }
+
+ @Override
+ public long getLength()
+ {
+ return length;
+ }
+
+ @Override
+ public boolean isReproducible()
+ {
+ return true;
+ }
+
+ @Override
+ protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent)
+ {
+ return new SubscriptionImpl(consumer, emitInitialContent);
+ }
+
+ private class SubscriptionImpl extends AbstractSubscription
+ {
+ private int index;
+
+ private SubscriptionImpl(Consumer consumer, boolean emitInitialContent)
+ {
+ super(consumer, emitInitialContent);
+ }
+
+ @Override
+ protected boolean produceContent(Producer producer) throws IOException
+ {
+ if (index < 0)
+ throw new EOFException("Demand after last content");
+ ByteBuffer buffer = BufferUtil.EMPTY_BUFFER;
+ if (index < bytes.length)
+ buffer = ByteBuffer.wrap(bytes[index++]);
+ boolean lastContent = index == bytes.length;
+ if (lastContent)
+ index = -1;
+ return producer.produce(buffer, lastContent, Callback.NOOP);
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java
index 174b2e25356..2d369b08fa4 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java
@@ -85,7 +85,10 @@ import org.eclipse.jetty.util.Callback;
* content.offer(ByteBuffer.wrap("some content".getBytes()));
* }
*
+ *
+ * @deprecated use {@link AsyncRequestContent} instead.
*/
+@Deprecated
public class DeferredContentProvider implements AsyncContentProvider, Callback, Closeable
{
private static final Chunk CLOSE = new Chunk(BufferUtil.EMPTY_BUFFER, Callback.NOOP);
@@ -285,6 +288,7 @@ public class DeferredContentProvider implements AsyncContentProvider, Callback,
synchronized (lock)
{
chunk = current;
+ current = null;
if (chunk != null)
{
--size;
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java
index 280494282d8..2bff3857e3a 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormContentProvider.java
@@ -30,7 +30,10 @@ import org.eclipse.jetty.util.Fields;
/**
* A {@link ContentProvider} for form uploads with the
* "application/x-www-form-urlencoded" content type.
+ *
+ * @deprecated use {@link FormRequestContent} instead.
*/
+@Deprecated
public class FormContentProvider extends StringContentProvider
{
public FormContentProvider(Fields fields)
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormRequestContent.java
new file mode 100644
index 00000000000..03f29139c16
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/FormRequestContent.java
@@ -0,0 +1,78 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.util;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.charset.UnsupportedCharsetException;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.Fields;
+
+/**
+ *
A {@link Request.Content} for form uploads with the
+ * "application/x-www-form-urlencoded" content type.
+ */
+public class FormRequestContent extends StringRequestContent
+{
+ public FormRequestContent(Fields fields)
+ {
+ this(fields, StandardCharsets.UTF_8);
+ }
+
+ public FormRequestContent(Fields fields, Charset charset)
+ {
+ super("application/x-www-form-urlencoded", convert(fields, charset), charset);
+ }
+
+ public static String convert(Fields fields)
+ {
+ return convert(fields, StandardCharsets.UTF_8);
+ }
+
+ public static String convert(Fields fields, Charset charset)
+ {
+ // Assume 32 chars between name and value.
+ StringBuilder builder = new StringBuilder(fields.getSize() * 32);
+ for (Fields.Field field : fields)
+ {
+ for (String value : field.getValues())
+ {
+ if (builder.length() > 0)
+ builder.append("&");
+ builder.append(encode(field.getName(), charset)).append("=").append(encode(value, charset));
+ }
+ }
+ return builder.toString();
+ }
+
+ private static String encode(String value, Charset charset)
+ {
+ try
+ {
+ return URLEncoder.encode(value, charset.name());
+ }
+ catch (UnsupportedEncodingException x)
+ {
+ throw new UnsupportedCharsetException(charset.name());
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java
index 2f81b71f94b..c46bb958df3 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java
@@ -50,7 +50,10 @@ import org.slf4j.LoggerFactory;
* The {@link InputStream} passed to the constructor is by default closed when is it fully
* consumed (or when an exception is thrown while reading it), unless otherwise specified
* to the {@link #InputStreamContentProvider(java.io.InputStream, int, boolean) constructor}.
+ *
+ * @deprecated use {@link InputStreamRequestContent} instead
*/
+@Deprecated
public class InputStreamContentProvider implements ContentProvider, Callback, Closeable
{
private static final Logger LOG = LoggerFactory.getLogger(InputStreamContentProvider.class);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java
new file mode 100644
index 00000000000..586b3f07e4d
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamRequestContent.java
@@ -0,0 +1,149 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.util;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IO;
+
+/**
+ *
A {@link Request.Content} that produces content from an {@link InputStream}.
+ *
The input stream is read once and therefore fully consumed.
+ *
It is possible to specify, at the constructor, a buffer size used to read
+ * content from the stream, by default 1024 bytes.
+ *
The {@link InputStream} passed to the constructor is by default closed
+ * when is it fully consumed.
+ */
+public class InputStreamRequestContent extends AbstractRequestContent
+{
+ private static final int DEFAULT_BUFFER_SIZE = 4096;
+
+ private final InputStream stream;
+ private final int bufferSize;
+ private Subscription subscription;
+
+ public InputStreamRequestContent(InputStream stream)
+ {
+ this(stream, DEFAULT_BUFFER_SIZE);
+ }
+
+ public InputStreamRequestContent(String contentType, InputStream stream)
+ {
+ this(contentType, stream, DEFAULT_BUFFER_SIZE);
+ }
+
+ public InputStreamRequestContent(InputStream stream, int bufferSize)
+ {
+ this("application/octet-stream", stream, bufferSize);
+ }
+
+ public InputStreamRequestContent(String contentType, InputStream stream, int bufferSize)
+ {
+ super(contentType);
+ this.stream = stream;
+ this.bufferSize = bufferSize;
+ }
+
+ @Override
+ protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent)
+ {
+ if (subscription != null)
+ throw new IllegalStateException("Multiple subscriptions not supported on " + this);
+ return subscription = new SubscriptionImpl(consumer, emitInitialContent);
+ }
+
+ @Override
+ public void fail(Throwable failure)
+ {
+ super.fail(failure);
+ close();
+ }
+
+ protected ByteBuffer onRead(byte[] buffer, int offset, int length)
+ {
+ return ByteBuffer.wrap(buffer, offset, length);
+ }
+
+ protected void onReadFailure(Throwable failure)
+ {
+ }
+
+ private void close()
+ {
+ IO.close(stream);
+ }
+
+ private class SubscriptionImpl extends AbstractSubscription
+ {
+ private boolean terminated;
+
+ private SubscriptionImpl(Consumer consumer, boolean emitInitialContent)
+ {
+ super(consumer, emitInitialContent);
+ }
+
+ @Override
+ protected boolean produceContent(Producer producer) throws IOException
+ {
+ if (terminated)
+ throw new EOFException("Demand after last content");
+ byte[] bytes = new byte[bufferSize];
+ int read = read(bytes);
+ ByteBuffer buffer = BufferUtil.EMPTY_BUFFER;
+ boolean last = true;
+ if (read < 0)
+ {
+ close();
+ terminated = true;
+ }
+ else
+ {
+ buffer = onRead(bytes, 0, read);
+ last = false;
+ }
+ return producer.produce(buffer, last, Callback.NOOP);
+ }
+
+ private int read(byte[] bytes) throws IOException
+ {
+ try
+ {
+ return stream.read(bytes);
+ }
+ catch (Throwable x)
+ {
+ onReadFailure(x);
+ throw x;
+ }
+ }
+
+ @Override
+ public void fail(Throwable failure)
+ {
+ super.fail(failure);
+ close();
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
index 94d7dac43b8..4db7ec1f7d6 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamResponseListener.java
@@ -27,6 +27,7 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@@ -76,12 +77,13 @@ import org.slf4j.LoggerFactory;
public class InputStreamResponseListener extends Listener.Adapter
{
private static final Logger LOG = LoggerFactory.getLogger(InputStreamResponseListener.class);
- private static final DeferredContentProvider.Chunk EOF = new DeferredContentProvider.Chunk(BufferUtil.EMPTY_BUFFER, Callback.NOOP);
+ private static final Chunk EOF = new Chunk(BufferUtil.EMPTY_BUFFER, Callback.NOOP);
+
private final Object lock = this;
private final CountDownLatch responseLatch = new CountDownLatch(1);
private final CountDownLatch resultLatch = new CountDownLatch(1);
private final AtomicReference stream = new AtomicReference<>();
- private final Queue chunks = new ArrayDeque<>();
+ private final Queue chunks = new ArrayDeque<>();
private Response response;
private Result result;
private Throwable failure;
@@ -120,7 +122,7 @@ public class InputStreamResponseListener extends Listener.Adapter
{
if (LOG.isDebugEnabled())
LOG.debug("Queueing content {}", content);
- chunks.add(new DeferredContentProvider.Chunk(content, callback));
+ chunks.add(new Chunk(content, callback));
lock.notifyAll();
}
}
@@ -268,7 +270,7 @@ public class InputStreamResponseListener extends Listener.Adapter
{
while (true)
{
- DeferredContentProvider.Chunk chunk = chunks.peek();
+ Chunk chunk = chunks.peek();
if (chunk == null || chunk == EOF)
break;
callbacks.add(chunk.callback);
@@ -299,7 +301,7 @@ public class InputStreamResponseListener extends Listener.Adapter
Callback callback = null;
synchronized (lock)
{
- DeferredContentProvider.Chunk chunk;
+ Chunk chunk;
while (true)
{
chunk = chunks.peek();
@@ -367,4 +369,16 @@ public class InputStreamResponseListener extends Listener.Adapter
super.close();
}
}
+
+ private static class Chunk
+ {
+ private final ByteBuffer buffer;
+ private final Callback callback;
+
+ private Chunk(ByteBuffer buffer, Callback callback)
+ {
+ this.buffer = Objects.requireNonNull(buffer);
+ this.callback = Objects.requireNonNull(callback);
+ }
+ }
}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java
index 8252e3e7413..8f40c1217ad 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartContentProvider.java
@@ -63,7 +63,10 @@ import org.slf4j.LoggerFactory;
* <input type="file" name="icon" />
* </form>
*
+ *
+ * @deprecated use {@link MultiPartRequestContent} instead.
*/
+@Deprecated
public class MultiPartContentProvider extends AbstractTypedContentProvider implements AsyncContentProvider, Closeable
{
private static final Logger LOG = LoggerFactory.getLogger(MultiPartContentProvider.class);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java
new file mode 100644
index 00000000000..7acfdb20c89
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/MultiPartRequestContent.java
@@ -0,0 +1,392 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpField;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.io.RuntimeIOException;
+import org.eclipse.jetty.util.Callback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
A {@link Request.Content} for form uploads with the {@code "multipart/form-data"}
+ * content type.
+ */
+public class MultiPartRequestContent extends AbstractRequestContent implements Closeable
+{
+ private static final Logger LOG = LoggerFactory.getLogger(MultiPartRequestContent.class);
+ private static final byte[] COLON_SPACE_BYTES = new byte[]{':', ' '};
+ private static final byte[] CR_LF_BYTES = new byte[]{'\r', '\n'};
+
+ private static String makeBoundary()
+ {
+ Random random = new Random();
+ StringBuilder builder = new StringBuilder("JettyHttpClientBoundary");
+ int length = builder.length();
+ while (builder.length() < length + 16)
+ {
+ long rnd = random.nextLong();
+ builder.append(Long.toString(rnd < 0 ? -rnd : rnd, 36));
+ }
+ builder.setLength(length + 16);
+ return builder.toString();
+ }
+
+ private final List parts = new ArrayList<>();
+ private final ByteBuffer firstBoundary;
+ private final ByteBuffer middleBoundary;
+ private final ByteBuffer onlyBoundary;
+ private final ByteBuffer lastBoundary;
+ private long length;
+ private boolean closed;
+ private Subscription subscription;
+
+ public MultiPartRequestContent()
+ {
+ this(makeBoundary());
+ }
+
+ public MultiPartRequestContent(String boundary)
+ {
+ super("multipart/form-data; boundary=" + boundary);
+ String firstBoundaryLine = "--" + boundary + "\r\n";
+ this.firstBoundary = ByteBuffer.wrap(firstBoundaryLine.getBytes(StandardCharsets.US_ASCII));
+ String middleBoundaryLine = "\r\n" + firstBoundaryLine;
+ this.middleBoundary = ByteBuffer.wrap(middleBoundaryLine.getBytes(StandardCharsets.US_ASCII));
+ String onlyBoundaryLine = "--" + boundary + "--\r\n";
+ this.onlyBoundary = ByteBuffer.wrap(onlyBoundaryLine.getBytes(StandardCharsets.US_ASCII));
+ String lastBoundaryLine = "\r\n" + onlyBoundaryLine;
+ this.lastBoundary = ByteBuffer.wrap(lastBoundaryLine.getBytes(StandardCharsets.US_ASCII));
+ this.length = -1;
+ }
+
+ @Override
+ public long getLength()
+ {
+ return length;
+ }
+
+ @Override
+ protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent)
+ {
+ if (!closed)
+ throw new IllegalStateException("MultiPartRequestContent must be closed before sending the request");
+ if (subscription != null)
+ throw new IllegalStateException("Multiple subscriptions not supported on " + this);
+ length = calculateLength();
+ return subscription = new SubscriptionImpl(consumer, emitInitialContent);
+ }
+
+ @Override
+ public void fail(Throwable failure)
+ {
+ parts.stream()
+ .map(part -> part.content)
+ .forEach(content -> content.fail(failure));
+ }
+
+ /**
+ *
Adds a field part with the given {@code name} as field name, and the given
+ * {@code content} as part content.
+ *
The {@code Content-Type} of this part will be obtained from:
+ *
+ *
the {@code Content-Type} header in the {@code fields} parameter; otherwise
+ *
the {@link Request.Content#getContentType()}
+ *
+ *
+ * @param name the part name
+ * @param content the part content
+ * @param fields the headers associated with this part
+ */
+ public void addFieldPart(String name, Request.Content content, HttpFields fields)
+ {
+ addPart(new Part(name, null, content, fields));
+ }
+
+ /**
+ *
Adds a file part with the given {@code name} as field name, the given
+ * {@code fileName} as file name, and the given {@code content} as part content.
+ *
The {@code Content-Type} of this part will be obtained from:
+ *
+ *
the {@code Content-Type} header in the {@code fields} parameter; otherwise
+ *
the {@link Request.Content#getContentType()}
+ *
+ *
+ * @param name the part name
+ * @param fileName the file name associated to this part
+ * @param content the part content
+ * @param fields the headers associated with this part
+ */
+ public void addFilePart(String name, String fileName, Request.Content content, HttpFields fields)
+ {
+ addPart(new Part(name, fileName, content, fields));
+ }
+
+ private void addPart(Part part)
+ {
+ parts.add(part);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Added {}", part);
+ }
+
+ @Override
+ public void close()
+ {
+ closed = true;
+ }
+
+ private long calculateLength()
+ {
+ // Compute the length, if possible.
+ if (parts.isEmpty())
+ {
+ return onlyBoundary.remaining();
+ }
+ else
+ {
+ long result = 0;
+ for (int i = 0; i < parts.size(); ++i)
+ {
+ result += (i == 0) ? firstBoundary.remaining() : middleBoundary.remaining();
+ Part part = parts.get(i);
+ long partLength = part.length;
+ result += partLength;
+ if (partLength < 0)
+ {
+ result = -1;
+ break;
+ }
+ }
+ if (result > 0)
+ result += lastBoundary.remaining();
+ return result;
+ }
+ }
+
+ private static class Part
+ {
+ private final String name;
+ private final String fileName;
+ private final Request.Content content;
+ private final HttpFields fields;
+ private final ByteBuffer headers;
+ private final long length;
+
+ private Part(String name, String fileName, Request.Content content, HttpFields fields)
+ {
+ this.name = name;
+ this.fileName = fileName;
+ this.content = content;
+ this.fields = fields;
+ this.headers = headers();
+ this.length = content.getLength() < 0 ? -1 : headers.remaining() + content.getLength();
+ }
+
+ private ByteBuffer headers()
+ {
+ try
+ {
+ // Compute the Content-Disposition.
+ String contentDisposition = "Content-Disposition: form-data; name=\"" + name + "\"";
+ if (fileName != null)
+ contentDisposition += "; filename=\"" + fileName + "\"";
+ contentDisposition += "\r\n";
+
+ // Compute the Content-Type.
+ String contentType = fields == null ? null : fields.get(HttpHeader.CONTENT_TYPE);
+ if (contentType == null)
+ contentType = content.getContentType();
+ contentType = "Content-Type: " + contentType + "\r\n";
+
+ if (fields == null || fields.size() == 0)
+ {
+ String headers = contentDisposition;
+ headers += contentType;
+ headers += "\r\n";
+ return ByteBuffer.wrap(headers.getBytes(StandardCharsets.UTF_8));
+ }
+
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream((fields.size() + 1) * contentDisposition.length());
+ buffer.write(contentDisposition.getBytes(StandardCharsets.UTF_8));
+ buffer.write(contentType.getBytes(StandardCharsets.UTF_8));
+ for (HttpField field : fields)
+ {
+ if (HttpHeader.CONTENT_TYPE.equals(field.getHeader()))
+ continue;
+ buffer.write(field.getName().getBytes(StandardCharsets.US_ASCII));
+ buffer.write(COLON_SPACE_BYTES);
+ String value = field.getValue();
+ if (value != null)
+ buffer.write(value.getBytes(StandardCharsets.UTF_8));
+ buffer.write(CR_LF_BYTES);
+ }
+ buffer.write(CR_LF_BYTES);
+ return ByteBuffer.wrap(buffer.toByteArray());
+ }
+ catch (IOException x)
+ {
+ throw new RuntimeIOException(x);
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x[name=%s,fileName=%s,length=%d,headers=%s]",
+ getClass().getSimpleName(),
+ hashCode(),
+ name,
+ fileName,
+ content.getLength(),
+ fields);
+ }
+ }
+
+ private class SubscriptionImpl extends AbstractSubscription implements Consumer
+ {
+ private State state = State.FIRST_BOUNDARY;
+ private int index;
+ private Subscription subscription;
+
+ private SubscriptionImpl(Consumer consumer, boolean emitInitialContent)
+ {
+ super(consumer, emitInitialContent);
+ }
+
+ @Override
+ protected boolean produceContent(Producer producer) throws IOException
+ {
+ ByteBuffer buffer;
+ boolean last = false;
+ switch (state)
+ {
+ case FIRST_BOUNDARY:
+ {
+ if (parts.isEmpty())
+ {
+ state = State.COMPLETE;
+ buffer = onlyBoundary.slice();
+ last = true;
+ break;
+ }
+ else
+ {
+ state = State.HEADERS;
+ buffer = firstBoundary.slice();
+ break;
+ }
+ }
+ case HEADERS:
+ {
+ Part part = parts.get(index);
+ Request.Content content = part.content;
+ subscription = content.subscribe(this, true);
+ state = State.CONTENT;
+ buffer = part.headers.slice();
+ break;
+ }
+ case CONTENT:
+ {
+ buffer = null;
+ subscription.demand();
+ break;
+ }
+ case MIDDLE_BOUNDARY:
+ {
+ state = State.HEADERS;
+ buffer = middleBoundary.slice();
+ break;
+ }
+ case LAST_BOUNDARY:
+ {
+ state = State.COMPLETE;
+ buffer = lastBoundary.slice();
+ last = true;
+ break;
+ }
+ case COMPLETE:
+ {
+ throw new EOFException("Demand after last content");
+ }
+ default:
+ {
+ throw new IllegalStateException("Invalid state " + state);
+ }
+ }
+ return producer.produce(buffer, last, Callback.NOOP);
+ }
+
+ @Override
+ public void onContent(ByteBuffer buffer, boolean last, Callback callback)
+ {
+ if (last)
+ {
+ ++index;
+ if (index < parts.size())
+ state = State.MIDDLE_BOUNDARY;
+ else
+ state = State.LAST_BOUNDARY;
+ }
+ notifyContent(buffer, false, callback);
+ }
+
+ @Override
+ public void onFailure(Throwable failure)
+ {
+ if (subscription != null)
+ subscription.fail(failure);
+ }
+ }
+
+ private enum State
+ {
+ FIRST_BOUNDARY, HEADERS, CONTENT, MIDDLE_BOUNDARY, LAST_BOUNDARY, COMPLETE
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java
index edffa00bd68..fe015c6f314 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamContentProvider.java
@@ -72,7 +72,10 @@ import org.eclipse.jetty.util.Callback;
* output.write("some content".getBytes());
* }
*
+ *
+ * @deprecated use {@link OutputStreamRequestContent} instead
*/
+@Deprecated
public class OutputStreamContentProvider implements AsyncContentProvider, Callback, Closeable
{
private final DeferredContentProvider deferred = new DeferredContentProvider();
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamRequestContent.java
new file mode 100644
index 00000000000..f093456c575
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/OutputStreamRequestContent.java
@@ -0,0 +1,125 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.util;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.concurrent.ExecutionException;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.api.Response;
+import org.eclipse.jetty.util.FutureCallback;
+
+/**
+ *
A {@link Request.Content} that provides content asynchronously through an {@link OutputStream}
+ * similar to {@link AsyncRequestContent}.
+ *
{@link OutputStreamRequestContent} can only be used in conjunction with
+ * {@link Request#send(Response.CompleteListener)} (and not with its blocking counterpart
+ * {@link Request#send()}) because it provides content asynchronously.
+ *
Content must be provided by writing to the {@link #getOutputStream() output stream}
+ * that must be {@link OutputStream#close() closed} when all content has been provided.
+ *
Example usage:
+ *
+ * HttpClient httpClient = ...;
+ *
+ * // Use try-with-resources to autoclose the output stream.
+ * OutputStreamRequestContent content = new OutputStreamRequestContent();
+ * try (OutputStream output = content.getOutputStream())
+ * {
+ * httpClient.newRequest("localhost", 8080)
+ * .content(content)
+ * .send(new Response.CompleteListener()
+ * {
+ * @Override
+ * public void onComplete(Result result)
+ * {
+ * // Your logic here
+ * }
+ * });
+ *
+ * // At a later time...
+ * output.write("some content".getBytes());
+ *
+ * // Even later...
+ * output.write("more content".getBytes());
+ * } // Implicit call to output.close().
+ *
+ */
+public class OutputStreamRequestContent extends AsyncRequestContent
+{
+ private final AsyncOutputStream output;
+
+ public OutputStreamRequestContent()
+ {
+ this("application/octet-stream");
+ }
+
+ public OutputStreamRequestContent(String contentType)
+ {
+ super(contentType);
+ this.output = new AsyncOutputStream();
+ }
+
+ public OutputStream getOutputStream()
+ {
+ return output;
+ }
+
+ private class AsyncOutputStream extends OutputStream
+ {
+ @Override
+ public void write(int b) throws IOException
+ {
+ write(new byte[]{(byte)b}, 0, 1);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException
+ {
+ try
+ {
+ FutureCallback callback = new FutureCallback();
+ offer(ByteBuffer.wrap(b, off, len), callback);
+ callback.get();
+ }
+ catch (InterruptedException x)
+ {
+ throw new InterruptedIOException();
+ }
+ catch (ExecutionException x)
+ {
+ throw new IOException(x.getCause());
+ }
+ }
+
+ @Override
+ public void flush() throws IOException
+ {
+ OutputStreamRequestContent.this.flush();
+ }
+
+ @Override
+ public void close()
+ {
+ OutputStreamRequestContent.this.close();
+ }
+ }
+}
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java
index ffa41c752bb..40d3877b147 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathContentProvider.java
@@ -43,7 +43,10 @@ import org.slf4j.LoggerFactory;
* If a {@link ByteBufferPool} is provided via {@link #setByteBufferPool(ByteBufferPool)},
* the buffer will be allocated from that pool, otherwise one buffer will be
* allocated and used to read the file.
+ *
+ * @deprecated use {@link PathRequestContent} instead.
*/
+@Deprecated
public class PathContentProvider extends AbstractTypedContentProvider
{
private static final Logger LOG = LoggerFactory.getLogger(PathContentProvider.class);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java
new file mode 100644
index 00000000000..20737ab2dec
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/PathRequestContent.java
@@ -0,0 +1,177 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.util;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.AccessDeniedException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.io.ByteBufferPool;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
+import org.eclipse.jetty.util.IO;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
A {@link Request.Content} for files using JDK 7's {@code java.nio.file} APIs.
+ *
It is possible to specify, at the constructor, a buffer size used to read
+ * content from the stream, by default 4096 bytes.
+ * If a {@link ByteBufferPool} is provided via {@link #setByteBufferPool(ByteBufferPool)},
+ * the buffer will be allocated from that pool, otherwise one buffer will be
+ * allocated and used to read the file.
* It is possible to specify, at the constructor, an encoding used to convert
* the string into bytes, by default UTF-8.
+ *
+ * @deprecated use {@link StringRequestContent} instead.
*/
+@Deprecated
public class StringContentProvider extends BytesContentProvider
{
public StringContentProvider(String content)
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringRequestContent.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringRequestContent.java
new file mode 100644
index 00000000000..41054c62857
--- /dev/null
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/StringRequestContent.java
@@ -0,0 +1,52 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.util;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+
+import org.eclipse.jetty.client.api.Request;
+
+/**
+ *
A {@link Request.Content} for strings.
+ *
It is possible to specify, at the constructor, an encoding used to convert
+ * the string into bytes, by default UTF-8.
+ */
+public class StringRequestContent extends BytesRequestContent
+{
+ public StringRequestContent(String content)
+ {
+ this("text/plain;charset=UTF-8", content);
+ }
+
+ public StringRequestContent(String content, Charset encoding)
+ {
+ this("text/plain;charset=" + encoding.name(), content, encoding);
+ }
+
+ public StringRequestContent(String contentType, String content)
+ {
+ this(contentType, content, StandardCharsets.UTF_8);
+ }
+
+ public StringRequestContent(String contentType, String content, Charset encoding)
+ {
+ super(contentType, content.getBytes(encoding));
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java
index 8f3d2144e92..fab10cb6b6c 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ClientConnectionCloseTest.java
@@ -29,8 +29,8 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
-import org.eclipse.jetty.client.util.DeferredContentProvider;
-import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.client.util.AsyncRequestContent;
+import org.eclipse.jetty.client.util.StringRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.http.HttpStatus;
@@ -87,7 +87,7 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
var request = client.newRequest(host, port)
.scheme(scenario.getScheme())
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
- .content(new StringContentProvider("0"))
+ .body(new StringRequestContent("0"))
.onRequestSuccess(r ->
{
HttpDestination destination = (HttpDestination)client.resolveDestination(r);
@@ -184,12 +184,12 @@ public class ClientConnectionCloseTest extends AbstractHttpClientServerTest
String host = "localhost";
int port = connector.getLocalPort();
- DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.allocate(8));
+ AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.allocate(8));
CountDownLatch resultLatch = new CountDownLatch(1);
var request = client.newRequest(host, port)
.scheme(scenario.getScheme())
.header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
- .content(content)
+ .body(content)
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
.onRequestSuccess(r ->
{
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
index c90329b07a2..08139dbf1d6 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/ConnectionPoolTest.java
@@ -33,7 +33,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
-import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@@ -213,7 +213,7 @@ public class ConnectionPoolTest
break;
case POST:
request.header(HttpHeader.CONTENT_LENGTH, String.valueOf(contentLength));
- request.content(new BytesContentProvider(new byte[contentLength]));
+ request.body(new BytesRequestContent(new byte[contentLength]));
break;
default:
throw new IllegalStateException();
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
index b10039ea904..d054243ebbb 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAuthenticationTest.java
@@ -22,9 +22,7 @@ import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
-import java.util.Iterator;
import java.util.List;
-import java.util.NoSuchElementException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -37,15 +35,15 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.Authentication;
import org.eclipse.jetty.client.api.Authentication.HeaderInfo;
import org.eclipse.jetty.client.api.AuthenticationStore;
-import org.eclipse.jetty.client.api.ContentProvider;
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.Response.Listener;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.AbstractAuthentication;
+import org.eclipse.jetty.client.util.AbstractRequestContent;
+import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.BasicAuthentication;
-import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.DigestAuthentication;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
@@ -60,6 +58,8 @@ import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.Attributes;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.security.Constraint;
@@ -460,7 +460,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
CountDownLatch resultLatch = new CountDownLatch(1);
byte[] data = new byte[]{'h', 'e', 'l', 'l', 'o'};
- DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(data))
+ AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(data))
{
@Override
public boolean isReproducible()
@@ -470,7 +470,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
};
Request request = client.newRequest(uri)
.path("/secure")
- .content(content);
+ .body(content);
request.send(result ->
{
if (result.isSucceeded() && result.getResponse().getStatus() == HttpStatus.UNAUTHORIZED_401)
@@ -527,7 +527,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
authenticationStore.addAuthentication(authentication);
AtomicBoolean fail = new AtomicBoolean(true);
- GeneratingContentProvider content = new GeneratingContentProvider(index ->
+ GeneratingRequestContent content = new GeneratingRequestContent(index ->
{
switch (index)
{
@@ -546,9 +546,8 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
catch (InterruptedException ignored)
{
}
-
// Trigger request failure.
- throw new RuntimeException();
+ throw new RuntimeException("explicitly_thrown_by_test");
}
else
{
@@ -563,7 +562,7 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.path("/secure")
- .content(content)
+ .body(content)
.onResponseSuccess(r -> authLatch.countDown())
.send(result ->
{
@@ -803,23 +802,16 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
assertEquals(headerInfo.getParameter("nonce"), "1523430383=");
}
- private static class GeneratingContentProvider implements ContentProvider
+ private static class GeneratingRequestContent extends AbstractRequestContent
{
- private static final ByteBuffer DONE = ByteBuffer.allocate(0);
-
private final IntFunction generator;
- private GeneratingContentProvider(IntFunction generator)
+ private GeneratingRequestContent(IntFunction generator)
{
+ super("application/octet-stream");
this.generator = generator;
}
- @Override
- public long getLength()
- {
- return -1;
- }
-
@Override
public boolean isReproducible()
{
@@ -827,36 +819,32 @@ public class HttpClientAuthenticationTest extends AbstractHttpClientServerTest
}
@Override
- public Iterator iterator()
+ protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent)
{
- return new Iterator()
+ return new SubscriptionImpl(consumer, emitInitialContent);
+ }
+
+ private class SubscriptionImpl extends AbstractSubscription
+ {
+ private int index;
+
+ public SubscriptionImpl(Consumer consumer, boolean emitInitialContent)
{
- private int index;
- public ByteBuffer current;
+ super(consumer, emitInitialContent);
+ }
- @Override
- @SuppressWarnings("ReferenceEquality")
- public boolean hasNext()
+ @Override
+ protected boolean produceContent(Producer producer)
+ {
+ ByteBuffer buffer = generator.apply(index++);
+ boolean last = false;
+ if (buffer == null)
{
- if (current == null)
- {
- current = generator.apply(index++);
- if (current == null)
- current = DONE;
- }
- return current != DONE;
+ buffer = BufferUtil.EMPTY_BUFFER;
+ last = true;
}
-
- @Override
- public ByteBuffer next()
- {
- ByteBuffer result = current;
- current = null;
- if (result == null)
- throw new NoSuchElementException();
- return result;
- }
- };
+ return producer.produce(buffer, last, Callback.NOOP);
+ }
}
}
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java
index a7c32775530..a9a4f2d42ac 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientFailureTest.java
@@ -28,7 +28,7 @@ import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
-import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
@@ -116,16 +116,16 @@ public class HttpClientFailureTest
});
client.start();
- final CountDownLatch commitLatch = new CountDownLatch(1);
- final CountDownLatch completeLatch = new CountDownLatch(1);
- DeferredContentProvider content = new DeferredContentProvider();
+ CountDownLatch commitLatch = new CountDownLatch(1);
+ CountDownLatch completeLatch = new CountDownLatch(1);
+ AsyncRequestContent content = new AsyncRequestContent();
client.newRequest("localhost", connector.getLocalPort())
.onRequestCommit(request ->
{
connectionRef.get().getEndPoint().close();
commitLatch.countDown();
})
- .content(content)
+ .body(content)
.idleTimeout(2, TimeUnit.SECONDS)
.send(result ->
{
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java
index a6ef79dcaa7..442c317d4a5 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientRedirectTest.java
@@ -36,7 +36,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
-import org.eclipse.jetty.client.util.ByteBufferContentProvider;
+import org.eclipse.jetty.client.util.ByteBufferRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
@@ -153,7 +153,7 @@ public class HttpClientRedirectTest extends AbstractHttpClientServerTest
.scheme(scenario.getScheme())
.method(HttpMethod.POST)
.path("/307/localhost/done")
- .content(new ByteBufferContentProvider(ByteBuffer.wrap(data)))
+ .body(new ByteBufferRequestContent(ByteBuffer.wrap(data)))
.timeout(5, TimeUnit.SECONDS)
.send();
assertNotNull(response);
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java
index eab4e28f00d..411d2d3a3d5 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientSynchronizationTest.java
@@ -47,20 +47,22 @@ public class HttpClientSynchronizationTest extends AbstractHttpClientServerTest
server.stop();
int count = 10;
- final CountDownLatch latch = new CountDownLatch(count);
+ CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; ++i)
{
Request request = client.newRequest("localhost", port)
- .scheme(scenario.getScheme());
+ .scheme(scenario.getScheme())
+ .path("/" + i);
- synchronized (this)
+ Object lock = this;
+ synchronized (lock)
{
request.send(new Response.Listener.Adapter()
{
@Override
public void onFailure(Response response, Throwable failure)
{
- synchronized (HttpClientSynchronizationTest.this)
+ synchronized (lock)
{
assertThat(failure, Matchers.instanceOf(ConnectException.class));
latch.countDown();
@@ -80,20 +82,22 @@ public class HttpClientSynchronizationTest extends AbstractHttpClientServerTest
start(scenario, new EmptyServerHandler());
int count = 10;
- final CountDownLatch latch = new CountDownLatch(count);
+ CountDownLatch latch = new CountDownLatch(count);
for (int i = 0; i < count; ++i)
{
Request request = client.newRequest("localhost", connector.getLocalPort())
- .scheme(scenario.getScheme());
+ .scheme(scenario.getScheme())
+ .path("/" + i);
- synchronized (this)
+ Object lock = this;
+ synchronized (lock)
{
request.send(new Response.Listener.Adapter()
{
@Override
public void onComplete(Result result)
{
- synchronized (HttpClientSynchronizationTest.this)
+ synchronized (lock)
{
assertFalse(result.isFailed());
latch.countDown();
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
index 868fc335bb4..fe2efa598db 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java
@@ -38,10 +38,8 @@ import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.NoSuchElementException;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
@@ -59,7 +57,6 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.Connection;
-import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Request;
@@ -67,11 +64,12 @@ import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
+import org.eclipse.jetty.client.util.AbstractRequestContent;
+import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.BufferingResponseListener;
-import org.eclipse.jetty.client.util.BytesContentProvider;
-import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.client.util.FutureResponseListener;
-import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.client.util.StringRequestContent;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpHeader;
@@ -231,7 +229,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
});
String value1 = "\u20AC";
- String paramValue1 = URLEncoder.encode(value1, "UTF-8");
+ String paramValue1 = URLEncoder.encode(value1, StandardCharsets.UTF_8);
String query = paramName1 + "=" + paramValue1 + "&" + paramName2;
ContentResponse response = client.GET(scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/?" + query);
@@ -268,9 +266,9 @@ public class HttpClientTest extends AbstractHttpClientServerTest
String value11 = "\u20AC";
String value12 = "\u20AA";
String value2 = "&";
- String paramValue11 = URLEncoder.encode(value11, "UTF-8");
- String paramValue12 = URLEncoder.encode(value12, "UTF-8");
- String paramValue2 = URLEncoder.encode(value2, "UTF-8");
+ String paramValue11 = URLEncoder.encode(value11, StandardCharsets.UTF_8);
+ String paramValue12 = URLEncoder.encode(value12, StandardCharsets.UTF_8);
+ String paramValue2 = URLEncoder.encode(value2, StandardCharsets.UTF_8);
String query = paramName1 + "=" + paramValue11 + "&" + paramName1 + "=" + paramValue12 + "&" + paramName2 + "=" + paramValue2;
ContentResponse response = client.GET(scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/?" + query);
@@ -318,7 +316,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
{
String paramName = "a";
String paramValue = "\u20AC";
- String encodedParamValue = URLEncoder.encode(paramValue, "UTF-8");
+ String encodedParamValue = URLEncoder.encode(paramValue, StandardCharsets.UTF_8);
start(scenario, new AbstractHandler()
{
@Override
@@ -372,7 +370,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
ContentResponse response = client.POST(scenario.getScheme() + "://localhost:" + connector.getLocalPort() + "/?b=1")
.param(paramName, paramValue)
- .content(new BytesContentProvider(content))
+ .body(new BytesRequestContent(content))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -404,7 +402,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
if (!Arrays.equals(content, bytes))
request.abort(new Exception());
})
- .content(new BytesContentProvider(content))
+ .body(new BytesRequestContent(content))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -435,7 +433,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
buffer.get(bytes);
assertEquals(bytes[0], progress.getAndIncrement());
})
- .content(new BytesContentProvider(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4}))
+ .body(new BytesRequestContent(new byte[]{0}, new byte[]{1}, new byte[]{2}, new byte[]{3}, new byte[]{4}))
.timeout(5, TimeUnit.SECONDS)
.send();
@@ -511,7 +509,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
client.setMaxConnectionsPerDestination(1);
- try (StacklessLogging stackless = new StacklessLogging(org.eclipse.jetty.server.HttpChannel.class))
+ try (StacklessLogging ignored = new StacklessLogging(org.eclipse.jetty.server.HttpChannel.class))
{
CountDownLatch latch = new CountDownLatch(2);
client.newRequest("localhost", connector.getLocalPort())
@@ -630,36 +628,23 @@ public class HttpClientTest extends AbstractHttpClientServerTest
CountDownLatch latch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
- // The second ByteBuffer set to null will throw an exception
- .content(new ContentProvider()
+ .body(new AbstractRequestContent("application/octet-stream")
{
@Override
- public long getLength()
+ protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent)
{
- return -1;
- }
-
- @Override
- public Iterator iterator()
- {
- return new Iterator<>()
+ return new AbstractSubscription(consumer, emitInitialContent)
{
- @Override
- public boolean hasNext()
- {
- return true;
- }
+ private int count;
@Override
- public ByteBuffer next()
+ protected boolean produceContent(Producer producer) throws Exception
{
- throw new NoSuchElementException("explicitly_thrown_by_test");
- }
-
- @Override
- public void remove()
- {
- throw new UnsupportedOperationException();
+ if (count == 2)
+ throw new IOException("explicitly_thrown_by_test");
+ ByteBuffer buffer = BufferUtil.allocate(512);
+ ++count;
+ return producer.produce(buffer, false, Callback.NOOP);
}
};
}
@@ -1244,7 +1229,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
// Send the headers at this point, then write the content
- byte[] content = "TEST".getBytes("UTF-8");
+ byte[] content = "TEST".getBytes(StandardCharsets.UTF_8);
response.setContentLength(content.length);
response.flushBuffer();
response.getOutputStream().write(content);
@@ -1413,11 +1398,11 @@ public class HttpClientTest extends AbstractHttpClientServerTest
}
});
- DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0}));
+ AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(new byte[]{0}));
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.version(version)
- .content(content);
+ .body(content);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);
// Wait some time to simulate a slow request.
@@ -1530,7 +1515,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
client = new HttpClient(new HttpClientTransportOverHTTP(clientConnector)
{
@Override
- public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException
+ public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context)
{
return new HttpConnectionOverHTTP(endPoint, context)
{
@@ -1658,7 +1643,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
assertCopyRequest(client.newRequest("http://example.com/some/url")
.method(HttpMethod.HEAD)
.version(HttpVersion.HTTP_2)
- .content(new StringContentProvider("some string"))
+ .body(new StringRequestContent("some string"))
.timeout(321, TimeUnit.SECONDS)
.idleTimeout(2221, TimeUnit.SECONDS)
.followRedirects(true)
@@ -1668,7 +1653,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
assertCopyRequest(client.newRequest("https://example.com")
.method(HttpMethod.POST)
.version(HttpVersion.HTTP_1_0)
- .content(new StringContentProvider("some other string"))
+ .body(new StringRequestContent("some other string"))
.timeout(123231, TimeUnit.SECONDS)
.idleTimeout(232342, TimeUnit.SECONDS)
.followRedirects(false)
@@ -1797,7 +1782,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
start(scenario, new AbstractHandler()
{
@Override
- public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
baseRequest.setHandled(true);
ServletOutputStream output = response.getOutputStream();
@@ -1845,7 +1830,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
assertEquals(original.getURI(), copy.getURI());
assertEquals(original.getMethod(), copy.getMethod());
assertEquals(original.getVersion(), copy.getVersion());
- assertEquals(original.getContent(), copy.getContent());
+ assertEquals(original.getBody(), copy.getBody());
assertEquals(original.getIdleTimeout(), copy.getIdleTimeout());
assertEquals(original.getTimeout(), copy.getTimeout());
assertEquals(original.isFollowRedirects(), copy.isFollowRedirects());
@@ -1910,7 +1895,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
.scheme(scheme)
.method("POST")
.param("attempt", String.valueOf(retries))
- .content(new StringContentProvider("0123456789ABCDEF"))
+ .body(new StringRequestContent("0123456789ABCDEF"))
.send(this);
}
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java
index 7572259e681..a3573378a5a 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientUploadDuringServerShutdownTest.java
@@ -32,7 +32,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.http.HttpChannelOverHTTP;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
-import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
@@ -116,7 +116,7 @@ public class HttpClientUploadDuringServerShutdownTest
{
int length = 16 * 1024 * 1024 + random.nextInt(16 * 1024 * 1024);
client.newRequest("localhost", 8888)
- .content(new BytesContentProvider(new byte[length]))
+ .body(new BytesRequestContent(new byte[length]))
.send(result -> latch.countDown());
long sleep = 1 + random.nextInt(10);
TimeUnit.MILLISECONDS.sleep(sleep);
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
index 26b58540a99..5d622444395 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java
@@ -32,7 +32,7 @@ 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.ByteBufferContentProvider;
+import org.eclipse.jetty.client.util.ByteBufferRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.logging.StacklessLogging;
@@ -411,7 +411,7 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest
CountDownLatch latch = new CountDownLatch(1);
ByteBuffer buffer = ByteBuffer.allocate(16 * 1024 * 1024);
Arrays.fill(buffer.array(), (byte)'x');
- request.content(new ByteBufferContentProvider(buffer))
+ request.body(new ByteBufferRequestContent(buffer))
.send(new Response.Listener.Adapter()
{
@Override
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
index aca7bb99461..3884303361e 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpRequestAbortTest.java
@@ -32,7 +32,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Result;
-import org.eclipse.jetty.client.util.ByteBufferContentProvider;
+import org.eclipse.jetty.client.util.ByteBufferRequestContent;
import org.eclipse.jetty.logging.StacklessLogging;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO;
@@ -268,7 +268,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
{
aborted.set(r.abort(cause));
latch.countDown();
- }).content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
+ }).body(new ByteBufferRequestContent(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
{
@Override
public long getLength()
@@ -323,7 +323,7 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
{
aborted.set(r.abort(cause));
latch.countDown();
- }).content(new ByteBufferContentProvider(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
+ }).body(new ByteBufferRequestContent(ByteBuffer.wrap(new byte[]{0}), ByteBuffer.wrap(new byte[]{1}))
{
@Override
public long getLength()
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java
index 191a60dafd1..c85c98c253a 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpResponseAbortTest.java
@@ -24,11 +24,10 @@ import java.nio.ByteBuffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.client.util.DeferredContentProvider;
+import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.jupiter.params.ParameterizedTest;
@@ -104,7 +103,7 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
start(scenario, new AbstractHandler()
{
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
try
{
@@ -141,7 +140,7 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
start(scenario, new AbstractHandler()
{
@Override
- public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
try
{
@@ -159,18 +158,18 @@ public class HttpResponseAbortTest extends AbstractHttpClientServerTest
}
});
- final DeferredContentProvider contentProvider = new DeferredContentProvider(ByteBuffer.allocate(1));
- final AtomicInteger completes = new AtomicInteger();
- final CountDownLatch completeLatch = new CountDownLatch(1);
+ AsyncRequestContent requestContent = new AsyncRequestContent(ByteBuffer.allocate(1));
+ AtomicInteger completes = new AtomicInteger();
+ CountDownLatch completeLatch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
- .content(contentProvider)
+ .body(requestContent)
.onResponseContent((response, content) ->
{
try
{
response.abort(new Exception());
- contentProvider.close();
+ requestContent.close();
// Delay to let the request side to finish its processing.
Thread.sleep(1000);
}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java
new file mode 100644
index 00000000000..c49d3c2c946
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/NetworkTrafficListenerTest.java
@@ -0,0 +1,525 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
+import org.eclipse.jetty.client.util.FormRequestContent;
+import org.eclipse.jetty.client.util.StringRequestContent;
+import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpHeaderValue;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.io.ClientConnector;
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.ManagedSelector;
+import org.eclipse.jetty.io.NetworkTrafficListener;
+import org.eclipse.jetty.io.NetworkTrafficSocketChannelEndPoint;
+import org.eclipse.jetty.io.SelectorManager;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.NetworkTrafficServerConnector;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.BufferUtil;
+import org.eclipse.jetty.util.Fields;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class NetworkTrafficListenerTest
+{
+ private static final String END_OF_CONTENT = "~";
+
+ private Server server;
+ private NetworkTrafficServerConnector connector;
+ private NetworkTrafficHttpClient client;
+
+ private void start(Handler handler) throws Exception
+ {
+ startServer(handler);
+ startClient();
+ }
+
+ private void startServer(Handler handler) throws Exception
+ {
+ server = new Server();
+ connector = new NetworkTrafficServerConnector(server);
+ connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false);
+ connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false);
+ server.addConnector(connector);
+ server.setHandler(handler);
+ server.start();
+ }
+
+ private void startClient() throws Exception
+ {
+ client = new NetworkTrafficHttpClient(new AtomicReference<>());
+ client.start();
+ }
+
+ @AfterEach
+ public void dispose() throws Exception
+ {
+ if (client != null)
+ client.stop();
+ if (server != null)
+ server.stop();
+ }
+
+ @Test
+ public void testOpenedClosedAreInvoked() throws Exception
+ {
+ startServer(null);
+
+ CountDownLatch openedLatch = new CountDownLatch(1);
+ CountDownLatch closedLatch = new CountDownLatch(1);
+ connector.setNetworkTrafficListener(new NetworkTrafficListener()
+ {
+ public volatile Socket socket;
+
+ @Override
+ public void opened(Socket socket)
+ {
+ this.socket = socket;
+ openedLatch.countDown();
+ }
+
+ @Override
+ public void closed(Socket socket)
+ {
+ if (this.socket == socket)
+ closedLatch.countDown();
+ }
+ });
+ int port = connector.getLocalPort();
+
+ // Connect to the server
+ try (Socket ignored = new Socket("localhost", port))
+ {
+ assertTrue(openedLatch.await(10, TimeUnit.SECONDS));
+ }
+ assertTrue(closedLatch.await(10, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void testTrafficWithNoResponseContentOnNonPersistentConnection() throws Exception
+ {
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse)
+ {
+ request.setHandled(true);
+ }
+ });
+
+ AtomicReference serverIncoming = new AtomicReference<>("");
+ CountDownLatch serverIncomingLatch = new CountDownLatch(1);
+ AtomicReference serverOutgoing = new AtomicReference<>("");
+ CountDownLatch serverOutgoingLatch = new CountDownLatch(1);
+ connector.setNetworkTrafficListener(new NetworkTrafficListener()
+ {
+ @Override
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ serverIncomingLatch.countDown();
+ }
+
+ @Override
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ serverOutgoingLatch.countDown();
+ }
+ });
+
+ AtomicReference clientIncoming = new AtomicReference<>("");
+ CountDownLatch clientIncomingLatch = new CountDownLatch(1);
+ AtomicReference clientOutgoing = new AtomicReference<>("");
+ CountDownLatch clientOutgoingLatch = new CountDownLatch(1);
+ client.listener.set(new NetworkTrafficListener()
+ {
+ @Override
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ clientOutgoingLatch.countDown();
+ }
+
+ @Override
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ clientIncomingLatch.countDown();
+ }
+ });
+
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .header(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())
+ .send();
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+
+ assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS));
+ assertEquals(clientOutgoing.get(), serverIncoming.get());
+ assertEquals(serverOutgoing.get(), clientIncoming.get());
+ }
+
+ @Test
+ public void testTrafficWithResponseContentOnPersistentConnection() throws Exception
+ {
+ String responseContent = "response_content" + END_OF_CONTENT;
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException
+ {
+ request.setHandled(true);
+ ServletOutputStream output = servletResponse.getOutputStream();
+ output.write(responseContent.getBytes(StandardCharsets.UTF_8));
+ }
+ });
+
+ AtomicReference serverIncoming = new AtomicReference<>("");
+ CountDownLatch serverIncomingLatch = new CountDownLatch(1);
+ AtomicReference serverOutgoing = new AtomicReference<>("");
+ CountDownLatch serverOutgoingLatch = new CountDownLatch(1);
+ connector.setNetworkTrafficListener(new NetworkTrafficListener()
+ {
+ @Override
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ serverIncomingLatch.countDown();
+ }
+
+ @Override
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ serverOutgoingLatch.countDown();
+ }
+ });
+
+ AtomicReference clientIncoming = new AtomicReference<>("");
+ CountDownLatch clientIncomingLatch = new CountDownLatch(1);
+ AtomicReference clientOutgoing = new AtomicReference<>("");
+ CountDownLatch clientOutgoingLatch = new CountDownLatch(1);
+ client.listener.set(new NetworkTrafficListener()
+ {
+ @Override
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ clientOutgoingLatch.countDown();
+ }
+
+ @Override
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ clientIncomingLatch.countDown();
+ }
+ });
+
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort()).send();
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+ assertEquals(responseContent, response.getContentAsString());
+
+ assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS));
+ assertEquals(clientOutgoing.get(), serverIncoming.get());
+ assertEquals(serverOutgoing.get(), clientIncoming.get());
+ }
+
+ @Test
+ public void testTrafficWithResponseContentChunkedOnPersistentConnection() throws Exception
+ {
+ String responseContent = "response_content";
+ String responseChunk1 = responseContent.substring(0, responseContent.length() / 2);
+ String responseChunk2 = responseContent.substring(responseContent.length() / 2);
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException
+ {
+ request.setHandled(true);
+ ServletOutputStream output = servletResponse.getOutputStream();
+ output.write(responseChunk1.getBytes(StandardCharsets.UTF_8));
+ output.flush();
+ output.write(responseChunk2.getBytes(StandardCharsets.UTF_8));
+ output.flush();
+ }
+ });
+
+ AtomicReference serverIncoming = new AtomicReference<>("");
+ CountDownLatch serverIncomingLatch = new CountDownLatch(1);
+ AtomicReference serverOutgoing = new AtomicReference<>("");
+ CountDownLatch serverOutgoingLatch = new CountDownLatch(1);
+ connector.setNetworkTrafficListener(new NetworkTrafficListener()
+ {
+ @Override
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ serverIncomingLatch.countDown();
+ }
+
+ @Override
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ if (serverOutgoing.get().endsWith("\r\n0\r\n\r\n"))
+ serverOutgoingLatch.countDown();
+ }
+ });
+
+ AtomicReference clientIncoming = new AtomicReference<>("");
+ CountDownLatch clientIncomingLatch = new CountDownLatch(1);
+ AtomicReference clientOutgoing = new AtomicReference<>("");
+ CountDownLatch clientOutgoingLatch = new CountDownLatch(1);
+ client.listener.set(new NetworkTrafficListener()
+ {
+ @Override
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ clientOutgoingLatch.countDown();
+ }
+
+ @Override
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ if (clientIncoming.get().endsWith("\r\n0\r\n\r\n"))
+ clientIncomingLatch.countDown();
+ }
+ });
+
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort()).send();
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+
+ assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS));
+ assertEquals(clientOutgoing.get(), serverIncoming.get());
+ assertEquals(serverOutgoing.get(), clientIncoming.get());
+ }
+
+ @Test
+ public void testTrafficWithRequestContentWithResponseRedirectOnPersistentConnection() throws Exception
+ {
+ String location = "/redirect";
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException
+ {
+ request.setHandled(true);
+ servletResponse.sendRedirect(location);
+ }
+ });
+
+ AtomicReference serverIncoming = new AtomicReference<>("");
+ CountDownLatch serverIncomingLatch = new CountDownLatch(1);
+ AtomicReference serverOutgoing = new AtomicReference<>("");
+ CountDownLatch serverOutgoingLatch = new CountDownLatch(1);
+ connector.setNetworkTrafficListener(new NetworkTrafficListener()
+ {
+ @Override
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ serverIncomingLatch.countDown();
+ }
+
+ @Override
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ serverOutgoingLatch.countDown();
+ }
+ });
+
+ AtomicReference clientIncoming = new AtomicReference<>("");
+ CountDownLatch clientIncomingLatch = new CountDownLatch(1);
+ AtomicReference clientOutgoing = new AtomicReference<>("");
+ CountDownLatch clientOutgoingLatch = new CountDownLatch(1);
+ client.listener.set(new NetworkTrafficListener()
+ {
+ @Override
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ clientOutgoingLatch.countDown();
+ }
+
+ @Override
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ clientIncomingLatch.countDown();
+ }
+ });
+
+ client.setFollowRedirects(false);
+ Fields fields = new Fields();
+ fields.put("a", "1");
+ fields.put("b", "2");
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .body(new FormRequestContent(fields))
+ .send();
+ assertEquals(HttpStatus.FOUND_302, response.getStatus());
+
+ assertTrue(clientOutgoingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(serverIncomingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS));
+ assertEquals(clientOutgoing.get(), serverIncoming.get());
+ assertEquals(serverOutgoing.get(), clientIncoming.get());
+ }
+
+ @Test
+ public void testTrafficWithBigRequestContentOnPersistentConnection() throws Exception
+ {
+ start(new AbstractHandler()
+ {
+ @Override
+ public void handle(String uri, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException
+ {
+ // Read and discard the request body to make the test more
+ // reliable, otherwise there is a race between request body
+ // upload and response download
+ InputStream input = servletRequest.getInputStream();
+ byte[] buffer = new byte[4096];
+ while (true)
+ {
+ int read = input.read(buffer);
+ if (read < 0)
+ break;
+ }
+ request.setHandled(true);
+ }
+ });
+
+ AtomicReference serverIncoming = new AtomicReference<>("");
+ AtomicReference serverOutgoing = new AtomicReference<>("");
+ CountDownLatch serverOutgoingLatch = new CountDownLatch(1);
+ connector.setNetworkTrafficListener(new NetworkTrafficListener()
+ {
+ @Override
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ serverIncoming.set(serverIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ serverOutgoing.set(serverOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ serverOutgoingLatch.countDown();
+ }
+ });
+
+ AtomicReference clientIncoming = new AtomicReference<>("");
+ CountDownLatch clientIncomingLatch = new CountDownLatch(1);
+ AtomicReference clientOutgoing = new AtomicReference<>("");
+ client.listener.set(new NetworkTrafficListener()
+ {
+ @Override
+ public void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ clientOutgoing.set(clientOutgoing.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public void incoming(Socket socket, ByteBuffer bytes)
+ {
+ clientIncoming.set(clientIncoming.get() + BufferUtil.toString(bytes, StandardCharsets.UTF_8));
+ clientIncomingLatch.countDown();
+ }
+ });
+
+ // Generate a large request content.
+ String requestContent = "0123456789ABCDEF";
+ for (int i = 0; i < 16; ++i)
+ {
+ requestContent += requestContent;
+ }
+
+ ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
+ .body(new StringRequestContent(requestContent))
+ .send();
+ assertEquals(HttpStatus.OK_200, response.getStatus());
+
+ assertTrue(serverOutgoingLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(clientIncomingLatch.await(1, TimeUnit.SECONDS));
+ assertEquals(clientOutgoing.get(), serverIncoming.get());
+ assertTrue(clientOutgoing.get().length() > requestContent.length());
+ assertEquals(serverOutgoing.get(), clientIncoming.get());
+ }
+
+ private static class NetworkTrafficHttpClient extends HttpClient
+ {
+ private final AtomicReference listener;
+
+ private NetworkTrafficHttpClient(AtomicReference listener)
+ {
+ super(new HttpClientTransportOverHTTP(new ClientConnector()
+ {
+ @Override
+ protected SelectorManager newSelectorManager()
+ {
+ return new ClientSelectorManager(getExecutor(), getScheduler(), getSelectors())
+ {
+ @Override
+ protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey selectionKey)
+ {
+ return new NetworkTrafficSocketChannelEndPoint(channel, selector, selectionKey, getScheduler(), getIdleTimeout().toMillis(), listener.get());
+ }
+ };
+ }
+ }));
+ this.listener = listener;
+ }
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java
index 1d6d86a792d..81a06f8ec8f 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/api/Usage.java
@@ -32,12 +32,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.BasicAuthentication;
-import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.FutureResponseListener;
-import org.eclipse.jetty.client.util.InputStreamContentProvider;
+import org.eclipse.jetty.client.util.InputStreamRequestContent;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
-import org.eclipse.jetty.client.util.OutputStreamContentProvider;
+import org.eclipse.jetty.client.util.OutputStreamRequestContent;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.FuturePromise;
@@ -101,16 +101,12 @@ public class Usage
client.newRequest("localhost", 8080)
// Send asynchronously
- .send(new Response.CompleteListener()
+ .send(result ->
{
- @Override
- public void onComplete(Result result)
+ if (result.isSucceeded())
{
- if (result.isSucceeded())
- {
- responseRef.set(result.getResponse());
- latch.countDown();
- }
+ responseRef.set(result.getResponse());
+ latch.countDown();
}
});
@@ -278,7 +274,7 @@ public class Usage
ContentResponse response = client.newRequest("localhost", 8080)
// Provide the content as InputStream
- .content(new InputStreamContentProvider(input))
+ .body(new InputStreamRequestContent(input))
.send();
assertEquals(200, response.getStatus());
@@ -290,11 +286,11 @@ public class Usage
HttpClient client = new HttpClient();
client.start();
- OutputStreamContentProvider content = new OutputStreamContentProvider();
+ OutputStreamRequestContent content = new OutputStreamRequestContent();
try (OutputStream output = content.getOutputStream())
{
client.newRequest("localhost", 8080)
- .content(content)
+ .body(content)
.send(result -> assertEquals(200, result.getResponse().getStatus()));
output.write(new byte[1024]);
@@ -308,15 +304,15 @@ public class Usage
public void testProxyUsage() throws Exception
{
// In proxies, we receive the headers but not the content, so we must be able to send the request with
- // a lazy content provider that does not block request.send(...)
+ // a lazy request content that does not block request.send(...)
HttpClient client = new HttpClient();
client.start();
- final AtomicBoolean sendContent = new AtomicBoolean(true);
- DeferredContentProvider async = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0, 1, 2}));
+ AtomicBoolean sendContent = new AtomicBoolean(true);
+ AsyncRequestContent async = new AsyncRequestContent(ByteBuffer.wrap(new byte[]{0, 1, 2}));
client.newRequest("localhost", 8080)
- .content(async)
+ .body(async)
.send(new Response.Listener.Adapter()
{
@Override
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
index 374fa8eda0b..e7c1df5d2aa 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java
@@ -33,7 +33,7 @@ import org.eclipse.jetty.client.api.Connection;
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.ByteBufferContentProvider;
+import org.eclipse.jetty.client.util.ByteBufferRequestContent;
import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.eclipse.jetty.util.Promise;
import org.hamcrest.Matchers;
@@ -201,7 +201,7 @@ public class HttpSenderOverHTTPTest
HttpConnectionOverHTTP connection = new HttpConnectionOverHTTP(endPoint, destination, new Promise.Adapter());
Request request = client.newRequest(URI.create("http://localhost/"));
String content = "abcdef";
- request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8))));
+ request.body(new ByteBufferRequestContent(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8))));
final CountDownLatch headersLatch = new CountDownLatch(1);
final CountDownLatch successLatch = new CountDownLatch(1);
request.listener(new Request.Listener.Adapter()
@@ -237,7 +237,7 @@ public class HttpSenderOverHTTPTest
Request request = client.newRequest(URI.create("http://localhost/"));
String content1 = "0123456789";
String content2 = "abcdef";
- request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8))));
+ request.body(new ByteBufferRequestContent(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8))));
final CountDownLatch headersLatch = new CountDownLatch(1);
final CountDownLatch successLatch = new CountDownLatch(1);
request.listener(new Request.Listener.Adapter()
@@ -273,7 +273,7 @@ public class HttpSenderOverHTTPTest
Request request = client.newRequest(URI.create("http://localhost/"));
String content1 = "0123456789";
String content2 = "ABCDEF";
- request.content(new ByteBufferContentProvider(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8)))
+ request.body(new ByteBufferRequestContent(ByteBuffer.wrap(content1.getBytes(StandardCharsets.UTF_8)), ByteBuffer.wrap(content2.getBytes(StandardCharsets.UTF_8)))
{
@Override
public long getLength()
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/AsyncRequestContentTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/AsyncRequestContentTest.java
new file mode 100644
index 00000000000..03dee4af220
--- /dev/null
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/util/AsyncRequestContentTest.java
@@ -0,0 +1,151 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.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.util;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.util.Callback;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AsyncRequestContentTest
+{
+ private ExecutorService executor;
+
+ @BeforeEach
+ public void prepare()
+ {
+ executor = Executors.newCachedThreadPool();
+ }
+
+ @AfterEach
+ public void dispose()
+ {
+ executor.shutdownNow();
+ }
+
+ @Test
+ public void testWhenEmptyFlushDoesNotBlock() throws Exception
+ {
+ AsyncRequestContent content = new AsyncRequestContent();
+
+ Future> task = executor.submit(() ->
+ {
+ content.flush();
+ return null;
+ });
+
+ assertTrue(await(task, 5000));
+ }
+
+ @Test
+ public void testOfferFlushDemandBlocksUntilSucceeded() throws Exception
+ {
+ AsyncRequestContent content = new AsyncRequestContent();
+ content.offer(ByteBuffer.allocate(1));
+
+ Future> task = executor.submit(() ->
+ {
+ content.flush();
+ return null;
+ });
+
+ // Wait until flush() blocks.
+ assertFalse(await(task, 500));
+
+ AtomicReference callbackRef = new AtomicReference<>();
+ content.subscribe((buffer, last, callback) -> callbackRef.set(callback), true).demand();
+
+ // Flush should block until the callback is succeeded.
+ assertFalse(await(task, 500));
+
+ callbackRef.get().succeeded();
+
+ // Flush should return.
+ assertTrue(await(task, 5000));
+ }
+
+ @Test
+ public void testCloseFlushDoesNotBlock() throws Exception
+ {
+ AsyncRequestContent content = new AsyncRequestContent();
+ content.close();
+
+ Future> task = executor.submit(() ->
+ {
+ content.flush();
+ return null;
+ });
+
+ assertTrue(await(task, 5000));
+ }
+
+ @Test
+ public void testStallThenCloseProduces() throws Exception
+ {
+ AsyncRequestContent content = new AsyncRequestContent();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ Request.Content.Subscription subscription = content.subscribe((buffer, last, callback) ->
+ {
+ callback.succeeded();
+ if (last)
+ latch.countDown();
+ }, true);
+
+ // Demand the initial content.
+ subscription.demand();
+
+ // Content must not be the last one.
+ assertFalse(latch.await(1, TimeUnit.SECONDS));
+
+ // Demand more content, now we are stalled.
+ subscription.demand();
+
+ // Close, we must be notified.
+ content.close();
+
+ assertTrue(latch.await(5, TimeUnit.SECONDS));
+ }
+
+ private boolean await(Future> task, long time) throws Exception
+ {
+ try
+ {
+ task.get(time, TimeUnit.MILLISECONDS);
+ return true;
+ }
+ catch (TimeoutException x)
+ {
+ return false;
+ }
+ }
+}
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/util/DeferredContentProviderTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/util/DeferredContentProviderTest.java
deleted file mode 100644
index 88304c9f249..00000000000
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/util/DeferredContentProviderTest.java
+++ /dev/null
@@ -1,151 +0,0 @@
-//
-// ========================================================================
-// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
-//
-// This program and the accompanying materials are made available under
-// the terms of the Eclipse Public License 2.0 which is available at
-// https://www.eclipse.org/legal/epl-2.0
-//
-// This Source Code may also be made available under the following
-// Secondary Licenses when the conditions for such availability set
-// forth in the Eclipse Public License, v. 2.0 are satisfied:
-// the Apache License v2.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.util;
-
-import java.nio.ByteBuffer;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import org.eclipse.jetty.util.Callback;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-public class DeferredContentProviderTest
-{
- private ExecutorService executor;
-
- @BeforeEach
- public void prepare() throws Exception
- {
- executor = Executors.newCachedThreadPool();
- }
-
- @AfterEach
- public void dispose() throws Exception
- {
- executor.shutdownNow();
- }
-
- @Test
- public void testWhenEmptyFlushDoesNotBlock() throws Exception
- {
- final DeferredContentProvider provider = new DeferredContentProvider();
-
- Future> task = executor.submit(new Callable
*
The smaller bucket is defined as a fraction of the bigger bucket.
+ * rocking bamboo fountain,
+ * where the bamboo is the smaller bucket and the pool is the bigger bucket.
*
The algorithm works in this way.
*
The initial bigger bucket (BB) capacity is 100, and let's imagine the smaller
* bucket (SB) being 40% of the bigger bucket: 40.
@@ -50,6 +51,16 @@ import org.eclipse.jetty.util.annotation.ManagedObject;
* with delta=45.
*
The application consumes the remaining 15, so now SB=15, and no window
* control frame is emitted.
+ *
The {@code bufferRatio} controls how often the window control frame is
+ * emitted.
+ *
A {@code bufferRatio=0.0} means that a window control frame is emitted
+ * every time the application consumes a data frame. This may result in too many
+ * window control frames be emitted, but may allow the sender to avoid stalling.
+ *
A {@code bufferRatio=1.0} means that a window control frame is emitted
+ * only when the application has consumed a whole window. This minimizes the
+ * number of window control frames emitted, but may cause the sender to stall,
+ * waiting for the window control frame.
+ *
The default value is {@code bufferRatio=0.5}.
*/
@ManagedObject
public class BufferingFlowControlStrategy extends AbstractFlowControlStrategy
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
index 8341bd43b8c..6166aba962d 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java
@@ -541,7 +541,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
synchronized (this)
{
HeadersFrame[] frameOut = new HeadersFrame[1];
- stream = newStream(frame, frameOut);
+ stream = newLocalStream(frame, frameOut);
stream.setListener(listener);
ControlEntry entry = new ControlEntry(frameOut[0], stream, new StreamPromiseCallback(promise, stream));
queued = flusher.append(entry);
@@ -567,7 +567,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
* allocated stream id, or null if not interested in the modified headers frame
* @return a new stream
*/
- public IStream newStream(HeadersFrame frameIn, HeadersFrame[] frameOut)
+ public IStream newLocalStream(HeadersFrame frameIn, HeadersFrame[] frameOut)
{
HeadersFrame frame = frameIn;
int streamId = frameIn.getStreamId();
@@ -825,6 +825,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
int maxCount = getMaxRemoteStreams();
if (maxCount >= 0 && remoteCount - remoteClosing >= maxCount)
{
+ updateLastRemoteStreamId(streamId);
reset(new ResetFrame(streamId, ErrorCode.REFUSED_STREAM_ERROR.code), Callback.NOOP);
return null;
}
diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java
index 5b5aa5c6ade..19f11d7b2ec 100644
--- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java
+++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java
@@ -21,6 +21,7 @@ package org.eclipse.jetty.http2.api;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.Map;
+import java.util.concurrent.CompletableFuture;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.GoAwayFrame;
@@ -55,6 +56,20 @@ import org.eclipse.jetty.util.Promise;
*/
public interface Session
{
+ /**
+ *
Sends the given HEADERS {@code frame} to create a new {@link Stream}.
+ *
+ * @param frame the HEADERS frame containing the HTTP headers
+ * @param listener the listener that gets notified of stream events
+ * @return a CompletableFuture that is notified of the stream creation
+ */
+ public default CompletableFuture newStream(HeadersFrame frame, Stream.Listener listener)
+ {
+ Promise.Completable result = new Promise.Completable<>();
+ newStream(frame, result, listener);
+ return result;
+ }
+
/**
*
Sends the given HEADERS {@code frame} to create a new {@link Stream}.
+ *
+ * @param frame the DATA frame to send
+ * @return the CompletableFuture that gets notified when the frame has been sent
+ */
+ public default CompletableFuture data(DataFrame frame)
+ {
+ Callback.Completable result = new Callback.Completable();
+ data(frame, result);
+ return result;
+ }
+
/**
*
Sends the given DATA {@code frame}.
*
@@ -174,7 +189,7 @@ public interface Stream
/**
*
Callback method invoked when a PUSH_PROMISE frame has been received.
*
- * @param stream the stream
+ * @param stream the pushed stream
* @param frame the PUSH_PROMISE frame received
* @return a Stream.Listener that will be notified of pushed stream events
*/
diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java
index 1f17ae4bfcd..2493fbf6678 100644
--- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java
+++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/ClientConnectionFactoryOverHTTP2.java
@@ -26,6 +26,8 @@ import java.util.Map;
import org.eclipse.jetty.client.HttpClientTransport;
import org.eclipse.jetty.client.HttpDestination;
import org.eclipse.jetty.client.api.Connection;
+import org.eclipse.jetty.client.dynamic.HttpClientTransportDynamic;
+import org.eclipse.jetty.client.http.HttpClientConnectionFactory;
import org.eclipse.jetty.http2.client.HTTP2Client;
import org.eclipse.jetty.http2.client.HTTP2ClientConnectionFactory;
import org.eclipse.jetty.io.ClientConnectionFactory;
@@ -56,19 +58,25 @@ public class ClientConnectionFactoryOverHTTP2 extends ContainerLifeCycle impleme
return factory.newConnection(endPoint, context);
}
- public static class H2 extends Info
+ /**
+ *
Representation of the {@code HTTP/2} application protocol used by {@link HttpClientTransportDynamic}.
+ *
+ * @see HttpClientConnectionFactory#HTTP11
+ */
+ public static class HTTP2 extends Info
{
- public H2(HTTP2Client client)
- {
- super(List.of("h2"), new ClientConnectionFactoryOverHTTP2(client));
- }
- }
+ private static final List protocols = List.of("h2", "h2c");
+ private static final List h2c = List.of("h2c");
- public static class H2C extends Info
- {
- public H2C(HTTP2Client client)
+ public HTTP2(HTTP2Client client)
{
- super(List.of("h2c"), new ClientConnectionFactoryOverHTTP2(client));
+ super(new ClientConnectionFactoryOverHTTP2(client));
+ }
+
+ @Override
+ public List getProtocols(boolean secure)
+ {
+ return secure ? protocols : h2c;
}
@Override
@@ -119,5 +127,11 @@ public class ClientConnectionFactoryOverHTTP2 extends ContainerLifeCycle impleme
throw new UncheckedIOException(x);
}
}
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s@%x%s", getClass().getSimpleName(), hashCode(), protocols);
+ }
}
}
diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java
index 40e22015323..4f537b3fe57 100644
--- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java
+++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpConnectionOverHTTP2.java
@@ -108,7 +108,7 @@ public class HttpConnectionOverHTTP2 extends HttpConnection implements Sweeper.S
MetaData.Request metaData = new MetaData.Request(request.getMethod(), new HttpURI(request.getURI()), HttpVersion.HTTP_2, request.getHeaders());
// We do not support upgrade requests with content, so endStream=true.
HeadersFrame frame = new HeadersFrame(metaData, null, true);
- IStream stream = ((HTTP2Session)session).newStream(frame, null);
+ IStream stream = ((HTTP2Session)session).newLocalStream(frame, null);
stream.updateClose(frame.isEndStream(), CloseState.Event.AFTER_SEND);
HttpExchange exchange = request.getConversation().getExchanges().peekLast();
diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java
index b3aeacaa3de..c8057aae86c 100644
--- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java
+++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpSenderOverHTTP2.java
@@ -19,10 +19,10 @@
package org.eclipse.jetty.http2.client.http;
import java.net.URI;
+import java.nio.ByteBuffer;
import java.util.function.Consumer;
import java.util.function.Supplier;
-import org.eclipse.jetty.client.HttpContent;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpSender;
@@ -35,6 +35,7 @@ import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http2.api.Stream;
import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
+import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.slf4j.Logger;
@@ -56,7 +57,7 @@ public class HttpSenderOverHTTP2 extends HttpSender
}
@Override
- protected void sendHeaders(HttpExchange exchange, final HttpContent content, final Callback callback)
+ protected void sendHeaders(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, final Callback callback)
{
HttpRequest request = exchange.getRequest();
boolean isTunnel = HttpMethod.CONNECT.is(request.getMethod());
@@ -92,31 +93,10 @@ public class HttpSenderOverHTTP2 extends HttpSender
}
else
{
- if (content.hasContent())
- {
- headersFrame = new HeadersFrame(metaData, null, false);
- promise = new HeadersPromise(request, callback, stream ->
- {
- if (expects100Continue(request))
- {
- // Don't send the content yet.
- callback.succeeded();
- }
- else
- {
- boolean advanced = content.advance();
- boolean lastContent = content.isLast();
- if (advanced || lastContent)
- sendContent(stream, content, trailerSupplier, callback);
- else
- callback.succeeded();
- }
- });
- }
- else
+ if (BufferUtil.isEmpty(contentBuffer) && lastContent)
{
HttpFields trailers = trailerSupplier == null ? null : trailerSupplier.get();
- boolean endStream = trailers == null || trailers.size() <= 0;
+ boolean endStream = trailers == null || trailers.size() == 0;
headersFrame = new HeadersFrame(metaData, null, endStream);
promise = new HeadersPromise(request, callback, stream ->
{
@@ -126,6 +106,12 @@ public class HttpSenderOverHTTP2 extends HttpSender
sendTrailers(stream, trailers, callback);
});
}
+ else
+ {
+ headersFrame = new HeadersFrame(metaData, null, false);
+ promise = new HeadersPromise(request, callback, stream ->
+ sendContent(stream, contentBuffer, lastContent, trailerSupplier, callback));
+ }
}
// TODO optimize the send of HEADERS and DATA frames.
HttpChannelOverHTTP2 channel = getHttpChannel();
@@ -151,38 +137,57 @@ public class HttpSenderOverHTTP2 extends HttpSender
}
@Override
- protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
+ protected void sendContent(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback)
{
- if (content.isConsumed())
+ Stream stream = getHttpChannel().getStream();
+ Supplier trailerSupplier = exchange.getRequest().getTrailers();
+ sendContent(stream, contentBuffer, lastContent, trailerSupplier, callback);
+ }
+
+ private void sendContent(Stream stream, ByteBuffer buffer, boolean lastContent, Supplier trailerSupplier, Callback callback)
+ {
+ boolean hasContent = buffer.hasRemaining();
+ if (lastContent)
{
- // The superclass calls sendContent() one more time after the last content.
- // This is necessary for HTTP/1.1 to generate the terminal chunk (with trailers),
- // but it's not necessary for HTTP/2 so we just succeed the callback.
- callback.succeeded();
+ // Call the trailers supplier as late as possible.
+ HttpFields trailers = trailerSupplier == null ? null : trailerSupplier.get();
+ boolean hasTrailers = trailers != null && trailers.size() > 0;
+ if (hasContent)
+ {
+ DataFrame dataFrame = new DataFrame(stream.getId(), buffer, !hasTrailers);
+ Callback dataCallback = callback;
+ if (hasTrailers)
+ dataCallback = Callback.from(() -> sendTrailers(stream, trailers, callback), callback::failed);
+ stream.data(dataFrame, dataCallback);
+ }
+ else
+ {
+ if (hasTrailers)
+ {
+ sendTrailers(stream, trailers, callback);
+ }
+ else
+ {
+ DataFrame dataFrame = new DataFrame(stream.getId(), buffer, true);
+ stream.data(dataFrame, callback);
+ }
+ }
}
else
{
- Stream stream = getHttpChannel().getStream();
- Supplier trailerSupplier = exchange.getRequest().getTrailers();
- sendContent(stream, content, trailerSupplier, callback);
+ if (hasContent)
+ {
+ DataFrame dataFrame = new DataFrame(stream.getId(), buffer, false);
+ stream.data(dataFrame, callback);
+ }
+ else
+ {
+ // Don't send empty non-last content.
+ callback.succeeded();
+ }
}
}
- private void sendContent(Stream stream, HttpContent content, Supplier trailerSupplier, Callback callback)
- {
- boolean lastContent = content.isLast();
- HttpFields trailers = null;
- boolean endStream = false;
- if (lastContent)
- {
- trailers = trailerSupplier == null ? null : trailerSupplier.get();
- endStream = trailers == null || trailers.size() == 0;
- }
- DataFrame dataFrame = new DataFrame(stream.getId(), content.getByteBuffer(), endStream);
- HttpFields fTrailers = trailers;
- stream.data(dataFrame, endStream || !lastContent ? callback : Callback.from(() -> sendTrailers(stream, fTrailers, callback), callback::failed));
- }
-
private void sendTrailers(Stream stream, HttpFields trailers, Callback callback)
{
MetaData metaData = new MetaData(HttpVersion.HTTP_2, trailers);
diff --git a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/RequestTrailersTest.java b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/RequestTrailersTest.java
index a94a2f88f61..89f1e3f20ab 100644
--- a/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/RequestTrailersTest.java
+++ b/jetty-http2/http2-http-client-transport/src/test/java/org/eclipse/jetty/http2/client/http/RequestTrailersTest.java
@@ -25,8 +25,8 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.api.ContentResponse;
-import org.eclipse.jetty.client.util.DeferredContentProvider;
-import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.client.util.AsyncRequestContent;
+import org.eclipse.jetty.client.util.StringRequestContent;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpVersion;
@@ -82,7 +82,7 @@ public class RequestTrailersTest extends AbstractTest
HttpFields trailers = new HttpFields();
request.trailers(() -> trailers);
if (content != null)
- request.content(new StringContentProvider(content));
+ request.body(new StringRequestContent(content));
ContentResponse response = request.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
@@ -92,7 +92,7 @@ public class RequestTrailersTest extends AbstractTest
}
@Test
- public void testEmptyTrailersWithDeferredContent() throws Exception
+ public void testEmptyTrailersWithAsyncContent() throws Exception
{
start(new ServerSessionListener.Adapter()
{
@@ -121,8 +121,8 @@ public class RequestTrailersTest extends AbstractTest
HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort());
HttpFields trailers = new HttpFields();
request.trailers(() -> trailers);
- DeferredContentProvider content = new DeferredContentProvider();
- request.content(content);
+ AsyncRequestContent content = new AsyncRequestContent();
+ request.body(content);
CountDownLatch latch = new CountDownLatch(1);
request.send(result ->
@@ -132,16 +132,16 @@ public class RequestTrailersTest extends AbstractTest
latch.countDown();
});
- // Send deferred content after a while.
+ // Send async content after a while.
Thread.sleep(1000);
- content.offer(ByteBuffer.wrap("deferred_content".getBytes(StandardCharsets.UTF_8)));
+ content.offer(ByteBuffer.wrap("async_content".getBytes(StandardCharsets.UTF_8)));
content.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
- public void testEmptyTrailersWithEmptyDeferredContent() throws Exception
+ public void testEmptyTrailersWithEmptyAsyncContent() throws Exception
{
start(new ServerSessionListener.Adapter()
{
@@ -170,8 +170,8 @@ public class RequestTrailersTest extends AbstractTest
HttpRequest request = (HttpRequest)client.newRequest("localhost", connector.getLocalPort());
HttpFields trailers = new HttpFields();
request.trailers(() -> trailers);
- DeferredContentProvider content = new DeferredContentProvider();
- request.content(content);
+ AsyncRequestContent content = new AsyncRequestContent();
+ request.body(content);
CountDownLatch latch = new CountDownLatch(1);
request.send(result ->
@@ -181,7 +181,7 @@ public class RequestTrailersTest extends AbstractTest
latch.countDown();
});
- // Send deferred content after a while.
+ // Send async content after a while.
Thread.sleep(1000);
content.close();
diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java
index d9bc67dc04c..e8ff9759227 100644
--- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java
+++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpTransportOverHTTP2.java
@@ -314,18 +314,25 @@ public class HttpTransportOverHTTP2 implements HttpTransport
return transportCallback.onIdleTimeout(failure);
}
+ /**
+ * @return true if error sent, false if upgraded or aborted.
+ */
boolean prepareUpgrade()
{
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttachment();
Request request = channel.getRequest();
if (request.getHttpInput().hasContent())
return channel.sendErrorOrAbort("Unexpected content in CONNECT request");
+
Connection connection = (Connection)request.getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
+ if (connection == null)
+ return channel.sendErrorOrAbort("No UPGRADE_CONNECTION_ATTRIBUTE available");
+
EndPoint endPoint = connection.getEndPoint();
endPoint.upgrade(connection);
stream.setAttachment(endPoint);
- // Only now that we have switched the attachment,
- // we can demand DATA frames to process them.
+
+ // Only now that we have switched the attachment, we can demand DATA frames to process them.
stream.demand(1);
if (LOG.isDebugEnabled())
@@ -340,21 +347,6 @@ public class HttpTransportOverHTTP2 implements HttpTransport
Object attachment = stream.getAttachment();
if (attachment instanceof HttpChannelOverHTTP2)
{
- // TODO: we used to "fake" a 101 response to upgrade the endpoint
- // but we don't anymore, so this code should be deleted.
- HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)attachment;
- if (channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101)
- {
- Connection connection = (Connection)channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE);
- EndPoint endPoint = connection.getEndPoint();
- // TODO: check that endPoint implements HTTP2Channel.
- if (LOG.isDebugEnabled())
- LOG.debug("Tunnelling DATA frames through {}", endPoint);
- endPoint.upgrade(connection);
- stream.setAttachment(endPoint);
- return;
- }
-
// If the stream is not closed, it is still reading the request content.
// Send a reset to the other end so that it stops sending data.
if (!stream.isClosed())
@@ -366,6 +358,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
// Consume the existing queued data frames to
// avoid stalling the session flow control.
+ HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)attachment;
channel.consumeInput();
}
}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
index 4e555e7878e..d66a5901b38 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java
@@ -66,26 +66,21 @@ public interface ClientConnectionFactory
}
/**
- *
A holder for a list of protocol strings identifying a network protocol
+ *
A holder for a list of protocol strings identifying an application protocol
* (for example {@code ["h2", "h2-17", "h2-16"]}) and a {@link ClientConnectionFactory}
* that creates connections that speak that network protocol.
*/
- public static class Info extends ContainerLifeCycle
+ public abstract static class Info extends ContainerLifeCycle
{
- private final List protocols;
private final ClientConnectionFactory factory;
- public Info(List protocols, ClientConnectionFactory factory)
+ public Info(ClientConnectionFactory factory)
{
- this.protocols = protocols;
this.factory = factory;
addBean(factory);
}
- public List getProtocols()
- {
- return protocols;
- }
+ public abstract List getProtocols(boolean secure);
public ClientConnectionFactory getClientConnectionFactory()
{
@@ -98,20 +93,14 @@ public interface ClientConnectionFactory
* @param candidates the candidates to match against
* @return whether one of the protocols of this class is present in the candidates
*/
- public boolean matches(List candidates)
+ public boolean matches(List candidates, boolean secure)
{
- return protocols.stream().anyMatch(p -> candidates.stream().anyMatch(c -> c.equalsIgnoreCase(p)));
+ return getProtocols(secure).stream().anyMatch(p -> candidates.stream().anyMatch(c -> c.equalsIgnoreCase(p)));
}
public void upgrade(EndPoint endPoint, Map context)
{
throw new UnsupportedOperationException(this + " does not support upgrade to another protocol");
}
-
- @Override
- public String toString()
- {
- return String.format("%s@%x%s", getClass().getSimpleName(), hashCode(), protocols);
- }
}
}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
index abe77a5673e..a997de63001 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java
@@ -30,6 +30,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executor;
+import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.Promise;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.ssl.SslContextFactory;
@@ -281,15 +282,7 @@ public class ClientConnector extends ContainerLifeCycle
protected void safeClose(Closeable closeable)
{
- try
- {
- if (closeable != null)
- closeable.close();
- }
- catch (Throwable x)
- {
- LOG.trace("IGNORED", x);
- }
+ IO.close(closeable);
}
protected void configure(SocketChannel channel) throws IOException
@@ -308,7 +301,7 @@ public class ClientConnector extends ContainerLifeCycle
protected class ClientSelectorManager extends SelectorManager
{
- protected ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
+ public ClientSelectorManager(Executor executor, Scheduler scheduler, int selectors)
{
super(executor, scheduler, selectors);
}
@@ -330,6 +323,18 @@ public class ClientConnector extends ContainerLifeCycle
return factory.newConnection(endPoint, context);
}
+ @Override
+ public void connectionOpened(Connection connection, Object context)
+ {
+ super.connectionOpened(connection, context);
+ @SuppressWarnings("unchecked")
+ Map contextMap = (Map)context;
+ @SuppressWarnings("unchecked")
+ Promise promise = (Promise)contextMap.get(CONNECTION_PROMISE_CONTEXT_KEY);
+ if (promise != null)
+ promise.succeeded(connection);
+ }
+
@Override
protected void connectionFailed(SelectableChannel channel, Throwable failure, Object attachment)
{
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java
index afa3410f882..fbdd71ad77b 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java
@@ -268,12 +268,13 @@ public class ManagedSelector extends ContainerLifeCycle implements Dumpable
private void createEndPoint(SelectableChannel channel, SelectionKey selectionKey) throws IOException
{
EndPoint endPoint = _selectorManager.newEndPoint(channel, this, selectionKey);
- Connection connection = _selectorManager.newConnection(channel, endPoint, selectionKey.attachment());
+ Object context = selectionKey.attachment();
+ Connection connection = _selectorManager.newConnection(channel, endPoint, context);
endPoint.setConnection(connection);
selectionKey.attach(endPoint);
endPoint.onOpen();
endPointOpened(endPoint);
- _selectorManager.connectionOpened(connection);
+ _selectorManager.connectionOpened(connection, context);
if (LOG.isDebugEnabled())
LOG.debug("Created {}", endPoint);
}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
index 719bab5b04f..f909fa140db 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficListener.java
@@ -24,7 +24,7 @@ import java.nio.ByteBuffer;
/**
*
A listener for raw network traffic within Jetty.
*
{@link NetworkTrafficListener}s can be installed in a
- * org.eclipse.jetty.server.nio.NetworkTrafficSelectChannelConnector,
+ * {@code org.eclipse.jetty.server.NetworkTrafficServerConnector},
* and are notified of the following network traffic events:
*
*
Connection opened, when the server has accepted the connection from a remote client
@@ -45,7 +45,9 @@ public interface NetworkTrafficListener
*
* @param socket the socket associated with the remote client
*/
- public void opened(Socket socket);
+ default void opened(Socket socket)
+ {
+ }
/**
*
Callback method invoked when bytes sent by a remote client arrived on the server.
@@ -53,7 +55,9 @@ public interface NetworkTrafficListener
* @param socket the socket associated with the remote client
* @param bytes the read-only buffer containing the incoming bytes
*/
- public void incoming(Socket socket, ByteBuffer bytes);
+ default void incoming(Socket socket, ByteBuffer bytes)
+ {
+ }
/**
*
Callback method invoked when bytes are sent to a remote client from the server.
@@ -62,7 +66,9 @@ public interface NetworkTrafficListener
* @param socket the socket associated with the remote client
* @param bytes the read-only buffer containing the outgoing bytes
*/
- public void outgoing(Socket socket, ByteBuffer bytes);
+ default void outgoing(Socket socket, ByteBuffer bytes)
+ {
+ }
/**
*
Callback method invoked when a connection to a remote client has been closed.
@@ -74,31 +80,7 @@ public interface NetworkTrafficListener
*
* @param socket the (closed) socket associated with the remote client
*/
- public void closed(Socket socket);
-
- /**
- *
A commodity class that implements {@link NetworkTrafficListener} with empty methods.
Callback method invoked when a connection is opened.
*
* @param connection the connection just opened
+ * @param context the attachment associated with the creation of the connection
+ * @see #newConnection(SelectableChannel, EndPoint, Object)
*/
- public void connectionOpened(Connection connection)
+ public void connectionOpened(Connection connection, Object context)
{
try
{
diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/pom.xml b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/pom.xml
new file mode 100644
index 00000000000..ac59d8c0f05
--- /dev/null
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/pom.xml
@@ -0,0 +1,21 @@
+
+
+
+ jetty-issue
+ org.mehdi
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ MyLibrary
+
+
+
+ org.eclipse.jetty.toolchain
+ jetty-servlet-api
+ provided
+
+
+
diff --git a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/chapter.adoc b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyAnnotation.java
similarity index 73%
rename from jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/chapter.adoc
rename to jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyAnnotation.java
index fe028abc9b0..1cd7ca1d240 100644
--- a/jetty-documentation/src/main/asciidoc/embedded-guide/server/clients/http/chapter.adoc
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyAnnotation.java
@@ -16,12 +16,14 @@
// ========================================================================
//
-[[http-client]]
-== HTTP Client
+package jettyissue;
-include::http-client-intro.adoc[]
-include::http-client-api.adoc[]
-include::http-client-cookie.adoc[]
-include::http-client-authentication.adoc[]
-include::http-client-proxy.adoc[]
-include::http-client-transport.adoc[]
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface MyAnnotation {
+}
diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyServletContainerInitializer.java b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyServletContainerInitializer.java
new file mode 100644
index 00000000000..f03d464b1fe
--- /dev/null
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/java/jettyissue/MyServletContainerInitializer.java
@@ -0,0 +1,32 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
+//
+// This program and the accompanying materials are made available under
+// the terms of the Eclipse Public License 2.0 which is available at
+// https://www.eclipse.org/legal/epl-2.0
+//
+// This Source Code may also be made available under the following
+// Secondary Licenses when the conditions for such availability set
+// forth in the Eclipse Public License, v. 2.0 are satisfied:
+// the Apache License v2.0 which is available at
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+// ========================================================================
+//
+
+package jettyissue;
+
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.annotation.HandlesTypes;
+import java.util.Set;
+
+@HandlesTypes({MyAnnotation.class})
+public class MyServletContainerInitializer implements ServletContainerInitializer {
+ public void onStartup(Set> c, ServletContext ctx) throws ServletException {
+ System.out.println("STARTED"+c);
+ }
+}
diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
new file mode 100644
index 00000000000..9e9784f1616
--- /dev/null
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyLibrary/src/main/resources/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -0,0 +1 @@
+jettyissue.MyServletContainerInitializer
diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/pom.xml b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/pom.xml
new file mode 100644
index 00000000000..d0fa56d98ba
--- /dev/null
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/pom.xml
@@ -0,0 +1,66 @@
+
+
+
+ jetty-issue
+ org.mehdi
+ 1.0-SNAPSHOT
+
+ 4.0.0
+
+ MyWebApp
+ jar
+
+
+ ${project.build.directory}/jetty-run-mojo.txt
+
+
+
+
+ org.eclipse.jetty.toolchain
+ jetty-servlet-api
+ provided
+
+
+ org.mehdi
+ MyLibrary
+
+
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-maven-plugin
+
+
+ start-jetty
+ test-compile
+
+ start
+
+
+
+
+ jetty.port.file
+ ${jetty.port.file}
+
+
+ true
+ ${basedir}/src/config/jetty.xml
+ ${basedir}/src/config/context.xml
+ true
+
+ jar
+
+
+
+
+
+
+
+
+
diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/context.xml b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/context.xml
new file mode 100644
index 00000000000..3eb5570a37d
--- /dev/null
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/context.xml
@@ -0,0 +1,7 @@
+
+
+
+
+ /setbycontextxml
+
+
diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/jetty.xml b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/jetty.xml
new file mode 100644
index 00000000000..9193b42df99
--- /dev/null
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/config/jetty.xml
@@ -0,0 +1,39 @@
+
+
+
+
+ https
+
+ 32768
+ 8192
+ 8192
+ 1024
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 30000
+
+
+
+
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/package-info.java b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/java/jettyissue/NormalClass.java
similarity index 89%
rename from jetty-server/src/main/java/org/eclipse/jetty/server/nio/package-info.java
rename to jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/java/jettyissue/NormalClass.java
index e0922dc6709..de6ccc334ed 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/package-info.java
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/java/jettyissue/NormalClass.java
@@ -16,8 +16,9 @@
// ========================================================================
//
-/**
- * Jetty Server : Core Server Connector
- */
-package org.eclipse.jetty.server.nio;
+package jettyissue;
+
+@MyAnnotation
+public class NormalClass {
+}
diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/webapp/index.html b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/webapp/index.html
new file mode 100644
index 00000000000..b7b5cdc61de
--- /dev/null
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/MyWebApp/src/main/webapp/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Title
+
+
+ Hello World!
+
+
diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/invoker.properties b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/invoker.properties
new file mode 100644
index 00000000000..ac620b04a8b
--- /dev/null
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/invoker.properties
@@ -0,0 +1 @@
+invoker.goals = test -e
diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/pom.xml b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/pom.xml
new file mode 100644
index 00000000000..1380e37256e
--- /dev/null
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/pom.xml
@@ -0,0 +1,32 @@
+
+
+ 4.0.0
+
+ org.eclipse.jetty.its
+ it-parent-pom
+ 0.0.1-SNAPSHOT
+
+
+
+ org.mehdi
+ jetty-issue
+ pom
+ 1.0-SNAPSHOT
+
+ MyLibrary
+ MyWebApp
+
+
+
+
+
+ org.mehdi
+ MyLibrary
+ ${project.version}
+
+
+
+
+
diff --git a/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/postbuild.groovy b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/postbuild.groovy
new file mode 100644
index 00000000000..75cfafa3fee
--- /dev/null
+++ b/jetty-maven-plugin/src/it/jetty-run-mojo-jar-scan-it/postbuild.groovy
@@ -0,0 +1,3 @@
+File buildLog = new File( basedir, 'build.log' )
+assert buildLog.text.contains( 'Started Server' )
+assert buildLog.text.contains( 'STARTED[class jettyissue.NormalClass]')
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractUnassembledWebAppMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractUnassembledWebAppMojo.java
index 577e084063c..0b7c88b512a 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractUnassembledWebAppMojo.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractUnassembledWebAppMojo.java
@@ -215,11 +215,6 @@ public abstract class AbstractUnassembledWebAppMojo extends AbstractWebAppMojo
*/
protected Collection getWebInfLibArtifacts()
{
- //if this project isn't a war, then don't calculate web-inf lib
- String type = project.getArtifact().getType();
- if (!"war".equalsIgnoreCase(type) && !"zip".equalsIgnoreCase(type))
- return Collections.emptyList();
-
return project.getArtifacts().stream()
.filter(this::isArtifactOKForWebInfLib)
.collect(Collectors.toList());
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractWebAppMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractWebAppMojo.java
index c4b1dd0b7df..464b6c2595e 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractWebAppMojo.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractWebAppMojo.java
@@ -59,6 +59,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.IncludeExcludeSet;
import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.resource.Resource;
/**
@@ -606,9 +607,12 @@ public abstract class AbstractWebAppMojo extends AbstractMojo
{
for (Map.Entry e : mergedSystemProperties.entrySet())
{
- System.setProperty(e.getKey(), e.getValue());
- if (getLog().isDebugEnabled())
- getLog().debug("Set system property " + e.getKey() + "=" + e.getValue());
+ if (!StringUtil.isEmpty(e.getKey()) && !StringUtil.isEmpty(e.getValue()))
+ {
+ System.setProperty(e.getKey(), e.getValue());
+ if (getLog().isDebugEnabled())
+ getLog().debug("Set system property " + e.getKey() + "=" + e.getValue());
+ }
}
}
}
diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java
index 5a026388d7c..e55b24f1e12 100644
--- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java
+++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunMojo.java
@@ -65,13 +65,13 @@ public class JettyRunMojo extends AbstractUnassembledWebAppMojo
* can force redeployment by typing a linefeed character at the command line.
*/
@Parameter(defaultValue = "0", property = "jetty.scan", required = true)
- protected int scan;
-
+ protected int scan;
+
/**
* Scanner to check for files changes to cause redeploy
*/
protected Scanner scanner;
-
+
/**
* Only one of the following will be used, depending the mode
* the mojo is started in: EMBED, FORK, DISTRO
@@ -92,7 +92,7 @@ public class JettyRunMojo extends AbstractUnassembledWebAppMojo
try
{
//start jetty
- embedder = newJettyEmbedder();
+ embedder = newJettyEmbedder();
embedder.setExitVm(true);
embedder.setStopAtShutdown(true);
embedder.start();
@@ -213,7 +213,7 @@ public class JettyRunMojo extends AbstractUnassembledWebAppMojo
Resource r = Resource.newResource(webApp.getDescriptor());
scanner.addFile(r.getFile().toPath());
}
-
+
if (webApp.getJettyEnvXml() != null)
scanner.addFile(new File(webApp.getJettyEnvXml()).toPath());
@@ -227,13 +227,13 @@ public class JettyRunMojo extends AbstractUnassembledWebAppMojo
{
scanner.addFile(new File(webApp.getOverrideDescriptor()).toPath());
}
-
+
File jettyWebXmlFile = findJettyWebXmlFile(new File(webAppSourceDirectory,"WEB-INF"));
if (jettyWebXmlFile != null)
{
scanner.addFile(jettyWebXmlFile.toPath());
}
-
+
//make sure each of the war artifacts is added to the scanner
for (Artifact a:mavenProjectHelper.getWarPluginInfo().getWarArtifacts())
{
@@ -243,7 +243,7 @@ public class JettyRunMojo extends AbstractUnassembledWebAppMojo
else
scanner.addFile(f.toPath());
}
-
+
//set up any extra files or dirs to watch
configureScanTargetPatterns(scanner);
@@ -269,7 +269,7 @@ public class JettyRunMojo extends AbstractUnassembledWebAppMojo
}
}
}
-
+
if (webApp.getClasses() != null && webApp.getClasses().exists())
{
Path p = webApp.getClasses().toPath();
@@ -289,7 +289,7 @@ public class JettyRunMojo extends AbstractUnassembledWebAppMojo
s = "glob:" + s;
includeExcludes.include(p.getFileSystem().getPathMatcher(s));
}
- }
+ }
}
if (webApp.getWebInfLib() != null)
@@ -303,30 +303,30 @@ public class JettyRunMojo extends AbstractUnassembledWebAppMojo
}
}
}
-
+
/**
* Stop an executing webapp and restart it after optionally
* reconfiguring it.
- *
+ *
* @param reconfigure if true, the scanner will
* be reconfigured after changes to the pom. If false, only
* the webapp will be reconfigured.
- *
+ *
* @throws Exception
*/
- public void restartWebApp(boolean reconfigure) throws Exception
+ public void restartWebApp(boolean reconfigure) throws Exception
{
getLog().info("Restarting " + webApp);
getLog().debug("Stopping webapp ...");
if (scanner != null)
scanner.stop();
-
+
switch (deployMode)
{
case EMBED:
{
getLog().debug("Reconfiguring webapp ...");
-
+
verifyPomConfiguration();
// check if we need to reconfigure the scanner,
// which is if the pom changes
@@ -347,7 +347,7 @@ public class JettyRunMojo extends AbstractUnassembledWebAppMojo
if (scanner != null)
scanner.start();
getLog().info("Restart completed at " + new Date().toString());
-
+
break;
}
case FORK:
diff --git a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java
index 07c6f6fc22d..a6650addae3 100644
--- a/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java
+++ b/jetty-openid/src/main/java/org/eclipse/jetty/security/openid/OpenIdCredentials.java
@@ -26,7 +26,7 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
-import org.eclipse.jetty.client.util.FormContentProvider;
+import org.eclipse.jetty.client.util.FormRequestContent;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.ajax.JSON;
import org.slf4j.Logger;
@@ -176,9 +176,9 @@ public class OpenIdCredentials implements Serializable
fields.add("client_secret", configuration.getClientSecret());
fields.add("redirect_uri", redirectUri);
fields.add("grant_type", "authorization_code");
- FormContentProvider formContentProvider = new FormContentProvider(fields);
+ FormRequestContent formContent = new FormRequestContent(fields);
Request request = httpClient.POST(configuration.getTokenEndpoint())
- .content(formContentProvider)
+ .body(formContent)
.timeout(10, TimeUnit.SECONDS);
ContentResponse response = request.send();
String responseBody = response.getContentAsString();
diff --git a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
index b8ebe520cab..28a8ee7bb65 100644
--- a/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
+++ b/jetty-osgi/jetty-osgi-boot-jsp/pom.xml
@@ -56,9 +56,77 @@
org.eclipse.jetty.osgi.boot!org.eclipse.jetty.osgi.boot.*
- org.eclipse.jdt.*;resolution:=optional, org.eclipse.jdt.core.compiler.*;resolution:=optional, com.sun.el;resolution:=optional, com.sun.el.lang;resolution:=optional, com.sun.el.parser;resolution:=optional, com.sun.el.util;resolution:=optional, javax.el;version="[3.0,3.1)", javax.servlet;version="[3.1,4.1)", javax.servlet.resources;version="[3.1,4.1)", javax.servlet.jsp.resources;version="[2.3,4.1)", javax.servlet.jsp;version="[2.3,2.4.1)", javax.servlet.jsp.el;version="[2.3,2.4.1)", javax.servlet.jsp.tagext;version="[2.3,2.4.1)", javax.servlet.jsp.jstl.core;version="1.2";resolution:=optional, javax.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional, javax.servlet.jsp.jstl.sql;version="1.2";resolution:=optional, javax.servlet.jsp.jstl.tlv;version="1.2";resolution:=optional, org.apache.el;version="[8.0.23,10)";resolution:=optional, org.apache.el.lang;version="[8.0.23,10)";resolution:=optional, org.apache.el.stream;version="[8.0.23,10)";resolution:=optional, org.apache.el.util;version="[8.0.23,10)";resolution:=optional, org.apache.el.parser;version="[8.0.23,10)";resolution:=optional, org.apache.jasper;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.compiler;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.compiler.tagplugin;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.runtime;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.security;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.servlet;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.tagplugins.jstl;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.util;version="[8.0.23,10)";resolution:=optional, org.apache.jasper.xmlparser;version="[8.0.23,10)";resolution:=optional, org.apache.taglibs.standard;version="1.2";resolution:=optional, org.apache.taglibs.standard.extra.spath;version="1.2";resolution:=optional, org.apache.taglibs.standard.functions;version="1.2";resolution:=optional, org.apache.taglibs.standard.lang.jstl;version="1.2";resolution:=optional, org.apache.taglibs.standard.lang.jstl.parser;version="1.2";resolution:=optional, org.apache.taglibs.standard.lang.jstl.test;version="1.2";resolution:=optional, org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2";resolution:=optional, org.apache.taglibs.standard.lang.support;version="1.2";resolution:=optional, org.apache.taglibs.standard.resources;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.common.core;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.common.fmt;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.common.sql;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.common.xml;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.el.core;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.el.fmt;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.el.sql;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.el.xml;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.rt.core;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.rt.fmt;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.rt.sql;version="1.2";resolution:=optional, org.apache.taglibs.standard.tag.rt.xml;version="1.2";resolution:=optional, org.apache.taglibs.standard.tei;version="1.2";resolution:=optional, org.apache.taglibs.standard.tlv;version="1.2";resolution:=optional, org.apache.tomcat;version="[8.0.23,10)";resolution:=optional, org.eclipse.jetty.jsp;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))";resolution:=optional, org.osgi.*, org.xml.*;resolution:=optional, org.xml.sax.*;resolution:=optional, javax.xml.*;resolution:=optional, org.w3c.dom;resolution:=optional, org.w3c.dom.ls;resolution:=optional, javax.xml.parser;resolution:=optional
+
+ org.eclipse.jdt.*;resolution:=optional,
+ org.eclipse.jdt.core.compiler.*;resolution:=optional,
+ com.sun.el;resolution:=optional,
+ com.sun.el.lang;resolution:=optional,
+ com.sun.el.parser;resolution:=optional,
+ com.sun.el.util;resolution:=optional,
+ javax.el;version="[3.0,3.1)",
+ javax.servlet;version="[3.1,4.1)",
+ javax.servlet.resources;version="[3.1,4.1)",
+ javax.servlet.jsp.resources;version="[2.3,4.1)",
+ javax.servlet.jsp;version="[2.3,2.4.1)",
+ javax.servlet.jsp.el;version="[2.3,2.4.1)",
+ javax.servlet.jsp.tagext;version="[2.3,2.4.1)",
+ javax.servlet.jsp.jstl.core;version="1.2";resolution:=optional,
+ javax.servlet.jsp.jstl.fmt;version="1.2";resolution:=optional,
+ javax.servlet.jsp.jstl.sql;version="1.2";resolution:=optional,
+ javax.servlet.jsp.jstl.tlv;version="1.2";resolution:=optional,
+ org.apache.el;version="[8.0.23,10)";resolution:=optional,
+ org.apache.el.lang;version="[8.0.23,10)";resolution:=optional,
+ org.apache.el.stream;version="[8.0.23,10)";resolution:=optional,
+ org.apache.el.util;version="[8.0.23,10)";resolution:=optional,
+ org.apache.el.parser;version="[8.0.23,10)";resolution:=optional,
+ org.apache.jasper;version="[8.0.23,10)";resolution:=optional,
+ org.apache.jasper.compiler;version="[8.0.23,10)";resolution:=optional,
+ org.apache.jasper.compiler.tagplugin;version="[8.0.23,10)";resolution:=optional,
+ org.apache.jasper.runtime;version="[8.0.23,10)";resolution:=optional,
+ org.apache.jasper.security;version="[8.0.23,10)";resolution:=optional,
+ org.apache.jasper.servlet;version="[8.0.23,10)";resolution:=optional,
+ org.apache.jasper.tagplugins.jstl;version="[8.0.23,10)";resolution:=optional,
+ org.apache.jasper.util;version="[8.0.23,10)";resolution:=optional,
+ org.apache.jasper.xmlparser;version="[8.0.23,10)";resolution:=optional,
+ org.apache.taglibs.standard;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.extra.spath;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.functions;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.lang.jstl;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.lang.jstl.parser;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.lang.jstl.test;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.lang.jstl.test.beans;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.lang.support;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.resources;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.common.core;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.common.fmt;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.common.sql;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.common.xml;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.el.core;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.el.fmt;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.el.sql;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.el.xml;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.rt.core;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.rt.fmt;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.rt.sql;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tag.rt.xml;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tei;version="1.2";resolution:=optional,
+ org.apache.taglibs.standard.tlv;version="1.2";resolution:=optional,
+ org.apache.tomcat;version="[8.0.23,10)";resolution:=optional,
+ org.eclipse.jetty.jsp;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))";resolution:=optional,
+ org.slf4j.*,
+ org.osgi.*,
+ org.xml.*;resolution:=optional,
+ org.xml.sax.*;resolution:=optional,
+ javax.xml.*;resolution:=optional,
+ org.w3c.dom;resolution:=optional,
+ org.w3c.dom.ls;resolution:=optional,
+ javax.xml.parser;resolution:=optional
- org.eclipse.jetty.jsp.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))",org.apache.jasper.*;version="8.0.23",org.apache.el.*;version="8.0.23"
+
+ org.eclipse.jetty.jsp.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))",
+ org.apache.jasper.*;version="8.0.23",
+ org.apache.el.*;version="8.0.23"
+
diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml
index 443fe24c095..1bfdd01b97c 100644
--- a/jetty-osgi/jetty-osgi-boot/pom.xml
+++ b/jetty-osgi/jetty-osgi-boot/pom.xml
@@ -71,7 +71,7 @@
org.eclipse.jetty.osgi.boot;singleton:=trueorg.eclipse.jetty.osgi.boot.JettyBootstrapActivatororg.eclipse.jetty.*;version="[$(version;===;${parsedVersion.osgiVersion}),$(version;==+;${parsedVersion.osgiVersion}))"
- javax.mail;version="1.4.0";resolution:=optional, javax.mail.event;version="1.4.0";resolution:=optional, javax.mail.internet;version="1.4.0";resolution:=optional, javax.mail.search;version="1.4.0";resolution:=optional, javax.mail.util;version="1.4.0";resolution:=optional, javax.servlet;version="[3.1,4.1)", javax.servlet.http;version="[3.1,4.1)", javax.transaction;version="1.1.0";resolution:=optional, javax.transaction.xa;version="1.1.0";resolution:=optional, org.objectweb.asm;version="5";resolution:=optional, org.osgi.framework, org.osgi.service.cm;version="1.2.0", org.osgi.service.packageadmin, org.osgi.service.startlevel;version="1.0.0", org.osgi.service.url;version="1.0.0", org.osgi.util.tracker;version="1.3.0", org.slf4j;resolution:=optional, org.slf4j.spi;resolution:=optional, org.slf4j.helpers;resolution:=optional, org.xml.sax, org.xml.sax.helpers, org.eclipse.jetty.annotations;resolution:=optional, *
+ javax.mail;version="1.4.0";resolution:=optional, javax.mail.event;version="1.4.0";resolution:=optional, javax.mail.internet;version="1.4.0";resolution:=optional, javax.mail.search;version="1.4.0";resolution:=optional, javax.mail.util;version="1.4.0";resolution:=optional, javax.servlet;version="[3.1,4.1)", javax.servlet.http;version="[3.1,4.1)", javax.transaction;version="1.1.0";resolution:=optional, javax.transaction.xa;version="1.1.0";resolution:=optional, org.objectweb.asm;version="5";resolution:=optional, org.osgi.framework, org.osgi.service.cm;version="1.2.0", org.osgi.service.packageadmin, org.osgi.service.startlevel;version="1.0.0", org.osgi.service.url;version="1.0.0", org.osgi.util.tracker;version="1.3.0", org.xml.sax, org.xml.sax.helpers, org.eclipse.jetty.annotations;resolution:=optional, *
osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"
diff --git a/jetty-osgi/test-jetty-osgi/README.txt b/jetty-osgi/test-jetty-osgi/README.txt
new file mode 100644
index 00000000000..30e029fc6ce
--- /dev/null
+++ b/jetty-osgi/test-jetty-osgi/README.txt
@@ -0,0 +1,206 @@
+Unit Tests with OSGi
+====================
+
+The unit tests use PaxExam https://ops4j1.jira.com/wiki/spaces/PAXEXAM4/overview
+to fork a jvm to start an OSGi container (currently eclipse) and deploy the jetty
+jars as osgi bundles, along with the jetty-osgi infrastructure (like jetty-osgi-boot).
+
+To run all the tests:
+ mvn test
+
+To run a particular test:
+ mvn test -Dtest=[name of test]
+
+
+At the time of writing, PaxExam only works with junit-4, so you may not be
+able to invoke them easily from your IDE.
+
+
+Logging
+-------
+
+By default, very little log info comes out of the tests. If you wish to see more
+logging information, you can control this from the command line.
+
+There are 2 sources of logging information: 1) the pax environment and 2) jetty logs.
+
+To set the logging level for the pax environment use the following system property:
+
+ mvn -Dpax.exam.LEVEL=[log level]
+
+INFO, WARN and TRACE are known to work.
+
+To set the logging level for the jetty logs edit the src/test/resources/jetty-logging.properties
+to set the logging level you want and rerun your tests. The usual jetty logging levels apply.
+
+
+
+General Test Diagnostics
+------------------------
+
+There are generally only ever 2 things wrong with the jetty/osgi interworking:
+
+1. you've added or changed an existing jetty-whatever subproject to use the java ServiceLoader, but you haven't put the right entries into the jar's manifest to allow ServiceLoader to work in osgi
+
+2. you've upgraded the jvm version and the version of Aries SpiFly that we use to provide ServiceLoader functionality in osgi does not support parsing java classes compiled with the new version
+
+
+* Diagnosing problem 1:
+
+Can be an obvious failure, because the osgi test that exercises your feature fails. Worst case is that your code substitutes the missing service with some default that isn't your new feature and it's never detected because the test still works. That's a problem with the design of the test, c'est la vie.
+
+Other problems with misconfigured manifests are usually to do with missing or incorrect Import-Package/Export-Package statements. This usually isn't a problem because we mostly automatically generate these using the mvn bnd tool during assembly of the jar, but can become a problem if the manifest has been manually cobbled together in the pom.xml. You'll notice these failures because an osgi test will fail with messages something like the following:
+
+Bundle [id:24, url:mvn:org.eclipse.jetty/jetty-util/10.0.0-SNAPSHOT] is not resolved
+
+To diagnose that further, you can rerun the test, and ask it to spit out a list of the status of every jetty jar that is deployed. To do that, supply the following system property at the command line:
+mvn -Dbundle.debug=true
+
+You'll see several lines of output like the following. All of the bundles should be state 32 (active) or state 4 (resolved):
+
+ACTIVE 32
+RESOLVED 4
+INSTALLED 2
+0 org.eclipse.osgi System Bundle 3.15.100.v20191114-1701 32
+1 org.ops4j.pax.exam file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.exam_4.13.1.jar 4.13.1 32
+2 org.ops4j.pax.exam.inject file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.exam.inject_4.13.1.jar 4.13.1 32
+3 org.ops4j.pax.exam.extender.service file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.exam.extender.service_4.13.1.jar 4.13.1 32
+4 osgi.cmpn file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/osgi.cmpn_4.3.1.201210102024.jar 4.3.1.201210102024 32
+5 org.ops4j.pax.logging.pax-logging-api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.logging.pax-logging-api_1.10.1.jar 1.10.1 32
+6 org.ops4j.base file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.base_1.5.0.jar 1.5.0 32
+7 org.ops4j.pax.swissbox.core file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.swissbox.core_1.8.2.jar 1.8.2 32
+8 org.ops4j.pax.swissbox.extender file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.swissbox.extender_1.8.2.jar 1.8.2 32
+9 org.ops4j.pax.swissbox.framework file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.swissbox.framework_1.8.2.jar 1.8.2 32
+10 org.ops4j.pax.swissbox.lifecycle file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.swissbox.lifecycle_1.8.2.jar 1.8.2 32
+11 org.ops4j.pax.swissbox.tracker file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.swissbox.tracker_1.8.2.jar 1.8.2 32
+12 org.apache.geronimo.specs.geronimo-atinject_1.0_spec file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.apache.geronimo.specs.geronimo-atinject_1.0_spec_1.0.jar 1.0.0 32
+13 org.ops4j.pax.tipi.junit file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.tipi.junit_4.12.0.1.jar 4.12.0.1 32
+14 org.ops4j.pax.tipi.hamcrest.core file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.tipi.hamcrest.core_1.3.0.1.jar 1.3.0.1 32
+15 org.ops4j.pax.exam.invoker.junit file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.ops4j.pax.exam.invoker.junit_4.13.1.jar 4.13.1 32
+16 org.eclipse.jetty.servlet-api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.servlet-api_4.0.3.jar 4.0.3 32
+17 org.objectweb.asm file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.objectweb.asm_7.2.0.jar 7.2.0 32
+18 org.objectweb.asm.commons file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.objectweb.asm.commons_7.2.0.jar 7.2.0 32
+19 org.objectweb.asm.tree file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.objectweb.asm.tree_7.2.0.jar 7.2.0 32
+20 org.apache.aries.spifly.dynamic.bundle file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.apache.aries.spifly.dynamic.bundle_1.2.3.jar 1.2.3 32
+21 jakarta.annotation-api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/jakarta.annotation-api_1.3.4.jar 1.3.4 32
+22 org.apache.geronimo.specs.geronimo-jta_1.1_spec file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.apache.geronimo.specs.geronimo-jta_1.1_spec_1.1.1.jar 1.1.1 32
+23 slf4j.api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/slf4j.api_2.0.0.alpha1.jar 2.0.0.alpha1 4
+24 slf4j.log4j12 file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/slf4j.log4j12_2.0.0.alpha1.jar 2.0.0.alpha1 4
+25 org.eclipse.jetty.util file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.util_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+26 org.eclipse.jetty.deploy file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.deploy_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+27 org.eclipse.jetty.server file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.server_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+28 org.eclipse.jetty.servlet file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.servlet_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+29 org.eclipse.jetty.http file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.http_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+30 org.eclipse.jetty.xml file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.xml_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+31 org.eclipse.jetty.webapp file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.webapp_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+32 org.eclipse.jetty.io file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.io_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+33 org.eclipse.jetty.security file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.security_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+34 org.eclipse.jetty.servlets file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.servlets_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+35 org.eclipse.jetty.client file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.client_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+36 org.eclipse.jetty.jndi file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.jndi_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+37 org.eclipse.jetty.plus file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.plus_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+38 org.eclipse.jetty.annotations file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.annotations_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+39 org.eclipse.jetty.websocket.core file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.core_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+40 org.eclipse.jetty.websocket.servlet file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.servlet_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+41 org.eclipse.jetty.websocket.util file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.util_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+42 org.eclipse.jetty.websocket.api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.api_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+43 org.eclipse.jetty.websocket.server file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.server_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+44 org.eclipse.jetty.websocket.client file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.client_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+45 org.eclipse.jetty.websocket.common file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.common_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+46 org.eclipse.jetty.websocket-api file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket-api_1.1.2.jar 1.1.2 4
+47 org.eclipse.jetty.websocket.javax.server file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.javax.server_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 4
+48 org.eclipse.jetty.websocket.javax.client file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.javax.client_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 4
+49 org.eclipse.jetty.websocket.javax.common file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.websocket.javax.common_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 4
+50 org.eclipse.jetty.osgi.boot file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.osgi.boot_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+51 org.eclipse.jetty.alpn.java.client file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.alpn.java.client_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+52 org.eclipse.jetty.alpn.client file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.alpn.client_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+53 javax.servlet.jsp.jstl file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/javax.servlet.jsp.jstl_1.2.0.v201105211821.jar 1.2.0.v201105211821 32
+54 org.mortbay.jasper.apache-el file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.mortbay.jasper.apache-el_9.0.29.jar 9.0.29 32
+55 org.mortbay.jasper.apache-jsp file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.mortbay.jasper.apache-jsp_9.0.29.jar 9.0.29 32
+56 org.eclipse.jetty.apache-jsp file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.apache-jsp_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+57 org.glassfish.web.javax.servlet.jsp.jstl file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.glassfish.web.javax.servlet.jsp.jstl_1.2.2.jar 1.2.2 32
+58 org.eclipse.jdt.core.compiler.batch file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jdt.core.compiler.batch_3.19.0.v20190903-0936.jar 3.19.0.v20190903-0936 32
+59 org.eclipse.jetty.osgi.boot.jsp file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.osgi.boot.jsp_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 4
+60 org.eclipse.jetty.tests.webapp file:/home/janb/src/jetty-eclipse/jetty-10.0.x/jetty-osgi/test-jetty-osgi/target/1584628869418-0/pax-exam-downloads/org.eclipse.jetty.tests.webapp_10.0.0.SNAPSHOT.jar 10.0.0.SNAPSHOT 32
+61 PAXEXAM-PROBE-d9c5a341-5c98-4084-b814-8303880cb447 local 0.0.0 32
+
+If one of them isn't active (32) and you think it should be, you can edit the src of the test and call a method to generate more information next time you run the test:
+
+TestOSGiUtil.getBundle(BundleContext, String)
+
+Where BundleContext is the field called bundleContext in the unit test class, and the String is the symbolic name of the jar. For example, for jetty-util, the symbolic name is org.eclipse.jetty.util. You can find it on the list above. You can also look into the pom.xml for the relevant jetty module and find it. If it's a 3rd party jar, you'll have to look in the META-INF/MANIFEST.MF.
+
+
+
+* Diagnosing Problem 2
+
+If you've upgraded the jetty jar and that's all you've changed, then more than likely it's a SpiFly versioning problem. SpiFly internally uses asm to parse classes to find services for the ServiceLoader, so it can be that the version of asm used by SpiFly doesn't support the higher jvm version. At the time of writing SpiFly is shipping an older version of asm baked into their jars, so very likely it won't support anything above jdk13.
+
+Also, if you don't see any test failures with unresolved jar messages, then it's also a good indicator that the problem is with SpiFly versioning. Unfortunately, when the problem is a jvm/SpiFly versioning mismatch, the osgi paxexam environment doesn't output any good log messages. There is a java.lang.Error that is thrown from inside asm that the environment doesn't pass on in any useful fashion.
+
+To try and catch this error, you can modify the ServletInstanceWrapper at line 163 to catch Throwable instead of Exception:
+https://github.com/eclipse/jetty.project/blob/jetty-10.0.x/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/internal/serverfactory/ServerInstanceWrapper.java#L163
+
+When you do this, you get output like the following:
+
+java.lang.ClassFormatError: Unexpected error from weaving hook.
+at org.eclipse.osgi.internal.weaving.WeavingHookConfigurator.processClass(WeavingHookConfigurator.java:77)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.processClass(ClasspathManager.java:736)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.defineClass(ClasspathManager.java:707)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.findClassImpl(ClasspathManager.java:640)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.findLocalClassImpl(ClasspathManager.java:608)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.findLocalClassImpl(ClasspathManager.java:588)
+at org.eclipse.osgi.internal.loader.classpath.ClasspathManager.findLocalClass(ClasspathManager.java:567)
+at org.eclipse.osgi.internal.loader.ModuleClassLoader.findLocalClass(ModuleClassLoader.java:346)
+at org.eclipse.osgi.internal.loader.BundleLoader.findLocalClass(BundleLoader.java:398)
+at org.eclipse.osgi.internal.loader.sources.SingleSourcePackage.loadClass(SingleSourcePackage.java:41)
+at org.eclipse.osgi.internal.loader.BundleLoader.findClassInternal(BundleLoader.java:460)
+at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:425)
+at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:417)
+at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:171)
+at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
+at org.eclipse.jetty.osgi.boot.internal.serverfactory.ServerInstanceWrapper.configure(ServerInstanceWrapper.java:143)
+at org.eclipse.jetty.osgi.boot.internal.serverfactory.DefaultJettyAtJettyHomeHelper.startJettyAtJettyHome(DefaultJettyAtJettyHomeHelper.java:211)
+at org.eclipse.jetty.osgi.boot.JettyBootstrapActivator.start(JettyBootstrapActivator.java:98)
+at org.eclipse.osgi.internal.framework.BundleContextImpl$3.run(BundleContextImpl.java:842)
+at org.eclipse.osgi.internal.framework.BundleContextImpl$3.run(BundleContextImpl.java:1)
+at java.base/java.security.AccessController.doPrivileged(AccessController.java:554)
+at org.eclipse.osgi.internal.framework.BundleContextImpl.startActivator(BundleContextImpl.java:834)
+at org.eclipse.osgi.internal.framework.BundleContextImpl.start(BundleContextImpl.java:791)
+at org.eclipse.osgi.internal.framework.EquinoxBundle.startWorker0(EquinoxBundle.java:1015)
+at org.eclipse.osgi.internal.framework.EquinoxBundle$EquinoxModule.startWorker(EquinoxBundle.java:365)
+at org.eclipse.osgi.container.Module.doStart(Module.java:603)
+at org.eclipse.osgi.container.Module.start(Module.java:467)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel$2.run(ModuleContainer.java:1844)
+at org.eclipse.osgi.internal.framework.EquinoxContainerAdaptor$1$1.execute(EquinoxContainerAdaptor.java:136)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.incStartLevel(ModuleContainer.java:1837)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.incStartLevel(ModuleContainer.java:1780)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.doContainerStartLevel(ModuleContainer.java:1742)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.dispatchEvent(ModuleContainer.java:1664)
+at org.eclipse.osgi.container.ModuleContainer$ContainerStartLevel.dispatchEvent(ModuleContainer.java:1)
+at org.eclipse.osgi.framework.eventmgr.EventManager.dispatchEvent(EventManager.java:234)
+at org.eclipse.osgi.framework.eventmgr.EventManager$EventThread.run(EventManager.java:345)
+Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 58
+at org.objectweb.asm.ClassReader.(ClassReader.java:195)
+at org.objectweb.asm.ClassReader.(ClassReader.java:176)
+at org.objectweb.asm.ClassReader.(ClassReader.java:162)
+at org.objectweb.asm.ClassReader.(ClassReader.java:283)
+at org.apache.aries.spifly.dynamic.OSGiFriendlyClassWriter.getCommonSuperClass(OSGiFriendlyClassWriter.java:81)
+at org.objectweb.asm.SymbolTable.addMergedType(SymbolTable.java:1200)
+at org.objectweb.asm.Frame.merge(Frame.java:1299)
+at org.objectweb.asm.Frame.merge(Frame.java:1207)
+at org.objectweb.asm.MethodWriter.computeAllFrames(MethodWriter.java:1607)
+at org.objectweb.asm.MethodWriter.visitMaxs(MethodWriter.java:1543)
+at org.objectweb.asm.MethodVisitor.visitMaxs(MethodVisitor.java:762)
+at org.objectweb.asm.commons.LocalVariablesSorter.visitMaxs(LocalVariablesSorter.java:147)
+at org.objectweb.asm.ClassReader.readCode(ClassReader.java:2431)
+at org.objectweb.asm.ClassReader.readMethod(ClassReader.java:1283)
+at org.objectweb.asm.ClassReader.accept(ClassReader.java:688)
+at org.objectweb.asm.ClassReader.accept(ClassReader.java:400)
+at org.apache.aries.spifly.dynamic.ClientWeavingHook.weave(ClientWeavingHook.java:60)
+at org.eclipse.osgi.internal.weaving.WovenClassImpl.call(WovenClassImpl.java:175)
+at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.notifyHookPrivileged(ServiceRegistry.java:1343)
+at org.eclipse.osgi.internal.serviceregistry.ServiceRegistry.notifyHooksPrivileged(ServiceRegistry.java:1323)
+at org.eclipse.osgi.internal.weaving.WovenClassImpl.callHooks(WovenClassImpl.java:270)
+at org.eclipse.osgi.internal.weaving.WeavingHookConfigurator.processClass(WeavingHookConfigurator.java:71)
+... 35 more
diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml
index d82c7e2dde9..01be0b6dfdb 100644
--- a/jetty-osgi/test-jetty-osgi/pom.xml
+++ b/jetty-osgi/test-jetty-osgi/pom.xml
@@ -72,18 +72,31 @@
org.ops4j.pax.tinybundlestinybundles
- 2.1.1
+ 3.0.0
+ test
+
+
+ biz.aQute.bnd
+ bnd
+
+ org.ops4j.pax.urlpax-url-wrap${pax.url.version}test
+
+
+ biz.aQute.bnd
+ bndlib
+
+ biz.aQute.bnd
- bndlib
- 2.4.0
+ biz.aQute.bndlib
+ 5.0.0org.osgi
@@ -105,6 +118,12 @@
+
+ org.eclipse.jetty
+ jetty-slf4j-impl
+ ${project.version}
+ test
+ org.eclipse.jetty.osgijetty-osgi-boot
@@ -409,18 +428,6 @@
jetty-test-helpertest
-
- org.slf4j
- slf4j-api
- ${slf4j.version}
- test
-
-
- org.slf4j
- slf4j-log4j12
- ${slf4j.version}
- test
- org.ow2.asmasm
diff --git a/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties b/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties
deleted file mode 100644
index c2be11c689a..00000000000
--- a/jetty-osgi/test-jetty-osgi/src/main/resources/jetty-logging.properties
+++ /dev/null
@@ -1 +0,0 @@
-org.eclipse.jetty.LEVEL=INFO
\ No newline at end of file
diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java
index 3e34573a5bc..2e9f7393b82 100644
--- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java
+++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java
@@ -51,8 +51,6 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty;
@RunWith(PaxExam.class)
public class TestJettyOSGiBootContextAsService
{
- private static final String LOG_LEVEL = "WARN";
-
@Inject
BundleContext bundleContext = null;
@@ -60,6 +58,9 @@ public class TestJettyOSGiBootContextAsService
public static Option[] configure()
{
ArrayList