Merged branch 'jetty-9.3.x' into 'master'.
This commit is contained in:
commit
cb79379b79
|
@ -21,21 +21,66 @@ package org.eclipse.jetty.http2;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard HTTP/2 error codes.
|
||||||
|
*/
|
||||||
public enum ErrorCode
|
public enum ErrorCode
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Indicates no errors.
|
||||||
|
*/
|
||||||
NO_ERROR(0),
|
NO_ERROR(0),
|
||||||
|
/**
|
||||||
|
* Indicates a generic HTTP/2 protocol violation.
|
||||||
|
*/
|
||||||
PROTOCOL_ERROR(1),
|
PROTOCOL_ERROR(1),
|
||||||
|
/**
|
||||||
|
* Indicates an internal error.
|
||||||
|
*/
|
||||||
INTERNAL_ERROR(2),
|
INTERNAL_ERROR(2),
|
||||||
|
/**
|
||||||
|
* Indicates a HTTP/2 flow control violation.
|
||||||
|
*/
|
||||||
FLOW_CONTROL_ERROR(3),
|
FLOW_CONTROL_ERROR(3),
|
||||||
|
/**
|
||||||
|
* Indicates that a SETTINGS frame did not receive a reply in a timely manner.
|
||||||
|
*/
|
||||||
SETTINGS_TIMEOUT_ERROR(4),
|
SETTINGS_TIMEOUT_ERROR(4),
|
||||||
|
/**
|
||||||
|
* Indicates that a stream frame has been received after the stream was closed.
|
||||||
|
*/
|
||||||
STREAM_CLOSED_ERROR(5),
|
STREAM_CLOSED_ERROR(5),
|
||||||
|
/**
|
||||||
|
* Indicates that a frame has an invalid length.
|
||||||
|
*/
|
||||||
FRAME_SIZE_ERROR(6),
|
FRAME_SIZE_ERROR(6),
|
||||||
|
/**
|
||||||
|
* Indicates that a stream was rejected before application processing.
|
||||||
|
*/
|
||||||
REFUSED_STREAM_ERROR(7),
|
REFUSED_STREAM_ERROR(7),
|
||||||
|
/**
|
||||||
|
* Indicates that a stream is no longer needed.
|
||||||
|
*/
|
||||||
CANCEL_STREAM_ERROR(8),
|
CANCEL_STREAM_ERROR(8),
|
||||||
|
/**
|
||||||
|
* Indicates inability to maintain the HPACK compression context.
|
||||||
|
*/
|
||||||
COMPRESSION_ERROR(9),
|
COMPRESSION_ERROR(9),
|
||||||
|
/**
|
||||||
|
* Indicates that the connection established by a HTTP CONNECT was abnormally closed.
|
||||||
|
*/
|
||||||
HTTP_CONNECT_ERROR(10),
|
HTTP_CONNECT_ERROR(10),
|
||||||
|
/**
|
||||||
|
* Indicates that the other peer might be generating excessive load.
|
||||||
|
*/
|
||||||
ENHANCE_YOUR_CALM_ERROR(11),
|
ENHANCE_YOUR_CALM_ERROR(11),
|
||||||
|
/**
|
||||||
|
* Indicates that the transport properties do not meet minimum security requirements.
|
||||||
|
*/
|
||||||
INADEQUATE_SECURITY_ERROR(12),
|
INADEQUATE_SECURITY_ERROR(12),
|
||||||
|
/**
|
||||||
|
* Indicates that HTTP/1.1 must be used rather than HTTP/2.
|
||||||
|
*/
|
||||||
HTTP_1_1_REQUIRED_ERROR(13);
|
HTTP_1_1_REQUIRED_ERROR(13);
|
||||||
|
|
||||||
public final int code;
|
public final int code;
|
||||||
|
|
|
@ -24,8 +24,11 @@ import org.eclipse.jetty.client.HttpExchange;
|
||||||
import org.eclipse.jetty.client.HttpReceiver;
|
import org.eclipse.jetty.client.HttpReceiver;
|
||||||
import org.eclipse.jetty.client.HttpSender;
|
import org.eclipse.jetty.client.HttpSender;
|
||||||
import org.eclipse.jetty.client.api.Result;
|
import org.eclipse.jetty.client.api.Result;
|
||||||
|
import org.eclipse.jetty.http2.ErrorCode;
|
||||||
import org.eclipse.jetty.http2.api.Session;
|
import org.eclipse.jetty.http2.api.Session;
|
||||||
import org.eclipse.jetty.http2.api.Stream;
|
import org.eclipse.jetty.http2.api.Stream;
|
||||||
|
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
|
|
||||||
public class HttpChannelOverHTTP2 extends HttpChannel
|
public class HttpChannelOverHTTP2 extends HttpChannel
|
||||||
{
|
{
|
||||||
|
@ -33,6 +36,7 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
||||||
private final Session session;
|
private final Session session;
|
||||||
private final HttpSenderOverHTTP2 sender;
|
private final HttpSenderOverHTTP2 sender;
|
||||||
private final HttpReceiverOverHTTP2 receiver;
|
private final HttpReceiverOverHTTP2 receiver;
|
||||||
|
private Stream stream;
|
||||||
|
|
||||||
public HttpChannelOverHTTP2(HttpDestination destination, HttpConnectionOverHTTP2 connection, Session session)
|
public HttpChannelOverHTTP2(HttpDestination destination, HttpConnectionOverHTTP2 connection, Session session)
|
||||||
{
|
{
|
||||||
|
@ -65,6 +69,16 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
||||||
return receiver;
|
return receiver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Stream getStream()
|
||||||
|
{
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStream(Stream stream)
|
||||||
|
{
|
||||||
|
this.stream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send()
|
public void send()
|
||||||
{
|
{
|
||||||
|
@ -79,6 +93,19 @@ public class HttpChannelOverHTTP2 extends HttpChannel
|
||||||
connection.release(this);
|
connection.release(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean abort(HttpExchange exchange, Throwable requestFailure, Throwable responseFailure)
|
||||||
|
{
|
||||||
|
boolean aborted = super.abort(exchange, requestFailure, responseFailure);
|
||||||
|
if (aborted)
|
||||||
|
{
|
||||||
|
Stream stream = getStream();
|
||||||
|
if (stream != null)
|
||||||
|
stream.reset(new ResetFrame(stream.getId(), ErrorCode.CANCEL_STREAM_ERROR.code), Callback.NOOP);
|
||||||
|
}
|
||||||
|
return aborted;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void exchangeTerminated(HttpExchange exchange, Result result)
|
public void exchangeTerminated(HttpExchange exchange, Result result)
|
||||||
{
|
{
|
||||||
|
|
|
@ -89,7 +89,10 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
|
||||||
{
|
{
|
||||||
HttpExchange exchange = getHttpExchange();
|
HttpExchange exchange = getHttpExchange();
|
||||||
if (exchange == null)
|
if (exchange == null)
|
||||||
|
{
|
||||||
|
callback.failed(new IOException("terminated"));
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (responseContent(exchange, frame.getData(), callback))
|
if (responseContent(exchange, frame.getData(), callback))
|
||||||
{
|
{
|
||||||
|
|
|
@ -33,8 +33,6 @@ import org.eclipse.jetty.util.Promise;
|
||||||
|
|
||||||
public class HttpSenderOverHTTP2 extends HttpSender
|
public class HttpSenderOverHTTP2 extends HttpSender
|
||||||
{
|
{
|
||||||
private Stream stream;
|
|
||||||
|
|
||||||
public HttpSenderOverHTTP2(HttpChannelOverHTTP2 channel)
|
public HttpSenderOverHTTP2(HttpChannelOverHTTP2 channel)
|
||||||
{
|
{
|
||||||
super(channel);
|
super(channel);
|
||||||
|
@ -59,7 +57,7 @@ public class HttpSenderOverHTTP2 extends HttpSender
|
||||||
@Override
|
@Override
|
||||||
public void succeeded(Stream stream)
|
public void succeeded(Stream stream)
|
||||||
{
|
{
|
||||||
HttpSenderOverHTTP2.this.stream = stream;
|
getHttpChannel().setStream(stream);
|
||||||
stream.setIdleTimeout(request.getIdleTimeout());
|
stream.setIdleTimeout(request.getIdleTimeout());
|
||||||
|
|
||||||
if (content.hasContent() && !expects100Continue(request))
|
if (content.hasContent() && !expects100Continue(request))
|
||||||
|
@ -95,15 +93,9 @@ public class HttpSenderOverHTTP2 extends HttpSender
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
Stream stream = getHttpChannel().getStream();
|
||||||
DataFrame frame = new DataFrame(stream.getId(), content.getByteBuffer(), content.isLast());
|
DataFrame frame = new DataFrame(stream.getId(), content.getByteBuffer(), content.isLast());
|
||||||
stream.data(frame, callback);
|
stream.data(frame, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void reset()
|
|
||||||
{
|
|
||||||
super.reset();
|
|
||||||
stream = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,11 @@
|
||||||
package org.eclipse.jetty.http2.client.http;
|
package org.eclipse.jetty.http2.client.http;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
||||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||||
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
|
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.http2.server.RawHTTP2ServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.ConnectionFactory;
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
import org.eclipse.jetty.server.HttpConfiguration;
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
|
@ -38,25 +41,38 @@ public class AbstractTest
|
||||||
protected ServerConnector connector;
|
protected ServerConnector connector;
|
||||||
protected HttpClient client;
|
protected HttpClient client;
|
||||||
|
|
||||||
protected void start(int maxConcurrentStreams, Handler handler) throws Exception
|
protected void start(ServerSessionListener listener) throws Exception
|
||||||
|
{
|
||||||
|
prepareServer(new RawHTTP2ServerConnectionFactory(new HttpConfiguration(), listener));
|
||||||
|
server.start();
|
||||||
|
prepareClient();
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void start(Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
prepareServer(new HTTP2ServerConnectionFactory(new HttpConfiguration()));
|
||||||
|
server.setHandler(handler);
|
||||||
|
server.start();
|
||||||
|
prepareClient();
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void prepareServer(ConnectionFactory connectionFactory)
|
||||||
{
|
{
|
||||||
QueuedThreadPool serverExecutor = new QueuedThreadPool();
|
QueuedThreadPool serverExecutor = new QueuedThreadPool();
|
||||||
serverExecutor.setName("server");
|
serverExecutor.setName("server");
|
||||||
server = new Server(serverExecutor);
|
server = new Server(serverExecutor);
|
||||||
|
connector = new ServerConnector(server, 1, 1, connectionFactory);
|
||||||
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(new HttpConfiguration());
|
|
||||||
http2.setMaxConcurrentStreams(maxConcurrentStreams);
|
|
||||||
connector = new ServerConnector(server, 1, 1, http2);
|
|
||||||
server.addConnector(connector);
|
server.addConnector(connector);
|
||||||
|
}
|
||||||
|
|
||||||
server.setHandler(handler);
|
protected void prepareClient() throws Exception
|
||||||
server.start();
|
{
|
||||||
|
|
||||||
client = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client()), null);
|
client = new HttpClient(new HttpClientTransportOverHTTP2(new HTTP2Client()), null);
|
||||||
QueuedThreadPool clientExecutor = new QueuedThreadPool();
|
QueuedThreadPool clientExecutor = new QueuedThreadPool();
|
||||||
clientExecutor.setName("client");
|
clientExecutor.setName("client");
|
||||||
client.setExecutor(clientExecutor);
|
client.setExecutor(clientExecutor);
|
||||||
client.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
|
|
@ -18,19 +18,32 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.http2.client.http;
|
package org.eclipse.jetty.http2.client.http;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.HttpClient;
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
|
import org.eclipse.jetty.http.MetaData;
|
||||||
|
import org.eclipse.jetty.http2.api.Stream;
|
||||||
|
import org.eclipse.jetty.http2.api.server.ServerSessionListener;
|
||||||
import org.eclipse.jetty.http2.client.HTTP2Client;
|
import org.eclipse.jetty.http2.client.HTTP2Client;
|
||||||
|
import org.eclipse.jetty.http2.frames.DataFrame;
|
||||||
|
import org.eclipse.jetty.http2.frames.HeadersFrame;
|
||||||
|
import org.eclipse.jetty.http2.frames.ResetFrame;
|
||||||
|
import org.eclipse.jetty.util.Callback;
|
||||||
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class HttpClientTransportOverHTTP2Test
|
public class HttpClientTransportOverHTTP2Test extends AbstractTest
|
||||||
{
|
{
|
||||||
@Test
|
@Test
|
||||||
public void testPropertiesAreForwarded() throws Exception
|
public void testPropertiesAreForwarded() throws Exception
|
||||||
|
@ -56,6 +69,83 @@ public class HttpClientTransportOverHTTP2Test
|
||||||
Assert.assertTrue(http2Client.isStopped());
|
Assert.assertTrue(http2Client.isStopped());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRequestAbortSendsResetFrame() throws Exception
|
||||||
|
{
|
||||||
|
CountDownLatch resetLatch = new CountDownLatch(1);
|
||||||
|
start(new ServerSessionListener.Adapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||||
|
{
|
||||||
|
return new Stream.Listener.Adapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onReset(Stream stream, ResetFrame frame)
|
||||||
|
{
|
||||||
|
resetLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.onRequestCommit(request -> request.abort(new Exception("explicitly_aborted_by_test")))
|
||||||
|
.send();
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
catch (ExecutionException x)
|
||||||
|
{
|
||||||
|
Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testResponseAbortSendsResetFrame() throws Exception
|
||||||
|
{
|
||||||
|
CountDownLatch resetLatch = new CountDownLatch(1);
|
||||||
|
start(new ServerSessionListener.Adapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Stream.Listener onNewStream(Stream stream, HeadersFrame frame)
|
||||||
|
{
|
||||||
|
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, HttpStatus.OK_200, new HttpFields());
|
||||||
|
stream.headers(new HeadersFrame(stream.getId(), metaData, null, false), new Callback()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void succeeded()
|
||||||
|
{
|
||||||
|
ByteBuffer data = ByteBuffer.allocate(1024);
|
||||||
|
stream.data(new DataFrame(stream.getId(), data, false), NOOP);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Stream.Listener.Adapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void onReset(Stream stream, ResetFrame frame)
|
||||||
|
{
|
||||||
|
resetLatch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
client.newRequest("localhost", connector.getLocalPort())
|
||||||
|
.onResponseContent((response, buffer) -> response.abort(new Exception("explicitly_aborted_by_test")))
|
||||||
|
.send();
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
catch (ExecutionException x)
|
||||||
|
{
|
||||||
|
Assert.assertTrue(resetLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Ignore
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
public void testExternalServer() throws Exception
|
public void testExternalServer() throws Exception
|
||||||
|
|
|
@ -28,6 +28,9 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import org.eclipse.jetty.client.api.ContentResponse;
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
@ -35,6 +38,17 @@ import org.junit.Test;
|
||||||
|
|
||||||
public class MaxConcurrentStreamsTest extends AbstractTest
|
public class MaxConcurrentStreamsTest extends AbstractTest
|
||||||
{
|
{
|
||||||
|
private void start(int maxConcurrentStreams, Handler handler) throws Exception
|
||||||
|
{
|
||||||
|
HTTP2ServerConnectionFactory http2 = new HTTP2ServerConnectionFactory(new HttpConfiguration());
|
||||||
|
http2.setMaxConcurrentStreams(maxConcurrentStreams);
|
||||||
|
prepareServer(http2);
|
||||||
|
server.setHandler(handler);
|
||||||
|
server.start();
|
||||||
|
prepareClient();
|
||||||
|
client.start();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOneConcurrentStream() throws Exception
|
public void testOneConcurrentStream() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
@ -51,7 +52,7 @@ public class HttpInput extends ServletInputStream implements Runnable
|
||||||
private final static Content EARLY_EOF_CONTENT = new EofContent("EARLY_EOF");
|
private final static Content EARLY_EOF_CONTENT = new EofContent("EARLY_EOF");
|
||||||
|
|
||||||
private final byte[] _oneByteBuffer = new byte[1];
|
private final byte[] _oneByteBuffer = new byte[1];
|
||||||
private final Queue<Content> _inputQ = new ArrayDeque<>();
|
private final Deque<Content> _inputQ = new ArrayDeque<>();
|
||||||
private final HttpChannelState _channelState;
|
private final HttpChannelState _channelState;
|
||||||
private ReadListener _listener;
|
private ReadListener _listener;
|
||||||
private State _state = STREAM;
|
private State _state = STREAM;
|
||||||
|
@ -369,6 +370,33 @@ public class HttpInput extends ServletInputStream implements Runnable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds some content to the start of this input stream.
|
||||||
|
* <p>Typically used to push back content that has
|
||||||
|
* been read, perhaps mutated. The bytes prepended are
|
||||||
|
* deducted for the contentConsumed total</p>
|
||||||
|
* @param item the content to add
|
||||||
|
* @return true if content channel woken for read
|
||||||
|
*/
|
||||||
|
public boolean prependContent(Content item)
|
||||||
|
{
|
||||||
|
boolean woken=false;
|
||||||
|
synchronized (_inputQ)
|
||||||
|
{
|
||||||
|
_inputQ.push(item);
|
||||||
|
_contentConsumed-=item.remaining();
|
||||||
|
if (LOG.isDebugEnabled())
|
||||||
|
LOG.debug("{} prependContent {}", this, item);
|
||||||
|
|
||||||
|
if (_listener==null)
|
||||||
|
_inputQ.notify();
|
||||||
|
else
|
||||||
|
woken=_channelState.onReadPossible();
|
||||||
|
}
|
||||||
|
|
||||||
|
return woken;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds some content to this input stream.
|
* Adds some content to this input stream.
|
||||||
*
|
*
|
||||||
|
|
|
@ -768,6 +768,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
||||||
old_context = __context.get();
|
old_context = __context.get();
|
||||||
__context.set(_scontext);
|
__context.set(_scontext);
|
||||||
|
|
||||||
|
enterScope(null, getState());
|
||||||
|
|
||||||
// defers the calling of super.doStart()
|
// defers the calling of super.doStart()
|
||||||
startContext();
|
startContext();
|
||||||
|
|
||||||
|
@ -855,7 +857,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu
|
||||||
ClassLoader old_classloader = null;
|
ClassLoader old_classloader = null;
|
||||||
ClassLoader old_webapploader = null;
|
ClassLoader old_webapploader = null;
|
||||||
Thread current_thread = null;
|
Thread current_thread = null;
|
||||||
|
exitScope(null);
|
||||||
Context old_context = __context.get();
|
Context old_context = __context.get();
|
||||||
__context.set(_scontext);
|
__context.set(_scontext);
|
||||||
try
|
try
|
||||||
|
|
|
@ -221,6 +221,67 @@ public class HttpInputTest
|
||||||
assertThat(_history.poll(),nullValue());
|
assertThat(_history.poll(),nullValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReRead() throws Exception
|
||||||
|
{
|
||||||
|
_in.addContent(new TContent("AB"));
|
||||||
|
_in.addContent(new TContent("CD"));
|
||||||
|
_fillAndParseSimulate.offer("EF");
|
||||||
|
_fillAndParseSimulate.offer("GH");
|
||||||
|
assertThat(_in.available(),equalTo(2));
|
||||||
|
assertThat(_in.isFinished(),equalTo(false));
|
||||||
|
assertThat(_in.isReady(),equalTo(true));
|
||||||
|
|
||||||
|
assertThat(_in.getContentConsumed(),equalTo(0L));
|
||||||
|
assertThat(_in.read(),equalTo((int)'A'));
|
||||||
|
assertThat(_in.getContentConsumed(),equalTo(1L));
|
||||||
|
assertThat(_in.read(),equalTo((int)'B'));
|
||||||
|
assertThat(_in.getContentConsumed(),equalTo(2L));
|
||||||
|
|
||||||
|
assertThat(_history.poll(),equalTo("Content succeeded AB"));
|
||||||
|
assertThat(_history.poll(),nullValue());
|
||||||
|
assertThat(_in.read(),equalTo((int)'C'));
|
||||||
|
assertThat(_in.read(),equalTo((int)'D'));
|
||||||
|
|
||||||
|
assertThat(_history.poll(),equalTo("Content succeeded CD"));
|
||||||
|
assertThat(_history.poll(),nullValue());
|
||||||
|
assertThat(_in.read(),equalTo((int)'E'));
|
||||||
|
|
||||||
|
_in.prependContent(new HttpInput.Content(BufferUtil.toBuffer("abcde")));
|
||||||
|
|
||||||
|
assertThat(_in.available(),equalTo(5));
|
||||||
|
assertThat(_in.isFinished(),equalTo(false));
|
||||||
|
assertThat(_in.isReady(),equalTo(true));
|
||||||
|
|
||||||
|
assertThat(_in.getContentConsumed(),equalTo(0L));
|
||||||
|
assertThat(_in.read(),equalTo((int)'a'));
|
||||||
|
assertThat(_in.getContentConsumed(),equalTo(1L));
|
||||||
|
assertThat(_in.read(),equalTo((int)'b'));
|
||||||
|
assertThat(_in.getContentConsumed(),equalTo(2L));
|
||||||
|
assertThat(_in.read(),equalTo((int)'c'));
|
||||||
|
assertThat(_in.read(),equalTo((int)'d'));
|
||||||
|
assertThat(_in.read(),equalTo((int)'e'));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
assertThat(_in.read(),equalTo((int)'F'));
|
||||||
|
|
||||||
|
assertThat(_history.poll(),equalTo("produceContent 2"));
|
||||||
|
assertThat(_history.poll(),equalTo("Content succeeded EF"));
|
||||||
|
assertThat(_history.poll(),nullValue());
|
||||||
|
|
||||||
|
assertThat(_in.read(),equalTo((int)'G'));
|
||||||
|
assertThat(_in.read(),equalTo((int)'H'));
|
||||||
|
|
||||||
|
assertThat(_history.poll(),equalTo("Content succeeded GH"));
|
||||||
|
assertThat(_history.poll(),nullValue());
|
||||||
|
|
||||||
|
assertThat(_in.getContentConsumed(),equalTo(8L));
|
||||||
|
|
||||||
|
assertThat(_history.poll(),nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBlockingRead() throws Exception
|
public void testBlockingRead() throws Exception
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,146 +0,0 @@
|
||||||
//
|
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// All rights reserved. This program and the accompanying materials
|
|
||||||
// are made available under the terms of the Eclipse Public License v1.0
|
|
||||||
// and Apache License v2.0 which accompanies this distribution.
|
|
||||||
//
|
|
||||||
// The Eclipse Public License is available at
|
|
||||||
// http://www.eclipse.org/legal/epl-v10.html
|
|
||||||
//
|
|
||||||
// The Apache License v2.0 is available at
|
|
||||||
// http://www.opensource.org/licenses/apache2.0.php
|
|
||||||
//
|
|
||||||
// You may elect to redistribute this code under either of these licenses.
|
|
||||||
// ========================================================================
|
|
||||||
//
|
|
||||||
|
|
||||||
package org.eclipse.jetty.servlet;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.Matchers.is;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
import javax.servlet.MultipartConfigElement;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServlet;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import javax.servlet.http.Part;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpTester;
|
|
||||||
import org.eclipse.jetty.server.LocalConnector;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
|
||||||
import org.eclipse.jetty.toolchain.test.FS;
|
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|
||||||
import org.junit.AfterClass;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class RequestGetPartsTest
|
|
||||||
{
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public static class DumpPartInfoServlet extends HttpServlet
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
|
||||||
{
|
|
||||||
resp.setContentType("text/plain");
|
|
||||||
PrintWriter out = resp.getWriter();
|
|
||||||
|
|
||||||
for(Part part: req.getParts())
|
|
||||||
{
|
|
||||||
out.printf("Got part: name=%s, size=%,d, filename=%s%n",part.getName(), part.getSize(), part.getSubmittedFileName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Server server;
|
|
||||||
private static LocalConnector connector;
|
|
||||||
private static File locationDir;
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void startServer() throws Exception
|
|
||||||
{
|
|
||||||
Path tmpDir = MavenTestingUtils.getTargetTestingPath("testrequest_getparts");
|
|
||||||
FS.ensureEmpty(tmpDir);
|
|
||||||
|
|
||||||
locationDir = tmpDir.toFile();
|
|
||||||
|
|
||||||
server = new Server();
|
|
||||||
connector = new LocalConnector(server);
|
|
||||||
server.addConnector(connector);
|
|
||||||
|
|
||||||
ServletContextHandler context = new ServletContextHandler();
|
|
||||||
context.setContextPath("/");
|
|
||||||
server.setHandler(context);
|
|
||||||
|
|
||||||
ServletHolder holder = context.addServlet(DumpPartInfoServlet.class,"/dump/*");
|
|
||||||
String location = locationDir.getAbsolutePath();
|
|
||||||
long maxFileSize = 1024*1024*5;
|
|
||||||
long maxRequestSize = 1024*1024*10;
|
|
||||||
int fileSizeThreshold = 1;
|
|
||||||
MultipartConfigElement multipartConfig = new MultipartConfigElement(location,maxFileSize,maxRequestSize,fileSizeThreshold);
|
|
||||||
((ServletHolder.Registration) holder.getRegistration()).setMultipartConfig(multipartConfig);
|
|
||||||
|
|
||||||
server.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterClass
|
|
||||||
public static void stopServer() throws Exception
|
|
||||||
{
|
|
||||||
server.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMultiFileUpload_SameName() throws Exception
|
|
||||||
{
|
|
||||||
// generated and parsed test
|
|
||||||
HttpTester.Request request = HttpTester.newRequest();
|
|
||||||
HttpTester.Response response;
|
|
||||||
|
|
||||||
// test GET
|
|
||||||
request.setMethod("POST");
|
|
||||||
request.setURI("/dump/");
|
|
||||||
request.setVersion("HTTP/1.1");
|
|
||||||
request.setHeader("Host","tester");
|
|
||||||
request.setHeader("Connection","close");
|
|
||||||
|
|
||||||
String boundary="XyXyXy";
|
|
||||||
request.setHeader("Content-Type","multipart/form-data; boundary=" + boundary);
|
|
||||||
|
|
||||||
String crocMsg = "See ya later, aligator.";
|
|
||||||
String aligMsg = "In a while, crocodile.";
|
|
||||||
|
|
||||||
StringBuilder content = new StringBuilder();
|
|
||||||
content.append("--").append(boundary).append("\r\n");
|
|
||||||
content.append("Content-Disposition: form-data; name=\"same\"; filename=\"crocodile.dat\"\r\n");
|
|
||||||
content.append("Content-Type: application/octet-stream\r\n");
|
|
||||||
content.append("\r\n");
|
|
||||||
content.append(crocMsg).append("\r\n");
|
|
||||||
content.append("--").append(boundary).append("\r\n");
|
|
||||||
content.append("Content-Disposition: form-data; name=\"same\"; filename=\"aligator.dat\"\r\n");
|
|
||||||
content.append("Content-Type: application/octet-stream\r\n");
|
|
||||||
content.append("\r\n");
|
|
||||||
content.append(aligMsg).append("\r\n");
|
|
||||||
content.append("--").append(boundary).append("--\r\n");
|
|
||||||
content.append("\r\n");
|
|
||||||
|
|
||||||
request.setContent(content.toString());
|
|
||||||
|
|
||||||
response = HttpTester.parseResponse(connector.getResponses(request.generate()));
|
|
||||||
assertThat("Response status", response.getStatus(), is(HttpServletResponse.SC_OK));
|
|
||||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
|
||||||
|
|
||||||
String responseContents = response.getContent();
|
|
||||||
assertThat("response.contents", responseContents, containsString(String.format("Got part: name=same, size=%d, filename=crocodile.dat",crocMsg.length())));
|
|
||||||
assertThat("response.contents", responseContents, containsString(String.format("Got part: name=same, size=%d, filename=aligator.dat",aligMsg.length())));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,10 +18,9 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.servlets;
|
package org.eclipse.jetty.servlets;
|
||||||
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
|
@ -31,15 +30,10 @@ import static org.junit.Assert.assertTrue;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.Reader;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.servlet.DispatcherType;
|
import javax.servlet.DispatcherType;
|
||||||
|
@ -51,12 +45,9 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
import org.eclipse.jetty.http.HttpTester;
|
import org.eclipse.jetty.http.HttpTester;
|
||||||
import org.eclipse.jetty.servlet.FilterHolder;
|
import org.eclipse.jetty.servlet.FilterHolder;
|
||||||
import org.eclipse.jetty.servlet.ServletTester;
|
import org.eclipse.jetty.servlet.ServletTester;
|
||||||
import org.eclipse.jetty.toolchain.test.FS;
|
|
||||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class MultipartFilterTest
|
public class MultipartFilterTest
|
||||||
|
@ -65,45 +56,16 @@ public class MultipartFilterTest
|
||||||
private ServletTester tester;
|
private ServletTester tester;
|
||||||
FilterHolder multipartFilter;
|
FilterHolder multipartFilter;
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public static class FilenameServlet extends TestServlet
|
public static class FilenameServlet extends TestServlet
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||||
{
|
{
|
||||||
assertThat(req.getAttribute("fileup"), notNullValue());
|
assertNotNull(req.getAttribute("fileup"));
|
||||||
super.doPost(req, resp);
|
super.doPost(req, resp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public static class ParameterListServlet extends TestServlet
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
|
||||||
{
|
|
||||||
resp.setContentType("text/plain");
|
|
||||||
PrintWriter out = resp.getWriter();
|
|
||||||
|
|
||||||
Enumeration<String> pnames = req.getParameterNames();
|
|
||||||
while (pnames.hasMoreElements())
|
|
||||||
{
|
|
||||||
String pname = pnames.nextElement();
|
|
||||||
Object param = req.getParameter(pname);
|
|
||||||
out.printf("Parameter[%s] = ",pname);
|
|
||||||
if (param == null)
|
|
||||||
{
|
|
||||||
out.println(" <null>");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
out.printf("(%s) %s%n",param.getClass().getName(),param);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public static class BoundaryServlet extends TestServlet
|
public static class BoundaryServlet extends TestServlet
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -111,11 +73,9 @@ public class MultipartFilterTest
|
||||||
{
|
{
|
||||||
//we have configured the multipart filter to always store to disk (file size threshold == 1)
|
//we have configured the multipart filter to always store to disk (file size threshold == 1)
|
||||||
//but fileName has no filename param, so only the attribute should be set
|
//but fileName has no filename param, so only the attribute should be set
|
||||||
assertThat("getParameter('fileName')", req.getParameter("fileName"), nullValue());
|
assertNull(req.getParameter("fileName"));
|
||||||
assertThat("getAttribute('fileName')", req.getAttribute("fileName"), notNullValue());
|
assertNotNull(req.getAttribute("fileName"));
|
||||||
|
|
||||||
File f = (File)req.getAttribute("fileName");
|
File f = (File)req.getAttribute("fileName");
|
||||||
assertThat("File exists", f.exists(), is(true));
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
IO.copy(new FileInputStream(f), baos);
|
IO.copy(new FileInputStream(f), baos);
|
||||||
assertEquals(getServletContext().getAttribute("fileName"), baos.toString());
|
assertEquals(getServletContext().getAttribute("fileName"), baos.toString());
|
||||||
|
@ -133,36 +93,34 @@ public class MultipartFilterTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public static class TestServlet extends DumpServlet
|
public static class TestServlet extends DumpServlet
|
||||||
{
|
{
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Override
|
@Override
|
||||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||||
{
|
{
|
||||||
String fileup = req.getParameter("fileup");
|
assertNotNull(req.getParameter("fileup"));
|
||||||
assertThat("getParameter('fileup')",fileup,notNullValue());
|
System.err.println("Fileup="+req.getParameter("fileup"));
|
||||||
|
assertNotNull(req.getParameter("fileup"+MultiPartFilter.CONTENT_TYPE_SUFFIX));
|
||||||
System.err.println("Fileup=" + req.getParameter("fileup"));
|
assertEquals(req.getParameter("fileup"+MultiPartFilter.CONTENT_TYPE_SUFFIX), "application/octet-stream");
|
||||||
|
|
||||||
String fileupType = req.getParameter("fileup" + MultiPartFilter.CONTENT_TYPE_SUFFIX);
|
|
||||||
assertThat("req.getParameter('fileup'+CONTENT_TYPE_SUFFIX)",fileupType,is("application/octet-stream"));
|
|
||||||
super.doPost(req, resp);
|
super.doPost(req, resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception
|
public void setUp() throws Exception
|
||||||
{
|
{
|
||||||
Path tmpDir = MavenTestingUtils.getTargetTestingPath("testmultupart");
|
_dir = File.createTempFile("testmultupart",null);
|
||||||
FS.ensureEmpty(tmpDir);
|
assertTrue(_dir.delete());
|
||||||
|
assertTrue(_dir.mkdir());
|
||||||
_dir = tmpDir.toFile();
|
_dir.deleteOnExit();
|
||||||
|
assertTrue(_dir.isDirectory());
|
||||||
|
|
||||||
tester=new ServletTester("/context");
|
tester=new ServletTester("/context");
|
||||||
tester.getContext().setResourceBase(tmpDir.toString());
|
tester.getContext().setResourceBase(_dir.getCanonicalPath());
|
||||||
tester.getContext().addServlet(TestServlet.class, "/");
|
tester.getContext().addServlet(TestServlet.class, "/");
|
||||||
tester.getContext().setAttribute("javax.servlet.context.tempdir", _dir);
|
tester.getContext().setAttribute("javax.servlet.context.tempdir", _dir);
|
||||||
multipartFilter = tester.getContext().addFilter(MultiPartFilter.class,"/*", EnumSet.of(DispatcherType.REQUEST));
|
multipartFilter = tester.getContext().addFilter(MultiPartFilter.class,"/*", EnumSet.of(DispatcherType.REQUEST));
|
||||||
|
@ -832,7 +790,6 @@ public class MultipartFilterTest
|
||||||
assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
|
assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public static class TestServletParameterMap extends DumpServlet
|
public static class TestServletParameterMap extends DumpServlet
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
|
@ -843,7 +800,7 @@ public class MultipartFilterTest
|
||||||
super.doPost(req, resp);
|
super.doPost(req, resp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that the getParameterMap() call is correctly unencoding the parameters in the
|
* Validate that the getParameterMap() call is correctly unencoding the parameters in the
|
||||||
* map that it returns.
|
* map that it returns.
|
||||||
|
@ -873,7 +830,7 @@ public class MultipartFilterTest
|
||||||
"Content-Type: application/octet-stream\r\n\r\n"+
|
"Content-Type: application/octet-stream\r\n\r\n"+
|
||||||
"How now brown cow."+
|
"How now brown cow."+
|
||||||
"\r\n--" + boundary + "\r\n"+
|
"\r\n--" + boundary + "\r\n"+
|
||||||
"Content-Disposition: form-data; name=\"strup\""+ // FIXME: this is missing a "\r\n"??
|
"Content-Disposition: form-data; name=\"strup\""+
|
||||||
"Content-Type: application/octet-stream\r\n\r\n"+
|
"Content-Type: application/octet-stream\r\n\r\n"+
|
||||||
"How now brown cow."+
|
"How now brown cow."+
|
||||||
"\r\n--" + boundary + "--\r\n\r\n";
|
"\r\n--" + boundary + "--\r\n\r\n";
|
||||||
|
@ -884,119 +841,6 @@ public class MultipartFilterTest
|
||||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||||
assertTrue(response.getContent().indexOf("brown cow")>=0);
|
assertTrue(response.getContent().indexOf("brown cow")>=0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that the uploaded file can be accessed on the name it was given
|
|
||||||
* @throws Exception on test failure
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Ignore("Fails for Bug #486394")
|
|
||||||
public void testFileUpload_AccessViaFilename() throws Exception
|
|
||||||
{
|
|
||||||
tester.addServlet(ParameterListServlet.class,"/paramlist");
|
|
||||||
|
|
||||||
multipartFilter.setInitParameter("fileOutputBuffer", "1");
|
|
||||||
multipartFilter.setInitParameter("deleteFiles", "false");
|
|
||||||
multipartFilter.setInitParameter("writeFilesWithFilenames", "true");
|
|
||||||
|
|
||||||
// generated and parsed test
|
|
||||||
HttpTester.Request request = HttpTester.newRequest();
|
|
||||||
HttpTester.Response response;
|
|
||||||
|
|
||||||
// test GET
|
|
||||||
request.setMethod("POST");
|
|
||||||
request.setURI("/context/paramlist");
|
|
||||||
request.setVersion("HTTP/1.1");
|
|
||||||
request.setHeader("Host","tester");
|
|
||||||
request.setHeader("Connection","close");
|
|
||||||
String boundary="XyXyXy";
|
|
||||||
request.setHeader("Content-Type","multipart/form-data; boundary=" + boundary);
|
|
||||||
|
|
||||||
StringBuilder content = new StringBuilder();
|
|
||||||
content.append("--").append(boundary).append("\r\n");
|
|
||||||
content.append("Content-Disposition: form-data; name=\"file\"; filename=\"tiny.dat\"\r\n");
|
|
||||||
content.append("Content-Type: application/octet-stream\r\n");
|
|
||||||
content.append("\r\n");
|
|
||||||
content.append("How now brown cow.\r\n");
|
|
||||||
content.append("--").append(boundary).append("--\r\n");
|
|
||||||
content.append("\r\n");
|
|
||||||
|
|
||||||
request.setContent(content.toString());
|
|
||||||
|
|
||||||
response = HttpTester.parseResponse(tester.getResponses(request.generate()));
|
|
||||||
assertThat("Response status", response.getStatus(), is(HttpServletResponse.SC_OK));
|
|
||||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
|
||||||
|
|
||||||
String contents = assertUploadedFileExists("tiny.dat");
|
|
||||||
assertThat("contents", contents, containsString("How now brown cow."));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate that the two upload files, with the same name="" on two different parts,
|
|
||||||
* can be accessed with the filename="" portions.
|
|
||||||
* @throws Exception on test failure
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
@Ignore("Fails for Bug #486394")
|
|
||||||
public void testTwoFileUploads_AccessViaFilename() throws Exception
|
|
||||||
{
|
|
||||||
tester.addServlet(ParameterListServlet.class,"/paramlist");
|
|
||||||
|
|
||||||
multipartFilter.setInitParameter("fileOutputBuffer", "1");
|
|
||||||
multipartFilter.setInitParameter("deleteFiles", "false");
|
|
||||||
multipartFilter.setInitParameter("writeFilesWithFilenames", "true");
|
|
||||||
|
|
||||||
// generated and parsed test
|
|
||||||
HttpTester.Request request = HttpTester.newRequest();
|
|
||||||
HttpTester.Response response;
|
|
||||||
|
|
||||||
// test GET
|
|
||||||
request.setMethod("POST");
|
|
||||||
request.setURI("/context/paramlist");
|
|
||||||
request.setVersion("HTTP/1.1");
|
|
||||||
request.setHeader("Host","tester");
|
|
||||||
request.setHeader("Connection","close");
|
|
||||||
|
|
||||||
String boundary="XyXyXy";
|
|
||||||
request.setHeader("Content-Type","multipart/form-data; boundary=" + boundary);
|
|
||||||
|
|
||||||
StringBuilder content = new StringBuilder();
|
|
||||||
content.append("--").append(boundary).append("\r\n");
|
|
||||||
content.append("Content-Disposition: form-data; name=\"same\"; filename=\"crocodile.dat\"\r\n");
|
|
||||||
content.append("Content-Type: application/octet-stream\r\n");
|
|
||||||
content.append("\r\n");
|
|
||||||
content.append("See ya later, aligator.\r\n");
|
|
||||||
content.append("--").append(boundary).append("\r\n");
|
|
||||||
content.append("Content-Disposition: form-data; name=\"same\"; filename=\"aligator.dat\"\r\n");
|
|
||||||
content.append("Content-Type: application/octet-stream\r\n");
|
|
||||||
content.append("\r\n");
|
|
||||||
content.append("In a while, crocodile.\r\n");
|
|
||||||
content.append("--").append(boundary).append("--\r\n");
|
|
||||||
content.append("\r\n");
|
|
||||||
|
|
||||||
request.setContent(content.toString());
|
|
||||||
|
|
||||||
response = HttpTester.parseResponse(tester.getResponses(request.generate()));
|
|
||||||
assertThat("Response status", response.getStatus(), is(HttpServletResponse.SC_OK));
|
|
||||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
|
||||||
|
|
||||||
String contents = assertUploadedFileExists("crocodile.dat");
|
|
||||||
assertThat("contents", contents, containsString("See ya later, aligator."));
|
|
||||||
|
|
||||||
contents = assertUploadedFileExists("aligator.dat");
|
|
||||||
assertThat("contents", contents, containsString("In a while, crocodile."));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String assertUploadedFileExists(String filename) throws IOException
|
|
||||||
{
|
|
||||||
File uploadedFile = new File(_dir,filename);
|
|
||||||
assertThat("Uploaded File[" + uploadedFile + "].exists",uploadedFile.exists(),is(true));
|
|
||||||
|
|
||||||
try (Reader reader = new FileReader(uploadedFile))
|
|
||||||
{
|
|
||||||
return IO.toString(reader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TestServletCharSet extends HttpServlet
|
public static class TestServletCharSet extends HttpServlet
|
||||||
{
|
{
|
||||||
|
@ -1103,9 +947,13 @@ public class MultipartFilterTest
|
||||||
assertTrue(response.getContent().indexOf("000")>=0);
|
assertTrue(response.getContent().indexOf("000")>=0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
|
|
||||||
public static class DumpServlet extends HttpServlet
|
public static class DumpServlet extends HttpServlet
|
||||||
{
|
{
|
||||||
|
private static final long serialVersionUID = 201012011130L;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
* @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,7 +23,9 @@ import java.io.IOException;
|
||||||
import org.eclipse.jetty.io.ByteBufferPool;
|
import org.eclipse.jetty.io.ByteBufferPool;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||||
|
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
|
import org.eclipse.jetty.util.component.Dumpable;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.websocket.api.BatchMode;
|
import org.eclipse.jetty.websocket.api.BatchMode;
|
||||||
|
@ -38,7 +40,7 @@ import org.eclipse.jetty.websocket.common.LogicalConnection;
|
||||||
import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
|
import org.eclipse.jetty.websocket.common.scopes.WebSocketContainerScope;
|
||||||
|
|
||||||
@ManagedObject("Abstract Extension")
|
@ManagedObject("Abstract Extension")
|
||||||
public abstract class AbstractExtension extends ContainerLifeCycle implements Extension
|
public abstract class AbstractExtension extends AbstractLifeCycle implements Dumpable, Extension
|
||||||
{
|
{
|
||||||
private final Logger log;
|
private final Logger log;
|
||||||
private WebSocketPolicy policy;
|
private WebSocketPolicy policy;
|
||||||
|
@ -52,11 +54,15 @@ public abstract class AbstractExtension extends ContainerLifeCycle implements Ex
|
||||||
{
|
{
|
||||||
log = Log.getLogger(this.getClass());
|
log = Log.getLogger(this.getClass());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
public String dump()
|
||||||
|
{
|
||||||
|
return ContainerLifeCycle.dump(this);
|
||||||
|
}
|
||||||
|
|
||||||
public void dump(Appendable out, String indent) throws IOException
|
public void dump(Appendable out, String indent) throws IOException
|
||||||
{
|
{
|
||||||
super.dump(out, indent);
|
|
||||||
// incoming
|
// incoming
|
||||||
dumpWithHeading(out, indent, "incoming", this.nextIncoming);
|
dumpWithHeading(out, indent, "incoming", this.nextIncoming);
|
||||||
dumpWithHeading(out, indent, "outgoing", this.nextOutgoing);
|
dumpWithHeading(out, indent, "outgoing", this.nextOutgoing);
|
||||||
|
|
|
@ -29,6 +29,7 @@ import org.eclipse.jetty.util.IteratingCallback;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
import org.eclipse.jetty.util.annotation.ManagedAttribute;
|
||||||
import org.eclipse.jetty.util.annotation.ManagedObject;
|
import org.eclipse.jetty.util.annotation.ManagedObject;
|
||||||
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
import org.eclipse.jetty.util.component.ContainerLifeCycle;
|
||||||
|
import org.eclipse.jetty.util.component.LifeCycle;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.websocket.api.BatchMode;
|
import org.eclipse.jetty.websocket.api.BatchMode;
|
||||||
|
@ -89,6 +90,11 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames
|
||||||
Extension ext = exts.next();
|
Extension ext = exts.next();
|
||||||
ext.setNextOutgoingFrames(nextOutgoing);
|
ext.setNextOutgoingFrames(nextOutgoing);
|
||||||
nextOutgoing = ext;
|
nextOutgoing = ext;
|
||||||
|
|
||||||
|
if (ext instanceof LifeCycle)
|
||||||
|
{
|
||||||
|
addBean(ext,true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect incomings
|
// Connect incomings
|
||||||
|
|
|
@ -74,28 +74,34 @@ public abstract class CompressExtension extends AbstractExtension
|
||||||
|
|
||||||
private final Queue<FrameEntry> entries = new ConcurrentArrayQueue<>();
|
private final Queue<FrameEntry> entries = new ConcurrentArrayQueue<>();
|
||||||
private final IteratingCallback flusher = new Flusher();
|
private final IteratingCallback flusher = new Flusher();
|
||||||
private final Deflater deflater;
|
private Deflater deflaterImpl;
|
||||||
private final Inflater inflater;
|
private Inflater inflaterImpl;
|
||||||
protected AtomicInteger decompressCount = new AtomicInteger(0);
|
protected AtomicInteger decompressCount = new AtomicInteger(0);
|
||||||
private int tailDrop = TAIL_DROP_NEVER;
|
private int tailDrop = TAIL_DROP_NEVER;
|
||||||
private int rsvUse = RSV_USE_ALWAYS;
|
private int rsvUse = RSV_USE_ALWAYS;
|
||||||
|
|
||||||
protected CompressExtension()
|
protected CompressExtension()
|
||||||
{
|
{
|
||||||
deflater = new Deflater(Deflater.DEFAULT_COMPRESSION,NOWRAP);
|
|
||||||
inflater = new Inflater(NOWRAP);
|
|
||||||
tailDrop = getTailDropMode();
|
tailDrop = getTailDropMode();
|
||||||
rsvUse = getRsvUseMode();
|
rsvUse = getRsvUseMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Deflater getDeflater()
|
public Deflater getDeflater()
|
||||||
{
|
{
|
||||||
return deflater;
|
if (deflaterImpl == null)
|
||||||
|
{
|
||||||
|
deflaterImpl = new Deflater(Deflater.DEFAULT_COMPRESSION,NOWRAP);
|
||||||
|
}
|
||||||
|
return deflaterImpl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Inflater getInflater()
|
public Inflater getInflater()
|
||||||
{
|
{
|
||||||
return inflater;
|
if (inflaterImpl == null)
|
||||||
|
{
|
||||||
|
inflaterImpl = new Inflater(NOWRAP);
|
||||||
|
}
|
||||||
|
return inflaterImpl;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -155,6 +161,8 @@ public abstract class CompressExtension extends AbstractExtension
|
||||||
}
|
}
|
||||||
byte[] output = new byte[DECOMPRESS_BUF_SIZE];
|
byte[] output = new byte[DECOMPRESS_BUF_SIZE];
|
||||||
|
|
||||||
|
Inflater inflater = getInflater();
|
||||||
|
|
||||||
while(buf.hasRemaining() && inflater.needsInput())
|
while(buf.hasRemaining() && inflater.needsInput())
|
||||||
{
|
{
|
||||||
if (!supplyInput(inflater,buf))
|
if (!supplyInput(inflater,buf))
|
||||||
|
@ -346,6 +354,16 @@ public abstract class CompressExtension extends AbstractExtension
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStop() throws Exception
|
||||||
|
{
|
||||||
|
if(deflaterImpl != null)
|
||||||
|
deflaterImpl.end();
|
||||||
|
if(inflaterImpl != null)
|
||||||
|
inflaterImpl.end();
|
||||||
|
super.doStop();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString()
|
public String toString()
|
||||||
|
@ -429,6 +447,8 @@ public abstract class CompressExtension extends AbstractExtension
|
||||||
LOG.debug("Compressing {}: {} bytes in {} bytes chunk",entry,remaining,outputLength);
|
LOG.debug("Compressing {}: {} bytes in {} bytes chunk",entry,remaining,outputLength);
|
||||||
|
|
||||||
boolean needsCompress = true;
|
boolean needsCompress = true;
|
||||||
|
|
||||||
|
Deflater deflater = getDeflater();
|
||||||
|
|
||||||
if (deflater.needsInput() && !supplyInput(deflater,data))
|
if (deflater.needsInput() && !supplyInput(deflater,data))
|
||||||
{
|
{
|
||||||
|
|
|
@ -21,6 +21,7 @@ package org.eclipse.jetty.http.client;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
import java.io.InterruptedIOException;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
|
@ -35,6 +36,7 @@ import org.eclipse.jetty.client.util.BytesContentProvider;
|
||||||
import org.eclipse.jetty.client.util.FutureResponseListener;
|
import org.eclipse.jetty.client.util.FutureResponseListener;
|
||||||
import org.eclipse.jetty.http.HttpMethod;
|
import org.eclipse.jetty.http.HttpMethod;
|
||||||
import org.eclipse.jetty.http.HttpStatus;
|
import org.eclipse.jetty.http.HttpStatus;
|
||||||
|
import org.eclipse.jetty.http2.FlowControlStrategy;
|
||||||
import org.eclipse.jetty.server.Request;
|
import org.eclipse.jetty.server.Request;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
@ -239,6 +241,50 @@ public class HttpClientTest extends AbstractTest
|
||||||
Assert.assertEquals(chunks * chunkSize, Integer.parseInt(response.getContentAsString()));
|
Assert.assertEquals(chunks * chunkSize, Integer.parseInt(response.getContentAsString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRequestAfterFailedRequest() throws Exception
|
||||||
|
{
|
||||||
|
int length = FlowControlStrategy.DEFAULT_WINDOW_SIZE;
|
||||||
|
start(new AbstractHandler()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
|
||||||
|
{
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
response.getOutputStream().write(new byte[length]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make a request with a large enough response buffer.
|
||||||
|
org.eclipse.jetty.client.api.Request request = client.newRequest(newURI());
|
||||||
|
FutureResponseListener listener = new FutureResponseListener(request, length);
|
||||||
|
request.send(listener);
|
||||||
|
ContentResponse response = listener.get(5, TimeUnit.SECONDS);
|
||||||
|
Assert.assertEquals(response.getStatus(), 200);
|
||||||
|
|
||||||
|
// Make a request with a small response buffer, should fail.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
request = client.newRequest(newURI());
|
||||||
|
listener = new FutureResponseListener(request, length / 10);
|
||||||
|
request.send(listener);
|
||||||
|
listener.get(5, TimeUnit.SECONDS);
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
catch (ExecutionException x)
|
||||||
|
{
|
||||||
|
// Buffering capacity exceeded.
|
||||||
|
x.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that we can make another request.
|
||||||
|
request = client.newRequest(newURI());
|
||||||
|
listener = new FutureResponseListener(request, length);
|
||||||
|
request.send(listener);
|
||||||
|
response = listener.get(5, TimeUnit.SECONDS);
|
||||||
|
Assert.assertEquals(response.getStatus(), 200);
|
||||||
|
}
|
||||||
|
|
||||||
private void sleep(long time) throws IOException
|
private void sleep(long time) throws IOException
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
Loading…
Reference in New Issue