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:
commit
fa54c74946
|
@ -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
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
* +---+ +---+ +---+ +---+ +---+
|
||||
* | | | | | | | | | |
|
||||
* +---+ +---+ +---+ +---+ +---+
|
||||
* ^ ^ ^ ^
|
||||
* | | --> 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()));
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
case FAILURE:
|
||||
if (current == RequestState.FAILURE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
else
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
private class CommitCallback implements Callback
|
||||
@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);
|
||||
}
|
||||
|
||||
@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
|
||||
{
|
||||
committed = true;
|
||||
if (headersToCommit(exchange))
|
||||
{
|
||||
proceed = true;
|
||||
// Was any content sent while committing?
|
||||
ByteBuffer contentBuffer = content.getContent();
|
||||
if (contentBuffer != null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
|
||||
while (true)
|
||||
{
|
||||
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;
|
||||
}
|
||||
// There was some concurrent error?
|
||||
if (!proceed)
|
||||
return;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleteFailure(Throwable failure)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else if (expect100)
|
||||
{
|
||||
if (LOG.isDebugEnabled())
|
||||
LOG.debug("Expecting 100 Continue for {}", exchange.getRequest());
|
||||
}
|
||||
else
|
||||
{
|
||||
demand();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void failed(Throwable failure)
|
||||
public void failed(Throwable x)
|
||||
{
|
||||
HttpContent content = HttpSender.this.content;
|
||||
if (content == null)
|
||||
return;
|
||||
content.failed(failure);
|
||||
anyToFailure(failure);
|
||||
if (callback != null)
|
||||
callback.failed(x);
|
||||
anyToFailure(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InvocationType getInvocationType()
|
||||
{
|
||||
return InvocationType.NON_BLOCKING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/**
|
||||
|
|
|
@ -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 => produce an empty content</li>
|
||||
* <li>when the content is available:
|
||||
* <ul>
|
||||
* <li>when {@code emitInitialContent == false} => produce an empty content</li>
|
||||
* <li>when {@code emitInitialContent == true} => 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 => do not produce content</li>
|
||||
* <li>when the content is available => 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
this.exchange = exchange;
|
||||
this.contentBuffer = contentBuffer;
|
||||
this.lastContent = lastContent;
|
||||
this.callback = callback;
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
release();
|
||||
callback.failed(cause);
|
||||
}
|
||||
|
||||
private void release()
|
||||
{
|
||||
pool.release(buffer);
|
||||
}
|
||||
super.failed(x);
|
||||
HttpClient httpClient = getHttpChannel().getHttpDestination().getHttpClient();
|
||||
ByteBufferPool bufferPool = httpClient.getByteBufferPool();
|
||||
bufferPool.release(chunkBuffer);
|
||||
chunkBuffer = null;
|
||||
contentBuffer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,7 +63,10 @@ import org.slf4j.LoggerFactory;
|
|||
* <input type="file" name="icon" />
|
||||
* </form>
|
||||
* </pre>
|
||||
*
|
||||
* @deprecated use {@link MultiPartRequestContent} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class MultiPartContentProvider extends AbstractTypedContentProvider implements AsyncContentProvider, Closeable
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MultiPartContentProvider.class);
|
||||
|
|
|
@ -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>
|
||||
* <form method="POST" enctype="multipart/form-data" accept-charset="UTF-8">
|
||||
* <input type="text" name="field" value="foo" />
|
||||
* <input type="file" name="icon" />
|
||||
* </form>
|
||||
* </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
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
* {
|
||||
* @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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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 ->
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 ByteBuffer current;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
public boolean hasNext()
|
||||
public SubscriptionImpl(Consumer consumer, boolean emitInitialContent)
|
||||
{
|
||||
if (current == null)
|
||||
{
|
||||
current = generator.apply(index++);
|
||||
if (current == null)
|
||||
current = DONE;
|
||||
}
|
||||
return current != DONE;
|
||||
super(consumer, emitInitialContent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer next()
|
||||
protected boolean produceContent(Producer producer)
|
||||
{
|
||||
ByteBuffer result = current;
|
||||
current = null;
|
||||
if (result == null)
|
||||
throw new NoSuchElementException();
|
||||
return result;
|
||||
ByteBuffer buffer = generator.apply(index++);
|
||||
boolean last = false;
|
||||
if (buffer == null)
|
||||
{
|
||||
buffer = BufferUtil.EMPTY_BUFFER;
|
||||
last = true;
|
||||
}
|
||||
return producer.produce(buffer, last, Callback.NOOP);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ->
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
return new AbstractSubscription(consumer, emitInitialContent)
|
||||
{
|
||||
private int count;
|
||||
|
||||
@Override
|
||||
public Iterator<ByteBuffer> iterator()
|
||||
protected boolean produceContent(Producer producer) throws Exception
|
||||
{
|
||||
return new Iterator<>()
|
||||
{
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer next()
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,17 +101,13 @@ public class Usage
|
|||
|
||||
client.newRequest("localhost", 8080)
|
||||
// Send asynchronously
|
||||
.send(new Response.CompleteListener()
|
||||
{
|
||||
@Override
|
||||
public void onComplete(Result result)
|
||||
.send(result ->
|
||||
{
|
||||
if (result.isSucceeded())
|
||||
{
|
||||
responseRef.set(result.getResponse());
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS));
|
||||
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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`.
|
||||
Here’s 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]]
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,36 +137,55 @@ public class HttpSenderOverHTTP2 extends HttpSender
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void sendContent(HttpExchange exchange, HttpContent content, Callback callback)
|
||||
{
|
||||
if (content.isConsumed())
|
||||
{
|
||||
// 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();
|
||||
}
|
||||
else
|
||||
protected void sendContent(HttpExchange exchange, ByteBuffer contentBuffer, boolean lastContent, Callback callback)
|
||||
{
|
||||
Stream stream = getHttpChannel().getStream();
|
||||
Supplier<HttpFields> trailerSupplier = exchange.getRequest().getTrailers();
|
||||
sendContent(stream, content, trailerSupplier, callback);
|
||||
}
|
||||
sendContent(stream, contentBuffer, lastContent, trailerSupplier, callback);
|
||||
}
|
||||
|
||||
private void sendContent(Stream stream, HttpContent content, Supplier<HttpFields> trailerSupplier, Callback callback)
|
||||
private void sendContent(Stream stream, ByteBuffer buffer, boolean lastContent, Supplier<HttpFields> trailerSupplier, Callback callback)
|
||||
{
|
||||
boolean lastContent = content.isLast();
|
||||
HttpFields trailers = null;
|
||||
boolean endStream = false;
|
||||
boolean hasContent = buffer.hasRemaining();
|
||||
if (lastContent)
|
||||
{
|
||||
trailers = trailerSupplier == null ? null : trailerSupplier.get();
|
||||
endStream = trailers == null || trailers.size() == 0;
|
||||
// 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
|
||||
{
|
||||
if (hasContent)
|
||||
{
|
||||
DataFrame dataFrame = new DataFrame(stream.getId(), buffer, false);
|
||||
stream.data(dataFrame, callback);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't send empty non-last content.
|
||||
callback.succeeded();
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
Callback wrapped = Callback.from(() -> succeeded(callback, last), failure -> failed(callback, failure));
|
||||
if (buffer.hasRemaining())
|
||||
{
|
||||
delegate.offer(buffer, wrapped);
|
||||
}
|
||||
else
|
||||
{
|
||||
wrapped.succeeded();
|
||||
}
|
||||
if (last)
|
||||
delegate.close();
|
||||
}
|
||||
|
||||
ByteBuffer buffer = iterator.next();
|
||||
if (buffer == null)
|
||||
return Action.IDLE;
|
||||
private void succeeded(Callback callback, boolean last)
|
||||
{
|
||||
callback.succeeded();
|
||||
if (!last)
|
||||
subscription.demand();
|
||||
}
|
||||
|
||||
deferred.offer(buffer, this);
|
||||
return Action.SCHEDULED;
|
||||
private void failed(Callback callback, Throwable failure)
|
||||
{
|
||||
callback.failed(failure);
|
||||
onFailure(failure);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void succeeded()
|
||||
public void onFailure(Throwable failure)
|
||||
{
|
||||
if (iterator instanceof Callback)
|
||||
((Callback)iterator).succeeded();
|
||||
super.succeeded();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteSuccess()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (iterator instanceof Closeable)
|
||||
((Closeable)iterator).close();
|
||||
deferred.close();
|
||||
}
|
||||
catch (Throwable x)
|
||||
{
|
||||
_log.trace("IGNORED", x);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCompleteFailure(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 ->
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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<>();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 è
|
||||
byte[] data = new byte[]{(byte)0xC3, (byte)0xA8}; // UTF-8 representation of è
|
||||
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());
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue