Merge branch 'jetty-9.3.x' of github.com:eclipse/jetty.project into jetty-9.3.x

This commit is contained in:
Joakim Erdfelt 2016-03-02 09:07:33 -07:00
commit 4e426e9be3
26 changed files with 458 additions and 181 deletions

View File

@ -1,5 +1,48 @@
jetty-9.3.8-SNAPSHOT
jetty-9.3.8.RC0 - 25 February 2016
+ 81 Exception not always thrown in Jetty to application when upload part is
too big
+ 82 Request.getPart() that results in Exception still allows other parts to
be fetched
+ 251 Removing SSLEngine.beginHandshake() calls
+ 285 PathContentProvider - Use of Direct buffers without pooling
+ 298 qtp threads spin-locked in MBeanContainer.beanAdded
+ 342 Reintroducing Response parameter to logExtended
+ 344 init script does not properly display status of a non running service
+ 346 HttpParser RFC2616 Compliance mode
+ 347 Avoid sending request using a connection that is idle timing out
+ 352 Integrate session idling for MongoSessionManager
+ 354 Spin loop in case of exception thrown during accept()
+ 355 Improve close behaviour
+ 478918 Change javax.servlet.error,forward,include literals to
RequestDispatcher constants
+ 484446 InputStreamResponseListener's InputStream uses default read (3) and
blocks early on never-ending response.
+ 485306 HttpParser (HttpURI) mistaking basic auth password as a port number
+ 485469 permessage-deflate extension causes protocol error in Firefox/Chrome
+ 486394 Restore MultiPartFilter behavior with regards to temp file access
+ 486497 NPE in MappedLoginService
+ 486511 Server.getURI() returns wrong scheme on SSL/HTTPS
+ 486530 Handler added to WebAppContext prevents ServletContext initialization
+ 486589 HttpRequest has a wrong HTTP Version in HTTP/2.
+ 486604 Add debug logging of ErrorPageErrorHandler logic
+ 486674 Quickstart path attribute normalization should be based on longest
path match
+ 486829 Cancel stream error after a failed request with the HTTP/2.0 client.
+ 486877 Google Chrome flagging 'obsolete cipher suite' in Jetty and will soon
issue broken padlock
+ 486930 Selector does not correctly handle rejected execution exception
+ 487158 Switched SCM URIs to github
+ 487197 Deflater/Inflater memory leak with WebSocket permessage-deflate
extension
+ 487198 ContextScopeListener should be called on context start and stop
+ 487277 Introduce http-forwarded module for X-Forwarded support
+ 487354 Aborted request or response does not send RST_STREAM frame.
+ 487511 Jetty HTTP won't work on turkish systems.
+ 487714 Avoid NPE in close race for async write
+ 487750 HTTP/2 push must not be recursive.
jetty-9.2.15.v20160210 - 10 February 2016
+ 482042 New API, Allow customization of ServletHandler path mapping
+ 482243 Fixed GzipHandler for Include.
@ -21,9 +64,7 @@ jetty-9.2.15.v20160210 - 10 February 2016
+ 485535 jetty.sh results in FAILED when running service restart
+ 485663 NullPointerException in WebSocketSession during upgrade with DEBUG
logging
+ 485712 Quickstart web.xml is absolute
jetty-9.3.7.v20160115 - 15 January 2016
+ jetty-9.3.7.v20160115 - 15 January 2016
+ 471171 Support SYNC_FLUSH in GzipHandler
+ 485469 permessage-deflate extension causes protocol error in Firefox/Chrome
+ 485714 Update SSL configuration to mitigate SLOTH vulnerability

View File

@ -152,6 +152,9 @@ public abstract class PoolingHttpDestination<C extends Connection> extends HttpD
{
if (LOG.isDebugEnabled())
LOG.debug("Aborted before processing {}: {}", exchange, cause);
// Won't use this connection, release it back.
if (!connectionPool.release(connection))
connection.close();
// It may happen that the request is aborted before the exchange
// is created. Aborting the exchange a second time will result in
// a no-operation, so we just abort here to cover that edge case.

View File

@ -32,6 +32,7 @@ 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.http.HttpHeader;
import org.eclipse.jetty.http.HttpScheme;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.B64Code;
@ -157,4 +158,100 @@ public class HttpClientProxyTest extends AbstractHttpClientServerTest
Assert.assertEquals(status, response3.getStatus());
Assert.assertEquals(1, requests.get());
}
@Test
public void testAuthenticatedProxiedRequestWithRedirect() throws Exception
{
String user = "foo";
String password = "bar";
String credentials = B64Code.encode(user + ":" + password, StandardCharsets.ISO_8859_1);
String proxyHost = "localhost";
String serverHost = "server";
int serverPort = HttpScheme.HTTP.is(scheme) ? 80 : 443;
String realm = "test_realm";
int status = HttpStatus.NO_CONTENT_204;
start(new AbstractHandler()
{
@Override
public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
baseRequest.setHandled(true);
if (target.startsWith("/proxy"))
{
String authorization = request.getHeader(HttpHeader.PROXY_AUTHORIZATION.asString());
if (authorization == null)
{
response.setStatus(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407);
response.setHeader(HttpHeader.PROXY_AUTHENTICATE.asString(), "Basic realm=\"" + realm + "\"");
}
else
{
String prefix = "Basic ";
if (authorization.startsWith(prefix))
{
String attempt = authorization.substring(prefix.length());
if (credentials.equals(attempt))
{
// Change also the host, to verify that proxy authentication works in this case too.
response.sendRedirect(scheme + "://127.0.0.1:" + serverPort + "/server");
}
}
}
}
else if (target.startsWith("/server"))
{
response.setStatus(status);
}
else
{
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500);
}
}
});
int proxyPort = connector.getLocalPort();
client.getProxyConfiguration().getProxies().add(new HttpProxy(proxyHost, proxyPort));
ContentResponse response1 = client.newRequest(serverHost, serverPort)
.scheme(scheme)
.path("/proxy")
.timeout(5, TimeUnit.SECONDS)
.send();
// No Authentication available => 407.
Assert.assertEquals(HttpStatus.PROXY_AUTHENTICATION_REQUIRED_407, response1.getStatus());
// Add authentication...
URI uri = URI.create(scheme + "://" + proxyHost + ":" + proxyPort);
client.getAuthenticationStore().addAuthentication(new BasicAuthentication(uri, realm, user, password));
final AtomicInteger requests = new AtomicInteger();
client.getRequestListeners().add(new Request.Listener.Adapter()
{
@Override
public void onSuccess(Request request)
{
requests.incrementAndGet();
}
});
// ...and perform the request again => 407 + 302 + 204.
ContentResponse response2 = client.newRequest(serverHost, serverPort)
.scheme(scheme)
.path("/proxy")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(status, response2.getStatus());
Assert.assertEquals(3, requests.get());
// Now the authentication result is cached => 204.
requests.set(0);
ContentResponse response3 = client.newRequest(serverHost, serverPort)
.scheme(scheme)
.path("/server")
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(status, response3.getStatus());
Assert.assertEquals(1, requests.get());
}
}

View File

@ -25,6 +25,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -47,6 +48,33 @@ public class HttpRequestAbortTest extends AbstractHttpClientServerTest
super(sslContextFactory);
}
@Test
public void testAbortBeforeQueued() throws Exception
{
start(new EmptyServerHandler());
Exception failure = new Exception("oops");
try
{
Request request = client.newRequest("localhost", connector.getLocalPort())
.scheme(scheme)
.timeout(5, TimeUnit.SECONDS);
request.abort(failure);
request.send();
Assert.fail();
}
catch (ExecutionException x)
{
Assert.assertSame(failure, x.getCause());
// Make sure the pool is in a sane state.
HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, "localhost", connector.getLocalPort());
DuplexConnectionPool connectionPool = destination.getConnectionPool();
Assert.assertEquals(1, connectionPool.getConnectionCount());
Assert.assertEquals(0, connectionPool.getActiveConnections().size());
Assert.assertEquals(1, connectionPool.getIdleConnections().size());
}
}
@Test
public void testAbortOnQueued() throws Exception
{

View File

@ -89,7 +89,7 @@ public class ClientGenerator extends Generator
beginRequestBuffer.putInt(0x00_08_00_00);
// Hardcode RESPONDER role and KEEP_ALIVE flag
beginRequestBuffer.putLong(0x00_01_01_00_00_00_00_00L);
beginRequestBuffer.flip();
BufferUtil.flipToFlush(beginRequestBuffer, 0);
int index = 0;
while (fieldsLength > 0)
@ -129,7 +129,7 @@ public class ClientGenerator extends Generator
}
buffer.putShort(4, (short)length);
buffer.flip();
BufferUtil.flipToFlush(buffer, 0);
}
@ -140,7 +140,7 @@ public class ClientGenerator extends Generator
// Generate the last FCGI_PARAMS frame
lastParamsBuffer.putInt(0x01_04_00_00 + request);
lastParamsBuffer.putInt(0x00_00_00_00);
lastParamsBuffer.flip();
BufferUtil.flipToFlush(lastParamsBuffer, 0);
return result;
}

View File

@ -58,7 +58,7 @@ public class Generator
int length = Math.min(MAX_CONTENT_LENGTH, contentLength);
buffer.putShort((short)length);
buffer.putShort((short)0);
buffer.flip();
BufferUtil.flipToFlush(buffer, 0);
if (contentLength == 0)
break;

View File

@ -95,7 +95,7 @@ public class ServerGenerator extends Generator
buffer.put(bytes.get(i)).put(COLON).put(bytes.get(i + 1)).put(EOL);
buffer.put(EOL);
buffer.flip();
BufferUtil.flipToFlush(buffer, 0);
return generateContent(request, buffer, true, false, callback, FCGI.FrameType.STDOUT);
}
@ -129,7 +129,7 @@ public class ServerGenerator extends Generator
endRequestBuffer.putInt(0x00_08_00_00);
endRequestBuffer.putInt(aborted ? 1 : 0);
endRequestBuffer.putInt(0);
endRequestBuffer.flip();
BufferUtil.flipToFlush(endRequestBuffer, 0);
return endRequestBuffer;
}
}

View File

@ -26,7 +26,7 @@
<Arg>
<Ref id="Server"/>
</Arg>
<Set name="workerName"><Property name="jetty.gcloudSession.workerName" default="node1"/></Set>
<Set name="workerName"><Property name="jetty.gcloudSession.workerName"><Default>node<Env name="GAE_MODULE_INSTANCE" default="0"/></Default></Property></Set>
<Set name="config"><Ref id="gconf"/></Set>
</New>
</Set>

View File

@ -53,7 +53,8 @@ https://github.com/GoogleCloudPlatform/gcloud-java
http://www.apache.org/licenses/LICENSE-2.0.html
[ini-template]
## Unique identifier for this node in the cluster
## Unique identifier to force the workername for this node in the cluster
## If not set, will default to the string "node" plus the Env variable $GAE_MODULE_INSTANCE
# jetty.gcloudSession.workerName=node1

View File

@ -357,6 +357,7 @@ public class IdleTimeoutTest extends AbstractTest
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
dataLatch.countDown();
}

View File

@ -53,6 +53,7 @@ import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.util.ArrayQueue;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Promise;
import org.junit.Assert;
@ -168,12 +169,12 @@ public class PrefaceTest extends AbstractTest
ByteBuffer buffer = byteBufferPool.acquire(1024, true);
while (true)
{
BufferUtil.clearToFill(buffer);
int read = socket.read(buffer);
buffer.flip();
BufferUtil.flipToFlush(buffer, 0);
if (read < 0)
break;
parser.parse(buffer);
buffer.clear();
}
Assert.assertEquals(2, settings.size());
@ -248,9 +249,9 @@ public class PrefaceTest extends AbstractTest
ByteBuffer buffer = byteBufferPool.acquire(1024, true);
http1: while (true)
{
buffer.clear();
BufferUtil.clearToFill(buffer);
int read = socket.read(buffer);
buffer.flip();
BufferUtil.flipToFlush(buffer, 0);
if (read < 0)
Assert.fail();
@ -314,9 +315,9 @@ public class PrefaceTest extends AbstractTest
if (responded.get())
break;
buffer.clear();
BufferUtil.clearToFill(buffer);
int read = socket.read(buffer);
buffer.flip();
BufferUtil.flipToFlush(buffer, 0);
if (read < 0)
Assert.fail();
}

View File

@ -101,6 +101,7 @@ public class PushCacheFilterTest extends AbstractTest
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
warmupLatch.countDown();
}
});
@ -188,6 +189,7 @@ public class PushCacheFilterTest extends AbstractTest
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
warmupLatch.countDown();
}
});
@ -273,6 +275,7 @@ public class PushCacheFilterTest extends AbstractTest
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
warmupLatch.countDown();
}
});
@ -298,6 +301,7 @@ public class PushCacheFilterTest extends AbstractTest
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
pushLatch.countDown();
}
};
@ -325,6 +329,7 @@ public class PushCacheFilterTest extends AbstractTest
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
secondaryResponseLatch.countDown();
}
@ -372,6 +377,7 @@ public class PushCacheFilterTest extends AbstractTest
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
warmupLatch.countDown();
}
});
@ -655,6 +661,7 @@ public class PushCacheFilterTest extends AbstractTest
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
warmupLatch.countDown();
}
@ -676,6 +683,7 @@ public class PushCacheFilterTest extends AbstractTest
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
if (frame.isEndStream())
primaryResponseLatch.countDown();
}

View File

@ -129,7 +129,12 @@ public class StreamCloseTest extends AbstractTest
public void onData(final Stream stream, DataFrame frame, final Callback callback)
{
Assert.assertTrue(((HTTP2Stream)stream).isRemotelyClosed());
stream.data(frame, new Callback()
// We must copy the data that we send asynchronously.
ByteBuffer data = frame.getData();
ByteBuffer copy = ByteBuffer.allocate(data.remaining());
copy.put(data).flip();
stream.data(new DataFrame(stream.getId(), copy, frame.isEndStream()), new Callback()
{
@Override
public void succeeded()
@ -155,6 +160,7 @@ public class StreamCloseTest extends AbstractTest
public void onData(Stream stream, DataFrame frame, Callback callback)
{
// The sent data callback may not be notified yet here.
callback.succeeded();
completeLatch.countDown();
}
});

View File

@ -70,6 +70,10 @@ public class StreamCountTest extends AbstractTest
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, fields);
stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), callback);
}
else
{
callback.succeeded();
}
}
};
}
@ -144,6 +148,10 @@ public class StreamCountTest extends AbstractTest
MetaData.Response metaData = new MetaData.Response(HttpVersion.HTTP_2, 200, fields);
stream.headers(new HeadersFrame(stream.getId(), metaData, null, true), callback);
}
else
{
callback.succeeded();
}
}
};
}

View File

@ -171,6 +171,7 @@ public class StreamResetTest extends AbstractTest
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
stream1DataLatch.countDown();
}
});
@ -186,6 +187,7 @@ public class StreamResetTest extends AbstractTest
@Override
public void onData(Stream stream, DataFrame frame, Callback callback)
{
callback.succeeded();
stream2DataLatch.countDown();
}
});

View File

@ -46,9 +46,9 @@ public class HTTP2Flusher extends IteratingCallback
private final Map<IStream, Integer> streams = new HashMap<>();
private final List<Entry> resets = new ArrayList<>();
private final List<Entry> actives = new ArrayList<>();
private final Queue<Entry> completes = new ArrayDeque<>();
private final HTTP2Session session;
private final ByteBufferPool.Lease lease;
private boolean terminated;
public HTTP2Flusher(HTTP2Session session)
{
@ -58,57 +58,52 @@ public class HTTP2Flusher extends IteratingCallback
public void window(IStream stream, WindowUpdateFrame frame)
{
boolean added = false;
boolean closed;
synchronized (this)
{
if (!isClosed())
added = windows.offer(new WindowEntry(stream, frame));
closed = terminated;
if (!closed)
windows.offer(new WindowEntry(stream, frame));
}
// Flush stalled data.
if (added)
if (!closed)
iterate();
}
public boolean prepend(Entry entry)
{
boolean fail = false;
boolean closed;
synchronized (this)
{
if (isClosed())
{
fail = true;
}
else
closed = terminated;
if (!closed)
{
frames.add(0, entry);
if (LOG.isDebugEnabled())
LOG.debug("Prepended {}, frames={}", entry, frames.size());
}
}
if (fail)
if (closed)
closed(entry, new ClosedChannelException());
return !fail;
return !closed;
}
public boolean append(Entry entry)
{
boolean fail = false;
boolean closed;
synchronized (this)
{
if (isClosed())
{
fail = true;
}
else
closed = terminated;
if (!closed)
{
frames.offer(entry);
if (LOG.isDebugEnabled())
LOG.debug("Appended {}, frames={}", entry, frames.size());
}
}
if (fail)
if (closed)
closed(entry, new ClosedChannelException());
return !fail;
return !closed;
}
private Entry remove(int index)
@ -138,6 +133,9 @@ public class HTTP2Flusher extends IteratingCallback
synchronized (this)
{
if (terminated)
throw new ClosedChannelException();
// First thing, update the window sizes, so we can
// reason about the frames to remove from the queue.
while (!windows.isEmpty())
@ -226,12 +224,8 @@ public class HTTP2Flusher extends IteratingCallback
if (actives.isEmpty())
{
if (isClosed())
fail(new ClosedChannelException(), true);
if (LOG.isDebugEnabled())
LOG.debug("Flushed {}", session);
return Action.IDLE;
}
@ -259,20 +253,11 @@ public class HTTP2Flusher extends IteratingCallback
{
lease.recycle();
// Transfer active items to avoid reentrancy.
for (int i = 0; i < actives.size(); ++i)
completes.add(actives.get(i));
actives.clear();
if (LOG.isDebugEnabled())
LOG.debug("Written {} frames for {}", completes.size(), completes);
LOG.debug("Written {} frames for {}", actives.size(), actives);
// Drain the frames one by one to avoid reentrancy.
while (!completes.isEmpty())
{
Entry entry = completes.poll();
entry.succeeded();
}
actives.forEach(Entry::succeeded);
actives.clear();
super.succeeded();
}
@ -288,40 +273,40 @@ public class HTTP2Flusher extends IteratingCallback
{
lease.recycle();
// Transfer active items to avoid reentrancy.
for (int i = 0; i < actives.size(); ++i)
completes.add(actives.get(i));
actives.clear();
// Drain the frames one by one to avoid reentrancy.
while (!completes.isEmpty())
{
Entry entry = completes.poll();
entry.failed(x);
}
fail(x, isClosed());
}
private void fail(Throwable x, boolean closed)
{
Queue<Entry> queued;
boolean closed;
synchronized (this)
{
queued = new ArrayDeque<>(frames);
closed = terminated;
terminated = true;
if (LOG.isDebugEnabled())
LOG.debug("{}, active/queued={}/{}", closed ? "Closing" : "Failing", actives.size(), frames.size());
actives.addAll(frames);
frames.clear();
}
if (LOG.isDebugEnabled())
LOG.debug("{}, queued={}", closed ? "Closing" : "Failing", queued.size());
for (Entry entry : queued)
entry.failed(x);
actives.forEach(entry -> entry.failed(x));
actives.clear();
// If the failure came from within the
// flusher, we need to close the connection.
if (!closed)
session.abort(x);
}
void terminate()
{
boolean closed;
synchronized (this)
{
closed = terminated;
terminated = true;
if (LOG.isDebugEnabled())
LOG.debug("{}", closed ? "Terminated" : "Terminating");
}
if (!closed)
iterate();
}
private void closed(Entry entry, Throwable failure)
{
entry.failed(failure);

View File

@ -907,7 +907,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio
{
if (closed.compareAndSet(current, CloseState.CLOSED))
{
flusher.close();
flusher.terminate();
for (IStream stream : streams.values())
stream.close();
streams.clear();

View File

@ -19,6 +19,7 @@
package org.eclipse.jetty.http2.client.http;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Locale;
import org.eclipse.jetty.client.HttpChannel;
@ -34,6 +35,8 @@ import org.eclipse.jetty.http2.frames.DataFrame;
import org.eclipse.jetty.http2.frames.HeadersFrame;
import org.eclipse.jetty.http2.frames.PushPromiseFrame;
import org.eclipse.jetty.http2.frames.ResetFrame;
import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listener
@ -95,7 +98,42 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
return;
}
if (responseContent(exchange, frame.getData(), callback))
// We must copy the data since we do not know when the
// application will consume the bytes and the parsing
// will continue as soon as this method returns, eventually
// leading to reusing the underlying buffer for more reads.
ByteBufferPool byteBufferPool = getHttpDestination().getHttpClient().getByteBufferPool();
ByteBuffer original = frame.getData();
int length = original.remaining();
final ByteBuffer copy = byteBufferPool.acquire(length, original.isDirect());
BufferUtil.clearToFill(copy);
copy.put(original);
BufferUtil.flipToFlush(copy, 0);
Callback delegate = new Callback()
{
@Override
public boolean isNonBlocking()
{
return callback.isNonBlocking();
}
@Override
public void succeeded()
{
byteBufferPool.release(copy);
callback.succeeded();
}
@Override
public void failed(Throwable x)
{
byteBufferPool.release(copy);
callback.failed(x);
}
};
if (responseContent(exchange, copy, delegate))
{
if (frame.isEndStream())
responseSuccess(exchange);

View File

@ -164,15 +164,17 @@ public class HttpChannelOverHTTP2 extends HttpChannel
public Runnable requestContent(DataFrame frame, final Callback callback)
{
// We must copy the data since we do not know when the
// application will consume its bytes (we queue them by
// calling onContent()), and we cannot stop the parsing
// since there may be frames for other streams.
// application will consume the bytes (we queue them by
// calling onContent()), and the parsing will continue
// as soon as this method returns, eventually leading
// to reusing the underlying buffer for more reads.
final ByteBufferPool byteBufferPool = getByteBufferPool();
ByteBuffer original = frame.getData();
int length = original.remaining();
final ByteBuffer copy = byteBufferPool.acquire(length, original.isDirect());
BufferUtil.clearToFill(copy);
copy.put(original).flip();
copy.put(original);
BufferUtil.flipToFlush(copy, 0);
boolean handle = onContent(new HttpInput.Content(copy)
{

View File

@ -68,10 +68,10 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements ManagedSel
return SelectChannelEndPoint.this.toString()+":runUpdateKey";
}
};
private abstract class RejectableRunnable implements Runnable,Rejectable
{
@Override
@Override
public void reject()
{
try
@ -84,7 +84,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements ManagedSel
}
}
}
private final Runnable _runFillable = new RejectableRunnable()
{
@Override
@ -118,8 +118,8 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements ManagedSel
@Override
public void run()
{
getFillInterest().fillable();
getWriteFlusher().completeWrite();
getFillInterest().fillable();
}
@Override
@ -175,7 +175,7 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements ManagedSel
if (LOG.isDebugEnabled())
LOG.debug("onSelected {}->{} r={} w={} for {}", oldInterestOps, newInterestOps, readable, writable, this);
// Run non-blocking code immediately.
// This producer knows that this non-blocking code is special
// and that it must be run in this thread and not fed to the

View File

@ -61,16 +61,16 @@ public class HttpOutput extends ServletOutputStream implements Runnable
Interceptor getNextInterceptor();
boolean isOptimizedForDirectBuffers();
}
private static Logger LOG = Log.getLogger(HttpOutput.class);
private final HttpChannel _channel;
private final SharedBlockingCallback _writeBlock;
private Interceptor _interceptor;
/** Bytes written via the write API (excludes bytes written via sendContent). Used to autocommit once content length is written. */
private long _written;
private ByteBuffer _aggregate;
private int _bufferSize;
private int _commitSize;
@ -115,7 +115,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_commitSize=_bufferSize;
}
}
public HttpChannel getHttpChannel()
{
return _channel;
@ -130,7 +130,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
_interceptor=filter;
}
public boolean isWritten()
{
return _written > 0;
@ -155,11 +155,11 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
return _writeBlock.acquire();
}
private void write(ByteBuffer content, boolean complete) throws IOException
{
try (Blocker blocker = _writeBlock.acquire())
{
{
write(content, complete, blocker);
blocker.block();
}
@ -314,7 +314,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case ERROR:
throw new EofException(_onError);
case CLOSED:
return;
@ -378,7 +378,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case ERROR:
throw new EofException(_onError);
case CLOSED:
throw new EofException("Closed");
@ -480,7 +480,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case ERROR:
throw new EofException(_onError);
case CLOSED:
throw new EofException("Closed");
@ -562,7 +562,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case ERROR:
throw new EofException(_onError);
case CLOSED:
throw new EofException("Closed");
@ -592,7 +592,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
if (LOG.isDebugEnabled())
LOG.debug("sendContent({})",BufferUtil.toDetailString(content));
write(content, true);
closed();
}
@ -672,7 +672,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
if (LOG.isDebugEnabled())
LOG.debug("sendContent(buffer={},{})",BufferUtil.toDetailString(content),callback);
write(content, true, new Callback()
{
@Override
@ -702,7 +702,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
if (LOG.isDebugEnabled())
LOG.debug("sendContent(stream={},{})",in,callback);
new InputStreamWritingCB(in, callback).iterate();
}
@ -717,7 +717,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
if (LOG.isDebugEnabled())
LOG.debug("sendContent(channel={},{})",in,callback);
new ReadableByteChannelWritingCB(in, callback).iterate();
}
@ -731,7 +731,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
if (LOG.isDebugEnabled())
LOG.debug("sendContent(http={},{})",httpContent,callback);
if (BufferUtil.hasContent(_aggregate))
{
callback.failed(new IOException("cannot sendContent() after write()"));
@ -755,7 +755,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case ERROR:
callback.failed(new EofException(_onError));
return;
case CLOSED:
callback.failed(new EofException("Closed"));
return;
@ -765,7 +765,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
break;
}
ByteBuffer buffer = _channel.useDirectBuffers() ? httpContent.getDirectBuffer() : null;
if (buffer == null)
@ -819,7 +819,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
resetBuffer();
_interceptor=_channel;
}
public void resetBuffer()
{
_written = 0;
@ -875,7 +875,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
case ERROR:
return true;
case CLOSED:
return true;
@ -918,7 +918,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
}
continue;
}
switch(_state.get())
{
case CLOSED:
@ -939,7 +939,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_onError = e;
}
break;
default:
_onError=new IllegalStateException("state="+_state.get());
}
@ -1062,7 +1062,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
{
_slice=_buffer.duplicate();
_buffer.position(_buffer.limit());
}
}
_complete=complete;
}
@ -1085,7 +1085,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
BufferUtil.flipToFlush(_aggregate, position);
return Action.SUCCEEDED;
}
// Is there data left to write?
if (_buffer.hasRemaining())
{
@ -1096,7 +1096,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
write(_buffer, _complete, this);
return Action.SCHEDULED;
}
// otherwise take a slice
int p=_buffer.position();
int l=Math.min(getBufferSize(),_buffer.remaining());
@ -1108,7 +1108,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
write(_slice, _complete && _completed, this);
return Action.SCHEDULED;
}
// all content written, but if we have not yet signal completion, we
// need to do so
if (_complete && !_completed)
@ -1168,7 +1168,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_channel.getByteBufferPool().release(_buffer);
return Action.SUCCEEDED;
}
// Read until buffer full or EOF
int len=0;
while (len<_buffer.capacity() && !_eof)
@ -1218,7 +1218,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_in=in;
_buffer = _channel.getByteBufferPool().acquire(getBufferSize(), _channel.useDirectBuffers());
}
@Override
protected Action process() throws Exception
{
@ -1233,16 +1233,16 @@ public class HttpOutput extends ServletOutputStream implements Runnable
_channel.getByteBufferPool().release(_buffer);
return Action.SUCCEEDED;
}
// Read from stream until buffer full or EOF
_buffer.clear();
BufferUtil.clearToFill(_buffer);
while (_buffer.hasRemaining() && !_eof)
_eof = (_in.read(_buffer)) < 0;
// write what we have
_buffer.flip();
BufferUtil.flipToFlush(_buffer, 0);
write(_buffer,_eof,this);
return Action.SCHEDULED;
}

View File

@ -167,7 +167,7 @@ public class Request implements HttpServletRequest
private String _pathInfo;
private boolean _secure;
private boolean _asyncSupported = true;
private String _asyncNotSupportedSource = null;
private boolean _newContext;
private boolean _cookiesExtracted = false;
private boolean _handled = false;
@ -1653,7 +1653,7 @@ public class Request implements HttpServletRequest
@Override
public boolean isAsyncSupported()
{
return _asyncSupported;
return _asyncNotSupportedSource==null;
}
/* ------------------------------------------------------------ */
@ -1828,7 +1828,7 @@ public class Request implements HttpServletRequest
if (_async!=null)
_async.reset();
_async=null;
_asyncSupported = true;
_asyncNotSupportedSource = null;
_handled = false;
if (_attributes != null)
_attributes.clearAttributes();
@ -1898,9 +1898,9 @@ public class Request implements HttpServletRequest
}
/* ------------------------------------------------------------ */
public void setAsyncSupported(boolean supported)
public void setAsyncSupported(boolean supported,String source)
{
_asyncSupported = supported;
_asyncNotSupportedSource = supported?null:(source==null?"unknown":source);
}
/* ------------------------------------------------------------ */
@ -2220,8 +2220,8 @@ public class Request implements HttpServletRequest
@Override
public AsyncContext startAsync() throws IllegalStateException
{
if (!_asyncSupported)
throw new IllegalStateException("!asyncSupported");
if (_asyncNotSupportedSource!=null)
throw new IllegalStateException("!asyncSupported: "+_asyncNotSupportedSource);
HttpChannelState state = getHttpChannelState();
if (_async==null)
_async=new AsyncContextState(state);
@ -2234,8 +2234,8 @@ public class Request implements HttpServletRequest
@Override
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) throws IllegalStateException
{
if (!_asyncSupported)
throw new IllegalStateException("!asyncSupported");
if (_asyncNotSupportedSource!=null)
throw new IllegalStateException("!asyncSupported: "+_asyncNotSupportedSource);
HttpChannelState state = getHttpChannelState();
if (_async==null)
_async=new AsyncContextState(state);

View File

@ -1660,17 +1660,21 @@ public class ServletHandler extends ScopedHandler
//if the request already does not support async, then the setting for the filter
//is irrelevant. However if the request supports async but this filter does not
//temporarily turn it off for the execution of the filter
boolean requestAsyncSupported = baseRequest.isAsyncSupported();
try
{
if (!_filterHolder.isAsyncSupported() && requestAsyncSupported)
baseRequest.setAsyncSupported(false);
if (baseRequest.isAsyncSupported() && !_filterHolder.isAsyncSupported())
{
try
{
baseRequest.setAsyncSupported(false,_filterHolder.toString());
filter.doFilter(request, response, _next);
}
finally
{
baseRequest.setAsyncSupported(true,null);
}
}
else
filter.doFilter(request, response, _next);
}
finally
{
baseRequest.setAsyncSupported(requestAsyncSupported);
}
return;
}
@ -1733,17 +1737,21 @@ public class ServletHandler extends ScopedHandler
//if the request already does not support async, then the setting for the filter
//is irrelevant. However if the request supports async but this filter does not
//temporarily turn it off for the execution of the filter
boolean requestAsyncSupported = _baseRequest.isAsyncSupported();
try
if (!holder.isAsyncSupported() && _baseRequest.isAsyncSupported())
{
if (!holder.isAsyncSupported() && requestAsyncSupported)
_baseRequest.setAsyncSupported(false);
try
{
_baseRequest.setAsyncSupported(false,holder.toString());
filter.doFilter(request, response, this);
}
finally
{
_baseRequest.setAsyncSupported(true,null);
}
}
else
filter.doFilter(request, response, this);
}
finally
{
_baseRequest.setAsyncSupported(requestAsyncSupported);
}
return;
}

View File

@ -829,10 +829,20 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
if (_identityService!=null)
old_run_as=_identityService.setRunAs(baseRequest.getResolvedUserIdentity(),_runAsToken);
if (!isAsyncSupported())
baseRequest.setAsyncSupported(false);
servlet.service(request,response);
if (baseRequest.isAsyncSupported() && !isAsyncSupported())
{
try
{
baseRequest.setAsyncSupported(false,this.toString());
servlet.service(request,response);
}
finally
{
baseRequest.setAsyncSupported(true,null);
}
}
else
servlet.service(request,response);
servlet_error=false;
}
catch(UnavailableException e)
@ -842,8 +852,6 @@ public class ServletHolder extends Holder<Servlet> implements UserIdentity.Scope
}
finally
{
baseRequest.setAsyncSupported(suspendable);
// pop run-as role
if (_identityService!=null)
_identityService.unsetRunAs(old_run_as);

View File

@ -56,6 +56,7 @@ import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.StdErrLog;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
@ -63,6 +64,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.eclipse.jetty.util.log.Log.getLogger;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
@ -112,6 +114,9 @@ public class AsyncServletTest
_servletHandler.addServletWithMapping(holder,"/path2/*");
_servletHandler.addServletWithMapping(holder,"/p th3/*");
_servletHandler.addServletWithMapping(new ServletHolder(new FwdServlet()),"/fwd/*");
ServletHolder holder2=new ServletHolder("NoAsync",_servlet);
holder2.setAsyncSupported(false);
_servletHandler.addServletWithMapping(holder2,"/noasync/*");
_server.start();
_port=_connector.getLocalPort();
__history.clear();
@ -163,7 +168,45 @@ public class AsyncServletTest
assertContains("NORMAL",response);
}
@Test
public void testAsyncNotSupportedNoAsync() throws Exception
{
_expectedCode="200 ";
String response=process("noasync","",null);
Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 200 OK"));
assertThat(__history,contains(
"REQUEST /ctx/noasync/info",
"initial"
));
assertContains("NORMAL",response);
}
@Test
public void testAsyncNotSupportedAsync() throws Exception
{
((StdErrLog)getLogger(ServletHandler.class)).setHideStacks(true);
try
{
_expectedCode="500 ";
String response=process("noasync","start=200",null);
Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 500 "));
assertThat(__history,contains(
"REQUEST /ctx/noasync/info",
"initial"
));
assertContains("HTTP ERROR: 500",response);
assertContains("!asyncSupported",response);
assertContains("AsyncServletTest$AsyncServlet",response);
}
finally
{
((StdErrLog)getLogger(ServletHandler.class)).setHideStacks(false);
}
}
@Test
public void testStart() throws Exception
{

View File

@ -19,22 +19,13 @@
package org.eclipse.jetty.util;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.OpenOption;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
@ -44,6 +35,12 @@ import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public class BufferUtilTest
{
@Test
@ -159,7 +156,7 @@ public class BufferUtilTest
assertEquals(2,from.remaining());
assertEquals("1234567890",BufferUtil.toString(to));
}
@Test
@ -172,7 +169,7 @@ public class BufferUtilTest
assertEquals("123",BufferUtil.toString(to));
BufferUtil.append(to,from.array(),3,2);
assertEquals("12345",BufferUtil.toString(to));
try
{
BufferUtil.append(to,from.array(),0,5);
@ -181,7 +178,7 @@ public class BufferUtilTest
catch(BufferOverflowException e)
{}
}
@Test
public void testPutDirect() throws Exception
@ -296,7 +293,7 @@ public class BufferUtilTest
int capacity = BufferUtil.TEMP_BUFFER_SIZE*2+1024;
testWriteToWithBufferThatDoesNotExposeArray(capacity);
}
@Test
public void testEnsureCapacity() throws Exception
@ -305,13 +302,13 @@ public class BufferUtilTest
assertTrue(b==BufferUtil.ensureCapacity(b, 0));
assertTrue(b==BufferUtil.ensureCapacity(b, 10));
assertTrue(b==BufferUtil.ensureCapacity(b, b.capacity()));
ByteBuffer b1 = BufferUtil.ensureCapacity(b, 64);
assertTrue(b!=b1);
assertEquals(64, b1.capacity());
assertEquals("Goodbye Cruel World", BufferUtil.toString(b1));
b1.position(8);
b1.limit(13);
assertEquals("Cruel", BufferUtil.toString(b1));
@ -328,9 +325,9 @@ public class BufferUtilTest
assertEquals(64, b3.capacity());
assertEquals("Cruel", BufferUtil.toString(b3));
assertEquals(0, b3.arrayOffset());
}
private void testWriteToWithBufferThatDoesNotExposeArray(int capacity) throws IOException
{
@ -342,7 +339,7 @@ public class BufferUtilTest
BufferUtil.writeTo(buffer.asReadOnlyBuffer(), out);
assertThat("Bytes in out equal bytes in buffer", Arrays.equals(bytes, out.toByteArray()), is(true));
}
@Test
public void testMappedFile() throws Exception
{
@ -353,26 +350,26 @@ public class BufferUtilTest
{
out.write(data);
}
ByteBuffer mapped = BufferUtil.toMappedBuffer(file);
assertEquals(data,BufferUtil.toString(mapped));
assertTrue(BufferUtil.isMappedBuffer(mapped));
ByteBuffer direct = BufferUtil.allocateDirect(data.length());
direct.clear();
BufferUtil.clearToFill(direct);
direct.put(data.getBytes(StandardCharsets.ISO_8859_1));
direct.flip();
BufferUtil.flipToFlush(direct, 0);
assertEquals(data,BufferUtil.toString(direct));
assertFalse(BufferUtil.isMappedBuffer(direct));
ByteBuffer slice = direct.slice();
assertEquals(data,BufferUtil.toString(slice));
assertFalse(BufferUtil.isMappedBuffer(slice));
ByteBuffer duplicate = direct.duplicate();
assertEquals(data,BufferUtil.toString(duplicate));
assertFalse(BufferUtil.isMappedBuffer(duplicate));
ByteBuffer readonly = direct.asReadOnlyBuffer();
assertEquals(data,BufferUtil.toString(readonly));
assertFalse(BufferUtil.isMappedBuffer(readonly));