Merge pull request #4618 from eclipse/jetty-10.0.x-4400-review_httpclient_content

Issue #4400 - Review HttpClient's ContentProvider.
This commit is contained in:
Simone Bordet 2020-03-30 15:54:42 +02:00 committed by GitHub
commit fa54c74946
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
90 changed files with 4221 additions and 2222 deletions

View File

@ -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

View File

@ -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
{
/**

View File

@ -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);

View File

@ -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());

View File

@ -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);

View File

@ -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.
* <p>
* {@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.
* <pre>
* +---+ +---+ +---+ +---+ +---+
* | | | | | | | | | |
* +---+ +---+ +---+ +---+ +---+
* ^ ^ ^ ^
* | | --&gt; advance() | |
* | | last |
* | | |
* before | after
* |
* current
* </pre>
* At each valid (non-before and non-after) cursor position, {@link HttpContent} provides the following state:
* <ul>
* <li>the buffer containing the content to send, via {@link #getByteBuffer()}</li>
* <li>a copy of the content buffer that can be used for notifications, via {@link #getContent()}</li>
* <li>whether the buffer to write is the last one, via {@link #isLast()}</li>
* </ul>
* {@link HttpContent} may not have content, if the related {@link ContentProvider} is {@code null}, and this
* is reflected by {@link #hasContent()}.
* <p>
* {@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<ByteBuffer> iterator;
private ByteBuffer buffer;
private ByteBuffer content;
private boolean last;
public HttpContent(ContentProvider provider)
{
this.provider = provider;
this.iterator = provider == null ? Collections.<ByteBuffer>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.
* <p>
* 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()}).
* <p>
* 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<ByteBuffer> 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()));
}
}

View File

@ -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))
{

View File

@ -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)
{

View File

@ -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<HttpCookie> cookies;
private Map<String, Object> 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;
}

View File

@ -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)}.
* <p>
* {@link HttpSender} governs two state machines.
* <p>
* 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.
* <p>
* 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)}.
* <p>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)}.</p>
* <p>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.</p>
*
* @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> requestState = new AtomicReference<>(RequestState.QUEUED);
private final AtomicReference<SenderState> 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<Throwable> 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.
* <p>
* If there is more content to send, then {@link #sendContent(HttpExchange, HttpContent, Callback)}
* will be invoked.
* <p>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.</p>
* <p>If there is more content to send, then {@link #sendContent(HttpExchange, ByteBuffer, boolean, Callback)}
* will be invoked.</p>
*
* @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.
* <p>
* 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.
* <p>
* 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.
* <p>Implementations should send the given HTTP content over the wire.</p>
*
* @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;
}
}
}

View File

@ -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<Request.RequestListener> requestListeners = request.getRequestListeners(null);
for (int i = 0; i < requestListeners.size(); ++i)

View File

@ -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.</p>
*
* @deprecated use {@link Request.Content} instead, or {@link #toRequestContent(ContentProvider)}
* to convert ContentProvider to {@link Request.Content}.
*/
@Deprecated
public interface ContentProvider extends Iterable<ByteBuffer>
{
/**
* <p>Converts a ContentProvider to a {@link Request.Content}.</p>
*
* @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<ByteBuffer>
/**
* 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
{
/**

View File

@ -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
{
}
}
/**
* <p>A reactive model to produce request content, similar to {@link java.util.concurrent.Flow.Publisher}.</p>
* <p>Implementations receive the content consumer via {@link #subscribe(Consumer, boolean)},
* and return a {@link Subscription} as the link between producer and consumer.</p>
* <p>Content producers must notify content to the consumer only if there is demand.</p>
* <p>Content consumers can generate demand for content by invoking {@link Subscription#demand()}.</p>
* <p>Content production must follow this algorithm:</p>
* <ul>
* <li>the first time content is demanded
* <ul>
* <li>when the content is not available =&gt; produce an empty content</li>
* <li>when the content is available:
* <ul>
* <li>when {@code emitInitialContent == false} =&gt; produce an empty content</li>
* <li>when {@code emitInitialContent == true} =&gt; produce the content</li>
* </ul>
* </li>
* </ul>
* </li>
* <li>the second and subsequent times content is demanded
* <ul>
* <li>when the content is not available =&gt; do not produce content</li>
* <li>when the content is available =&gt; produce the content</li>
* </ul>
* </li>
* </ul>
*
* @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;
}
/**
* <p>Whether this content producer can produce exactly the same content more
* than once.</p>
* <p>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.</p>
* <p>The {@link HttpClient} implementation may use this method in particular
* cases where it detects that it is safe to retry a request that failed.</p>
*
* @return whether the content can be produced more than once
*/
public default boolean isReproducible()
{
return false;
}
/**
* <p>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}).</p>
*
* @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);
/**
* <p>Fails this request content, possibly failing and discarding accumulated
* content that was not demanded.</p>
* <p>The failure may be notified to the consumer at a later time, when the
* consumer demands for content.</p>
* <p>Typical failure: the request being aborted by user code, or idle timeouts.</p>
*
* @param failure the reason of the failure
*/
public default void fail(Throwable failure)
{
}
/**
* <p>A reactive model to consume request content, similar to {@link java.util.concurrent.Flow.Subscriber}.</p>
* <p>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.</p>
*/
public interface Consumer
{
/**
* <p>Callback method invoked by the producer when there is content available
* <em>and</em> there is demand for content.</p>
* <p>The {@code callback} is associated with the {@code buffer} to
* signal when the content buffer has been consumed.</p>
* <p>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)}.</p>
* <p>In case an exception is thrown by this method, it is equivalent to
* a call to {@link Subscription#fail(Throwable)}.</p>
*
* @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);
/**
* <p>Callback method invoked by the producer when it failed to produce content.</p>
* <p>Typical failure: a producer getting an exception while reading from an
* {@link InputStream} to produce content.</p>
*
* @param failure the reason of the failure
*/
public default void onFailure(Throwable failure)
{
}
}
/**
* <p>The link between a content producer and a content consumer.</p>
* <p>Content consumers can demand more content via {@link #demand()},
* or ask the content producer to stop producing content via
* {@link #fail(Throwable)}.</p>
*/
public interface Subscription
{
/**
* <p>Demands more content, which eventually results in
* {@link Consumer#onContent(ByteBuffer, boolean, Callback)} to be invoked.</p>
*/
public void demand();
/**
* <p>Fails the subscription, notifying the content producer to stop producing
* content.</p>
* <p>Typical failure: a proxy consumer waiting for more content (or waiting
* to demand content) that is failed by an error response from the server.</p>
*
* @param failure the reason of the failure
*/
public default void fail(Throwable failure)
{
}
}
}
}

View File

@ -21,12 +21,11 @@ package org.eclipse.jetty.client.http;
import java.nio.ByteBuffer;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpContent;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpRequestException;
import org.eclipse.jetty.client.HttpSender;
import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.MetaData;
@ -42,7 +41,14 @@ public class HttpSenderOverHTTP extends HttpSender
{
private static final Logger LOG = LoggerFactory.getLogger(HttpSenderOverHTTP.class);
private final IteratingCallback headersCallback = new HeadersCallback();
private final IteratingCallback contentCallback = new ContentCallback();
private final HttpGenerator generator = new HttpGenerator();
private HttpExchange exchange;
private MetaData.Request metaData;
private ByteBuffer contentBuffer;
private boolean lastContent;
private Callback callback;
private boolean shutdown;
public HttpSenderOverHTTP(HttpChannelOverHTTP channel)
@ -57,11 +63,26 @@ public class HttpSenderOverHTTP extends HttpSender
}
@Override
protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback)
protected void sendHeaders(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback)
{
try
{
new HeadersCallback(exchange, content, callback).iterate();
this.exchange = exchange;
this.contentBuffer = contentBuffer;
this.lastContent = lastContent;
this.callback = callback;
HttpRequest request = exchange.getRequest();
Request.Content requestContent = request.getBody();
long contentLength = requestContent == null ? -1 : requestContent.getLength();
String path = request.getPath();
String query = request.getQuery();
if (query != null)
path += "?" + query;
metaData = new MetaData.Request(request.getMethod(), new HttpURI(path), request.getVersion(), request.getHeaders(), contentLength);
metaData.setTrailerSupplier(request.getTrailers());
if (LOG.isDebugEnabled())
LOG.debug("Sending headers with content {} last={} for {}", BufferUtil.toDetailString(contentBuffer), lastContent, exchange.getRequest());
headersCallback.iterate();
}
catch (Throwable x)
{
@ -72,67 +93,17 @@ public class HttpSenderOverHTTP extends HttpSender
}
@Override
protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
protected void sendContent(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback)
{
try
{
HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient();
ByteBufferPool bufferPool = httpClient.getByteBufferPool();
boolean useDirectByteBuffers = httpClient.isUseOutputDirectByteBuffers();
ByteBuffer chunk = null;
while (true)
{
ByteBuffer contentBuffer = content.getByteBuffer();
boolean lastContent = content.isLast();
HttpGenerator.Result result = generator.generateRequest(null, null, chunk, contentBuffer, lastContent);
if (LOG.isDebugEnabled())
LOG.debug("Generated content ({} bytes) - {}/{}",
contentBuffer == null ? -1 : contentBuffer.remaining(),
result, generator);
switch (result)
{
case NEED_CHUNK:
{
chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, useDirectByteBuffers);
break;
}
case NEED_CHUNK_TRAILER:
{
chunk = bufferPool.acquire(httpClient.getRequestBufferSize(), useDirectByteBuffers);
break;
}
case FLUSH:
{
EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
if (chunk != null)
endPoint.write(new ByteBufferRecyclerCallback(callback, bufferPool, chunk), chunk, contentBuffer);
else
endPoint.write(callback, contentBuffer);
return;
}
case SHUTDOWN_OUT:
{
shutdownOutput();
break;
}
case CONTINUE:
{
if (lastContent)
break;
callback.succeeded();
return;
}
case DONE:
{
callback.succeeded();
return;
}
default:
{
throw new IllegalStateException(result.toString());
}
}
}
this.exchange = exchange;
this.contentBuffer = contentBuffer;
this.lastContent = lastContent;
this.callback = callback;
if (LOG.isDebugEnabled())
LOG.debug("Sending content {} last={} for {}", BufferUtil.toDetailString(contentBuffer), lastContent, exchange.getRequest());
contentCallback.iterate();
}
catch (Throwable x)
{
@ -145,6 +116,8 @@ public class HttpSenderOverHTTP extends HttpSender
@Override
protected void reset()
{
headersCallback.reset();
contentCallback.reset();
generator.reset();
super.reset();
}
@ -177,54 +150,30 @@ public class HttpSenderOverHTTP extends HttpSender
private class HeadersCallback extends IteratingCallback
{
private final HttpExchange exchange;
private final Callback callback;
private final MetaData.Request metaData;
private ByteBuffer headerBuffer;
private ByteBuffer chunkBuffer;
private ByteBuffer contentBuffer;
private boolean lastContent;
private boolean generated;
public HeadersCallback(HttpExchange exchange, HttpContent content, Callback callback)
private HeadersCallback()
{
super(false);
this.exchange = exchange;
this.callback = callback;
HttpRequest request = exchange.getRequest();
ContentProvider requestContent = request.getContent();
long contentLength = requestContent == null ? -1 : requestContent.getLength();
String path = request.getPath();
String query = request.getQuery();
if (query != null)
path += "?" + query;
metaData = new MetaData.Request(request.getMethod(), new HttpURI(path), request.getVersion(), request.getHeaders(), contentLength);
metaData.setTrailerSupplier(request.getTrailers());
if (!expects100Continue(request))
{
content.advance();
contentBuffer = content.getByteBuffer();
lastContent = content.isLast();
}
}
@Override
protected Action process() throws Exception
{
HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient();
ByteBufferPool byteBufferPool = httpClient.getByteBufferPool();
boolean useDirectByteBuffers = httpClient.isUseOutputDirectByteBuffers();
while (true)
{
HttpGenerator.Result result = generator.generateRequest(metaData, headerBuffer, chunkBuffer, contentBuffer, lastContent);
if (LOG.isDebugEnabled())
LOG.debug("Generated headers ({} bytes), chunk ({} bytes), content ({} bytes) - {}/{}",
LOG.debug("Generated headers ({} bytes), chunk ({} bytes), content ({} bytes) - {}/{} for {}",
headerBuffer == null ? -1 : headerBuffer.remaining(),
chunkBuffer == null ? -1 : chunkBuffer.remaining(),
contentBuffer == null ? -1 : contentBuffer.remaining(),
result, generator);
HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient();
ByteBufferPool byteBufferPool = httpClient.getByteBufferPool();
boolean useDirectByteBuffers = httpClient.isUseOutputDirectByteBuffers();
result, generator, exchange.getRequest());
switch (result)
{
case NEED_HEADER:
@ -332,37 +281,86 @@ public class HttpSenderOverHTTP extends HttpSender
}
}
private class ByteBufferRecyclerCallback extends Callback.Nested
private class ContentCallback extends IteratingCallback
{
private final ByteBufferPool pool;
private final ByteBuffer[] buffers;
private ByteBuffer chunkBuffer;
private ByteBufferRecyclerCallback(Callback callback, ByteBufferPool pool, ByteBuffer... buffers)
public ContentCallback()
{
super(callback);
this.pool = pool;
this.buffers = buffers;
super(false);
}
@Override
public void succeeded()
protected Action process() throws Exception
{
for (ByteBuffer buffer : buffers)
HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient();
ByteBufferPool bufferPool = httpClient.getByteBufferPool();
boolean useDirectByteBuffers = httpClient.isUseOutputDirectByteBuffers();
while (true)
{
assert !buffer.hasRemaining();
pool.release(buffer);
HttpGenerator.Result result = generator.generateRequest(null, null, chunkBuffer, contentBuffer, lastContent);
if (LOG.isDebugEnabled())
LOG.debug("Generated content ({} bytes, last={}) - {}/{}",
contentBuffer == null ? -1 : contentBuffer.remaining(),
lastContent, result, generator);
switch (result)
{
case NEED_CHUNK:
{
chunkBuffer = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, useDirectByteBuffers);
break;
}
case NEED_CHUNK_TRAILER:
{
chunkBuffer = bufferPool.acquire(httpClient.getRequestBufferSize(), useDirectByteBuffers);
break;
}
case FLUSH:
{
EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
if (chunkBuffer != null)
endPoint.write(this, chunkBuffer, contentBuffer);
else
endPoint.write(this, contentBuffer);
return Action.SCHEDULED;
}
case SHUTDOWN_OUT:
{
shutdownOutput();
break;
}
case CONTINUE:
{
break;
}
case DONE:
{
release();
callback.succeeded();
return Action.IDLE;
}
default:
{
throw new IllegalStateException(result.toString());
}
}
}
super.succeeded();
}
@Override
public void failed(Throwable x)
protected void onCompleteFailure(Throwable cause)
{
for (ByteBuffer buffer : buffers)
{
pool.release(buffer);
}
super.failed(x);
release();
callback.failed(cause);
}
private void release()
{
HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient();
ByteBufferPool bufferPool = httpClient.getByteBufferPool();
bufferPool.release(chunkBuffer);
chunkBuffer = null;
contentBuffer = null;
}
}
}

View File

@ -0,0 +1,329 @@
//
// ========================================================================
// 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.internal;
import java.io.Closeable;
import java.nio.ByteBuffer;
import java.util.Iterator;
import org.eclipse.jetty.client.AsyncContentProvider;
import org.eclipse.jetty.client.Synchronizable;
import org.eclipse.jetty.client.api.ContentProvider;
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;
import org.eclipse.jetty.util.thread.AutoLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>Implements the conversion from {@link ContentProvider} to {@link Request.Content}.</p>
*/
public class RequestContentAdapter implements Request.Content, Request.Content.Subscription, AsyncContentProvider.Listener, Callback
{
private static final Logger LOG = LoggerFactory.getLogger(RequestContentAdapter.class);
private final AutoLock lock = new AutoLock();
private final ContentProvider provider;
private Iterator<ByteBuffer> iterator;
private Consumer consumer;
private boolean emitInitialContent;
private boolean lastContent;
private boolean committed;
private int demand;
private boolean stalled;
private boolean hasContent;
private Throwable failure;
public RequestContentAdapter(ContentProvider provider)
{
this.provider = provider;
if (provider instanceof AsyncContentProvider)
((AsyncContentProvider)provider).setListener(this);
}
public ContentProvider getContentProvider()
{
return provider;
}
@Override
public String getContentType()
{
return provider instanceof ContentProvider.Typed ? ((ContentProvider.Typed)provider).getContentType() : null;
}
@Override
public long getLength()
{
return provider.getLength();
}
@Override
public boolean isReproducible()
{
return provider.isReproducible();
}
@Override
public Subscription subscribe(Consumer consumer, boolean emitInitialContent)
{
try (AutoLock ignored = lock.lock())
{
if (this.consumer != null && !isReproducible())
throw new IllegalStateException("Multiple subscriptions not supported on " + this);
this.iterator = provider.iterator();
this.consumer = consumer;
this.emitInitialContent = emitInitialContent;
this.lastContent = false;
this.committed = false;
this.demand = 0;
this.stalled = true;
this.hasContent = false;
}
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)
{
try (AutoLock ignored = lock.lock())
{
if (this.failure == null)
this.failure = failure;
}
failed(failure);
}
@Override
public void onContent()
{
boolean produce = false;
try (AutoLock ignored = lock.lock())
{
hasContent = true;
if (demand > 0)
{
produce = stalled;
if (stalled)
stalled = false;
}
}
if (LOG.isDebugEnabled())
LOG.debug("Content event, processing {} for {}", produce, this);
if (produce)
produce();
}
@Override
public void succeeded()
{
if (iterator instanceof Callback)
((Callback)iterator).succeeded();
if (lastContent && iterator instanceof Closeable)
IO.close((Closeable)iterator);
}
@Override
public void failed(Throwable x)
{
if (iterator == null)
failed(provider, x);
else
failed(iterator, x);
}
private void failed(Object object, Throwable failure)
{
if (object instanceof Callback)
((Callback)object).failed(failure);
if (object instanceof Closeable)
IO.close((Closeable)object);
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
private void produce()
{
while (true)
{
Throwable failure;
try (AutoLock ignored = lock.lock())
{
failure = this.failure;
}
if (failure != null)
{
notifyFailure(failure);
return;
}
if (committed)
{
ByteBuffer content = advance();
if (content != null)
{
notifyContent(content, lastContent);
}
else
{
try (AutoLock ignored = lock.lock())
{
// Call to advance() said there was no content,
// but some content may have arrived meanwhile.
if (hasContent)
{
hasContent = false;
continue;
}
else
{
stalled = true;
}
}
if (LOG.isDebugEnabled())
LOG.debug("No content, processing stalled for {}", this);
return;
}
}
else
{
committed = true;
if (emitInitialContent)
{
ByteBuffer content = advance();
if (content != null)
notifyContent(content, lastContent);
else
notifyContent(BufferUtil.EMPTY_BUFFER, false);
}
else
{
notifyContent(BufferUtil.EMPTY_BUFFER, false);
}
}
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;
}
}
}
private ByteBuffer advance()
{
if (iterator instanceof Synchronizable)
{
synchronized (((Synchronizable)iterator).getLock())
{
return next();
}
}
else
{
return next();
}
}
private ByteBuffer next()
{
boolean hasNext = iterator.hasNext();
ByteBuffer bytes = hasNext ? iterator.next() : null;
boolean hasMore = hasNext && iterator.hasNext();
lastContent = !hasMore;
return hasNext ? bytes : BufferUtil.EMPTY_BUFFER;
}
private void notifyContent(ByteBuffer buffer, boolean last)
{
try (AutoLock ignored = lock.lock())
{
--demand;
hasContent = false;
}
try
{
if (LOG.isDebugEnabled())
LOG.debug("Notifying content last={} {} for {}", last, BufferUtil.toDetailString(buffer), this);
consumer.onContent(buffer, last, this);
}
catch (Throwable 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;
try (AutoLock ignored = lock.lock())
{
demand = this.demand;
stalled = this.stalled;
}
return String.format("%s@%x[demand=%d,stalled=%b]", getClass().getSimpleName(), hashCode(), demand, stalled);
}
}

View File

@ -0,0 +1,257 @@
//
// ========================================================================
// 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 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;
/**
* <p>Partial implementation of {@link Request.Content}.</p>
*/
public abstract class AbstractRequestContent implements Request.Content
{
private static final Logger LOG = LoggerFactory.getLogger(AbstractRequestContent.class);
private final AutoLock lock = new AutoLock();
private final String contentType;
protected AbstractRequestContent(String contentType)
{
this.contentType = contentType;
}
@Override
public String getContentType()
{
return contentType;
}
@Override
public Subscription subscribe(Consumer consumer, boolean emitInitialContent)
{
Subscription subscription = newSubscription(consumer, emitInitialContent);
if (LOG.isDebugEnabled())
LOG.debug("Content subscription for {}: {}", subscription, consumer);
return subscription;
}
protected abstract Subscription newSubscription(Consumer consumer, boolean emitInitialContent);
/**
* <p>Partial implementation of {@code Subscription}.</p>
* <p>Implements the algorithm described in {@link Request.Content}.</p>
*/
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;
}
}
}
/**
* <p>Subclasses implement this method to produce content,
* without worrying about demand or exception handling.</p>
* <p>Typical implementation (pseudo code):</p>
* <pre>
* 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;
* }
* </pre>
*
* @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);
}
}

View File

@ -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;

View File

@ -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<Chunk> 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<Callback> 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);
}
}
}

View File

@ -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;

View File

@ -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;
/**
* <p>A {@link Request.Content} for {@link ByteBuffer}s.</p>
* <p>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);
}
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -85,7 +85,10 @@ import org.eclipse.jetty.util.Callback;
* content.offer(ByteBuffer.wrap("some content".getBytes()));
* }
* </pre>
*
* @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;

View File

@ -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)

View File

@ -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;
/**
* <p>A {@link Request.Content} for form uploads with the
* "application/x-www-form-urlencoded" content type.</p>
*/
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());
}
}
}

View File

@ -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);

View File

@ -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;
/**
* <p>A {@link Request.Content} that produces content from an {@link InputStream}.</p>
* <p>The input stream is read once and therefore fully consumed.</p>
* <p>It is possible to specify, at the constructor, a buffer size used to read
* content from the stream, by default 1024 bytes.</p>
* <p>The {@link InputStream} passed to the constructor is by default closed
* when is it fully consumed.</p>
*/
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();
}
}
}

View File

@ -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<InputStream> stream = new AtomicReference<>();
private final Queue<DeferredContentProvider.Chunk> chunks = new ArrayDeque<>();
private final Queue<Chunk> 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);
}
}
}

View File

@ -63,7 +63,10 @@ import org.slf4j.LoggerFactory;
* &lt;input type="file" name="icon" /&gt;
* &lt;/form&gt;
* </pre>
*
* @deprecated use {@link MultiPartRequestContent} instead.
*/
@Deprecated
public class MultiPartContentProvider extends AbstractTypedContentProvider implements AsyncContentProvider, Closeable
{
private static final Logger LOG = LoggerFactory.getLogger(MultiPartContentProvider.class);

View File

@ -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;
/**
* <p>A {@link Request.Content} for form uploads with the {@code "multipart/form-data"}
* content type.</p>
* <p>Example usage:</p>
* <pre>
* MultiPartRequestContent multiPart = new MultiPartRequestContent();
* multiPart.addFieldPart("field", new StringRequestContent("foo"), null);
* multiPart.addFilePart("icon", "img.png", new PathRequestContent(Paths.get("/tmp/img.png")), null);
* multiPart.close();
* ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
* .method(HttpMethod.POST)
* .content(multiPart)
* .send();
* </pre>
* <p>The above example would be the equivalent of submitting this form:</p>
* <pre>
* &lt;form method="POST" enctype="multipart/form-data" accept-charset="UTF-8"&gt;
* &lt;input type="text" name="field" value="foo" /&gt;
* &lt;input type="file" name="icon" /&gt;
* &lt;/form&gt;
* </pre>
*/
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<Part> 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));
}
/**
* <p>Adds a field part with the given {@code name} as field name, and the given
* {@code content} as part content.</p>
* <p>The {@code Content-Type} of this part will be obtained from:</p>
* <ul>
* <li>the {@code Content-Type} header in the {@code fields} parameter; otherwise</li>
* <li>the {@link Request.Content#getContentType()}</li>
* </ul>
*
* @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));
}
/**
* <p>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.</p>
* <p>The {@code Content-Type} of this part will be obtained from:</p>
* <ul>
* <li>the {@code Content-Type} header in the {@code fields} parameter; otherwise</li>
* <li>the {@link Request.Content#getContentType()}</li>
* </ul>
*
* @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
}
}

View File

@ -72,7 +72,10 @@ import org.eclipse.jetty.util.Callback;
* output.write("some content".getBytes());
* }
* </pre>
*
* @deprecated use {@link OutputStreamRequestContent} instead
*/
@Deprecated
public class OutputStreamContentProvider implements AsyncContentProvider, Callback, Closeable
{
private final DeferredContentProvider deferred = new DeferredContentProvider();

View File

@ -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;
/**
* <p>A {@link Request.Content} that provides content asynchronously through an {@link OutputStream}
* similar to {@link AsyncRequestContent}.</p>
* <p>{@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.</p>
* <p>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.</p>
* <p>Example usage:</p>
* <pre>
* 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()
* {
* &#64;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().
* </pre>
*/
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();
}
}
}

View File

@ -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.</p>
*
* @deprecated use {@link PathRequestContent} instead.
*/
@Deprecated
public class PathContentProvider extends AbstractTypedContentProvider
{
private static final Logger LOG = LoggerFactory.getLogger(PathContentProvider.class);

View File

@ -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;
/**
* <p>A {@link Request.Content} for files using JDK 7's {@code java.nio.file} APIs.</p>
* <p>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.</p>
*/
public class PathRequestContent extends AbstractRequestContent
{
private static final Logger LOG = LoggerFactory.getLogger(PathRequestContent.class);
private final Path filePath;
private final long fileSize;
private final int bufferSize;
private ByteBufferPool bufferPool;
private boolean useDirectByteBuffers = true;
public PathRequestContent(Path filePath) throws IOException
{
this(filePath, 4096);
}
public PathRequestContent(Path filePath, int bufferSize) throws IOException
{
this("application/octet-stream", filePath, bufferSize);
}
public PathRequestContent(String contentType, Path filePath) throws IOException
{
this(contentType, filePath, 4096);
}
public PathRequestContent(String contentType, Path filePath, int bufferSize) throws IOException
{
super(contentType);
if (!Files.isRegularFile(filePath))
throw new NoSuchFileException(filePath.toString());
if (!Files.isReadable(filePath))
throw new AccessDeniedException(filePath.toString());
this.filePath = filePath;
this.fileSize = Files.size(filePath);
this.bufferSize = bufferSize;
}
@Override
public long getLength()
{
return fileSize;
}
@Override
public boolean isReproducible()
{
return true;
}
public ByteBufferPool getByteBufferPool()
{
return bufferPool;
}
public void setByteBufferPool(ByteBufferPool byteBufferPool)
{
this.bufferPool = byteBufferPool;
}
public boolean isUseDirectByteBuffers()
{
return useDirectByteBuffers;
}
public void setUseDirectByteBuffers(boolean useDirectByteBuffers)
{
this.useDirectByteBuffers = useDirectByteBuffers;
}
@Override
protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent)
{
return new SubscriptionImpl(consumer, emitInitialContent);
}
private class SubscriptionImpl extends AbstractSubscription
{
private ReadableByteChannel channel;
private long readTotal;
private SubscriptionImpl(Consumer consumer, boolean emitInitialContent)
{
super(consumer, emitInitialContent);
}
@Override
protected boolean produceContent(Producer producer) throws IOException
{
ByteBuffer buffer;
boolean last;
if (channel == null)
{
channel = Files.newByteChannel(filePath, StandardOpenOption.READ);
if (LOG.isDebugEnabled())
LOG.debug("Opened file {}", filePath);
}
buffer = bufferPool == null
? BufferUtil.allocate(bufferSize, isUseDirectByteBuffers())
: bufferPool.acquire(bufferSize, isUseDirectByteBuffers());
BufferUtil.clearToFill(buffer);
int read = channel.read(buffer);
BufferUtil.flipToFlush(buffer, 0);
if (LOG.isDebugEnabled())
LOG.debug("Read {} bytes from {}", read, filePath);
if (!channel.isOpen() && read < 0)
throw new EOFException("EOF reached for " + filePath);
if (read > 0)
readTotal += read;
last = readTotal == fileSize;
if (last)
IO.close(channel);
return producer.produce(buffer, last, Callback.from(() -> release(buffer)));
}
private void release(ByteBuffer buffer)
{
if (bufferPool != null)
bufferPool.release(buffer);
}
@Override
public void fail(Throwable failure)
{
super.fail(failure);
IO.close(channel);
}
}
}

View File

@ -28,7 +28,10 @@ import org.eclipse.jetty.client.api.ContentProvider;
* <p>
* 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)

View File

@ -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;
/**
* <p>A {@link Request.Content} for strings.</p>
* <p>It is possible to specify, at the constructor, an encoding used to convert
* the string into bytes, by default UTF-8.</p>
*/
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));
}
}

View File

@ -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 ->
{

View File

@ -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();

View File

@ -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<ByteBuffer> generator;
private GeneratingContentProvider(IntFunction<ByteBuffer> generator)
private GeneratingRequestContent(IntFunction<ByteBuffer> 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<ByteBuffer> iterator()
protected Subscription newSubscription(Consumer consumer, boolean emitInitialContent)
{
return new Iterator<ByteBuffer>()
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);
}
}
}
}

View File

@ -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 ->
{

View File

@ -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);

View File

@ -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();

View File

@ -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<ByteBuffer> 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<String, Object> context) throws IOException
public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map<String, Object> 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);
}
}

View File

@ -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);

View File

@ -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

View File

@ -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()

View File

@ -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);
}

View File

@ -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

View File

@ -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<Connection>());
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()

View File

@ -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<Callback> 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;
}
}
}

View File

@ -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<Object>()
{
@Override
public Object call() throws Exception
{
provider.flush();
return null;
}
});
assertTrue(await(task, 5, TimeUnit.SECONDS));
}
@Test
public void testOfferFlushBlocksUntilSucceeded() throws Exception
{
final DeferredContentProvider provider = new DeferredContentProvider();
Iterator<ByteBuffer> iterator = provider.iterator();
provider.offer(ByteBuffer.allocate(0));
Future<?> task = executor.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
provider.flush();
return null;
}
});
// Wait until flush() blocks.
assertFalse(await(task, 1, TimeUnit.SECONDS));
// Consume the content and succeed the callback.
iterator.next();
((Callback)iterator).succeeded();
// Flush should return.
assertTrue(await(task, 5, TimeUnit.SECONDS));
}
@Test
public void testCloseFlushDoesNotBlock() throws Exception
{
final DeferredContentProvider provider = new DeferredContentProvider();
provider.close();
Future<?> task = executor.submit(new Callable<Object>()
{
@Override
public Object call() throws Exception
{
provider.flush();
return null;
}
});
// Wait until flush() blocks.
assertTrue(await(task, 5, TimeUnit.SECONDS));
}
@Test
public void testCloseNextHasNextReturnsFalse() throws Exception
{
DeferredContentProvider provider = new DeferredContentProvider();
Iterator<ByteBuffer> iterator = provider.iterator();
provider.close();
assertFalse(iterator.hasNext());
assertThrows(NoSuchElementException.class, () -> iterator.next());
assertFalse(iterator.hasNext());
}
private boolean await(Future<?> task, long time, TimeUnit unit) throws Exception
{
try
{
task.get(time, unit);
return true;
}
catch (TimeoutException x)
{
return false;
}
}
}

View File

@ -1,161 +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.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class InputStreamContentProviderTest
{
@Test
public void testHasNextFalseThenNext()
{
final AtomicBoolean closed = new AtomicBoolean();
InputStream stream = new InputStream()
{
@Override
public int read() throws IOException
{
return -1;
}
@Override
public void close() throws IOException
{
super.close();
closed.compareAndSet(false, true);
}
};
InputStreamContentProvider provider = new InputStreamContentProvider(stream);
Iterator<ByteBuffer> iterator = provider.iterator();
assertNotNull(iterator);
assertFalse(iterator.hasNext());
assertThrows(NoSuchElementException.class, () -> iterator.next());
assertFalse(iterator.hasNext());
assertTrue(closed.get());
}
@Test
public void testStreamWithContentThenNextThenNext()
{
final AtomicBoolean closed = new AtomicBoolean();
ByteArrayInputStream stream = new ByteArrayInputStream(new byte[]{1})
{
@Override
public void close() throws IOException
{
super.close();
closed.compareAndSet(false, true);
}
};
InputStreamContentProvider provider = new InputStreamContentProvider(stream);
Iterator<ByteBuffer> iterator = provider.iterator();
assertNotNull(iterator);
ByteBuffer buffer = iterator.next();
assertNotNull(buffer);
assertThrows(NoSuchElementException.class, () -> iterator.next());
assertFalse(iterator.hasNext());
assertTrue(closed.get());
}
@Test
public void testStreamWithExceptionThenNext()
{
final AtomicBoolean closed = new AtomicBoolean();
InputStream stream = new InputStream()
{
@Override
public int read() throws IOException
{
throw new IOException();
}
@Override
public void close() throws IOException
{
super.close();
closed.compareAndSet(false, true);
}
};
InputStreamContentProvider provider = new InputStreamContentProvider(stream);
Iterator<ByteBuffer> iterator = provider.iterator();
assertNotNull(iterator);
assertThrows(NoSuchElementException.class, () -> iterator.next());
assertFalse(iterator.hasNext());
assertTrue(closed.get());
}
@Test
public void testHasNextWithExceptionThenNext()
{
final AtomicBoolean closed = new AtomicBoolean();
InputStream stream = new InputStream()
{
@Override
public int read() throws IOException
{
throw new IOException();
}
@Override
public void close() throws IOException
{
super.close();
closed.compareAndSet(false, true);
}
};
InputStreamContentProvider provider = new InputStreamContentProvider(stream);
Iterator<ByteBuffer> iterator = provider.iterator();
assertNotNull(iterator);
assertTrue(iterator.hasNext());
assertThrows(NoSuchElementException.class, () -> iterator.next());
assertFalse(iterator.hasNext());
assertTrue(closed.get());
}
}

View File

@ -0,0 +1,266 @@
//
// ========================================================================
// 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.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.EmptyServerHandler;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class InputStreamContentTest
{
private Server server;
private ServerConnector connector;
private HttpClient client;
private void start(Handler handler) throws Exception
{
startServer(handler);
startClient();
}
private void startServer(Handler handler) throws Exception
{
QueuedThreadPool serverThreads = new QueuedThreadPool();
serverThreads.setName("server");
server = new Server(serverThreads);
connector = new ServerConnector(server, 1, 1);
server.addConnector(connector);
server.setHandler(handler);
server.start();
}
private void startClient() throws Exception
{
QueuedThreadPool clientThreads = new QueuedThreadPool();
clientThreads.setName("client");
client = new HttpClient();
client.setExecutor(clientThreads);
client.start();
}
@AfterEach
public void dispose() throws Exception
{
if (client != null)
client.stop();
if (server != null)
server.stop();
}
private static List<BiConsumer<Request, InputStream>> content()
{
return List.of(
(request, stream) -> request.body(new InputStreamRequestContent(stream)),
(request, stream) -> request.body(new InputStreamRequestContent(stream))
);
}
@ParameterizedTest
@MethodSource("content")
public void testInputStreamEmpty(BiConsumer<Request, InputStream> setContent) throws Exception
{
CountDownLatch serverLatch = new CountDownLatch(1);
start(new EmptyServerHandler()
{
@Override
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
serverLatch.countDown();
if (request.getInputStream().read() >= 0)
throw new IOException();
}
});
CountDownLatch closeLatch = new CountDownLatch(1);
InputStream stream = new InputStream()
{
@Override
public int read()
{
return -1;
}
@Override
public void close() throws IOException
{
super.close();
closeLatch.countDown();
}
};
Request request = client.newRequest("localhost", connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS);
setContent.accept(request, stream);
ContentResponse response = request.send();
assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
assertEquals(response.getStatus(), HttpStatus.OK_200);
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@MethodSource("content")
public void testInputStreamThrowing(BiConsumer<Request, InputStream> setContent) throws Exception
{
start(new EmptyServerHandler());
CountDownLatch closeLatch = new CountDownLatch(1);
InputStream stream = new InputStream()
{
@Override
public int read() throws IOException
{
throw new IOException();
}
@Override
public void close() throws IOException
{
super.close();
closeLatch.countDown();
}
};
Request request = client.newRequest("localhost", connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS);
setContent.accept(request, stream);
assertThrows(ExecutionException.class, request::send);
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@MethodSource("content")
public void testInputStreamThrowingAfterFirstRead(BiConsumer<Request, InputStream> setContent) throws Exception
{
byte singleByteContent = 0;
CountDownLatch serverLatch = new CountDownLatch(1);
start(new EmptyServerHandler()
{
@Override
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
assertEquals(singleByteContent, request.getInputStream().read());
serverLatch.countDown();
}
});
CountDownLatch closeLatch = new CountDownLatch(1);
InputStream stream = new InputStream()
{
private int reads;
@Override
public int read() throws IOException
{
if (++reads == 1)
return singleByteContent;
throw new IOException();
}
@Override
public void close() throws IOException
{
super.close();
closeLatch.countDown();
}
};
Request request = client.newRequest("localhost", connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS);
setContent.accept(request, stream);
assertThrows(ExecutionException.class, request::send);
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@MethodSource("content")
public void testInputStreamWithSmallContent(BiConsumer<Request, InputStream> setContent) throws Exception
{
testInputStreamWithContent(setContent, new byte[1024]);
}
@ParameterizedTest
@MethodSource("content")
public void testInputStreamWithLargeContent(BiConsumer<Request, InputStream> setContent) throws Exception
{
testInputStreamWithContent(setContent, new byte[64 * 1024 * 1024]);
}
private void testInputStreamWithContent(BiConsumer<Request, InputStream> setContent, byte[] content) throws Exception
{
CountDownLatch serverLatch = new CountDownLatch(1);
start(new EmptyServerHandler()
{
@Override
protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
serverLatch.countDown();
IO.copy(request.getInputStream(), IO.getNullStream());
}
});
CountDownLatch closeLatch = new CountDownLatch(1);
ByteArrayInputStream stream = new ByteArrayInputStream(content)
{
@Override
public void close() throws IOException
{
super.close();
closeLatch.countDown();
}
};
Request request = client.newRequest("localhost", connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS);
setContent.accept(request, stream);
ContentResponse response = request.send();
assertTrue(serverLatch.await(5, TimeUnit.SECONDS));
assertEquals(response.getStatus(), HttpStatus.OK_200);
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -20,7 +20,6 @@ package org.eclipse.jetty.client.util;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
@ -32,12 +31,10 @@ import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@ -63,7 +60,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
// @checkstyle-disable-check : AvoidEscapedUnicodeCharactersCheck
public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
public class MultiPartContentTest extends AbstractHttpClientServerTest
{
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
@ -79,12 +76,12 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
MultiPartRequestContent multiPart = new MultiPartRequestContent();
multiPart.close();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.method(HttpMethod.POST)
.content(multiPart)
.body(multiPart)
.send();
assertEquals(200, response.getStatus());
@ -109,13 +106,13 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
multiPart.addFieldPart(name, new StringContentProvider(value), null);
MultiPartRequestContent multiPart = new MultiPartRequestContent();
multiPart.addFieldPart(name, new StringRequestContent(value), null);
multiPart.close();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.method(HttpMethod.POST)
.content(multiPart)
.body(multiPart)
.send();
assertEquals(200, response.getStatus());
@ -123,7 +120,7 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void testFieldWithOverridenContentType(Scenario scenario) throws Exception
public void testFieldWithOverriddenContentType(Scenario scenario) throws Exception
{
String name = "field";
String value = "\u00e8";
@ -146,16 +143,16 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
MultiPartRequestContent multiPart = new MultiPartRequestContent();
HttpFields fields = new HttpFields();
fields.put(HttpHeader.CONTENT_TYPE, "text/plain;charset=" + encoding.name());
BytesContentProvider content = new BytesContentProvider(value.getBytes(encoding));
BytesRequestContent content = new BytesRequestContent(value.getBytes(encoding));
multiPart.addFieldPart(name, content, fields);
multiPart.close();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.method(HttpMethod.POST)
.content(multiPart)
.body(multiPart)
.send();
assertEquals(200, response.getStatus());
@ -181,15 +178,15 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
DeferredContentProvider content = new DeferredContentProvider();
MultiPartRequestContent multiPart = new MultiPartRequestContent();
AsyncRequestContent content = new AsyncRequestContent("text/plain");
multiPart.addFieldPart(name, content, null);
multiPart.close();
CountDownLatch responseLatch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.method(HttpMethod.POST)
.content(multiPart)
.body(multiPart)
.send(result ->
{
assertTrue(result.isSucceeded(), supply(result.getFailure()));
@ -233,8 +230,8 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
});
CountDownLatch closeLatch = new CountDownLatch(1);
MultiPartContentProvider multiPart = new MultiPartContentProvider();
InputStreamContentProvider content = new InputStreamContentProvider(new ByteArrayInputStream(data)
MultiPartRequestContent multiPart = new MultiPartRequestContent();
InputStreamRequestContent content = new InputStreamRequestContent(new ByteArrayInputStream(data)
{
@Override
public void close() throws IOException
@ -250,7 +247,7 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.method(HttpMethod.POST)
.content(multiPart)
.body(multiPart)
.send();
assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
@ -289,8 +286,8 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
PathContentProvider content = new PathContentProvider(contentType, tmpPath);
MultiPartRequestContent multiPart = new MultiPartRequestContent();
PathRequestContent content = new PathRequestContent(contentType, tmpPath);
content.setByteBufferPool(client.getByteBufferPool());
content.setUseDirectByteBuffers(client.isUseOutputDirectByteBuffers());
multiPart.addFilePart(name, tmpPath.getFileName().toString(), content, null);
@ -298,7 +295,7 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.method(HttpMethod.POST)
.content(multiPart)
.body(multiPart)
.send();
assertEquals(200, response.getStatus());
@ -356,16 +353,16 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
MultiPartRequestContent multiPart = new MultiPartRequestContent();
HttpFields fields = new HttpFields();
fields.put(headerName, headerValue);
multiPart.addFieldPart(field, new StringContentProvider(value, encoding), fields);
multiPart.addFilePart(fileField, tmpPath.getFileName().toString(), new PathContentProvider(tmpPath), null);
multiPart.addFieldPart(field, new StringRequestContent(value, encoding), fields);
multiPart.addFilePart(fileField, tmpPath.getFileName().toString(), new PathRequestContent(tmpPath), null);
multiPart.close();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.method(HttpMethod.POST)
.content(multiPart)
.body(multiPart)
.send();
assertEquals(200, response.getStatus());
@ -406,16 +403,18 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
}
});
MultiPartContentProvider multiPart = new MultiPartContentProvider();
DeferredContentProvider fieldContent = new DeferredContentProvider();
MultiPartRequestContent multiPart = new MultiPartRequestContent();
AsyncRequestContent fieldContent = new AsyncRequestContent();
multiPart.addFieldPart("field", fieldContent, null);
DeferredContentProvider fileContent = new DeferredContentProvider();
AsyncRequestContent fileContent = new AsyncRequestContent();
multiPart.addFilePart("file", "fileName", fileContent, null);
multiPart.close();
CountDownLatch responseLatch = new CountDownLatch(1);
client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.method(HttpMethod.POST)
.content(multiPart)
.body(multiPart)
.send(result ->
{
assertTrue(result.isSucceeded(), supply(result.getFailure()));
@ -435,51 +434,9 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
fieldContent.offer(encoding.encode(value));
fieldContent.close();
multiPart.close();
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@ArgumentsSource(ScenarioProvider.class)
public void testEachPartIsClosed(Scenario scenario) throws Exception
{
String name1 = "field1";
String value1 = "value1";
String name2 = "field2";
String value2 = "value2";
start(scenario, new AbstractMultiPartHandler()
{
@Override
protected void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
Collection<Part> parts = request.getParts();
assertEquals(2, parts.size());
Iterator<Part> iterator = parts.iterator();
Part part1 = iterator.next();
assertEquals(name1, part1.getName());
assertEquals(value1, IO.toString(part1.getInputStream()));
Part part2 = iterator.next();
assertEquals(name2, part2.getName());
assertEquals(value2, IO.toString(part2.getInputStream()));
}
});
AtomicInteger closeCount = new AtomicInteger();
MultiPartContentProvider multiPart = new MultiPartContentProvider();
multiPart.addFieldPart(name1, new CloseableStringContentProvider(value1, closeCount::incrementAndGet), null);
multiPart.addFieldPart(name2, new CloseableStringContentProvider(value2, closeCount::incrementAndGet), null);
multiPart.close();
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.method(HttpMethod.POST)
.content(multiPart)
.send();
assertEquals(200, response.getStatus());
assertEquals(2, closeCount.get());
}
private abstract static class AbstractMultiPartHandler extends AbstractHandler
{
@Override
@ -493,49 +450,4 @@ public class MultiPartContentProviderTest extends AbstractHttpClientServerTest
protected abstract void handle(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
}
private static class CloseableStringContentProvider extends StringContentProvider
{
private final Runnable closeFn;
private CloseableStringContentProvider(String content, Runnable closeFn)
{
super(content);
this.closeFn = closeFn;
}
@Override
public Iterator<ByteBuffer> iterator()
{
return new CloseableIterator<>(super.iterator());
}
private class CloseableIterator<T> implements Iterator<T>, Closeable
{
private final Iterator<T> iterator;
public CloseableIterator(Iterator<T> iterator)
{
this.iterator = iterator;
}
@Override
public boolean hasNext()
{
return iterator.hasNext();
}
@Override
public T next()
{
return iterator.next();
}
@Override
public void close()
{
closeFn.run();
}
}
}
}

View File

@ -0,0 +1,339 @@
//
// ========================================================================
// 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.ByteArrayInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Fields;
import org.eclipse.jetty.util.IO;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
public class RequestContentBehaviorTest
{
private static Path emptyFile;
private static Path smallFile;
@BeforeAll
public static void prepare() throws IOException
{
Path testPath = MavenTestingUtils.getTargetTestingPath();
Files.createDirectories(testPath);
emptyFile = testPath.resolve("empty.txt");
Files.write(emptyFile, new byte[0]);
smallFile = testPath.resolve("small.txt");
byte[] bytes = new byte[64];
Arrays.fill(bytes, (byte)'#');
Files.write(smallFile, bytes);
}
@AfterAll
public static void dispose() throws IOException
{
if (smallFile != null)
Files.delete(smallFile);
if (emptyFile != null)
Files.delete(emptyFile);
}
public static List<Request.Content> emptyContents() throws IOException
{
return List.of(
new AsyncRequestContent()
{
{
close();
}
},
new ByteBufferRequestContent(),
new BytesRequestContent(),
new FormRequestContent(new Fields()),
new InputStreamRequestContent(IO.getClosedStream()),
new MultiPartRequestContent()
{
{
close();
}
},
new PathRequestContent(emptyFile),
new StringRequestContent("")
);
}
@ParameterizedTest
@MethodSource("emptyContents")
public void testEmptyContentEmitInitialFirstDemand(Request.Content content) throws Exception
{
CountDownLatch latch = new CountDownLatch(1);
Request.Content.Subscription subscription = content.subscribe((buffer, last, callback) ->
{
if (last)
latch.countDown();
}, true);
subscription.demand();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@MethodSource("emptyContents")
public void testEmptyContentDontEmitInitialFirstDemand(Request.Content content) throws Exception
{
AtomicBoolean initial = new AtomicBoolean(true);
AtomicReference<CountDownLatch> latch = new AtomicReference<>(new CountDownLatch(1));
Request.Content.Subscription subscription = content.subscribe((buffer, last, callback) ->
{
if (initial.get())
{
if (!last)
latch.get().countDown();
}
else
{
if (last)
latch.get().countDown();
}
}, false);
// Initial demand should have last=false.
subscription.demand();
assertTrue(latch.get().await(5, TimeUnit.SECONDS));
// More demand should have last=true.
initial.set(false);
latch.set(new CountDownLatch(1));
subscription.demand();
assertTrue(latch.get().await(5, TimeUnit.SECONDS));
}
public static List<Request.Content> smallContents() throws IOException
{
return List.of(
new AsyncRequestContent(ByteBuffer.allocate(64))
{
{
close();
}
},
new ByteBufferRequestContent(ByteBuffer.allocate(64)),
new BytesRequestContent(new byte[64]),
new FormRequestContent(new Fields()
{
{
add("foo", "bar");
}
}),
new InputStreamRequestContent(new ByteArrayInputStream(new byte[64])),
new MultiPartRequestContent()
{
{
addFieldPart("field", new StringRequestContent("*".repeat(64)), null);
close();
}
},
new PathRequestContent(smallFile),
new StringRequestContent("x".repeat(64))
);
}
@ParameterizedTest
@MethodSource("smallContents")
public void testSmallContentEmitInitialFirstDemand(Request.Content content) throws Exception
{
AtomicBoolean initial = new AtomicBoolean(true);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Request.Content.Subscription> subscriptionRef = new AtomicReference<>();
Request.Content.Subscription subscription = content.subscribe((buffer, last, callback) ->
{
if (initial.getAndSet(false))
assertTrue(buffer.hasRemaining());
if (last)
latch.countDown();
else
subscriptionRef.get().demand();
}, true);
subscriptionRef.set(subscription);
// Initial demand.
subscription.demand();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@MethodSource("smallContents")
public void testSmallContentDontEmitInitialFirstDemand(Request.Content content) throws Exception
{
AtomicBoolean initial = new AtomicBoolean(true);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Request.Content.Subscription> subscriptionRef = new AtomicReference<>();
Request.Content.Subscription subscription = content.subscribe((buffer, last, callback) ->
{
if (initial.getAndSet(false))
{
assertFalse(buffer.hasRemaining());
assertFalse(last);
}
if (last)
latch.countDown();
else
subscriptionRef.get().demand();
}, false);
subscriptionRef.set(subscription);
// Initial demand.
subscription.demand();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@MethodSource("smallContents")
public void testSmallContentFailedAfterFirstDemand(Request.Content content)
{
Throwable testFailure = new Throwable("test_failure");
AtomicInteger notified = new AtomicInteger();
AtomicReference<Throwable> failureRef = new AtomicReference<>();
Request.Content.Subscription subscription = content.subscribe(new Request.Content.Consumer()
{
@Override
public void onContent(ByteBuffer buffer, boolean last, Callback callback)
{
notified.getAndIncrement();
}
@Override
public void onFailure(Throwable error)
{
testFailure.addSuppressed(new Throwable("suppressed"));
failureRef.compareAndSet(null, error);
}
}, false);
// Initial demand.
subscription.demand();
assertEquals(1, notified.get());
subscription.fail(testFailure);
subscription.demand();
assertEquals(1, notified.get());
Throwable failure = failureRef.get();
assertNotNull(failure);
assertSame(testFailure, failure);
assertEquals(1, failure.getSuppressed().length);
}
@ParameterizedTest
@MethodSource("smallContents")
public void testDemandAfterLastContentFails(Request.Content content) throws Exception
{
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Request.Content.Subscription> subscriptionRef = new AtomicReference<>();
AtomicReference<Throwable> failureRef = new AtomicReference<>();
Request.Content.Subscription subscription = content.subscribe(new Request.Content.Consumer()
{
@Override
public void onContent(ByteBuffer buffer, boolean last, Callback callback)
{
if (last)
latch.countDown();
else
subscriptionRef.get().demand();
}
@Override
public void onFailure(Throwable error)
{
error.addSuppressed(new Throwable("suppressed"));
failureRef.compareAndSet(null, error);
}
}, false);
subscriptionRef.set(subscription);
// Initial demand.
subscription.demand();
assertTrue(latch.await(5, TimeUnit.SECONDS));
// Demand more, should fail.
subscription.demand();
Throwable failure = failureRef.get();
assertNotNull(failure);
assertEquals(1, failure.getSuppressed().length);
}
@ParameterizedTest
@MethodSource("smallContents")
public void testReproducibleContentCanHaveMultipleSubscriptions(Request.Content content) throws Exception
{
assumeTrue(content.isReproducible());
CountDownLatch latch1 = new CountDownLatch(1);
Request.Content.Subscription subscription1 = content.subscribe((buffer, last, callback) ->
{
if (last)
latch1.countDown();
}, true);
CountDownLatch latch2 = new CountDownLatch(1);
Request.Content.Subscription subscription2 = content.subscribe((buffer, last, callback) ->
{
if (last)
latch2.countDown();
}, true);
// Initial demand.
subscription1.demand();
assertTrue(latch1.await(5, TimeUnit.SECONDS));
// Initial demand.
subscription2.demand();
assertTrue(latch2.await(5, TimeUnit.SECONDS));
}
}

View File

@ -291,7 +291,7 @@ public class SPNEGOAuthenticationTest extends AbstractHttpClientServerTest
requests.set(0);
ByteArrayInputStream input = new ByteArrayInputStream("hello_world".getBytes(StandardCharsets.UTF_8));
request = client.newRequest(uri).method("POST").path("/secure").content(new InputStreamContentProvider(input));
request = client.newRequest(uri).method("POST").path("/secure").body(new InputStreamRequestContent(input));
response = request.timeout(15, TimeUnit.SECONDS).send();
assertEquals(200, response.getStatus());
// Authentication expired, but POSTs are allowed.

View File

@ -108,7 +108,7 @@ public class TypedContentProviderTest extends AbstractHttpClientServerTest
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.method(HttpMethod.POST)
.content(new FormContentProvider(fields))
.body(new FormRequestContent(fields))
.header(HttpHeader.CONTENT_TYPE, contentType)
.send();
@ -135,7 +135,7 @@ public class TypedContentProviderTest extends AbstractHttpClientServerTest
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(scenario.getScheme())
.content(new StringContentProvider(null, content, StandardCharsets.UTF_8))
.body(new StringRequestContent(null, content, StandardCharsets.UTF_8))
.send();
assertEquals(200, response.getStatus());

View File

@ -188,7 +188,7 @@ Have a look at the link:{JDURL}/org/eclipse/jetty/client/api/Request.Listener.ht
Jetty's HTTP client provides a number of utility classes off the shelf to handle request content.
You can provide request content as `String`, `byte[]`, `ByteBuffer`, `java.nio.file.Path`, `InputStream`, and provide your own implementation of `org.eclipse.jetty.client.api.ContentProvider`.
You can provide request content as `String`, `byte[]`, `ByteBuffer`, `java.nio.file.Path`, `InputStream`, and provide your own implementation of `org.eclipse.jetty.client.api.Request.Content`.
Heres an example that provides the request content using `java.nio.file.Paths`:
[source, java, subs="{sub-order}"]
@ -199,47 +199,47 @@ ContentResponse response = httpClient.newRequest("http://domain.com/upload")
.send();
----
This is equivalent to using the `PathContentProvider` utility class:
This is equivalent to using the `PathRequestContent` utility class:
[source, java, subs="{sub-order}"]
----
ContentResponse response = httpClient.newRequest("http://domain.com/upload")
.method(HttpMethod.POST)
.content(new PathContentProvider(Paths.get("file_to_upload.txt")), "text/plain")
.body(new PathRequestContent("text/plain", Paths.get("file_to_upload.txt")))
.send();
----
Alternatively, you can use `FileInputStream` via the `InputStreamContentProvider` utility class:
Alternatively, you can use `FileInputStream` via the `InputStreamRequestContent` utility class:
[source, java, subs="{sub-order}"]
----
ContentResponse response = httpClient.newRequest("http://domain.com/upload")
.method(HttpMethod.POST)
.content(new InputStreamContentProvider(new FileInputStream("file_to_upload.txt")), "text/plain")
.body(new InputStreamRequestContent("text/plain", new FileInputStream("file_to_upload.txt")))
.send();
----
Since `InputStream` is blocking, then also the send of the request will block if the input stream blocks, even in case of usage of the asynchronous `HttpClient` APIs.
If you have already read the content in memory, you can pass it as a `byte[]` using the `BytesContentProvider` utility class:
If you have already read the content in memory, you can pass it as a `byte[]` using the `BytesRequestContent` utility class:
[source, java, subs="{sub-order}"]
----
byte[] bytes = ...;
ContentResponse response = httpClient.newRequest("http://domain.com/upload")
.method(HttpMethod.POST)
.content(new BytesContentProvider(bytes), "text/plain")
.body(new BytesRequestContent("text/plain", bytes))
.send();
----
If the request content is not immediately available, but your application will be notified of the content to send, you can use `DeferredContentProvider` in this way:
If the request content is not immediately available, but your application will be notified of the content to send, you can use `AsyncRequestContent` in this way:
[source, java, subs="{sub-order}"]
----
DeferredContentProvider content = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
httpClient.newRequest("http://domain.com/upload")
.method(HttpMethod.POST)
.content(content)
.body(content)
.send(new Response.CompleteListener()
{
@Override
@ -249,17 +249,17 @@ httpClient.newRequest("http://domain.com/upload")
}
});
// Content not available yet here
// Content not available yet here.
...
// An event happens, now content is available
// An event happens, now content is available.
byte[] bytes = ...;
content.offer(ByteBuffer.wrap(bytes));
...
// All content has arrived
// All content has arrived.
content.close();
----
@ -267,19 +267,19 @@ While the request content is awaited and consequently uploaded by the client app
In this case, `Response.Listener` callbacks will be invoked before the request is fully sent.
This allows fine-grained control of the request/response conversation: for example the server may reject contents that are too big, send a response to the client, which in turn may stop the content upload.
Another way to provide request content is by using an `OutputStreamContentProvider`,
which allows applications to write request content when it is available to the `OutputStream` provided by `OutputStreamContentProvider`:
Another way to provide request content is by using an `OutputStreamRequestContent`,
which allows applications to write request content when it is available to the `OutputStream` provided by `OutputStreamRequestContent`:
[source, java, subs="{sub-order}"]
----
OutputStreamContentProvider content = new OutputStreamContentProvider();
OutputStreamRequestContent content = new OutputStreamRequestContent();
// Use try-with-resources to close the OutputStream when all content is written
// Use try-with-resources to close the OutputStream when all content is written.
try (OutputStream output = content.getOutputStream())
{
client.newRequest("localhost", 8080)
.method(HttpMethod.POST)
.content(content)
.body(content)
.send(new Response.CompleteListener()
{
@Override
@ -291,10 +291,11 @@ try (OutputStream output = content.getOutputStream())
...
// Write content
writeContent(output);
// Write content.
byte[] bytes = ...;
output.write(bytes);
}
// End of try-with-resource, output.close() called automatically to signal end of content
// End of try-with-resource, output.close() called automatically to signal end of content.
----
[[http-client-response-content]]

View File

@ -19,11 +19,11 @@
package org.eclipse.jetty.fcgi.client.http;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Locale;
import org.eclipse.jetty.client.HttpChannel;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpContent;
import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpSender;
import org.eclipse.jetty.client.api.Request;
@ -33,7 +33,6 @@ import org.eclipse.jetty.fcgi.generator.Generator;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.StringUtil;
@ -56,7 +55,7 @@ public class HttpSenderOverFCGI extends HttpSender
}
@Override
protected void sendHeaders(HttpExchange exchange, HttpContent content, Callback callback)
protected void sendHeaders(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback)
{
Request request = exchange.getRequest();
// Copy the request headers to be able to convert them properly
@ -102,32 +101,31 @@ public class HttpSenderOverFCGI extends HttpSender
transport.customize(request, fcgiHeaders);
int id = getHttpChannel().getRequest();
boolean hasContent = content.hasContent();
Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders,
hasContent ? callback : Callback.NOOP);
if (hasContent)
if (contentBuffer.hasRemaining() || lastContent)
{
getHttpChannel().flush(headersResult);
Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders, Callback.NOOP);
Generator.Result contentResult = generator.generateRequestContent(id, contentBuffer, lastContent, callback);
getHttpChannel().flush(headersResult, contentResult);
}
else
{
Generator.Result noContentResult = generator.generateRequestContent(id, BufferUtil.EMPTY_BUFFER, true, callback);
getHttpChannel().flush(headersResult, noContentResult);
Generator.Result headersResult = generator.generateRequestHeaders(id, fcgiHeaders, callback);
getHttpChannel().flush(headersResult);
}
}
@Override
protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
protected void sendContent(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback)
{
if (content.isConsumed())
if (contentBuffer.hasRemaining() || lastContent)
{
callback.succeeded();
int request = getHttpChannel().getRequest();
Generator.Result result = generator.generateRequestContent(request, contentBuffer, lastContent, callback);
getHttpChannel().flush(result);
}
else
{
int request = getHttpChannel().getRequest();
Generator.Result result = generator.generateRequestContent(request, content.getByteBuffer(), content.isLast(), callback);
getHttpChannel().flush(result);
callback.succeeded();
}
}
}

View File

@ -23,6 +23,7 @@ import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
@ -40,8 +41,8 @@ import javax.servlet.http.HttpServletResponse;
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.util.BytesContentProvider;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.client.util.FutureResponseListener;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.io.MappedByteBufferPool;
@ -148,22 +149,22 @@ public class HttpClientTest extends AbstractHttpClientServerTest
response.setCharacterEncoding("UTF-8");
ServletOutputStream output = response.getOutputStream();
String paramValue1 = request.getParameter(paramName1);
output.write(paramValue1.getBytes("UTF-8"));
output.write(paramValue1.getBytes(StandardCharsets.UTF_8));
String paramValue2 = request.getParameter(paramName2);
assertEquals("", paramValue2);
output.write("empty".getBytes("UTF-8"));
output.write("empty".getBytes(StandardCharsets.UTF_8));
baseRequest.setHandled(true);
}
});
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(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
assertNotNull(response);
assertEquals(200, response.getStatus());
String content = new String(response.getContent(), "UTF-8");
String content = new String(response.getContent(), StandardCharsets.UTF_8);
assertEquals(value1 + "empty", content);
}
@ -182,10 +183,10 @@ public class HttpClientTest extends AbstractHttpClientServerTest
String[] paramValues1 = request.getParameterValues(paramName1);
for (String paramValue : paramValues1)
{
output.write(paramValue.getBytes("UTF-8"));
output.write(paramValue.getBytes(StandardCharsets.UTF_8));
}
String paramValue2 = request.getParameter(paramName2);
output.write(paramValue2.getBytes("UTF-8"));
output.write(paramValue2.getBytes(StandardCharsets.UTF_8));
baseRequest.setHandled(true);
}
});
@ -193,15 +194,15 @@ 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(scheme + "://localhost:" + connector.getLocalPort() + "/?" + query);
assertNotNull(response);
assertEquals(200, response.getStatus());
String content = new String(response.getContent(), "UTF-8");
String content = new String(response.getContent(), StandardCharsets.UTF_8);
assertEquals(value11 + value12 + value2, content);
}
@ -233,7 +234,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
assertNotNull(response);
assertEquals(200, response.getStatus());
assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8));
}
@Test
@ -258,14 +259,14 @@ public class HttpClientTest extends AbstractHttpClientServerTest
});
String uri = scheme + "://localhost:" + connector.getLocalPort() +
"/?" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8");
"/?" + paramName + "=" + URLEncoder.encode(paramValue, StandardCharsets.UTF_8);
ContentResponse response = client.POST(uri)
.timeout(5, TimeUnit.SECONDS)
.send();
assertNotNull(response);
assertEquals(200, response.getStatus());
assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8));
}
@Test
@ -297,7 +298,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
assertNotNull(response);
assertEquals(200, response.getStatus());
assertEquals(paramValue, new String(response.getContent(), "UTF-8"));
assertEquals(paramValue, new String(response.getContent(), StandardCharsets.UTF_8));
}
@Test
@ -326,7 +327,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest
{
ContentResponse response = client.POST(scheme + "://localhost:" + connector.getLocalPort() + "/?b=1")
.param(paramName, paramValue)
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -350,7 +351,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();
@ -372,7 +373,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[]{
.body(new BytesRequestContent(new byte[]{0}, new byte[]{1}, new byte[]{
2
}, new byte[]{3}, new byte[]{4}))
.timeout(5, TimeUnit.SECONDS)
@ -636,10 +637,10 @@ 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(scheme)
.content(content);
.body(content);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);
// Wait some time to simulate a slow request.

View File

@ -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<HttpFields> trailerSupplier = exchange.getRequest().getTrailers();
sendContent(stream, contentBuffer, lastContent, trailerSupplier, callback);
}
private void sendContent(Stream stream, ByteBuffer buffer, boolean lastContent, Supplier<HttpFields> 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<HttpFields> 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<HttpFields> 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);

View File

@ -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();

View File

@ -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();

View File

@ -25,8 +25,8 @@ import javax.inject.Inject;
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.MultiPartContentProvider;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.client.util.MultiPartRequestContent;
import org.eclipse.jetty.client.util.StringRequestContent;
import org.eclipse.jetty.http.HttpStatus;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -47,7 +47,6 @@ import static org.ops4j.pax.exam.CoreOptions.systemProperty;
* top of this.
*/
@RunWith(PaxExam.class)
public class TestJettyOSGiBootWithAnnotations
{
private static final String LOG_LEVEL = "WARN";
@ -133,10 +132,11 @@ public class TestJettyOSGiBootWithAnnotations
assertEquals("Response status code", HttpStatus.OK_200, response.getStatus());
content = response.getContentAsString();
TestOSGiUtil.assertContains("Response contents", content, "<h1>FRAGMENT</h1>");
MultiPartContentProvider multiPart = new MultiPartContentProvider();
multiPart.addFieldPart("field", new StringContentProvider("foo"), null);
MultiPartRequestContent multiPart = new MultiPartRequestContent();
multiPart.addFieldPart("field", new StringRequestContent("foo"), null);
multiPart.close();
response = client.newRequest("http://127.0.0.1:" + port + "/multi").method("POST")
.content(multiPart).send();
.body(multiPart).send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}
finally

View File

@ -26,6 +26,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.TimeUnit;
import java.util.zip.GZIPOutputStream;
@ -45,7 +46,7 @@ import org.eclipse.jetty.client.HttpClient;
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.DeferredContentProvider;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.ByteBufferPool;
@ -110,18 +111,20 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
// to allow optimization of the Content-Length header.
if (hasContent(clientRequest))
{
DeferredContentProvider provider = newProxyContentProvider(clientRequest, proxyResponse, proxyRequest);
proxyRequest.content(provider);
AsyncRequestContent content = newProxyRequestContent(clientRequest, proxyResponse, proxyRequest);
proxyRequest.body(content);
if (expects100Continue(clientRequest))
{
// Must delay the call to request.getInputStream()
// that sends the 100 Continue to the client.
proxyRequest.attribute(CLIENT_REQUEST_ATTRIBUTE, clientRequest);
proxyRequest.attribute(CONTINUE_ACTION_ATTRIBUTE, (Runnable)() ->
{
try
{
ServletInputStream input = clientRequest.getInputStream();
input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, provider));
input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, content));
}
catch (Throwable failure)
{
@ -133,7 +136,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
else
{
ServletInputStream input = clientRequest.getInputStream();
input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, provider));
input.setReadListener(newProxyReadListener(clientRequest, proxyResponse, proxyRequest, content));
}
}
else
@ -142,14 +145,14 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
}
}
protected DeferredContentProvider newProxyContentProvider(final HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest) throws IOException
protected AsyncRequestContent newProxyRequestContent(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest)
{
return new ProxyDeferredContentProvider(clientRequest);
return new ProxyAsyncRequestContent(clientRequest);
}
protected ReadListener newProxyReadListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, DeferredContentProvider provider)
protected ReadListener newProxyReadListener(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, AsyncRequestContent content)
{
return new ProxyReader(clientRequest, proxyResponse, proxyRequest, provider);
return new ProxyReader(clientRequest, proxyResponse, proxyRequest, content);
}
protected ProxyWriter newProxyWriteListener(HttpServletRequest clientRequest, Response proxyResponse)
@ -262,17 +265,17 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
private final HttpServletRequest clientRequest;
private final HttpServletResponse proxyResponse;
private final Request proxyRequest;
private final DeferredContentProvider provider;
private final AsyncRequestContent content;
private final int contentLength;
private final boolean expects100Continue;
private int length;
protected ProxyReader(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, DeferredContentProvider provider)
protected ProxyReader(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Request proxyRequest, AsyncRequestContent content)
{
this.clientRequest = clientRequest;
this.proxyResponse = proxyResponse;
this.proxyRequest = proxyRequest;
this.provider = provider;
this.content = content;
this.contentLength = clientRequest.getContentLength();
this.expects100Continue = expects100Continue(clientRequest);
}
@ -286,7 +289,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
@Override
public void onAllDataRead() throws IOException
{
if (!provider.isClosed())
if (!content.isClosed())
{
process(BufferUtil.EMPTY_BUFFER, new Callback()
{
@ -376,13 +379,13 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
for (ByteBuffer buffer : buffers)
{
newContentBytes += buffer.remaining();
provider.offer(buffer, counter);
this.content.offer(buffer, counter);
}
buffers.clear();
}
if (finished)
provider.close();
this.content.close();
if (_log.isDebugEnabled())
_log.debug("{} upstream content transformation {} -> {} bytes", getRequestId(clientRequest), contentBytes, newContentBytes);
@ -594,10 +597,10 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
protected class ProxyWriter implements WriteListener
{
private final Queue<DeferredContentProvider.Chunk> chunks = new ArrayDeque<>();
private final Queue<Chunk> chunks = new ArrayDeque<>();
private final HttpServletRequest clientRequest;
private final Response serverResponse;
private DeferredContentProvider.Chunk chunk;
private Chunk chunk;
private boolean writePending;
protected ProxyWriter(HttpServletRequest clientRequest, Response serverResponse)
@ -610,7 +613,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
{
if (_log.isDebugEnabled())
_log.debug("{} proxying content to downstream: {} bytes {}", getRequestId(clientRequest), content.remaining(), callback);
return chunks.offer(new DeferredContentProvider.Chunk(content, callback));
return chunks.offer(new Chunk(content, callback));
}
@Override
@ -629,7 +632,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
}
int length = 0;
DeferredContentProvider.Chunk chunk = null;
Chunk chunk = null;
while (output.isReady())
{
if (chunk != null)
@ -673,7 +676,7 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
@Override
public void onError(Throwable failure)
{
DeferredContentProvider.Chunk chunk = this.chunk;
Chunk chunk = this.chunk;
if (chunk != null)
chunk.callback.failed(failure);
else
@ -840,11 +843,11 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
}
}
private class ProxyDeferredContentProvider extends DeferredContentProvider
private class ProxyAsyncRequestContent extends AsyncRequestContent
{
private final HttpServletRequest clientRequest;
public ProxyDeferredContentProvider(HttpServletRequest clientRequest)
private ProxyAsyncRequestContent(HttpServletRequest clientRequest)
{
this.clientRequest = clientRequest;
}
@ -857,4 +860,16 @@ public class AsyncMiddleManServlet extends AbstractProxyServlet
return super.offer(buffer, callback);
}
}
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);
}
}
}

View File

@ -30,10 +30,9 @@ import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.DeferredContentProvider;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
@ -50,17 +49,16 @@ public class AsyncProxyServlet extends ProxyServlet
private static final String WRITE_LISTENER_ATTRIBUTE = AsyncProxyServlet.class.getName() + ".writeListener";
@Override
protected ContentProvider proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException
protected Request.Content proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException
{
ServletInputStream input = request.getInputStream();
DeferredContentProvider provider = new DeferredContentProvider();
input.setReadListener(newReadListener(request, response, proxyRequest, provider));
return provider;
AsyncRequestContent content = new AsyncRequestContent();
request.getInputStream().setReadListener(newReadListener(request, response, proxyRequest, content));
return content;
}
protected ReadListener newReadListener(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, DeferredContentProvider provider)
protected ReadListener newReadListener(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, AsyncRequestContent content)
{
return new StreamReader(request, response, proxyRequest, provider);
return new StreamReader(request, response, proxyRequest, content);
}
@Override
@ -131,28 +129,28 @@ public class AsyncProxyServlet extends ProxyServlet
private final HttpServletRequest request;
private final HttpServletResponse response;
private final Request proxyRequest;
private final DeferredContentProvider provider;
private final AsyncRequestContent content;
protected StreamReader(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, DeferredContentProvider provider)
protected StreamReader(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, AsyncRequestContent content)
{
this.request = request;
this.response = response;
this.proxyRequest = proxyRequest;
this.provider = provider;
this.content = content;
}
@Override
public void onDataAvailable() throws IOException
public void onDataAvailable()
{
iterate();
}
@Override
public void onAllDataRead() throws IOException
public void onAllDataRead()
{
if (_log.isDebugEnabled())
_log.debug("{} proxying content to upstream completed", getRequestId(request));
provider.close();
content.close();
}
@Override
@ -176,7 +174,7 @@ public class AsyncProxyServlet extends ProxyServlet
{
if (_log.isDebugEnabled())
_log.debug("{} proxying content to upstream: {} bytes", requestId, read);
onRequestContent(request, proxyRequest, provider, buffer, 0, read, this);
onRequestContent(request, proxyRequest, content, buffer, 0, read, this);
return Action.SCHEDULED;
}
else if (read < 0)
@ -192,9 +190,9 @@ public class AsyncProxyServlet extends ProxyServlet
return Action.IDLE;
}
protected void onRequestContent(HttpServletRequest request, Request proxyRequest, DeferredContentProvider provider, byte[] buffer, int offset, int length, Callback callback)
protected void onRequestContent(HttpServletRequest request, Request proxyRequest, AsyncRequestContent content, byte[] buffer, int offset, int length, Callback callback)
{
provider.offer(ByteBuffer.wrap(buffer, offset, length), callback);
content.offer(ByteBuffer.wrap(buffer, offset, length), callback);
}
@Override

View File

@ -18,11 +18,9 @@
package org.eclipse.jetty.proxy;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Iterator;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import javax.servlet.AsyncContext;
@ -31,16 +29,13 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.AsyncContentProvider;
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.api.Result;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.InputStreamContentProvider;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.InputStreamRequestContent;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback;
/**
* <p>Servlet 3.0 asynchronous proxy servlet.</p>
@ -93,15 +88,17 @@ public class ProxyServlet extends AbstractProxyServlet
{
if (expects100Continue(request))
{
DeferredContentProvider deferred = new DeferredContentProvider();
proxyRequest.content(deferred);
// Must delay the call to request.getInputStream()
// that sends the 100 Continue to the client.
AsyncRequestContent delegate = new AsyncRequestContent();
proxyRequest.body(delegate);
proxyRequest.attribute(CLIENT_REQUEST_ATTRIBUTE, request);
proxyRequest.attribute(CONTINUE_ACTION_ATTRIBUTE, (Runnable)() ->
{
try
{
ContentProvider provider = proxyRequestContent(request, response, proxyRequest);
new DelegatingContentProvider(request, proxyRequest, response, provider, deferred).iterate();
Request.Content content = proxyRequestContent(request, response, proxyRequest);
new DelegatingRequestContent(request, proxyRequest, response, content, delegate);
}
catch (Throwable failure)
{
@ -111,16 +108,25 @@ public class ProxyServlet extends AbstractProxyServlet
}
else
{
proxyRequest.content(proxyRequestContent(request, response, proxyRequest));
proxyRequest.body(proxyRequestContent(request, response, proxyRequest));
}
}
sendProxyRequest(request, response, proxyRequest);
}
protected ContentProvider proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException
/**
* Wraps the client-to-proxy request content in a {@code Request.Content} for the proxy-to-server request.
*
* @param request the client-to-proxy request
* @param response the proxy-to-client response
* @param proxyRequest the proxy-to-server request
* @return a proxy-to-server request content
* @throws IOException if the proxy-to-server request content cannot be created
*/
protected Request.Content proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException
{
return new ProxyInputStreamContentProvider(request, response, proxyRequest, request.getInputStream());
return new ProxyInputStreamRequestContent(request, response, proxyRequest, request.getInputStream());
}
@Override
@ -240,13 +246,13 @@ public class ProxyServlet extends AbstractProxyServlet
}
}
protected class ProxyInputStreamContentProvider extends InputStreamContentProvider
protected class ProxyInputStreamRequestContent extends InputStreamRequestContent
{
private final HttpServletResponse response;
private final Request proxyRequest;
private final HttpServletRequest request;
protected ProxyInputStreamContentProvider(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, InputStream input)
protected ProxyInputStreamRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest, InputStream input)
{
super(input);
this.request = request;
@ -265,11 +271,6 @@ public class ProxyServlet extends AbstractProxyServlet
{
if (_log.isDebugEnabled())
_log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length);
return onRequestContent(request, proxyRequest, buffer, offset, length);
}
protected ByteBuffer onRequestContent(HttpServletRequest request, Request proxyRequest, byte[] buffer, int offset, int length)
{
return super.onRead(buffer, offset, length);
}
@ -280,80 +281,57 @@ public class ProxyServlet extends AbstractProxyServlet
}
}
private class DelegatingContentProvider extends IteratingCallback implements AsyncContentProvider.Listener
private class DelegatingRequestContent implements Request.Content.Consumer
{
private final HttpServletRequest clientRequest;
private final Request proxyRequest;
private final HttpServletResponse proxyResponse;
private final Iterator<ByteBuffer> iterator;
private final DeferredContentProvider deferred;
private final AsyncRequestContent delegate;
private final Request.Content.Subscription subscription;
private DelegatingContentProvider(HttpServletRequest clientRequest, Request proxyRequest, HttpServletResponse proxyResponse, ContentProvider provider, DeferredContentProvider deferred)
private DelegatingRequestContent(HttpServletRequest clientRequest, Request proxyRequest, HttpServletResponse proxyResponse, Request.Content content, AsyncRequestContent delegate)
{
this.clientRequest = clientRequest;
this.proxyRequest = proxyRequest;
this.proxyResponse = proxyResponse;
this.iterator = provider.iterator();
this.deferred = deferred;
if (provider instanceof AsyncContentProvider)
((AsyncContentProvider)provider).setListener(this);
this.delegate = delegate;
this.subscription = content.subscribe(this, true);
this.subscription.demand();
}
@Override
protected Action process() throws Exception
public void onContent(ByteBuffer buffer, boolean last, Callback callback)
{
if (!iterator.hasNext())
return Action.SUCCEEDED;
ByteBuffer buffer = iterator.next();
if (buffer == null)
return Action.IDLE;
deferred.offer(buffer, this);
return Action.SCHEDULED;
}
@Override
public void succeeded()
{
if (iterator instanceof Callback)
((Callback)iterator).succeeded();
super.succeeded();
}
@Override
protected void onCompleteSuccess()
{
try
Callback wrapped = Callback.from(() -> succeeded(callback, last), failure -> failed(callback, failure));
if (buffer.hasRemaining())
{
if (iterator instanceof Closeable)
((Closeable)iterator).close();
deferred.close();
delegate.offer(buffer, wrapped);
}
catch (Throwable x)
else
{
_log.trace("IGNORED", x);
wrapped.succeeded();
}
if (last)
delegate.close();
}
private void succeeded(Callback callback, boolean last)
{
callback.succeeded();
if (!last)
subscription.demand();
}
private void failed(Callback callback, Throwable failure)
{
callback.failed(failure);
onFailure(failure);
}
@Override
protected void onCompleteFailure(Throwable failure)
public void onFailure(Throwable failure)
{
if (iterator instanceof Callback)
((Callback)iterator).failed(failure);
onClientRequestFailure(clientRequest, proxyRequest, proxyResponse, failure);
}
@Override
public InvocationType getInvocationType()
{
return InvocationType.NON_BLOCKING;
}
@Override
public void onContent()
{
iterate();
}
}
}

View File

@ -50,12 +50,11 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
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.util.BytesContentProvider;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.AsyncRequestContent;
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.HttpHeaderValue;
@ -224,11 +223,11 @@ public class AsyncMiddleManServletTest
startClient();
byte[] gzipBytes = gzip(bytes);
ContentProvider gzipContent = new BytesContentProvider(gzipBytes);
Request.Content gzipContent = new BytesRequestContent(gzipBytes);
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.header(HttpHeader.CONTENT_ENCODING, "gzip")
.content(gzipContent)
.body(gzipContent)
.timeout(5, TimeUnit.SECONDS)
.send();
@ -303,7 +302,7 @@ public class AsyncMiddleManServletTest
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.header(HttpHeader.CONTENT_ENCODING, "gzip")
.content(new BytesContentProvider(gzip(bytes)))
.body(new BytesRequestContent(gzip(bytes)))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -391,11 +390,11 @@ public class AsyncMiddleManServletTest
});
startClient();
DeferredContentProvider content = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
Request request = client.newRequest("localhost", serverConnector.getLocalPort());
FutureResponseListener listener = new FutureResponseListener(request);
request.header(HttpHeader.CONTENT_ENCODING, "gzip")
.content(content)
.body(content)
.send(listener);
byte[] bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(StandardCharsets.UTF_8);
content.offer(ByteBuffer.wrap(gzip(bytes)));
@ -440,7 +439,7 @@ public class AsyncMiddleManServletTest
byte[] bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(StandardCharsets.UTF_8);
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.header(HttpHeader.CONTENT_ENCODING, "gzip")
.content(new BytesContentProvider(gzip(bytes)))
.body(new BytesRequestContent(gzip(bytes)))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -484,7 +483,7 @@ public class AsyncMiddleManServletTest
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.header(HttpHeader.CONTENT_ENCODING, "gzip")
.content(new BytesContentProvider(gzip(bytes)))
.body(new BytesRequestContent(gzip(bytes)))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -511,7 +510,7 @@ public class AsyncMiddleManServletTest
byte[] bytes = new byte[1024];
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.content(new BytesContentProvider(bytes))
.body(new BytesRequestContent(bytes))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -521,7 +520,7 @@ public class AsyncMiddleManServletTest
@Test
public void testUpstreamTransformationThrowsAfterCommittingProxyRequest() throws Exception
{
try (StacklessLogging scope = new StacklessLogging(HttpChannel.class))
try (StacklessLogging ignored = new StacklessLogging(HttpChannel.class))
{
startServer(new EchoHttpServlet());
startProxy(new AsyncMiddleManServlet()
@ -546,10 +545,10 @@ public class AsyncMiddleManServletTest
});
startClient();
final CountDownLatch latch = new CountDownLatch(1);
DeferredContentProvider content = new DeferredContentProvider();
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent();
client.newRequest("localhost", serverConnector.getLocalPort())
.content(content)
.body(content)
.send(result ->
{
if (result.isSucceeded() && result.getResponse().getStatus() == 502)
@ -756,10 +755,10 @@ public class AsyncMiddleManServletTest
});
startClient();
final CountDownLatch latch = new CountDownLatch(1);
DeferredContentProvider content = new DeferredContentProvider();
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent();
client.newRequest("localhost", serverConnector.getLocalPort())
.content(content)
.body(content)
.send(result ->
{
System.err.println(result);
@ -777,7 +776,7 @@ public class AsyncMiddleManServletTest
@Test
public void testClientRequestReadFailsOnSecondRead() throws Exception
{
try (StacklessLogging scope = new StacklessLogging(HttpChannel.class))
try (StacklessLogging ignored = new StacklessLogging(HttpChannel.class))
{
startServer(new EchoHttpServlet());
startProxy(new AsyncMiddleManServlet()
@ -795,10 +794,10 @@ public class AsyncMiddleManServletTest
});
startClient();
final CountDownLatch latch = new CountDownLatch(1);
DeferredContentProvider content = new DeferredContentProvider();
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent();
client.newRequest("localhost", serverConnector.getLocalPort())
.content(content)
.body(content)
.send(result ->
{
if (result.getResponse().getStatus() == 502)
@ -1114,7 +1113,7 @@ public class AsyncMiddleManServletTest
// Send only part of the content; the proxy will idle timeout.
final byte[] data = new byte[]{'c', 'a', 'f', 'e'};
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.content(new BytesContentProvider(data)
.body(new BytesRequestContent(data)
{
@Override
public long getLength()
@ -1231,7 +1230,7 @@ public class AsyncMiddleManServletTest
}
@Override
public boolean transform(Source source, Sink sink) throws IOException
public boolean transform(Source source, Sink sink)
{
if (readSource)
{
@ -1320,10 +1319,10 @@ public class AsyncMiddleManServletTest
});
startClient();
DeferredContentProvider content = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
Request request = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.content(content);
.body(content);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);
@ -1368,10 +1367,10 @@ public class AsyncMiddleManServletTest
});
startClient();
DeferredContentProvider content = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
Request request = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.content(content);
.body(content);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);
@ -1439,10 +1438,10 @@ public class AsyncMiddleManServletTest
});
startClient();
DeferredContentProvider content = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
Request request = client.newRequest("localhost", serverConnector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.content(content);
.body(content);
FutureResponseListener listener = new FutureResponseListener(request);
request.send(listener);
@ -1607,9 +1606,9 @@ public class AsyncMiddleManServletTest
private static class Client extends HrefTransformer
{
@Override
protected String transform(String value) throws IOException
protected String transform(String value)
{
String result = PREFIX + URLEncoder.encode(value, "UTF-8");
String result = PREFIX + URLEncoder.encode(value, StandardCharsets.UTF_8);
LOG.debug("{} -> {}", value, result);
return result;
}
@ -1618,9 +1617,9 @@ public class AsyncMiddleManServletTest
private static class Server extends HrefTransformer
{
@Override
protected String transform(String value) throws IOException
protected String transform(String value)
{
String result = URLDecoder.decode(value.substring(PREFIX.length()), "UTF-8");
String result = URLDecoder.decode(value.substring(PREFIX.length()), StandardCharsets.UTF_8);
LOG.debug("{} <- {}", value, result);
return result;
}

View File

@ -48,7 +48,7 @@ import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.util.BasicAuthentication;
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.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
@ -257,7 +257,7 @@ public class ForwardProxyTLSServerTest
.path("/echo")
.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString())
.header(HttpHeader.CONTENT_LENGTH, String.valueOf(content.length()))
.content(new StringContentProvider(content))
.body(new StringRequestContent(content))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -320,7 +320,7 @@ public class ForwardProxyTLSServerTest
.path("/echo")
.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString())
.header(HttpHeader.CONTENT_LENGTH, String.valueOf(body2.length()))
.content(new StringContentProvider(body2));
.body(new StringRequestContent(body2));
// Make sure the second connection can send the exchange via the tunnel
FutureResponseListener listener2 = new FutureResponseListener(request2);

View File

@ -23,6 +23,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -35,12 +36,11 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
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.util.BytesContentProvider;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.tools.HttpTester;
import org.eclipse.jetty.logging.StacklessLogging;
@ -173,7 +173,7 @@ public class ProxyServletFailureTest
"Host: " + serverHostPort + "\r\n";
// Don't sent the \r\n that would signal the end of the headers.
OutputStream output = socket.getOutputStream();
output.write(request.getBytes("UTF-8"));
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
// Wait for idle timeout to fire.
@ -203,7 +203,7 @@ public class ProxyServletFailureTest
"Content-Length: 1\r\n" +
"\r\n";
OutputStream output = socket.getOutputStream();
output.write(request.getBytes("UTF-8"));
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
// Do not send the promised content, wait to idle timeout.
@ -239,7 +239,7 @@ public class ProxyServletFailureTest
"\r\n" +
"Z";
OutputStream output = socket.getOutputStream();
output.write(request.getBytes("UTF-8"));
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
// Do not send all the promised content, wait to idle timeout.
@ -261,7 +261,7 @@ public class ProxyServletFailureTest
{
final byte[] content = new byte[]{'C', '0', 'F', 'F', 'E', 'E'};
int expected;
ProxyServlet proxyServlet = null;
ProxyServlet proxyServlet;
if (proxyServletClass.isAssignableFrom(AsyncProxyServlet.class))
{
@ -270,9 +270,9 @@ public class ProxyServletFailureTest
proxyServlet = new AsyncProxyServlet()
{
@Override
protected ContentProvider proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException
protected Request.Content proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest) throws IOException
{
DeferredContentProvider provider = new DeferredContentProvider()
AsyncRequestContent requestContent = new AsyncRequestContent()
{
@Override
public boolean offer(ByteBuffer buffer, Callback callback)
@ -282,8 +282,8 @@ public class ProxyServletFailureTest
return super.offer(buffer.slice(), callback);
}
};
request.getInputStream().setReadListener(newReadListener(request, response, proxyRequest, provider));
return provider;
request.getInputStream().setReadListener(newReadListener(request, response, proxyRequest, requestContent));
return requestContent;
}
};
}
@ -293,9 +293,9 @@ public class ProxyServletFailureTest
proxyServlet = new ProxyServlet()
{
@Override
protected ContentProvider proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest)
protected Request.Content proxyRequestContent(HttpServletRequest request, HttpServletResponse response, Request proxyRequest)
{
return new BytesContentProvider(content)
return new BytesRequestContent(content)
{
@Override
public long getLength()
@ -316,7 +316,7 @@ public class ProxyServletFailureTest
try (StacklessLogging ignore = new StacklessLogging(HttpChannel.class))
{
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.send();
assertThat(response.toString(), response.getStatus(), is(expected));

View File

@ -19,13 +19,11 @@
package org.eclipse.jetty.proxy;
import java.io.IOException;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Stream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -33,7 +31,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpProxy;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
@ -57,11 +55,11 @@ public class ProxyServletLoadTest
{
public static Stream<Arguments> data()
{
return Arrays.asList(
return Stream.of(
ProxyServlet.class,
AsyncProxyServlet.class,
AsyncMiddleManServlet.class)
.stream().map(Arguments::of);
.map(Arguments::of);
}
private static final Logger LOG = LoggerFactory.getLogger(ProxyServletLoadTest.class);
@ -136,7 +134,7 @@ public class ProxyServletLoadTest
startServer(proxyServletClass, new HttpServlet()
{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
if (req.getHeader("Via") != null)
resp.addHeader(PROXIED_HEADER, "true");
@ -205,7 +203,7 @@ public class ProxyServletLoadTest
byte[] content = new byte[1024];
new Random().nextBytes(content);
ContentResponse response = client.newRequest(host, port).method(HttpMethod.POST).content(new BytesContentProvider(content))
ContentResponse response = client.newRequest(host, port).method(HttpMethod.POST).body(new BytesRequestContent(content))
.timeout(5, TimeUnit.SECONDS).send();
if (response.getStatus() != 200)

View File

@ -27,6 +27,7 @@ import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.HttpCookie;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
@ -69,9 +70,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.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.InputStreamResponseListener;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
@ -305,7 +306,7 @@ public class ProxyServletTest
new Random().nextBytes(content);
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.method(HttpMethod.POST)
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -344,7 +345,7 @@ public class ProxyServletTest
byte[] content = new byte[128 * 1024];
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.method(HttpMethod.POST)
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -387,7 +388,7 @@ public class ProxyServletTest
ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort())
.method(HttpMethod.POST)
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -933,7 +934,7 @@ public class ProxyServletTest
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
{
byte[] message = "tooshort".getBytes("ascii");
byte[] message = "tooshort".getBytes(StandardCharsets.US_ASCII);
resp.setContentType("text/plain;charset=ascii");
resp.setHeader("Content-Length", Long.toString(message.length + 1));
resp.getOutputStream().write(message);
@ -1283,7 +1284,7 @@ public class ProxyServletTest
CountDownLatch clientLatch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.onRequestContent((request, buffer) -> contentLatch.countDown())
.send(new BufferingResponseListener()
{
@ -1335,12 +1336,12 @@ public class ProxyServletTest
byte[] content = new byte[1024];
new Random().nextBytes(content);
int chunk1 = content.length / 2;
DeferredContentProvider contentProvider = new DeferredContentProvider();
contentProvider.offer(ByteBuffer.wrap(content, 0, chunk1));
AsyncRequestContent requestContent = new AsyncRequestContent();
requestContent.offer(ByteBuffer.wrap(content, 0, chunk1));
CountDownLatch clientLatch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(contentProvider)
.body(requestContent)
.send(new BufferingResponseListener()
{
@Override
@ -1359,8 +1360,8 @@ public class ProxyServletTest
// Wait a while and then offer more content.
Thread.sleep(1000);
contentProvider.offer(ByteBuffer.wrap(content, chunk1, content.length - chunk1));
contentProvider.close();
requestContent.offer(ByteBuffer.wrap(content, chunk1, content.length - chunk1));
requestContent.close();
assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
}
@ -1395,12 +1396,12 @@ public class ProxyServletTest
byte[] content = new byte[1024];
new Random().nextBytes(content);
int chunk1 = content.length / 2;
DeferredContentProvider contentProvider = new DeferredContentProvider();
contentProvider.offer(ByteBuffer.wrap(content, 0, chunk1));
AsyncRequestContent requestContent = new AsyncRequestContent();
requestContent.offer(ByteBuffer.wrap(content, 0, chunk1));
CountDownLatch clientLatch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(contentProvider)
.body(requestContent)
.send(result ->
{
if (result.isFailed())
@ -1448,7 +1449,7 @@ public class ProxyServletTest
CountDownLatch clientLatch = new CountDownLatch(1);
client.newRequest("localhost", serverConnector.getLocalPort())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.onRequestContent((request, buffer) -> contentLatch.countDown())
.send(result ->
{

View File

@ -503,7 +503,7 @@ public class MultiPartFormInputStreamTest
mpis.deleteParts(); // this should not be an NPE
}
@Test
public void testAsyncCleanUp() throws Exception
{

View File

@ -29,8 +29,8 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.FormContentProvider;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.FormRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
@ -131,13 +131,13 @@ public class FormTest
length = length + 1;
byte[] value = new byte[length];
Arrays.fill(value, (byte)'x');
DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(key), ByteBuffer.wrap(value));
AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(key), ByteBuffer.wrap(value));
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.method(HttpMethod.POST)
.path(contextPath + servletPath)
.header(HttpHeader.CONTENT_TYPE, MimeTypes.Type.FORM_ENCODED.asString())
.content(content)
.body(content)
.onRequestBegin(request ->
{
if (withContentLength)
@ -192,7 +192,7 @@ public class FormTest
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.method(HttpMethod.POST)
.path(contextPath + servletPath)
.content(new FormContentProvider(formParams))
.body(new FormRequestContent(formParams))
.send();
int expected = (maxFormKeys != null && maxFormKeys < 0)

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.servlet;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import javax.servlet.MultipartConfigElement;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
@ -30,8 +31,8 @@ import javax.servlet.http.Part;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.MultiPartContentProvider;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.client.util.MultiPartRequestContent;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.MimeTypes;
@ -44,8 +45,6 @@ import org.eclipse.jetty.util.IO;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@ -54,8 +53,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
public class MultiPartServletTest
{
private static final Logger LOG = LoggerFactory.getLogger(MultiPartServletTest.class);
private Server server;
private ServerConnector connector;
private HttpClient client;
@ -123,22 +120,19 @@ public class MultiPartServletTest
public void testTempFilesDeletedOnError() throws Exception
{
byte[] byteArray = new byte[LARGE_MESSAGE_SIZE];
for (int i = 0; i < byteArray.length; i++)
{
byteArray[i] = 1;
}
BytesContentProvider contentProvider = new BytesContentProvider(byteArray);
Arrays.fill(byteArray, (byte)1);
BytesRequestContent content = new BytesRequestContent(byteArray);
MultiPartContentProvider multiPart = new MultiPartContentProvider();
multiPart.addFieldPart("largePart", contentProvider, null);
MultiPartRequestContent multiPart = new MultiPartRequestContent();
multiPart.addFieldPart("largePart", content, null);
multiPart.close();
try (StacklessLogging stacklessLogging = new StacklessLogging(HttpChannel.class, MultiPartFormInputStream.class))
try (StacklessLogging ignored = new StacklessLogging(HttpChannel.class, MultiPartFormInputStream.class))
{
ContentResponse response = client.newRequest("localhost", connector.getLocalPort())
.scheme(HttpScheme.HTTP.asString())
.method(HttpMethod.POST)
.content(multiPart)
.body(multiPart)
.send();
assertEquals(500, response.getStatus());

View File

@ -44,8 +44,8 @@ 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.util.InputStreamResponseListener;
import org.eclipse.jetty.client.util.MultiPartContentProvider;
import org.eclipse.jetty.client.util.PathContentProvider;
import org.eclipse.jetty.client.util.MultiPartRequestContent;
import org.eclipse.jetty.client.util.PathRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.HttpConfiguration;
@ -101,7 +101,7 @@ public class HugeResourceTest
String.format("FileStore %s of %s needs at least 30GB of free space for this test (only had %,.2fGB)",
baseFileStore, staticBase, (double)(baseFileStore.getUnallocatedSpace() / GB)));
makeStaticFile(staticBase.resolve("test-1g.dat"), 1 * GB);
makeStaticFile(staticBase.resolve("test-1g.dat"), GB);
makeStaticFile(staticBase.resolve("test-4g.dat"), 4 * GB);
makeStaticFile(staticBase.resolve("test-10g.dat"), 10 * GB);
@ -116,7 +116,7 @@ public class HugeResourceTest
{
ArrayList<Arguments> ret = new ArrayList<>();
ret.add(Arguments.of("test-1g.dat", 1 * GB));
ret.add(Arguments.of("test-1g.dat", GB));
ret.add(Arguments.of("test-4g.dat", 4 * GB));
ret.add(Arguments.of("test-10g.dat", 10 * GB));
@ -133,7 +133,7 @@ public class HugeResourceTest
private static void makeStaticFile(Path staticFile, long size) throws IOException
{
byte[] buf = new byte[(int)(1 * MB)];
byte[] buf = new byte[(int)MB];
Arrays.fill(buf, (byte)'x');
ByteBuffer src = ByteBuffer.wrap(buf);
@ -248,9 +248,9 @@ public class HugeResourceTest
{
Path inputFile = staticBase.resolve(filename);
PathContentProvider pathContentProvider = new PathContentProvider(inputFile);
PathRequestContent content = new PathRequestContent(inputFile);
URI destUri = server.getURI().resolve("/post");
Request request = client.newRequest(destUri).method(HttpMethod.POST).content(pathContentProvider);
Request request = client.newRequest(destUri).method(HttpMethod.POST).body(content);
ContentResponse response = request.send();
assertThat("HTTP Response Code", response.getStatus(), is(200));
// dumpResponse(response);
@ -263,14 +263,15 @@ public class HugeResourceTest
@MethodSource("staticFiles")
public void testUploadMultipart(String filename, long expectedSize) throws Exception
{
MultiPartContentProvider multipart = new MultiPartContentProvider();
MultiPartRequestContent multipart = new MultiPartRequestContent();
Path inputFile = staticBase.resolve(filename);
String name = String.format("file-%d", expectedSize);
multipart.addFilePart(name, filename, new PathContentProvider(inputFile), null);
multipart.addFilePart(name, filename, new PathRequestContent(inputFile), null);
multipart.close();
URI destUri = server.getURI().resolve("/multipart");
client.setIdleTimeout(90_000);
Request request = client.newRequest(destUri).method(HttpMethod.POST).content(multipart);
Request request = client.newRequest(destUri).method(HttpMethod.POST).body(multipart);
ContentResponse response = request.send();
assertThat("HTTP Response Code", response.getStatus(), is(200));
// dumpResponse(response);

View File

@ -23,7 +23,7 @@ import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.FormContentProvider;
import org.eclipse.jetty.client.util.FormRequestContent;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.util.Fields;
import org.junit.jupiter.api.Test;
@ -215,7 +215,7 @@ public class DemoBaseTests extends AbstractDistributionTest
Fields form = new Fields();
form.add("Action", "New Session");
response = client.POST("http://localhost:" + httpPort + "/test/session/")
.content(new FormContentProvider(form))
.body(new FormRequestContent(form))
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
String content = response.getContentAsString();
@ -231,7 +231,7 @@ public class DemoBaseTests extends AbstractDistributionTest
form.add("Name", "Zed");
form.add("Value", "[alpha]");
response = client.POST(location)
.content(new FormContentProvider(form))
.body(new FormRequestContent(form))
.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
content = response.getContentAsString();

View File

@ -51,10 +51,10 @@ import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.http.HttpConnectionOverHTTP;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.InputStreamContentProvider;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.client.util.InputStreamRequestContent;
import org.eclipse.jetty.client.util.StringRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
@ -178,7 +178,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
ContentResponse response = scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path(scenario.servletPath)
.content(new StringContentProvider("0123456789"))
.body(new StringRequestContent("0123456789"))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -248,14 +248,14 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
});
String data = "0123456789";
DeferredContentProvider content = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
content.offer(ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)));
CountDownLatch responseLatch = new CountDownLatch(1);
CountDownLatch clientLatch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path(scenario.servletPath)
.content(content)
.body(content)
.onResponseSuccess(r -> responseLatch.countDown())
.timeout(5, TimeUnit.SECONDS)
.send(result ->
@ -324,7 +324,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
{
ContentResponse response = scenario.client.newRequest(scenario.newURI())
.path(scenario.servletPath)
.content(new StringContentProvider("0123456789"))
.body(new StringRequestContent("0123456789"))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -628,7 +628,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
.method(HttpMethod.POST)
.path(scenario.servletPath)
.header(HttpHeader.CONNECTION, "close")
.content(new StringContentProvider(text))
.body(new StringRequestContent(text))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -700,7 +700,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
byte[] data = "X".getBytes(StandardCharsets.UTF_8);
CountDownLatch clientLatch = new CountDownLatch(1);
DeferredContentProvider content = new DeferredContentProvider()
AsyncRequestContent content = new AsyncRequestContent()
{
@Override
public long getLength()
@ -711,7 +711,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path(scenario.servletPath)
.content(content)
.body(content)
.timeout(5, TimeUnit.SECONDS)
.send(new BufferingResponseListener()
{
@ -805,11 +805,11 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
byte[] data = "X".getBytes(StandardCharsets.UTF_8);
CountDownLatch clientLatch = new CountDownLatch(1);
DeferredContentProvider content = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path(scenario.servletPath)
.content(content)
.body(content)
.timeout(5, TimeUnit.SECONDS)
.send(new BufferingResponseListener()
{
@ -894,7 +894,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
.method(HttpMethod.POST)
.path(scenario.servletPath)
.header(HttpHeader.CONNECTION, "close")
.content(new StringContentProvider("XYZ"))
.body(new StringRequestContent("XYZ"))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -1025,13 +1025,13 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
});
String content = "0123456789ABCDEF";
DeferredContentProvider contentProvider = new DeferredContentProvider();
contentProvider.offer(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8)));
AsyncRequestContent requestContent = new AsyncRequestContent();
requestContent.offer(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8)));
CountDownLatch clientLatch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path(scenario.servletPath)
.content(contentProvider)
.body(requestContent)
.send(new BufferingResponseListener()
{
@Override
@ -1050,7 +1050,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
assertTrue(writeLatch.await(5, TimeUnit.SECONDS));
contentProvider.close();
requestContent.close();
assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
}
@ -1109,12 +1109,12 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
});
CountDownLatch responseLatch = new CountDownLatch(1);
DeferredContentProvider contentProvider = new DeferredContentProvider();
contentProvider.offer(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8)));
AsyncRequestContent requestContent = new AsyncRequestContent();
requestContent.offer(ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8)));
var request = scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path(scenario.servletPath)
.content(contentProvider)
.body(requestContent)
.onResponseSuccess(response ->
{
if (transport == HTTP)
@ -1169,7 +1169,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
// Wait for the response to arrive before finishing the request.
assertTrue(responseLatch.await(5, TimeUnit.SECONDS));
contentProvider.close();
requestContent.close();
assertTrue(errorLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientLatch.await(5, TimeUnit.SECONDS));
@ -1293,7 +1293,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
}
});
DeferredContentProvider contentProvider = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
CountDownLatch clientLatch = new CountDownLatch(1);
String expected =
@ -1308,7 +1308,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path(scenario.servletPath)
.content(contentProvider)
.body(content)
.send(new BufferingResponseListener()
{
@Override
@ -1324,20 +1324,20 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
}
});
contentProvider.offer(BufferUtil.toBuffer("S0"));
contentProvider.flush();
contentProvider.offer(BufferUtil.toBuffer("S1"));
contentProvider.flush();
contentProvider.offer(BufferUtil.toBuffer("S2"));
contentProvider.flush();
contentProvider.offer(BufferUtil.toBuffer("S3"));
contentProvider.flush();
contentProvider.offer(BufferUtil.toBuffer("S4"));
contentProvider.flush();
contentProvider.offer(BufferUtil.toBuffer("S5"));
contentProvider.flush();
contentProvider.offer(BufferUtil.toBuffer("S6"));
contentProvider.close();
content.offer(BufferUtil.toBuffer("S0"));
content.flush();
content.offer(BufferUtil.toBuffer("S1"));
content.flush();
content.offer(BufferUtil.toBuffer("S2"));
content.flush();
content.offer(BufferUtil.toBuffer("S3"));
content.flush();
content.offer(BufferUtil.toBuffer("S4"));
content.flush();
content.offer(BufferUtil.toBuffer("S5"));
content.flush();
content.offer(BufferUtil.toBuffer("S6"));
content.close();
assertTrue(clientLatch.await(10, TimeUnit.SECONDS));
}
@ -1373,7 +1373,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
ContentResponse response = scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path(scenario.servletPath)
.content(new InputStreamContentProvider(new ByteArrayInputStream(new byte[16 * 1024])
.body(new InputStreamRequestContent(new ByteArrayInputStream(new byte[16 * 1024])
{
@Override
public int read(byte[] b, int off, int len)
@ -1398,7 +1398,7 @@ public class AsyncIOServletTest extends AbstractTest<AsyncIOServletTest.AsyncTra
assertTrue(failures.isEmpty());
}
private class Listener implements ReadListener, WriteListener
private static class Listener implements ReadListener, WriteListener
{
private final Executor executor = Executors.newFixedThreadPool(32);
private final CompletableFuture<?> inputComplete = new CompletableFuture<>();

View File

@ -22,6 +22,8 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
@ -29,15 +31,17 @@ import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.client.util.InputStreamContentProvider;
import org.eclipse.jetty.client.util.OutputStreamContentProvider;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.InputStreamRequestContent;
import org.eclipse.jetty.client.util.OutputStreamRequestContent;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.Callback;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class AsyncRequestContentTest extends AbstractTest<TransportScenario>
@ -50,45 +54,45 @@ public class AsyncRequestContentTest extends AbstractTest<TransportScenario>
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
public void testEmptyDeferredContent(Transport transport) throws Exception
public void testEmptyAsyncContent(Transport transport) throws Exception
{
init(transport);
scenario.start(new ConsumeInputHandler());
DeferredContentProvider contentProvider = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
CountDownLatch latch = new CountDownLatch(1);
scenario.client.POST(scenario.newURI())
.content(contentProvider)
.body(content)
.send(result ->
{
if (result.isSucceeded() &&
result.getResponse().getStatus() == HttpStatus.OK_200)
latch.countDown();
});
contentProvider.close();
content.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
public void testDeferredContent(Transport transport) throws Exception
public void testAsyncContent(Transport transport) throws Exception
{
init(transport);
scenario.start(new ConsumeInputHandler());
DeferredContentProvider contentProvider = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
CountDownLatch latch = new CountDownLatch(1);
scenario.client.POST(scenario.newURI())
.content(contentProvider)
.body(content)
.send(result ->
{
if (result.isSucceeded() &&
result.getResponse().getStatus() == HttpStatus.OK_200)
latch.countDown();
});
contentProvider.offer(ByteBuffer.wrap(new byte[1]));
contentProvider.close();
content.offer(ByteBuffer.wrap(new byte[1]));
content.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ -100,18 +104,17 @@ public class AsyncRequestContentTest extends AbstractTest<TransportScenario>
init(transport);
scenario.start(new ConsumeInputHandler());
InputStreamContentProvider contentProvider =
new InputStreamContentProvider(new ByteArrayInputStream(new byte[0]));
InputStreamRequestContent content =
new InputStreamRequestContent(new ByteArrayInputStream(new byte[0]));
CountDownLatch latch = new CountDownLatch(1);
scenario.client.POST(scenario.newURI())
.content(contentProvider)
.body(content)
.send(result ->
{
if (result.isSucceeded() &&
result.getResponse().getStatus() == HttpStatus.OK_200)
latch.countDown();
});
contentProvider.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ -123,18 +126,17 @@ public class AsyncRequestContentTest extends AbstractTest<TransportScenario>
init(transport);
scenario.start(new ConsumeInputHandler());
InputStreamContentProvider contentProvider =
new InputStreamContentProvider(new ByteArrayInputStream(new byte[1]));
InputStreamRequestContent content =
new InputStreamRequestContent(new ByteArrayInputStream(new byte[1]));
CountDownLatch latch = new CountDownLatch(1);
scenario.client.POST(scenario.newURI())
.content(contentProvider)
.body(content)
.send(result ->
{
if (result.isSucceeded() &&
result.getResponse().getStatus() == HttpStatus.OK_200)
latch.countDown();
});
contentProvider.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ -146,17 +148,17 @@ public class AsyncRequestContentTest extends AbstractTest<TransportScenario>
init(transport);
scenario.start(new ConsumeInputHandler());
OutputStreamContentProvider contentProvider = new OutputStreamContentProvider();
OutputStreamRequestContent content = new OutputStreamRequestContent();
CountDownLatch latch = new CountDownLatch(1);
scenario.client.POST(scenario.newURI())
.content(contentProvider)
.body(content)
.send(result ->
{
if (result.isSucceeded() &&
result.getResponse().getStatus() == HttpStatus.OK_200)
latch.countDown();
});
contentProvider.close();
content.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ -168,24 +170,62 @@ public class AsyncRequestContentTest extends AbstractTest<TransportScenario>
init(transport);
scenario.start(new ConsumeInputHandler());
OutputStreamContentProvider contentProvider = new OutputStreamContentProvider();
OutputStreamRequestContent content = new OutputStreamRequestContent();
CountDownLatch latch = new CountDownLatch(1);
scenario.client.POST(scenario.newURI())
.content(contentProvider)
.body(content)
.send(result ->
{
if (result.isSucceeded() &&
result.getResponse().getStatus() == HttpStatus.OK_200)
latch.countDown();
});
OutputStream output = contentProvider.getOutputStream();
OutputStream output = content.getOutputStream();
output.write(new byte[1]);
output.flush();
contentProvider.close();
content.close();
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
public void testBufferReuseAfterCallbackCompleted(Transport transport) throws Exception
{
init(transport);
scenario.start(new ConsumeInputHandler());
AsyncRequestContent content = new AsyncRequestContent();
CountDownLatch latch = new CountDownLatch(1);
List<Byte> requestContent = new ArrayList<>();
scenario.client.POST(scenario.newURI())
.onRequestContent(((request, buffer) -> requestContent.add(buffer.get())))
.body(content)
.send(result ->
{
if (result.isSucceeded() &&
result.getResponse().getStatus() == HttpStatus.OK_200)
latch.countDown();
});
byte first = '1';
byte second = '2';
byte[] bytes = new byte[1];
bytes[0] = first;
ByteBuffer buffer = ByteBuffer.wrap(bytes);
content.offer(buffer, Callback.from(() ->
{
bytes[0] = second;
content.offer(ByteBuffer.wrap(bytes), Callback.from(content::close));
}));
assertTrue(latch.await(5, TimeUnit.SECONDS));
assertEquals(2, requestContent.size());
assertEquals(first, requestContent.get(0));
assertEquals(second, requestContent.get(1));
}
private static class ConsumeInputHandler extends AbstractHandler
{
@Override

View File

@ -26,7 +26,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.Connection;
@ -102,7 +102,7 @@ public class ConnectionStatisticsTest extends AbstractTest<TransportScenario>
long contentLength = content.length;
ContentResponse response = scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.CONNECTION, "close")
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.timeout(5, TimeUnit.SECONDS)
.send();

View File

@ -39,9 +39,9 @@ import org.eclipse.jetty.client.ContinueProtocolHandler;
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.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.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue;
@ -104,7 +104,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
ContentResponse response = scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(contents))
.body(new BytesRequestContent(contents))
.timeout(5, TimeUnit.SECONDS)
.send();
@ -145,7 +145,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
byte[] content2 = new byte[16384];
ContentResponse response = scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content1, content2)
.body(new BytesRequestContent(content1, content2)
{
@Override
public long getLength()
@ -185,7 +185,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
testExpect100ContinueWithContentRespondError(transport, 413);
}
private void testExpect100ContinueWithContentRespondError(Transport transport, final int error) throws Exception
private void testExpect100ContinueWithContentRespondError(Transport transport, int error) throws Exception
{
init(transport);
scenario.start(new AbstractHandler()
@ -200,10 +200,10 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
byte[] content1 = new byte[10240];
byte[] content2 = new byte[16384];
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content1, content2))
.body(new BytesRequestContent(content1, content2))
.send(new BufferingResponseListener()
{
@Override
@ -228,7 +228,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
public void testExpect100ContinueWithContentWithRedirect(Transport transport) throws Exception
{
init(transport);
final String data = "success";
String data = "success";
scenario.start(new AbstractHandler()
{
@Override
@ -250,12 +250,12 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
});
byte[] content = new byte[10240];
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path("/continue")
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.send(new BufferingResponseListener()
{
@Override
@ -278,7 +278,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
init(transport);
// A request with Expect: 100-Continue cannot receive non-final responses like 3xx
final String data = "success";
String data = "success";
scenario.start(new AbstractHandler()
{
@Override
@ -300,12 +300,12 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
});
byte[] content = new byte[10240];
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path("/redirect")
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.send(new BufferingResponseListener()
{
@Override
@ -329,7 +329,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
public void testExpect100ContinueWithContentWithResponseFailureBefore100Continue(Transport transport) throws Exception
{
init(transport);
final long idleTimeout = 1000;
long idleTimeout = 1000;
scenario.startServer(new AbstractHandler()
{
@Override
@ -349,10 +349,10 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
scenario.startClient(httpClient -> httpClient.setIdleTimeout(2 * idleTimeout));
byte[] content = new byte[1024];
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.idleTimeout(idleTimeout, TimeUnit.MILLISECONDS)
.send(new BufferingResponseListener()
{
@ -376,7 +376,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
public void testExpect100ContinueWithContentWithResponseFailureAfter100Continue(Transport transport) throws Exception
{
init(transport);
final long idleTimeout = 1000;
long idleTimeout = 1000;
scenario.startServer(new AbstractHandler()
{
@Override
@ -398,10 +398,10 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout));
byte[] content = new byte[1024];
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.send(new BufferingResponseListener()
{
@Override
@ -439,7 +439,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
@Override
public Response.Listener getResponseListener()
{
final Response.Listener listener = super.getResponseListener();
Response.Listener listener = super.getResponseListener();
return new Response.Listener.Adapter()
{
@Override
@ -458,10 +458,10 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
});
byte[] content = new byte[1024];
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(content))
.body(new BytesRequestContent(content))
.send(new BufferingResponseListener()
{
@Override
@ -495,17 +495,17 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
}
});
final byte[] chunk1 = new byte[]{0, 1, 2, 3};
final byte[] chunk2 = new byte[]{4, 5, 6, 7};
final byte[] data = new byte[chunk1.length + chunk2.length];
byte[] chunk1 = new byte[]{0, 1, 2, 3};
byte[] chunk2 = new byte[]{4, 5, 6, 7};
byte[] data = new byte[chunk1.length + chunk2.length];
System.arraycopy(chunk1, 0, data, 0, chunk1.length);
System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length);
final CountDownLatch latch = new CountDownLatch(1);
DeferredContentProvider content = new DeferredContentProvider();
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent();
scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(content)
.body(content)
.send(new BufferingResponseListener()
{
@Override
@ -546,17 +546,17 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
}
});
final byte[] chunk1 = new byte[]{0, 1, 2, 3};
final byte[] chunk2 = new byte[]{4, 5, 6, 7};
final byte[] data = new byte[chunk1.length + chunk2.length];
byte[] chunk1 = new byte[]{0, 1, 2, 3};
byte[] chunk2 = new byte[]{4, 5, 6, 7};
byte[] data = new byte[chunk1.length + chunk2.length];
System.arraycopy(chunk1, 0, data, 0, chunk1.length);
System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length);
final CountDownLatch latch = new CountDownLatch(1);
DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(chunk1));
CountDownLatch latch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(chunk1));
scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(content)
.body(content)
.send(new BufferingResponseListener()
{
@Override
@ -591,10 +591,10 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
}
});
final byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
final DeferredContentProvider content = new DeferredContentProvider();
byte[] data = new byte[]{0, 1, 2, 3, 4, 5, 6, 7};
AsyncRequestContent content = new AsyncRequestContent();
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.onRequestHeaders(request ->
@ -602,7 +602,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
content.offer(ByteBuffer.wrap(data));
content.close();
})
.content(content)
.body(content)
.send(new BufferingResponseListener()
{
@Override
@ -632,13 +632,13 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
}
});
final byte[] chunk1 = new byte[]{0, 1, 2, 3};
final byte[] chunk2 = new byte[]{4, 5, 6};
final byte[] data = new byte[chunk1.length + chunk2.length];
byte[] chunk1 = new byte[]{0, 1, 2, 3};
byte[] chunk2 = new byte[]{4, 5, 6};
byte[] data = new byte[chunk1.length + chunk2.length];
System.arraycopy(chunk1, 0, data, 0, chunk1.length);
System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length);
final DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(chunk1));
AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(chunk1));
scenario.client.getProtocolHandlers().put(new ContinueProtocolHandler()
{
@ -658,10 +658,10 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
}
});
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(content)
.body(content)
.send(new BufferingResponseListener()
{
@Override
@ -692,10 +692,10 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
{
server.bind(new InetSocketAddress("localhost", 0));
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest("localhost", server.getLocalPort())
.header(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE.asString())
.content(new BytesContentProvider(new byte[]{0}))
.body(new BytesRequestContent(new byte[]{0}))
.send(result ->
{
assertTrue(result.isSucceeded(), result.toString());
@ -756,7 +756,7 @@ public class HttpClientContinueTest extends AbstractTest<TransportScenario>
byte[] bytes = new byte[1024];
new Random().nextBytes(bytes);
ContentResponse response = scenario.client.newRequest(scenario.newURI())
.content(new BytesContentProvider(bytes))
.body(new BytesRequestContent(bytes))
.timeout(5, TimeUnit.SECONDS)
.send();

View File

@ -40,7 +40,7 @@ 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.http.HttpClientTransportOverHTTP;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
@ -241,7 +241,7 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
break;
case "POST":
request.header("X-Upload", String.valueOf(contentLength));
request.content(new BytesContentProvider(new byte[contentLength]));
request.body(new BytesRequestContent(new byte[contentLength]));
break;
}
@ -310,7 +310,7 @@ public class HttpClientLoadTest extends AbstractTest<HttpClientLoadTest.LoadTran
}
}
private class LoadHandler extends AbstractHandler
private static class LoadHandler extends AbstractHandler
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException

View File

@ -32,8 +32,6 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@ -52,18 +50,17 @@ 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.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.InputStreamContentProvider;
import org.eclipse.jetty.client.util.BytesRequestContent;
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.HttpStatus;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IO;
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
@ -128,7 +125,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
}
});
final AtomicLong requestTime = new AtomicLong();
AtomicLong requestTime = new AtomicLong();
ContentResponse response = scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.file(upload)
@ -150,7 +147,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
public void testDownload(Transport transport) throws Exception
{
init(transport);
final byte[] data = new byte[128 * 1024];
byte[] data = new byte[128 * 1024];
byte value = 1;
Arrays.fill(data, value);
scenario.start(new AbstractHandler()
@ -195,7 +192,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
public void testDownloadOfUTF8Content(Transport transport) throws Exception
{
init(transport);
final byte[] data = new byte[]{(byte)0xC3, (byte)0xA8}; // UTF-8 representation of &egrave;
byte[] data = new byte[]{(byte)0xC3, (byte)0xA8}; // UTF-8 representation of &egrave;
scenario.start(new AbstractHandler()
{
@Override
@ -237,7 +234,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
public void testDownloadWithFailure(Transport transport) throws Exception
{
init(transport);
final byte[] data = new byte[64 * 1024];
byte[] data = new byte[64 * 1024];
byte value = 1;
Arrays.fill(data, value);
scenario.start(new AbstractHandler()
@ -306,7 +303,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.content(new BytesContentProvider(new byte[]{0, 1, 2, 3}))
.body(new BytesRequestContent(new byte[]{0, 1, 2, 3}))
.send(listener);
Response response = listener.get(5, TimeUnit.SECONDS);
assertEquals(200, response.getStatus());
@ -510,11 +507,11 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
}
});
final byte[] data = new byte[]{0, 1, 2, 3};
byte[] data = new byte[]{0, 1, 2, 3};
ExecutionException e = assertThrows(ExecutionException.class, () ->
scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.content(new InputStreamContentProvider(new InputStream()
.body(new InputStreamRequestContent(new InputStream()
{
private int index = 0;
@ -527,7 +524,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
}, data.length / 2))
.timeout(5, TimeUnit.SECONDS)
.send());
assertThat(e.getCause(), instanceOf(NoSuchElementException.class));
assertThat(e.getCause(), instanceOf(ArrayIndexOutOfBoundsException.class));
}
@ParameterizedTest
@ -535,10 +532,10 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
public void testDownloadWithCloseBeforeContent(Transport transport) throws Exception
{
init(transport);
final byte[] data = new byte[128 * 1024];
byte[] data = new byte[128 * 1024];
byte value = 3;
Arrays.fill(data, value);
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
scenario.start(new AbstractHandler()
{
@Override
@ -582,9 +579,9 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
public void testDownloadWithCloseMiddleOfContent(Transport transport) throws Exception
{
init(transport);
final byte[] data1 = new byte[1024];
final byte[] data2 = new byte[1024];
final CountDownLatch latch = new CountDownLatch(1);
byte[] data1 = new byte[1024];
byte[] data2 = new byte[1024];
CountDownLatch latch = new CountDownLatch(1);
scenario.start(new AbstractHandler()
{
@Override
@ -635,7 +632,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
public void testDownloadWithCloseEndOfContent(Transport transport) throws Exception
{
init(transport);
final byte[] data = new byte[1024];
byte[] data = new byte[1024];
scenario.start(new AbstractHandler()
{
@Override
@ -688,12 +685,12 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
}
});
final CountDownLatch latch = new CountDownLatch(1);
try (DeferredContentProvider content = new DeferredContentProvider())
CountDownLatch latch = new CountDownLatch(1);
try (AsyncRequestContent content = new AsyncRequestContent())
{
scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.content(content)
.body(content)
.send(result ->
{
if (result.isSucceeded() && result.getResponse().getStatus() == 200)
@ -731,9 +728,9 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
}
});
final CountDownLatch latch = new CountDownLatch(1);
final AtomicInteger succeeds = new AtomicInteger();
try (DeferredContentProvider content = new DeferredContentProvider())
CountDownLatch latch = new CountDownLatch(1);
AtomicInteger succeeds = new AtomicInteger();
try (AsyncRequestContent content = new AsyncRequestContent())
{
// Make the content immediately available.
content.offer(ByteBuffer.allocate(1024), new Callback()
@ -747,7 +744,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.content(content)
.body(content)
.send(result ->
{
if (result.isSucceeded() && result.getResponse().getStatus() == 200)
@ -773,14 +770,14 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
}
});
final CountDownLatch latch = new CountDownLatch(1);
final byte[] data = new byte[512];
final DeferredContentProvider content = new DeferredContentProvider()
CountDownLatch latch = new CountDownLatch(1);
byte[] data = new byte[512];
AsyncRequestContent content = new AsyncRequestContent()
{
@Override
public void setListener(Listener listener)
public void demand()
{
super.setListener(listener);
super.demand();
// Simulate a concurrent call
offer(ByteBuffer.wrap(data));
close();
@ -789,91 +786,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.content(content)
.send(new BufferingResponseListener()
{
@Override
public void onComplete(Result result)
{
if (result.isSucceeded() &&
result.getResponse().getStatus() == 200 &&
Arrays.equals(data, getContent()))
latch.countDown();
}
});
assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@ParameterizedTest
@ArgumentsSource(TransportProvider.class)
public void testUploadWithDeferredContentProviderRacingWithIterator(Transport transport) throws Exception
{
init(transport);
scenario.start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
baseRequest.setHandled(true);
IO.copy(request.getInputStream(), response.getOutputStream());
}
});
final CountDownLatch latch = new CountDownLatch(1);
final byte[] data = new byte[512];
final AtomicReference<DeferredContentProvider> contentRef = new AtomicReference<>();
final DeferredContentProvider content = new DeferredContentProvider()
{
@Override
public Iterator<ByteBuffer> iterator()
{
return new Iterator<ByteBuffer>()
{
// Data for the deferred content iterator:
// [0] => deferred
// [1] => deferred
// [2] => data
private final byte[][] iteratorData = new byte[3][];
private final AtomicInteger index = new AtomicInteger();
{
iteratorData[0] = null;
iteratorData[1] = null;
iteratorData[2] = data;
}
@Override
public boolean hasNext()
{
return index.get() < iteratorData.length;
}
@Override
public ByteBuffer next()
{
byte[] chunk = iteratorData[index.getAndIncrement()];
ByteBuffer result = chunk == null ? null : ByteBuffer.wrap(chunk);
if (index.get() < iteratorData.length)
{
contentRef.get().offer(result == null ? BufferUtil.EMPTY_BUFFER : result);
contentRef.get().close();
}
return result;
}
@Override
public void remove()
{
}
};
}
};
contentRef.set(content);
scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.content(content)
.body(content)
.send(new BufferingResponseListener()
{
@Override
@ -904,12 +817,12 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
}
});
final byte[] data = new byte[512];
final CountDownLatch latch = new CountDownLatch(1);
OutputStreamContentProvider content = new OutputStreamContentProvider();
byte[] data = new byte[512];
CountDownLatch latch = new CountDownLatch(1);
OutputStreamRequestContent content = new OutputStreamRequestContent();
scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.content(content)
.body(content)
.send(new BufferingResponseListener()
{
@Override
@ -948,13 +861,13 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
}
});
final byte[] data = new byte[16 * 1024 * 1024];
byte[] data = new byte[16 * 1024 * 1024];
new Random().nextBytes(data);
final CountDownLatch latch = new CountDownLatch(1);
OutputStreamContentProvider content = new OutputStreamContentProvider();
CountDownLatch latch = new CountDownLatch(1);
OutputStreamRequestContent content = new OutputStreamRequestContent();
scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.content(content)
.body(content)
.send(new BufferingResponseListener(data.length)
{
@Override
@ -970,17 +883,9 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
// Make sure we provide the content *after* the request has been "sent".
Thread.sleep(1000);
try (InputStream input = new ByteArrayInputStream(data);
OutputStream output = content.getOutputStream())
try (OutputStream output = content.getOutputStream())
{
byte[] buffer = new byte[1024];
while (true)
{
int read = input.read(buffer);
if (read < 0)
break;
output.write(buffer, 0, read);
}
IO.copy(new ByteArrayInputStream(data), output);
}
assertTrue(latch.await(30, TimeUnit.SECONDS));
@ -995,15 +900,15 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
long connectTimeout = 1000;
scenario.start(new EmptyServerHandler(), httpClient -> httpClient.setConnectTimeout(connectTimeout));
final byte[] data = new byte[512];
final CountDownLatch latch = new CountDownLatch(1);
OutputStreamContentProvider content = new OutputStreamContentProvider();
byte[] data = new byte[512];
CountDownLatch latch = new CountDownLatch(1);
OutputStreamRequestContent content = new OutputStreamRequestContent();
String uri = "http://0.0.0.1";
if (scenario.getNetworkConnectorLocalPort().isPresent())
uri += ":" + scenario.getNetworkConnectorLocalPort().get();
scenario.client.newRequest(uri)
.scheme(scenario.getScheme())
.content(content)
.body(content)
.send(result ->
{
if (result.isFailed())
@ -1028,8 +933,8 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
init(transport);
scenario.start(new EmptyServerHandler());
final CountDownLatch failLatch = new CountDownLatch(2);
final Callback callback = new Callback()
CountDownLatch failLatch = new CountDownLatch(2);
Callback callback = new Callback()
{
@Override
public void failed(Throwable x)
@ -1038,11 +943,11 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
}
};
final CountDownLatch completeLatch = new CountDownLatch(1);
final DeferredContentProvider content = new DeferredContentProvider();
CountDownLatch completeLatch = new CountDownLatch(1);
AsyncRequestContent content = new AsyncRequestContent();
scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.content(content)
.body(content)
.onRequestBegin(request ->
{
content.offer(ByteBuffer.wrap(new byte[256]), callback);
@ -1058,7 +963,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
assertTrue(failLatch.await(5, TimeUnit.SECONDS));
// Make sure that adding more content results in the callback to be failed.
final CountDownLatch latch = new CountDownLatch(1);
CountDownLatch latch = new CountDownLatch(1);
content.offer(ByteBuffer.wrap(new byte[128]), new Callback()
{
@Override
@ -1079,7 +984,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
long connectTimeout = 1000;
scenario.start(new EmptyServerHandler(), httpClient -> httpClient.setConnectTimeout(connectTimeout));
final CountDownLatch closeLatch = new CountDownLatch(1);
CountDownLatch closeLatch = new CountDownLatch(1);
InputStream stream = new ByteArrayInputStream("test".getBytes(StandardCharsets.UTF_8))
{
@Override
@ -1089,15 +994,15 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
closeLatch.countDown();
}
};
InputStreamContentProvider content = new InputStreamContentProvider(stream);
InputStreamRequestContent content = new InputStreamRequestContent(stream);
final CountDownLatch completeLatch = new CountDownLatch(1);
CountDownLatch completeLatch = new CountDownLatch(1);
String uri = "http://0.0.0.1";
if (scenario.getNetworkConnectorLocalPort().isPresent())
uri += ":" + scenario.getNetworkConnectorLocalPort().get();
scenario.client.newRequest(uri)
.scheme(scenario.getScheme())
.content(content)
.body(content)
.send(result ->
{
assertTrue(result.isFailed());
@ -1113,7 +1018,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
public void testUploadWithConcurrentServerCloseClosesStream(Transport transport) throws Exception
{
init(transport);
final CountDownLatch serverLatch = new CountDownLatch(1);
CountDownLatch serverLatch = new CountDownLatch(1);
scenario.start(new AbstractHandler()
{
@Override
@ -1126,8 +1031,8 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
}
});
final AtomicBoolean commit = new AtomicBoolean();
final CountDownLatch closeLatch = new CountDownLatch(1);
AtomicBoolean commit = new AtomicBoolean();
CountDownLatch closeLatch = new CountDownLatch(1);
InputStream stream = new InputStream()
{
@Override
@ -1166,12 +1071,12 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
closeLatch.countDown();
}
};
InputStreamContentProvider provider = new InputStreamContentProvider(stream, 1);
InputStreamRequestContent content = new InputStreamRequestContent(stream, 1);
final CountDownLatch completeLatch = new CountDownLatch(1);
CountDownLatch completeLatch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.scheme(scenario.getScheme())
.content(provider)
.body(content)
.onRequestCommit(request -> commit.set(true))
.send(result ->
{
@ -1311,7 +1216,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
CountDownLatch latch = new CountDownLatch(1);
byte[] bytes = "[{\"key\":\"value\"}]".getBytes(StandardCharsets.UTF_8);
OutputStreamContentProvider content = new OutputStreamContentProvider()
OutputStreamRequestContent content = new OutputStreamRequestContent("application/json;charset=UTF-8")
{
@Override
public long getLength()
@ -1322,7 +1227,7 @@ public class HttpClientStreamTest extends AbstractTest<TransportScenario>
scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.path(scenario.servletPath)
.content(content, "application/json;charset=UTF-8")
.body(content)
.onResponseSuccess(response ->
{
assertEquals(HttpStatus.REQUEST_TIMEOUT_408, response.getStatus());

View File

@ -38,7 +38,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Destination;
import org.eclipse.jetty.client.api.Response;
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.client.util.InputStreamResponseListener;
import org.eclipse.jetty.http.HttpHeader;
@ -230,7 +230,7 @@ public class HttpClientTest extends AbstractTest<TransportScenario>
ContentResponse response = scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.content(new BytesContentProvider(bytes))
.body(new BytesRequestContent(bytes))
.timeout(15, TimeUnit.SECONDS)
.send();
@ -274,10 +274,10 @@ public class HttpClientTest extends AbstractTest<TransportScenario>
int chunks = 256;
int chunkSize = 16;
byte[][] bytes = IntStream.range(0, chunks).mapToObj(x -> new byte[chunkSize]).toArray(byte[][]::new);
BytesContentProvider contentProvider = new BytesContentProvider("application/octet-stream", bytes);
BytesRequestContent content = new BytesRequestContent("application/octet-stream", bytes);
ContentResponse response = scenario.client.newRequest(scenario.newURI())
.method(HttpMethod.POST)
.content(contentProvider)
.body(content)
.timeout(15, TimeUnit.SECONDS)
.send();

View File

@ -43,7 +43,7 @@ import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.InputStreamContentProvider;
import org.eclipse.jetty.client.util.InputStreamRequestContent;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.ClientConnectionFactory;
@ -159,7 +159,7 @@ public class HttpClientTimeoutTest extends AbstractTest<TransportScenario>
final CountDownLatch latch = new CountDownLatch(1);
final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
Request request = scenario.client.newRequest(scenario.newURI())
.content(new InputStreamContentProvider(new ByteArrayInputStream(content)))
.body(new InputStreamRequestContent(new ByteArrayInputStream(content)))
.timeout(2 * timeout, TimeUnit.MILLISECONDS);
request.send(new BufferingResponseListener()
{
@ -400,7 +400,7 @@ public class HttpClientTimeoutTest extends AbstractTest<TransportScenario>
}
});
assertTrue(latch.await(333 * connectTimeout, TimeUnit.MILLISECONDS));
assertTrue(latch.await(3 * connectTimeout, TimeUnit.MILLISECONDS));
assertNotNull(request.getAbortCause());
}
@ -521,7 +521,7 @@ public class HttpClientTimeoutTest extends AbstractTest<TransportScenario>
}
}
private class TimeoutHandler extends AbstractHandler
private static class TimeoutHandler extends AbstractHandler
{
private final long timeout;

View File

@ -35,7 +35,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.client.util.InputStreamResponseListener;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields;
@ -110,7 +110,7 @@ public class HttpTrailersTest extends AbstractTest<TransportScenario>
HttpRequest request = (HttpRequest)scenario.client.newRequest(scenario.newURI());
request = request.trailers(() -> trailers);
if (content != null)
request.method(HttpMethod.POST).content(new BytesContentProvider(content));
request.method(HttpMethod.POST).body(new BytesRequestContent(content));
ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
assertEquals(HttpStatus.OK_200, response.getStatus());
}

View File

@ -38,8 +38,8 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.client.util.AsyncRequestContent;
import org.eclipse.jetty.client.util.BufferingResponseListener;
import org.eclipse.jetty.client.util.DeferredContentProvider;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http2.FlowControlStrategy;
@ -181,9 +181,9 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
scenario.setServerIdleTimeout(idleTimeout);
CountDownLatch resultLatch = new CountDownLatch(2);
DeferredContentProvider content = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
scenario.client.POST(scenario.newURI())
.content(content)
.body(content)
.onResponseSuccess(response ->
{
if (response.getStatus() == HttpStatus.INTERNAL_SERVER_ERROR_500)
@ -244,10 +244,10 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
long idleTimeout = 2500;
scenario.setServerIdleTimeout(idleTimeout);
DeferredContentProvider contentProvider = new DeferredContentProvider(ByteBuffer.allocate(1));
AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.allocate(1));
CountDownLatch resultLatch = new CountDownLatch(1);
scenario.client.POST(scenario.newURI())
.content(contentProvider)
.body(content)
.send(result ->
{
if (result.getResponse().getStatus() == HttpStatus.INTERNAL_SERVER_ERROR_500)
@ -257,7 +257,7 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
// Async read should timeout.
assertTrue(handlerLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
// Complete the request.
contentProvider.close();
content.close();
assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
}
@ -362,10 +362,10 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
}
});
DeferredContentProvider contentProvider = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
BlockingQueue<Object> results = new BlockingArrayQueue<>();
scenario.client.newRequest(scenario.newURI())
.content(contentProvider)
.body(content)
.send(result ->
{
if (result.isFailed())
@ -376,10 +376,10 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
for (int i = 0; i < 3; ++i)
{
contentProvider.offer(ByteBuffer.allocate(bytesPerSecond / 2));
content.offer(ByteBuffer.allocate(bytesPerSecond / 2));
Thread.sleep(2500);
}
contentProvider.close();
content.close();
assertThat(scenario.requestLog.poll(5, TimeUnit.SECONDS), containsString(" 408"));
@ -389,7 +389,7 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
Object result = results.poll(5, TimeUnit.SECONDS);
assertNotNull(result);
if (result instanceof Integer)
assertThat((Integer)result, is(408));
assertThat(result, is(408));
else
assertThat(result, instanceOf(Throwable.class));
}
@ -419,10 +419,10 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
}
});
DeferredContentProvider contentProvider = new DeferredContentProvider();
AsyncRequestContent content = new AsyncRequestContent();
CountDownLatch resultLatch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.content(contentProvider)
.body(content)
.send(result ->
{
if (result.getResponse().getStatus() == HttpStatus.OK_200)
@ -431,10 +431,10 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
for (int i = 0; i < 3; ++i)
{
contentProvider.offer(ByteBuffer.allocate(bytesPerSecond * 2));
content.offer(ByteBuffer.allocate(bytesPerSecond * 2));
Thread.sleep(2500);
}
contentProvider.close();
content.close();
assertTrue(handlerLatch.await(5, TimeUnit.SECONDS));
assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
@ -454,10 +454,10 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
try (StacklessLogging ignore = new StacklessLogging(HttpChannel.class))
{
DeferredContentProvider contentProvider = new DeferredContentProvider(ByteBuffer.allocate(1));
AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.allocate(1));
CountDownLatch resultLatch = new CountDownLatch(1);
scenario.client.POST(scenario.newURI())
.content(contentProvider)
.body(content)
.send(result ->
{
if (result.getResponse().getStatus() == HttpStatus.INTERNAL_SERVER_ERROR_500)
@ -467,7 +467,7 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
// Blocking read should timeout.
assertTrue(handlerLatch.await(2 * httpIdleTimeout, TimeUnit.MILLISECONDS));
// Complete the request.
contentProvider.close();
content.close();
assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
}
}
@ -520,10 +520,10 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
});
scenario.setServerIdleTimeout(idleTimeout);
DeferredContentProvider contentProvider = new DeferredContentProvider(ByteBuffer.allocate(1));
AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.allocate(1));
CountDownLatch resultLatch = new CountDownLatch(1);
scenario.client.POST(scenario.newURI())
.content(contentProvider)
.body(content)
.send(result ->
{
if (result.getResponse().getStatus() == HttpStatus.INTERNAL_SERVER_ERROR_500)
@ -533,7 +533,7 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
// Async read should timeout.
assertTrue(handlerLatch.await(2 * idleTimeout, TimeUnit.MILLISECONDS));
// Complete the request.
contentProvider.close();
content.close();
assertTrue(resultLatch.await(5, TimeUnit.SECONDS));
}
@ -567,11 +567,11 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
System.arraycopy(data, 0, data1, 0, data1.length);
byte[] data2 = new byte[data.length - data1.length];
System.arraycopy(data, data1.length, data2, 0, data2.length);
DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(data1));
AsyncRequestContent content = new AsyncRequestContent(ByteBuffer.wrap(data1));
CountDownLatch latch = new CountDownLatch(1);
scenario.client.newRequest(scenario.newURI())
.path(scenario.servletPath)
.content(content)
.body(content)
.send(new BufferingResponseListener()
{
@Override
@ -691,30 +691,4 @@ public class ServerTimeoutsTest extends AbstractTest<TransportScenario>
}
}
}
private static class BlockingWriteHandler extends AbstractHandler
{
private final CountDownLatch handlerLatch;
private BlockingWriteHandler(CountDownLatch handlerLatch)
{
this.handlerLatch = handlerLatch;
}
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException
{
baseRequest.setHandled(true);
ServletOutputStream output = response.getOutputStream();
try
{
output.write(new byte[64 * 1024 * 1024]);
}
catch (IOException x)
{
handlerLatch.countDown();
throw x;
}
}
}
}

View File

@ -36,9 +36,9 @@ import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.client.util.DigestAuthentication;
import org.eclipse.jetty.client.util.StringContentProvider;
import org.eclipse.jetty.client.util.StringRequestContent;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.security.AbstractLoginService;
import org.eclipse.jetty.security.ConstraintMapping;
@ -53,7 +53,6 @@ import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Credential;
@ -63,6 +62,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class DigestPostTest
@ -178,12 +178,12 @@ public class DigestPostTest
String result = IO.toString(socket.getInputStream());
assertTrue(result.startsWith("HTTP/1.1 401 Unauthorized"));
assertEquals(null, _received);
assertNull(_received);
int n = result.indexOf("nonce=");
String nonce = result.substring(n + 7, result.indexOf('"', n + 7));
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] b = md.digest(String.valueOf(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())).getBytes(org.eclipse.jetty.util.StringUtil.__ISO_8859_1));
byte[] b = md.digest(String.valueOf(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())).getBytes(StandardCharsets.ISO_8859_1));
String cnonce = encode(b);
String digest = "Digest username=\"testuser\" realm=\"test\" nonce=\"" + nonce + "\" uri=\"/test/\" algorithm=MD5 response=\"" +
newResponse("POST", "/test/", cnonce, "testuser", "test", "password", nonce, "auth") +
@ -218,7 +218,7 @@ public class DigestPostTest
("POST /test/ HTTP/1.1\r\n" +
"Host: 127.0.0.1:" + ((NetworkConnector)_server.getConnectors()[0]).getLocalPort() + "\r\n" +
"Content-Length: " + bytes.length + "\r\n" +
"\r\n").getBytes("UTF-8"));
"\r\n").getBytes(StandardCharsets.UTF_8));
socket.getOutputStream().write(bytes);
socket.getOutputStream().flush();
@ -229,12 +229,12 @@ public class DigestPostTest
String result = new String(buf, 0, len, StandardCharsets.UTF_8);
assertTrue(result.startsWith("HTTP/1.1 401 Unauthorized"));
assertEquals(null, _received);
assertNull(_received);
int n = result.indexOf("nonce=");
String nonce = result.substring(n + 7, result.indexOf('"', n + 7));
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] b = md.digest(String.valueOf(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())).getBytes(StringUtil.__ISO_8859_1));
byte[] b = md.digest(String.valueOf(TimeUnit.NANOSECONDS.toMillis(System.nanoTime())).getBytes(StandardCharsets.ISO_8859_1));
String cnonce = encode(b);
String digest = "Digest username=\"testuser\" realm=\"test\" nonce=\"" + nonce + "\" uri=\"/test/\" algorithm=MD5 response=\"" +
newResponse("POST", "/test/", cnonce, "testuser", "test", "password", nonce, "auth") +
@ -246,7 +246,7 @@ public class DigestPostTest
"Host: 127.0.0.1:" + ((NetworkConnector)_server.getConnectors()[0]).getLocalPort() + "\r\n" +
"Content-Length: " + bytes.length + "\r\n" +
"Authorization: " + digest + "\r\n" +
"\r\n").getBytes("UTF-8"));
"\r\n").getBytes(StandardCharsets.UTF_8));
socket.getOutputStream().write(bytes);
socket.getOutputStream().flush();
@ -270,7 +270,7 @@ public class DigestPostTest
Request request = client.newRequest(srvUrl);
request.method(HttpMethod.POST);
request.content(new BytesContentProvider(__message.getBytes("UTF8")));
request.body(new BytesRequestContent(__message.getBytes(StandardCharsets.UTF_8)));
_received = null;
request = request.timeout(5, TimeUnit.SECONDS);
ContentResponse response = request.send();
@ -298,7 +298,7 @@ public class DigestPostTest
Request request = client.newRequest(srvUrl);
request.method(HttpMethod.POST);
request.content(new StringContentProvider(sent));
request.body(new StringRequestContent(sent));
_received = null;
request = request.timeout(5, TimeUnit.SECONDS);
ContentResponse response = request.send();
@ -334,30 +334,30 @@ public class DigestPostTest
MessageDigest md = MessageDigest.getInstance("MD5");
// calc A1 digest
md.update(principal.getBytes(StringUtil.__ISO_8859_1));
md.update(principal.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte)':');
md.update(realm.getBytes(StringUtil.__ISO_8859_1));
md.update(realm.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte)':');
md.update(credentials.getBytes(StringUtil.__ISO_8859_1));
md.update(credentials.getBytes(StandardCharsets.ISO_8859_1));
byte[] ha1 = md.digest();
// calc A2 digest
md.reset();
md.update(method.getBytes(StringUtil.__ISO_8859_1));
md.update(method.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte)':');
md.update(uri.getBytes(StringUtil.__ISO_8859_1));
md.update(uri.getBytes(StandardCharsets.ISO_8859_1));
byte[] ha2 = md.digest();
md.update(TypeUtil.toString(ha1, 16).getBytes(StringUtil.__ISO_8859_1));
md.update(TypeUtil.toString(ha1, 16).getBytes(StandardCharsets.ISO_8859_1));
md.update((byte)':');
md.update(nonce.getBytes(StringUtil.__ISO_8859_1));
md.update(nonce.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte)':');
md.update(NC.getBytes(StringUtil.__ISO_8859_1));
md.update(NC.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte)':');
md.update(cnonce.getBytes(StringUtil.__ISO_8859_1));
md.update(cnonce.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte)':');
md.update(qop.getBytes(StringUtil.__ISO_8859_1));
md.update(qop.getBytes(StandardCharsets.ISO_8859_1));
md.update((byte)':');
md.update(TypeUtil.toString(ha2, 16).getBytes(StringUtil.__ISO_8859_1));
md.update(TypeUtil.toString(ha2, 16).getBytes(StandardCharsets.ISO_8859_1));
byte[] digest = md.digest();
// check digest
@ -366,11 +366,11 @@ public class DigestPostTest
private static String encode(byte[] data)
{
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < data.length; i++)
StringBuilder buffer = new StringBuilder();
for (byte datum : data)
{
buffer.append(Integer.toHexString((data[i] & 0xf0) >>> 4));
buffer.append(Integer.toHexString(data[i] & 0x0f));
buffer.append(Integer.toHexString((datum & 0xf0) >>> 4));
buffer.append(Integer.toHexString(datum & 0x0f));
}
return buffer.toString();
}

View File

@ -22,6 +22,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
@ -29,7 +30,7 @@ import org.eclipse.jetty.client.api.AuthenticationStore;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.util.BasicAuthentication;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.client.util.BytesRequestContent;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.security.JDBCLoginService;
@ -80,7 +81,7 @@ public class JdbcLoginServiceTest
try (FileOutputStream out = new FileOutputStream(content))
{
out.write(_content.getBytes("utf-8"));
out.write(_content.getBytes(StandardCharsets.UTF_8));
}
//drop any tables that might have existed
@ -123,7 +124,7 @@ public class JdbcLoginServiceTest
Request request = _client.newRequest(_baseUri.resolve("output.txt"));
request.method(HttpMethod.PUT);
request.content(new BytesContentProvider(_content.getBytes()));
request.body(new BytesRequestContent(_content.getBytes()));
ContentResponse response = request.send();
int responseStatus = response.getStatus();
boolean statusOk = (responseStatus == 200 || responseStatus == 201);
@ -198,7 +199,7 @@ public class JdbcLoginServiceTest
Request request = _client.newRequest(_baseUri.resolve("test"));
request.method(HttpMethod.POST);
request.content(new BytesContentProvider(_content.getBytes()));
request.body(new BytesRequestContent(_content.getBytes()));
ContentResponse response = request.send();
assertEquals(HttpStatus.OK_200, response.getStatus());
assertEquals(_content, _testServer.getTestHandler().getRequestContent());

View File

@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.FormContentProvider;
import org.eclipse.jetty.client.util.FormRequestContent;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Server;
@ -96,7 +96,7 @@ public class RequestDispatchedSessionTest
ContentResponse response = client.newRequest(server.getURI().resolve("/login"))
.method(HttpMethod.POST)
.content(new FormContentProvider(postForm))
.body(new FormRequestContent(postForm))
.send();
assertThat("Response status", response.getStatus(), is(HttpStatus.OK_200));
}
@ -115,7 +115,6 @@ public class RequestDispatchedSessionTest
}
request.getSession(true).setAttribute(USERNAME, request.getParameter("username"));
request.getRequestDispatcher("/user").forward(request, response);
return;
}
}
}