Merge remote-tracking branch 'origin/jetty-9.4.x'

Conflicts:
	jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java
	jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppClassLoaderTest.java
This commit is contained in:
Joakim Erdfelt 2017-04-05 06:47:25 -07:00
commit 3aeb8dbd78
57 changed files with 1961 additions and 749 deletions

View File

@ -41,6 +41,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Supplier;
import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.client.api.ContentResponse;
@ -83,6 +84,7 @@ public class HttpRequest implements Request
private Map<String, Object> attributes; private Map<String, Object> attributes;
private List<RequestListener> requestListeners; private List<RequestListener> requestListeners;
private BiFunction<Request, Request, Response.CompleteListener> pushListener; private BiFunction<Request, Request, Response.CompleteListener> pushListener;
private Supplier<HttpFields> trailers;
protected HttpRequest(HttpClient client, HttpConversation conversation, URI uri) protected HttpRequest(HttpClient client, HttpConversation conversation, URI uri)
{ {
@ -589,6 +591,12 @@ public class HttpRequest implements Request
return this; return this;
} }
public HttpRequest trailers(Supplier<HttpFields> trailers)
{
this.trailers = trailers;
return this;
}
@Override @Override
public ContentProvider getContent() public ContentProvider getContent()
{ {
@ -725,6 +733,11 @@ public class HttpRequest implements Request
return pushListener; return pushListener;
} }
public Supplier<HttpFields> getTrailers()
{
return trailers;
}
@Override @Override
public boolean abort(Throwable cause) public boolean abort(Throwable cause)
{ {

View File

@ -23,6 +23,7 @@ import java.util.List;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
@ -34,6 +35,7 @@ public class HttpResponse implements Response
private HttpVersion version; private HttpVersion version;
private int status; private int status;
private String reason; private String reason;
private HttpFields trailers;
public HttpResponse(Request request, List<ResponseListener> listeners) public HttpResponse(Request request, List<ResponseListener> listeners)
{ {
@ -97,6 +99,19 @@ public class HttpResponse implements Response
return result; return result;
} }
public HttpFields getTrailers()
{
return trailers;
}
public HttpResponse trailer(HttpField trailer)
{
if (trailers == null)
trailers = new HttpFields();
trailers.add(trailer);
return this;
}
@Override @Override
public boolean abort(Throwable cause) public boolean abort(Throwable cause)
{ {

View File

@ -20,10 +20,12 @@ package org.eclipse.jetty.client;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.api.Result;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpHeaderValue;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
@ -31,7 +33,6 @@ import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.IteratingCallback;
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.util.thread.Invocable.InvocationType;
/** /**
* {@link HttpSender} abstracts the algorithm to send HTTP requests, so that subclasses only implement * {@link HttpSender} abstracts the algorithm to send HTTP requests, so that subclasses only implement
@ -42,9 +43,9 @@ import org.eclipse.jetty.util.thread.Invocable.InvocationType;
* {@link HttpSender} governs two state machines. * {@link HttpSender} governs two state machines.
* <p> * <p>
* The request state machine is updated by {@link HttpSender} as the various steps of sending a request * The request state machine is updated by {@link HttpSender} as the various steps of sending a request
* are executed, see <code>RequestState</code>. * are executed, see {@code RequestState}.
* At any point in time, a user thread may abort the request, which may (if the request has not been * At any point in time, a user thread may abort the request, which may (if the request has not been
* completely sent yet) move the request state machine to <code>RequestState#FAILURE</code>. * completely sent yet) move the request state machine to {@code RequestState#FAILURE}.
* The request state machine guarantees that the request steps are executed (by I/O threads) only if * The request state machine guarantees that the request steps are executed (by I/O threads) only if
* the request has not been failed already. * the request has not been failed already.
* <p> * <p>
@ -64,7 +65,8 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
private final AtomicReference<SenderState> senderState = new AtomicReference<>(SenderState.IDLE); private final AtomicReference<SenderState> senderState = new AtomicReference<>(SenderState.IDLE);
private final Callback commitCallback = new CommitCallback(); private final Callback commitCallback = new CommitCallback();
private final IteratingCallback contentCallback = new ContentCallback(); private final IteratingCallback contentCallback = new ContentCallback();
private final Callback lastCallback = new LastContentCallback(); private final Callback trailersCallback = new TrailersCallback();
private final Callback lastCallback = new LastCallback();
private final HttpChannel channel; private final HttpChannel channel;
private HttpContent content; private HttpContent content;
private Throwable failure; private Throwable failure;
@ -407,7 +409,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
/** /**
* Implementations should send the content at the {@link HttpContent} cursor position over the wire. * Implementations should send the content at the {@link HttpContent} cursor position over the wire.
* <p> * <p>
* The {@link HttpContent} cursor is advanced by {@link HttpSender} at the right time, and if more * The {@link HttpContent} cursor is advanced by HttpSender at the right time, and if more
* content needs to be sent, this method is invoked again; subclasses need only to send the content * content needs to be sent, this method is invoked again; subclasses need only to send the content
* at the {@link HttpContent} cursor position. * at the {@link HttpContent} cursor position.
* <p> * <p>
@ -422,6 +424,15 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
*/ */
protected abstract void sendContent(HttpExchange exchange, HttpContent content, Callback callback); protected abstract void sendContent(HttpExchange exchange, HttpContent content, Callback callback);
/**
* Implementations should send the HTTP trailers and notify the given {@code callback} of the
* result of this operation.
*
* @param exchange the exchange to send
* @param callback the callback to notify
*/
protected abstract void sendTrailers(HttpExchange exchange, Callback callback);
protected void reset() protected void reset()
{ {
HttpContent content = this.content; HttpContent content = this.content;
@ -674,13 +685,6 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
private class CommitCallback implements Callback private class CommitCallback implements Callback
{ {
@Override
public InvocationType getInvocationType()
{
return content.getInvocationType();
}
@Override @Override
public void succeeded() public void succeeded()
{ {
@ -721,10 +725,20 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
if (content == null) if (content == null)
return; return;
if (!content.hasContent()) HttpRequest request = exchange.getRequest();
Supplier<HttpFields> trailers = request.getTrailers();
boolean hasContent = content.hasContent();
if (!hasContent)
{ {
// No content to send, we are done. if (trailers == null)
someToSuccess(exchange); {
// No trailers or content to send, we are done.
someToSuccess(exchange);
}
else
{
sendTrailers(exchange, lastCallback);
}
} }
else else
{ {
@ -825,7 +839,9 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
if (lastContent) if (lastContent)
{ {
sendContent(exchange, content, lastCallback); HttpRequest request = exchange.getRequest();
Supplier<HttpFields> trailers = request.getTrailers();
sendContent(exchange, content, trailers == null ? lastCallback : trailersCallback);
return Action.IDLE; return Action.IDLE;
} }
@ -884,19 +900,35 @@ public abstract class HttpSender implements AsyncContentProvider.Listener
@Override @Override
protected void onCompleteSuccess() protected void onCompleteSuccess()
{ {
// Nothing to do, since we always return false from process(). // Nothing to do, since we always return IDLE from process().
// Termination is obtained via LastContentCallback. // Termination is obtained via LastCallback.
} }
} }
private class LastContentCallback implements Callback private class TrailersCallback implements Callback
{ {
@Override @Override
public InvocationType getInvocationType() public void succeeded()
{ {
return content.getInvocationType(); HttpExchange exchange = getHttpExchange();
if (exchange == null)
return;
sendTrailers(exchange, lastCallback);
} }
@Override
public void failed(Throwable x)
{
HttpContent content = HttpSender.this.content;
if (content == null)
return;
content.failed(x);
anyToFailure(x);
}
}
private class LastCallback implements Callback
{
@Override @Override
public void succeeded() public void succeeded()
{ {

View File

@ -277,6 +277,16 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res
return false; return false;
} }
@Override
public void parsedTrailer(HttpField trailer)
{
HttpExchange exchange = getHttpExchange();
if (exchange == null)
return;
exchange.getResponse().trailer(trailer);
}
@Override @Override
public boolean messageComplete() public boolean messageComplete()
{ {

View File

@ -23,26 +23,29 @@ import java.nio.ByteBuffer;
import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.HttpContent; import org.eclipse.jetty.client.HttpContent;
import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpRequestException; import org.eclipse.jetty.client.HttpRequestException;
import org.eclipse.jetty.client.HttpSender; import org.eclipse.jetty.client.HttpSender;
import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentProvider;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.IteratingCallback;
public class HttpSenderOverHTTP extends HttpSender public class HttpSenderOverHTTP extends HttpSender
{ {
private final HttpGenerator generator = new HttpGenerator(); private final HttpGenerator generator = new HttpGenerator();
private final HttpClient httpClient;
private boolean shutdown; private boolean shutdown;
public HttpSenderOverHTTP(HttpChannelOverHTTP channel) public HttpSenderOverHTTP(HttpChannelOverHTTP channel)
{ {
super(channel); super(channel);
httpClient = channel.getHttpDestination().getHttpClient();
} }
@Override @Override
@ -71,8 +74,7 @@ public class HttpSenderOverHTTP extends HttpSender
{ {
try try
{ {
HttpClient client = getHttpChannel().getHttpDestination().getHttpClient(); ByteBufferPool bufferPool = httpClient.getByteBufferPool();
ByteBufferPool bufferPool = client.getByteBufferPool();
ByteBuffer chunk = null; ByteBuffer chunk = null;
while (true) while (true)
{ {
@ -90,6 +92,11 @@ public class HttpSenderOverHTTP extends HttpSender
chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false); chunk = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false);
break; break;
} }
case NEED_CHUNK_TRAILER:
{
callback.succeeded();
return;
}
case FLUSH: case FLUSH:
{ {
EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint(); EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
@ -131,6 +138,21 @@ public class HttpSenderOverHTTP extends HttpSender
} }
} }
@Override
protected void sendTrailers(HttpExchange exchange, Callback callback)
{
try
{
new TrailersCallback(callback).iterate();
}
catch (Throwable x)
{
if (LOG.isDebugEnabled())
LOG.debug(x);
callback.failed(x);
}
}
@Override @Override
protected void reset() protected void reset()
{ {
@ -181,7 +203,7 @@ public class HttpSenderOverHTTP extends HttpSender
this.exchange = exchange; this.exchange = exchange;
this.callback = callback; this.callback = callback;
Request request = exchange.getRequest(); HttpRequest request = exchange.getRequest();
ContentProvider requestContent = request.getContent(); ContentProvider requestContent = request.getContent();
long contentLength = requestContent == null ? -1 : requestContent.getLength(); long contentLength = requestContent == null ? -1 : requestContent.getLength();
String path = request.getPath(); String path = request.getPath();
@ -189,6 +211,7 @@ public class HttpSenderOverHTTP extends HttpSender
if (query != null) if (query != null)
path += "?" + query; path += "?" + query;
metaData = new MetaData.Request(request.getMethod(), new HttpURI(path), request.getVersion(), request.getHeaders(), contentLength); metaData = new MetaData.Request(request.getMethod(), new HttpURI(path), request.getVersion(), request.getHeaders(), contentLength);
metaData.setTrailerSupplier(request.getTrailers());
if (!expects100Continue(request)) if (!expects100Continue(request))
{ {
@ -201,9 +224,6 @@ public class HttpSenderOverHTTP extends HttpSender
@Override @Override
protected Action process() throws Exception protected Action process() throws Exception
{ {
HttpClient client = getHttpChannel().getHttpDestination().getHttpClient();
ByteBufferPool bufferPool = client.getByteBufferPool();
while (true) while (true)
{ {
HttpGenerator.Result result = generator.generateRequest(metaData, headerBuffer, chunkBuffer, contentBuffer, lastContent); HttpGenerator.Result result = generator.generateRequest(metaData, headerBuffer, chunkBuffer, contentBuffer, lastContent);
@ -217,31 +237,28 @@ public class HttpSenderOverHTTP extends HttpSender
{ {
case NEED_HEADER: case NEED_HEADER:
{ {
headerBuffer = bufferPool.acquire(client.getRequestBufferSize(), false); headerBuffer = httpClient.getByteBufferPool().acquire(httpClient.getRequestBufferSize(), false);
break; break;
} }
case NEED_CHUNK: case NEED_CHUNK:
{ {
chunkBuffer = bufferPool.acquire(HttpGenerator.CHUNK_SIZE, false); chunkBuffer = httpClient.getByteBufferPool().acquire(HttpGenerator.CHUNK_SIZE, false);
break; break;
} }
case NEED_CHUNK_TRAILER:
{
return Action.SUCCEEDED;
}
case FLUSH: case FLUSH:
{ {
EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint(); EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
if (headerBuffer == null)
headerBuffer = BufferUtil.EMPTY_BUFFER;
if (chunkBuffer == null) if (chunkBuffer == null)
{ chunkBuffer = BufferUtil.EMPTY_BUFFER;
if (contentBuffer == null) if (contentBuffer == null)
endPoint.write(this, headerBuffer); contentBuffer = BufferUtil.EMPTY_BUFFER;
else endPoint.write(this, headerBuffer, chunkBuffer, contentBuffer);
endPoint.write(this, headerBuffer, contentBuffer);
}
else
{
if (contentBuffer == null)
endPoint.write(this, headerBuffer, chunkBuffer);
else
endPoint.write(this, headerBuffer, chunkBuffer, contentBuffer);
}
generated = true; generated = true;
return Action.SCHEDULED; return Action.SCHEDULED;
} }
@ -296,13 +313,91 @@ public class HttpSenderOverHTTP extends HttpSender
private void release() private void release()
{ {
HttpClient client = getHttpChannel().getHttpDestination().getHttpClient(); ByteBufferPool bufferPool = httpClient.getByteBufferPool();
ByteBufferPool bufferPool = client.getByteBufferPool(); if (headerBuffer != BufferUtil.EMPTY_BUFFER)
bufferPool.release(headerBuffer); bufferPool.release(headerBuffer);
headerBuffer = null; headerBuffer = null;
if (chunkBuffer != null) if (chunkBuffer != BufferUtil.EMPTY_BUFFER)
bufferPool.release(chunkBuffer); bufferPool.release(chunkBuffer);
chunkBuffer = null; chunkBuffer = null;
contentBuffer = null;
}
}
private class TrailersCallback extends IteratingCallback
{
private final Callback callback;
private ByteBuffer chunkBuffer;
public TrailersCallback(Callback callback)
{
this.callback = callback;
}
@Override
protected Action process() throws Throwable
{
while (true)
{
HttpGenerator.Result result = generator.generateRequest(null, null, chunkBuffer, null, true);
if (LOG.isDebugEnabled())
LOG.debug("Generated trailers {}/{}", result, generator);
switch (result)
{
case NEED_CHUNK_TRAILER:
{
chunkBuffer = httpClient.getByteBufferPool().acquire(httpClient.getRequestBufferSize(), false);
break;
}
case FLUSH:
{
EndPoint endPoint = getHttpChannel().getHttpConnection().getEndPoint();
endPoint.write(this, chunkBuffer);
return Action.SCHEDULED;
}
case SHUTDOWN_OUT:
{
shutdownOutput();
return Action.SUCCEEDED;
}
case DONE:
{
return Action.SUCCEEDED;
}
default:
{
throw new IllegalStateException(result.toString());
}
}
}
}
@Override
public void succeeded()
{
release();
super.succeeded();
}
@Override
public void failed(Throwable x)
{
release();
callback.failed(x);
super.failed(x);
}
@Override
protected void onCompleteSuccess()
{
super.onCompleteSuccess();
callback.succeeded();
}
private void release()
{
httpClient.getByteBufferPool().release(chunkBuffer);
chunkBuffer = null;
} }
} }

View File

@ -71,8 +71,6 @@ public abstract class AbstractHttpClientServerTest
sslContextFactory.setEndpointIdentificationAlgorithm(""); sslContextFactory.setEndpointIdentificationAlgorithm("");
sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
sslContextFactory.setKeyStorePassword("storepwd"); sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
sslContextFactory.setTrustStorePassword("storepwd");
} }
if (server == null) if (server == null)

View File

@ -72,8 +72,6 @@ public class HttpClientTLSTest
sslContextFactory.setEndpointIdentificationAlgorithm(""); sslContextFactory.setEndpointIdentificationAlgorithm("");
sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
sslContextFactory.setKeyStorePassword("storepwd"); sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
sslContextFactory.setTrustStorePassword("storepwd");
return sslContextFactory; return sslContextFactory;
} }

View File

@ -69,8 +69,6 @@ public class TLSServerConnectionCloseTest
sslContextFactory.setEndpointIdentificationAlgorithm(""); sslContextFactory.setEndpointIdentificationAlgorithm("");
sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
sslContextFactory.setKeyStorePassword("storepwd"); sslContextFactory.setKeyStorePassword("storepwd");
sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks");
sslContextFactory.setTrustStorePassword("storepwd");
QueuedThreadPool clientThreads = new QueuedThreadPool(); QueuedThreadPool clientThreads = new QueuedThreadPool();
clientThreads.setName("client"); clientThreads.setName("client");

View File

@ -0,0 +1,233 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.client.ssl;
import java.security.cert.Certificate;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSession;
import org.eclipse.jetty.client.EmptyServerHandler;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.io.ssl.SslHandshakeListener;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
/**
* In order to work, client authentication needs a certificate
* signed by a CA that also signed the server certificate.
* <p>
* For this test, the client certificate is signed with the server
* certificate, and the server certificate is self-signed.
*/
public class NeedWantClientAuthTest
{
private Server server;
private ServerConnector connector;
private HttpClient client;
private void startServer(SslContextFactory sslContextFactory, Handler handler) throws Exception
{
QueuedThreadPool serverThreads = new QueuedThreadPool();
serverThreads.setName("server");
server = new Server(serverThreads);
connector = new ServerConnector(server, sslContextFactory);
server.addConnector(connector);
server.setHandler(handler);
server.start();
}
private void startClient(SslContextFactory sslContextFactory) throws Exception
{
QueuedThreadPool clientThreads = new QueuedThreadPool();
clientThreads.setName("client");
client = new HttpClient(sslContextFactory);
client.setExecutor(clientThreads);
client.start();
}
private SslContextFactory createSslContextFactory()
{
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setEndpointIdentificationAlgorithm("");
sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks");
sslContextFactory.setKeyStorePassword("storepwd");
return sslContextFactory;
}
@After
public void dispose() throws Exception
{
if (client != null)
client.stop();
if (server != null)
server.stop();
}
@Test
public void testWantClientAuthWithoutAuth() throws Exception
{
SslContextFactory serverSSL = new SslContextFactory();
serverSSL.setKeyStorePath("src/test/resources/keystore.jks");
serverSSL.setKeyStorePassword("storepwd");
serverSSL.setWantClientAuth(true);
startServer(serverSSL, new EmptyServerHandler());
SslContextFactory clientSSL = new SslContextFactory(true);
startClient(clientSSL);
ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testWantClientAuthWithAuth() throws Exception
{
SslContextFactory serverSSL = new SslContextFactory();
serverSSL.setKeyStorePath("src/test/resources/keystore.jks");
serverSSL.setKeyStorePassword("storepwd");
serverSSL.setWantClientAuth(true);
startServer(serverSSL, new EmptyServerHandler());
CountDownLatch handshakeLatch = new CountDownLatch(1);
connector.addBean(new SslHandshakeListener()
{
@Override
public void handshakeSucceeded(Event event)
{
try
{
SSLSession session = event.getSSLEngine().getSession();
Certificate[] clientCerts = session.getPeerCertificates();
Assert.assertNotNull(clientCerts);
Assert.assertThat(clientCerts.length, Matchers.greaterThan(0));
handshakeLatch.countDown();
}
catch (Throwable x)
{
x.printStackTrace();
}
}
});
SslContextFactory clientSSL = new SslContextFactory(true);
clientSSL.setKeyStorePath("src/test/resources/client_keystore.jks");
clientSSL.setKeyStorePassword("storepwd");
startClient(clientSSL);
ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertTrue(handshakeLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testNeedClientAuthWithoutAuth() throws Exception
{
SslContextFactory serverSSL = new SslContextFactory();
serverSSL.setKeyStorePath("src/test/resources/keystore.jks");
serverSSL.setKeyStorePassword("storepwd");
serverSSL.setNeedClientAuth(true);
startServer(serverSSL, new EmptyServerHandler());
SslContextFactory clientSSL = new SslContextFactory(true);
startClient(clientSSL);
CountDownLatch handshakeLatch = new CountDownLatch(1);
client.addBean(new SslHandshakeListener()
{
@Override
public void handshakeFailed(Event event, Throwable failure)
{
Assert.assertThat(failure, Matchers.instanceOf(SSLHandshakeException.class));
handshakeLatch.countDown();
}
});
CountDownLatch latch = new CountDownLatch(1);
client.newRequest("https://localhost:" + connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send(result ->
{
if (result.isFailed())
latch.countDown();
});
Assert.assertTrue(handshakeLatch.await(5, TimeUnit.SECONDS));
Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
}
@Test
public void testNeedClientAuthWithAuth() throws Exception
{
SslContextFactory serverSSL = new SslContextFactory();
serverSSL.setKeyStorePath("src/test/resources/keystore.jks");
serverSSL.setKeyStorePassword("storepwd");
serverSSL.setNeedClientAuth(true);
startServer(serverSSL, new EmptyServerHandler());
CountDownLatch handshakeLatch = new CountDownLatch(1);
connector.addBean(new SslHandshakeListener()
{
@Override
public void handshakeSucceeded(Event event)
{
try
{
SSLSession session = event.getSSLEngine().getSession();
Certificate[] clientCerts = session.getPeerCertificates();
Assert.assertNotNull(clientCerts);
Assert.assertThat(clientCerts.length, Matchers.greaterThan(0));
handshakeLatch.countDown();
}
catch (Throwable x)
{
x.printStackTrace();
}
}
});
SslContextFactory clientSSL = new SslContextFactory(true);
clientSSL.setKeyStorePath("src/test/resources/client_keystore.jks");
clientSSL.setKeyStorePassword("storepwd");
startClient(clientSSL);
ContentResponse response = client.newRequest("https://localhost:" + connector.getLocalPort())
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertTrue(handshakeLatch.await(5, TimeUnit.SECONDS));
}
}

Binary file not shown.

View File

@ -19,7 +19,7 @@
This chapter discusses various options for configuring logging. This chapter discusses various options for configuring logging.
include::configuring-jetty-logging.adoc[] //include::configuring-jetty-logging.adoc[]
include::default-logging-with-stderrlog.adoc[] include::default-logging-with-stderrlog.adoc[]
include::configuring-jetty-request-logs.adoc[] include::configuring-jetty-request-logs.adoc[]
include::configuring-logging-modules.adoc[] include::configuring-logging-modules.adoc[]

View File

@ -58,6 +58,12 @@ INFO: Base directory was modified
The above command will add a new `requestlog.ini` file to your link:#start-vs-startd[`{$jetty.base}/start.d` directory]. The above command will add a new `requestlog.ini` file to your link:#start-vs-startd[`{$jetty.base}/start.d` directory].
____
[NOTE]
By default, request logs are not set to be appended, meaning a the log file is wiped clean upon sever restart.
You can change this setting by editing the `requestlog.ini` and un-commenting the line that reads `jetty.requestlog.append=true`.
____
The equivalent code for embedded usages of Jetty is: The equivalent code for embedded usages of Jetty is:
[source, java, subs="{sub-order}"] [source, java, subs="{sub-order}"]

View File

@ -45,6 +45,13 @@ INFO : Base directory was modified
The default configuration for logging output will create a file `${jetty.base}/logs/yyyy_mm_dd.stderrout.log` which allows configuration of the output directory by setting the `jetty.logs` property. The default configuration for logging output will create a file `${jetty.base}/logs/yyyy_mm_dd.stderrout.log` which allows configuration of the output directory by setting the `jetty.logs` property.
____
[NOTE]
By default, logs are not set to be appended, meaning a the log file is wiped clean upon sever restart.
You can change this setting by editing the `console-capture.ini` and un-commenting the line that reads `jetty.console-capture.append=true`.
____
Just enabling the `console-capture` will simply output the values of STDERR and STDOUT to a log file. Just enabling the `console-capture` will simply output the values of STDERR and STDOUT to a log file.
To customize the log further, a module named `logging-jetty` is available to provides a default properties file to configure. To customize the log further, a module named `logging-jetty` is available to provides a default properties file to configure.
As with `console-capture`, you activate the `logging-jetty` on the command line. As with `console-capture`, you activate the `logging-jetty` on the command line.

View File

@ -27,9 +27,9 @@ Authorization::
==== Configuring an Authentication mechanism ==== Configuring an Authentication mechanism
The jetty server supports several standard authentication mechanisms: http://en.wikipedia.org/wiki/Basic_access_authentication[BASIC]; http://en.wikipedia.org/wiki/Digest_authentication[DIGEST]; http://en.wikipedia.org/wiki/Form-based_authentication[FORM]; CLIENT-CERT; and other mechanisms can be plugged in using the extensible http://docs.oracle.com/cd/E19462-01/819-6717/gcszc/index.html[JASPI] or http://en.wikipedia.org/wiki/SPNEGO[SPNEGO] mechanisms. Jetty server supports several standard authentication mechanisms: http://en.wikipedia.org/wiki/Basic_access_authentication[BASIC]; http://en.wikipedia.org/wiki/Digest_authentication[DIGEST]; http://en.wikipedia.org/wiki/Form-based_authentication[FORM]; CLIENT-CERT; and other mechanisms can be plugged in using the extensible http://docs.oracle.com/cd/E19462-01/819-6717/gcszc/index.html[JASPI] or http://en.wikipedia.org/wiki/SPNEGO[SPNEGO] mechanisms.
Internally, configuring an authentication mechanism is done by setting an instance of a the link:{JDURL}/org/eclipse/jetty/security/Authenticator.html[Authenticator] interface onto the link:{JDURL}/org/eclipse/jetty/security/SecurityHandler.html[SecurityHandler] of the context, but in most cases it is done by declaring a `< login-config>` element in the standard web.xml descriptor or via annotations. Internally, configuring an authentication mechanism is done by setting an instance of a the link:{JDURL}/org/eclipse/jetty/security/Authenticator.html[Authenticator] interface onto the link:{JDURL}/org/eclipse/jetty/security/SecurityHandler.html[SecurityHandler] of the context, but in most cases it is done by declaring a `<login-config>` element in the standard web.xml descriptor or via annotations.
Below is an example taken from the link:{GITBROWSEURL}/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml?h=release-9[jetty-test-webapp web.xml] that configures BASIC authentication: Below is an example taken from the link:{GITBROWSEURL}/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml?h=release-9[jetty-test-webapp web.xml] that configures BASIC authentication:
@ -103,7 +103,7 @@ When a request is received for a protected resource, the web container checks if
The Servlet Specification does not address how the static security information in the `WEB-INF/web.xml` file is mapped to the runtime environment of the container. The Servlet Specification does not address how the static security information in the `WEB-INF/web.xml` file is mapped to the runtime environment of the container.
For Jetty, the link:{JDURL}/org/eclipse/jetty/security/LoginService.html[LoginService] performs this function. For Jetty, the link:{JDURL}/org/eclipse/jetty/security/LoginService.html[LoginService] performs this function.
A LoginService has a unique name, and gives access to information about a set of users. A `LoginService` has a unique name, and gives access to information about a set of users.
Each user has authentication information (e.g. a password) and a set of roles associated with him/herself. Each user has authentication information (e.g. a password) and a set of roles associated with him/herself.
You may configure one or many different LoginServices depending on your needs. You may configure one or many different LoginServices depending on your needs.
@ -114,7 +114,8 @@ When a request to a web application requires authentication or authorization, Je
==== Scoping Security Realms ==== Scoping Security Realms
A LoginService has a unique name, and is composed of a set of users. Each user has authentication information (for example, a password) and a set of roles associated with him/herself. A `LoginService` has a unique name, and is composed of a set of users.
Each user has authentication information (for example, a password) and a set of roles associated with him/herself.
You can configure one or many different realms depending on your needs. You can configure one or many different realms depending on your needs.
* Configure a single LoginService to share common security information across all of your web applications. * Configure a single LoginService to share common security information across all of your web applications.
@ -144,8 +145,8 @@ Here's an example of an xml file that defines an in-memory type of LoginService
---- ----
If you define more than one LoginService on a Server, you will need to specify which one you want used for each context. If you define more than one `LoginService` on a Server, you will need to specify which one you want used for each context.
You can do that by telling the context the name of the LoginService, or passing it the LoginService instance. You can do that by telling the context the name of the `LoginService`, or passing it the `LoginService` instance.
Here's an example of doing both of these, using a link:#deployable-descriptor-file[context xml file]: Here's an example of doing both of these, using a link:#deployable-descriptor-file[context xml file]:
[source, xml, subs="{sub-order}"] [source, xml, subs="{sub-order}"]
@ -170,7 +171,7 @@ Here's an example of doing both of these, using a link:#deployable-descriptor-fi
===== Per-Webapp Scoped ===== Per-Webapp Scoped
Alternatively, you can define a LoginService for just a single web application. Alternatively, you can define a `LoginService` for just a single web application.
Here's how to define the same HashLoginService, but inside a link:#deployable-descriptor-file[context xml file]: Here's how to define the same HashLoginService, but inside a link:#deployable-descriptor-file[context xml file]:
[source, xml, subs="{sub-order}"] [source, xml, subs="{sub-order}"]
@ -192,12 +193,12 @@ Here's how to define the same HashLoginService, but inside a link:#deployable-de
---- ----
Jetty provides a number of different LoginService types which can be seen in the next section. Jetty provides a number of different `LoginService` types which can be seen in the next section.
[[configuring-login-service]] [[configuring-login-service]]
==== Configuring a LoginService ==== Configuring a LoginService
A link:{JDURL}/org/eclipse/jetty/security/LoginService.html[LoginService] instance is required by each context/webapp that has a authentication mechanism, which is used to check the validity of the username and credentials collected by the authentication mechanism. Jetty provides the following implementations of LoginService: A link:{JDURL}/org/eclipse/jetty/security/LoginService.html[`LoginService`] instance is required by each context/webapp that has a authentication mechanism, which is used to check the validity of the username and credentials collected by the authentication mechanism. Jetty provides the following implementations of `LoginService`:
link:{JDURL}/org/eclipse/jetty/security/HashLoginService.html[HashLoginService]:: link:{JDURL}/org/eclipse/jetty/security/HashLoginService.html[HashLoginService]::
A user realm that is backed by a hash map that is filled either programatically or from a Java properties file. A user realm that is backed by a hash map that is filled either programatically or from a Java properties file.
@ -211,16 +212,16 @@ link:{JDURL}/org/eclipse/jetty/jaas/JAASLoginService.html[JAASLoginService]::
link:{JDURL}/org/eclipse/jetty/security/SpnegoLoginService.html[SpnegoLoginService]:: link:{JDURL}/org/eclipse/jetty/security/SpnegoLoginService.html[SpnegoLoginService]::
http://en.wikipedia.org/wiki/SPNEGO[SPNEGO] Authentication; see the section on link:#spnego-support[SPNEGO support] for more information. http://en.wikipedia.org/wiki/SPNEGO[SPNEGO] Authentication; see the section on link:#spnego-support[SPNEGO support] for more information.
An instance of a LoginService can be matched to a context/webapp by: An instance of a `LoginService` can be matched to a context/webapp by:
* A LoginService instance may be set directly on the SecurityHandler instance via embedded code or IoC XML * A `LoginService` instance may be set directly on the `SecurityHandler` instance via embedded code or IoC XML
* Matching the realm-name defined in web.xml with the name of a LoginService instance that has been added to the Server instance as a dependent bean * Matching the realm-name defined in web.xml with the name of a `LoginService` instance that has been added to the Server instance as a dependent bean
* If only a single LoginService instance has been set on the Server then it is used as the login service for the context * If only a single `LoginService` instance has been set on the Server then it is used as the login service for the context
[[hash-login-service]] [[hash-login-service]]
===== HashLoginService ===== HashLoginService
The HashLoginService is a simple and efficient login service that loads usernames, credentials and roles from a Java properties file in the format: The `HashLoginService` is a simple and efficient login service that loads usernames, credentials and roles from a Java properties file in the format:
[source,properties] [source,properties]
---- ----
@ -249,7 +250,7 @@ guest: guest,read-only
---- ----
You configure the HashLoginService with a name and a reference to the location of the properties file: You configure the `HashLoginService` with a name and a reference to the location of the properties file:
[source, xml, subs="{sub-order}"] [source, xml, subs="{sub-order}"]
---- ----
@ -378,12 +379,12 @@ When a user requests a resource that is access protected, the LoginService will
Until Servlet 3.1, role-based authorization could define: Until Servlet 3.1, role-based authorization could define:
* access granted to a set of named roles * Access granted to a set of named roles
* access totally forbidden, regardless of role * Access totally forbidden, regardless of role
* access granted to a user in any of the roles defined in the effective web.xml. * Access granted to a user in any of the roles defined in the effective web.xml.
This is indicated by the special value of "*" for the `<role-name>` of a `<auth-constraint> `in the `<security-constraint>` This is indicated by the special value of `*` for the `<role-name>` of a `<auth-constraint>` in the `<security-constraint>`
With the advent of Servlet 3.1, there is now another authorization: With the advent of Servlet 3.1, there is now another authorization:
* access granted to any user who is authenticated, regardless of roles. * Access granted to any user who is authenticated, regardless of roles.
This is indicated by the special value of "**" for the `<role-name>` of a `<auth-constraint>` in the `<security-constraint>` This is indicated by the special value of `**` for the `<role-name>` of a `<auth-constraint>` in the `<security-constraint>`

View File

@ -30,6 +30,12 @@ When you download and unpack the binary, it is extracted into a directory called
Put this directory in a convenient location. Put this directory in a convenient location.
The rest of the instructions in this documentation refer to this location as either `JETTY_HOME` or as `$(jetty.home).` The rest of the instructions in this documentation refer to this location as either `JETTY_HOME` or as `$(jetty.home).`
_____
[IMPORTANT]
It is important that only stable releases are used in production environments.
Versions that have been deprecated or are released as Milestones (M) or Release Candidates (RC) are not suitable for production as they may contain security flaws or incomplete/non-functioning feature sets.
_____
[[distribution-content]] [[distribution-content]]
==== Distribution Content ==== Distribution Content

View File

@ -17,6 +17,12 @@
[[quickstart-jetty-coordinates]] [[quickstart-jetty-coordinates]]
=== Finding Jetty in Maven === Finding Jetty in Maven
_____
[IMPORTANT]
It is important that only stable releases are used in production environments.
Versions that have been deprecated or are released as Milestones (M) or Release Candidates (RC) are not suitable for production as they may contain security flaws or incomplete/non-functioning feature sets.
_____
==== Maven Coordinates ==== Maven Coordinates
Jetty has existed in Maven Central almost since its inception, though the coordinates have changed over the years. Jetty has existed in Maven Central almost since its inception, though the coordinates have changed over the years.
@ -34,7 +40,7 @@ The top level Project Object Model (POM) for the Jetty project is located under
</dependency> </dependency>
---- ----
==== Changelogs in Central ==== Changelogs in Maven Central
The changes between versions of Jetty are tracked in a file called VERSIONS.txt, which is under source control and is generated on release. The changes between versions of Jetty are tracked in a file called VERSIONS.txt, which is under source control and is generated on release.
Those generated files are also uploaded into Maven Central during the release of the top level POM. You can find them as a classifier marked artifact. Those generated files are also uploaded into Maven Central during the release of the top level POM. You can find them as a classifier marked artifact.

View File

@ -21,6 +21,12 @@ Jetty 9 is the most recent version of Jetty and has a great many improvements ov
This documentation which focuses on Jetty 9. This documentation which focuses on Jetty 9.
While many people continue to use older versions of Jetty, we generally recommend using Jetty 9 as it represents the version of Jetty that we will actively maintain and improve over the next few years. While many people continue to use older versions of Jetty, we generally recommend using Jetty 9 as it represents the version of Jetty that we will actively maintain and improve over the next few years.
_____
[IMPORTANT]
It is important that only stable releases are used in production environments.
Versions that have been deprecated or are released as Milestones (M) or Release Candidates (RC) are not suitable for production as they may contain security flaws or incomplete/non-functioning feature sets.
_____
.Jetty Versions .Jetty Versions
[width="100%",cols="12%,9%,15%,6%,21%,10%,6%,21%",options="header",] [width="100%",cols="12%,9%,15%,6%,21%,10%,6%,21%",options="header",]
|======================================================================= |=======================================================================

View File

@ -125,4 +125,10 @@ public class HttpSenderOverFCGI extends HttpSender
getHttpChannel().flush(result); getHttpChannel().flush(result);
} }
} }
@Override
protected void sendTrailers(HttpExchange exchange, Callback callback)
{
callback.succeeded();
}
} }

View File

@ -18,8 +18,6 @@
package org.eclipse.jetty.http; package org.eclipse.jetty.http;
import static org.eclipse.jetty.http.HttpStatus.INTERNAL_SERVER_ERROR_500;
import java.io.IOException; import java.io.IOException;
import java.nio.BufferOverflowException; import java.nio.BufferOverflowException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -34,6 +32,8 @@ import org.eclipse.jetty.util.Trie;
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 static org.eclipse.jetty.http.HttpStatus.INTERNAL_SERVER_ERROR_500;
/** /**
* HttpGenerator. Builds HTTP Messages. * HttpGenerator. Builds HTTP Messages.
* <p> * <p>
@ -764,7 +764,7 @@ public class HttpGenerator
} }
// Else if we are HTTP/1.1 and the content length is unknown and we are either persistent // Else if we are HTTP/1.1 and the content length is unknown and we are either persistent
// or it is a request with content (which cannot EOF) or the app has requested chunking // or it is a request with content (which cannot EOF) or the app has requested chunking
else if (http11 && content_length<0 && (_persistent || assumed_content_request || chunked_hint)) else if (http11 && (chunked_hint || content_length<0 && (_persistent || assumed_content_request)))
{ {
// we use chunking // we use chunking
_endOfContent = EndOfContent.CHUNKED_CONTENT; _endOfContent = EndOfContent.CHUNKED_CONTENT;

View File

@ -129,7 +129,6 @@ public class HTTP2Client extends ContainerLifeCycle
private int initialSessionRecvWindow = FlowControlStrategy.DEFAULT_WINDOW_SIZE; private int initialSessionRecvWindow = FlowControlStrategy.DEFAULT_WINDOW_SIZE;
private int initialStreamRecvWindow = FlowControlStrategy.DEFAULT_WINDOW_SIZE; private int initialStreamRecvWindow = FlowControlStrategy.DEFAULT_WINDOW_SIZE;
private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F); private FlowControlStrategy.Factory flowControlStrategyFactory = () -> new BufferingFlowControlStrategy(0.5F);
private ExecutionStrategy.Factory executionStrategyFactory = new ProduceConsume.Factory();
@Override @Override
protected void doStart() throws Exception protected void doStart() throws Exception

View File

@ -31,12 +31,11 @@ import org.eclipse.jetty.io.ByteBufferPool;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
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.util.thread.ExecutionStrategy; import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.Invocable; import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill;
import org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume;
import org.eclipse.jetty.util.thread.strategy.ProduceExecuteConsume;
public class HTTP2Connection extends AbstractConnection public class HTTP2Connection extends AbstractConnection
{ {
@ -49,8 +48,7 @@ public class HTTP2Connection extends AbstractConnection
private final Parser parser; private final Parser parser;
private final ISession session; private final ISession session;
private final int bufferSize; private final int bufferSize;
private final ExecutionStrategy blockingStrategy; private final ExecutionStrategy strategy;
private final ExecutionStrategy nonBlockingStrategy;
public HTTP2Connection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, Parser parser, ISession session, int bufferSize) public HTTP2Connection(ByteBufferPool byteBufferPool, Executor executor, EndPoint endPoint, Parser parser, ISession session, int bufferSize)
{ {
@ -59,8 +57,9 @@ public class HTTP2Connection extends AbstractConnection
this.parser = parser; this.parser = parser;
this.session = session; this.session = session;
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
this.blockingStrategy = new ExecuteProduceConsume(producer, executor); this.strategy = new EatWhatYouKill(producer, executor, 0);
this.nonBlockingStrategy = new ProduceExecuteConsume(producer, executor);
LifeCycle.start(strategy);
} }
@Override @Override
@ -96,7 +95,7 @@ public class HTTP2Connection extends AbstractConnection
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("HTTP2 Open {} ", this); LOG.debug("HTTP2 Open {} ", this);
super.onOpen(); super.onOpen();
blockingStrategy.produce(); strategy.produce();
} }
@Override @Override
@ -105,26 +104,16 @@ public class HTTP2Connection extends AbstractConnection
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("HTTP2 Close {} ", this); LOG.debug("HTTP2 Close {} ", this);
super.onClose(); super.onClose();
LifeCycle.stop(strategy);
} }
@Override @Override
public void onFillable() public void onFillable()
{
throw new UnsupportedOperationException();
}
private void onFillableBlocking()
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("HTTP2 onFillableBlocking {} ", this); LOG.debug("HTTP2 onFillable {} ", this);
blockingStrategy.produce(); strategy.produce();
}
private void onFillableNonBlocking()
{
if (LOG.isDebugEnabled())
LOG.debug("HTTP2 onFillableNonBlocking {} ", this);
nonBlockingStrategy.produce();
} }
private int fill(EndPoint endPoint, ByteBuffer buffer) private int fill(EndPoint endPoint, ByteBuffer buffer)
@ -158,16 +147,7 @@ public class HTTP2Connection extends AbstractConnection
protected void offerTask(Runnable task, boolean dispatch) protected void offerTask(Runnable task, boolean dispatch)
{ {
offerTask(task); offerTask(task);
strategy.dispatch();
// Because producing calls parse and parse can call offerTask, we have to make sure
// we use the same strategy otherwise produce can be reentrant and that messes with
// the release mechanism. TODO is this test sufficient to protect from this?
ExecutionStrategy s = Invocable.isNonBlockingInvocation() ? nonBlockingStrategy : blockingStrategy;
if (dispatch)
// TODO Why again is this necessary?
s.dispatch();
else
s.produce();
} }
@Override @Override
@ -271,10 +251,7 @@ public class HTTP2Connection extends AbstractConnection
@Override @Override
public void succeeded() public void succeeded()
{ {
if (Invocable.isNonBlockingInvocation()) onFillable();
onFillableNonBlocking();
else
onFillableBlocking();
} }
@Override @Override

View File

@ -71,27 +71,37 @@ public class HttpReceiverOverHTTP2 extends HttpReceiver implements Stream.Listen
if (exchange == null) if (exchange == null)
return; return;
HttpResponse response = exchange.getResponse(); HttpResponse httpResponse = exchange.getResponse();
MetaData.Response metaData = (MetaData.Response)frame.getMetaData(); MetaData metaData = frame.getMetaData();
response.version(metaData.getHttpVersion()).status(metaData.getStatus()).reason(metaData.getReason()); if (metaData.isResponse())
if (responseBegin(exchange))
{ {
HttpFields headers = metaData.getFields(); MetaData.Response response = (MetaData.Response)frame.getMetaData();
for (HttpField header : headers) httpResponse.version(response.getHttpVersion()).status(response.getStatus()).reason(response.getReason());
{
if (!responseHeader(exchange, header))
return;
}
if (responseHeaders(exchange)) if (responseBegin(exchange))
{ {
int status = metaData.getStatus(); HttpFields headers = response.getFields();
boolean informational = HttpStatus.isInformational(status) && status != HttpStatus.SWITCHING_PROTOCOLS_101; for (HttpField header : headers)
if (frame.isEndStream() || informational) {
responseSuccess(exchange); if (!responseHeader(exchange, header))
return;
}
if (responseHeaders(exchange))
{
int status = response.getStatus();
boolean informational = HttpStatus.isInformational(status) && status != HttpStatus.SWITCHING_PROTOCOLS_101;
if (frame.isEndStream() || informational)
responseSuccess(exchange);
}
} }
} }
else
{
HttpFields trailers = metaData.getFields();
trailers.forEach(httpResponse::trailer);
responseSuccess(exchange);
}
} }
@Override @Override

View File

@ -19,11 +19,13 @@
package org.eclipse.jetty.http2.client.http; package org.eclipse.jetty.http2.client.http;
import java.net.URI; import java.net.URI;
import java.util.function.Supplier;
import org.eclipse.jetty.client.HttpContent; import org.eclipse.jetty.client.HttpContent;
import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpExchange;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpSender; import org.eclipse.jetty.client.HttpSender;
import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
@ -49,11 +51,13 @@ public class HttpSenderOverHTTP2 extends HttpSender
@Override @Override
protected void sendHeaders(HttpExchange exchange, final HttpContent content, final Callback callback) protected void sendHeaders(HttpExchange exchange, final HttpContent content, final Callback callback)
{ {
Request request = exchange.getRequest(); HttpRequest request = exchange.getRequest();
String path = relativize(request.getPath()); String path = relativize(request.getPath());
HttpURI uri = new HttpURI(request.getScheme(), request.getHost(), request.getPort(), path, null, request.getQuery(), null); HttpURI uri = new HttpURI(request.getScheme(), request.getHost(), request.getPort(), path, null, request.getQuery(), null);
MetaData.Request metaData = new MetaData.Request(request.getMethod(), uri, HttpVersion.HTTP_2, request.getHeaders()); MetaData.Request metaData = new MetaData.Request(request.getMethod(), uri, HttpVersion.HTTP_2, request.getHeaders());
HeadersFrame headersFrame = new HeadersFrame(metaData, null, !content.hasContent()); Supplier<HttpFields> trailers = request.getTrailers();
metaData.setTrailerSupplier(trailers);
HeadersFrame headersFrame = new HeadersFrame(metaData, null, trailers == null && !content.hasContent());
HttpChannelOverHTTP2 channel = getHttpChannel(); HttpChannelOverHTTP2 channel = getHttpChannel();
Promise<Stream> promise = new Promise<Stream>() Promise<Stream> promise = new Promise<Stream>()
{ {
@ -66,7 +70,7 @@ public class HttpSenderOverHTTP2 extends HttpSender
if (content.hasContent() && !expects100Continue(request)) if (content.hasContent() && !expects100Continue(request))
{ {
boolean advanced = content.advance(); boolean advanced = content.advance();
boolean lastContent = content.isLast(); boolean lastContent = trailers == null && content.isLast();
if (advanced || lastContent) if (advanced || lastContent)
{ {
DataFrame dataFrame = new DataFrame(stream.getId(), content.getByteBuffer(), lastContent); DataFrame dataFrame = new DataFrame(stream.getId(), content.getByteBuffer(), lastContent);
@ -115,8 +119,19 @@ public class HttpSenderOverHTTP2 extends HttpSender
else else
{ {
Stream stream = getHttpChannel().getStream(); Stream stream = getHttpChannel().getStream();
DataFrame frame = new DataFrame(stream.getId(), content.getByteBuffer(), content.isLast()); Supplier<HttpFields> trailers = exchange.getRequest().getTrailers();
DataFrame frame = new DataFrame(stream.getId(), content.getByteBuffer(), trailers == null && content.isLast());
stream.data(frame, callback); stream.data(frame, callback);
} }
} }
@Override
protected void sendTrailers(HttpExchange exchange, Callback callback)
{
Supplier<HttpFields> trailers = exchange.getRequest().getTrailers();
MetaData metaData = new MetaData(HttpVersion.HTTP_2, trailers.get());
Stream stream = getHttpChannel().getStream();
HeadersFrame trailersFrame = new HeadersFrame(stream.getId(), metaData, null, true);
stream.headers(trailersFrame, callback);
}
} }

View File

@ -173,7 +173,11 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection
LOG.debug("Processing trailers {} on {}", frame, stream); LOG.debug("Processing trailers {} on {}", frame, stream);
HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE); HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE);
if (channel != null) if (channel != null)
channel.onRequestTrailers(frame); {
Runnable task = channel.onRequestTrailers(frame);
if (task != null)
offerTask(task, false);
}
} }
public boolean onStreamTimeout(IStream stream, Throwable failure) public boolean onStreamTimeout(IStream stream, Throwable failure)

View File

@ -282,11 +282,12 @@ public class HttpChannelOverHTTP2 extends HttpChannel
return handle || wasDelayed ? this : null; return handle || wasDelayed ? this : null;
} }
public void onRequestTrailers(HeadersFrame frame) public Runnable onRequestTrailers(HeadersFrame frame)
{ {
HttpFields trailers = frame.getMetaData().getFields(); HttpFields trailers = frame.getMetaData().getFields();
onTrailers(trailers); if (trailers.size() > 0)
onRequestComplete(); onTrailers(trailers);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ {
Stream stream = getStream(); Stream stream = getStream();
@ -294,6 +295,14 @@ public class HttpChannelOverHTTP2 extends HttpChannel
stream.getId(), Integer.toHexString(stream.getSession().hashCode()), stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
System.lineSeparator(), trailers); System.lineSeparator(), trailers);
} }
boolean handle = onRequestComplete();
boolean wasDelayed = _delayedUntilContent;
_delayedUntilContent = false;
if (wasDelayed)
_handled = true;
return handle || wasDelayed ? this : null;
} }
public boolean isRequestHandled() public boolean isRequestHandled()

View File

@ -20,7 +20,9 @@ package org.eclipse.jetty.http2.server;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
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.HttpVersion;
import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MetaData;
@ -48,6 +50,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
private final Connector connector; private final Connector connector;
private final HTTP2ServerConnection connection; private final HTTP2ServerConnection connection;
private IStream stream; private IStream stream;
private MetaData metaData;
public HttpTransportOverHTTP2(Connector connector, HTTP2ServerConnection connection) public HttpTransportOverHTTP2(Connector connector, HTTP2ServerConnection connection)
{ {
@ -85,48 +88,60 @@ public class HttpTransportOverHTTP2 implements HttpTransport
public void send(MetaData.Response info, boolean isHeadRequest, ByteBuffer content, boolean lastContent, Callback callback) public void send(MetaData.Response info, boolean isHeadRequest, ByteBuffer content, boolean lastContent, Callback callback)
{ {
boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest; boolean hasContent = BufferUtil.hasContent(content) && !isHeadRequest;
if (info != null) if (info != null)
{ {
metaData = info;
int status = info.getStatus(); int status = info.getStatus();
boolean informational = HttpStatus.isInformational(status) && status != HttpStatus.SWITCHING_PROTOCOLS_101; boolean informational = HttpStatus.isInformational(status) && status != HttpStatus.SWITCHING_PROTOCOLS_101;
boolean committed = false; if (informational)
if (!informational)
committed = commit.compareAndSet(false, true);
if (committed || informational)
{ {
if (hasContent) if (transportCallback.start(callback, false))
{ sendHeaders(info, false, transportCallback);
Callback commitCallback = new Callback.Nested(callback)
{
@Override
public void succeeded()
{
if (transportCallback.start(callback, false))
send(content, lastContent, transportCallback);
}
};
if (transportCallback.start(commitCallback, true))
commit(info, false, transportCallback);
}
else
{
if (transportCallback.start(callback, false))
commit(info, lastContent, transportCallback);
}
} }
else else
{ {
callback.failed(new IllegalStateException("committed")); boolean needsCommit = commit.compareAndSet(false, true);
if (needsCommit)
{
Supplier<HttpFields> trailers = info.getTrailerSupplier();
if (hasContent)
{
Callback nested = trailers == null || !lastContent ? callback : new SendTrailers(callback);
Callback commitCallback = new Callback.Nested(nested)
{
@Override
public void succeeded()
{
if (transportCallback.start(nested, false))
sendContent(content, lastContent, trailers == null && lastContent, transportCallback);
}
};
if (transportCallback.start(commitCallback, true))
sendHeaders(info, false, transportCallback);
}
else
{
Callback nested = trailers == null ? callback : new SendTrailers(callback);
if (transportCallback.start(nested, true))
sendHeaders(info, trailers == null && lastContent, transportCallback);
}
}
else
{
callback.failed(new IllegalStateException("committed"));
}
} }
} }
else else
{ {
if (hasContent || lastContent) if (hasContent || lastContent)
{ {
if (transportCallback.start(callback, false)) Supplier<HttpFields> trailers = metaData.getTrailerSupplier();
send(content, lastContent, transportCallback); Callback nested = trailers == null ? callback : new SendTrailers(callback);
if (transportCallback.start(nested, false))
sendContent(content, lastContent, trailers == null && lastContent, transportCallback);
} }
else else
{ {
@ -171,7 +186,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
}, new Stream.Listener.Adapter()); // TODO: handle reset from the client ? }, new Stream.Listener.Adapter()); // TODO: handle reset from the client ?
} }
private void commit(MetaData.Response info, boolean endStream, Callback callback) private void sendHeaders(MetaData.Response info, boolean endStream, Callback callback)
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ {
@ -185,7 +200,7 @@ public class HttpTransportOverHTTP2 implements HttpTransport
stream.headers(frame, callback); stream.headers(frame, callback);
} }
private void send(ByteBuffer content, boolean lastContent, Callback callback) private void sendContent(ByteBuffer content, boolean lastContent, boolean endStream, Callback callback)
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
{ {
@ -193,10 +208,22 @@ public class HttpTransportOverHTTP2 implements HttpTransport
stream.getId(), Integer.toHexString(stream.getSession().hashCode()), stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
content.remaining(), lastContent ? " (last chunk)" : ""); content.remaining(), lastContent ? " (last chunk)" : "");
} }
DataFrame frame = new DataFrame(stream.getId(), content, lastContent); DataFrame frame = new DataFrame(stream.getId(), content, endStream);
stream.data(frame, callback); stream.data(frame, callback);
} }
private void sendTrailers(MetaData metaData, Callback callback)
{
if (LOG.isDebugEnabled())
{
LOG.debug("HTTP2 Response #{}/{}: trailers",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()));
}
HeadersFrame frame = new HeadersFrame(stream.getId(), metaData, null, true);
stream.headers(frame, callback);
}
public void onStreamFailure(Throwable failure) public void onStreamFailure(Throwable failure)
{ {
transportCallback.failed(failure); transportCallback.failed(failure);
@ -277,7 +304,9 @@ public class HttpTransportOverHTTP2 implements HttpTransport
} }
} }
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("HTTP2 Response #{} {}", stream.getId(), commit ? "committed" : "flushed content"); LOG.debug("HTTP2 Response #{}/{} {}",
stream.getId(), Integer.toHexString(stream.getSession().hashCode()),
commit ? "committed" : "flushed content");
if (callback != null) if (callback != null)
callback.succeeded(); callback.succeeded();
} }
@ -340,4 +369,19 @@ public class HttpTransportOverHTTP2 implements HttpTransport
{ {
IDLE, WRITING, FAILED, TIMEOUT IDLE, WRITING, FAILED, TIMEOUT
} }
private class SendTrailers extends Callback.Nested
{
private SendTrailers(Callback callback)
{
super(callback);
}
@Override
public void succeeded()
{
if (transportCallback.start(getCallback(), false))
sendTrailers(new MetaData(HttpVersion.HTTP_2, metaData.getTrailerSupplier().get()), transportCallback);
}
}
} }

View File

@ -48,6 +48,7 @@ import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Invocable.InvocationType; import org.eclipse.jetty.util.thread.Invocable.InvocationType;
import org.eclipse.jetty.util.thread.Locker; import org.eclipse.jetty.util.thread.Locker;
import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill;
import org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume; import org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume;
import org.eclipse.jetty.util.thread.strategy.ProduceExecuteConsume; import org.eclipse.jetty.util.thread.strategy.ProduceExecuteConsume;
@ -57,7 +58,7 @@ import org.eclipse.jetty.util.thread.strategy.ProduceExecuteConsume;
* happen for registered channels. When events happen, it notifies the {@link EndPoint} associated * happen for registered channels. When events happen, it notifies the {@link EndPoint} associated
* with the channel.</p> * with the channel.</p>
*/ */
public class ManagedSelector extends AbstractLifeCycle implements Dumpable public class ManagedSelector extends ContainerLifeCycle implements Dumpable
{ {
private static final Logger LOG = Log.getLogger(ManagedSelector.class); private static final Logger LOG = Log.getLogger(ManagedSelector.class);
@ -67,7 +68,6 @@ public class ManagedSelector extends AbstractLifeCycle implements Dumpable
private final SelectorManager _selectorManager; private final SelectorManager _selectorManager;
private final int _id; private final int _id;
private final ExecutionStrategy _strategy; private final ExecutionStrategy _strategy;
private final ExecutionStrategy _lowPriorityStrategy;
private Selector _selector; private Selector _selector;
public ManagedSelector(SelectorManager selectorManager, int id) public ManagedSelector(SelectorManager selectorManager, int id)
@ -76,8 +76,8 @@ public class ManagedSelector extends AbstractLifeCycle implements Dumpable
_id = id; _id = id;
SelectorProducer producer = new SelectorProducer(); SelectorProducer producer = new SelectorProducer();
Executor executor = selectorManager.getExecutor(); Executor executor = selectorManager.getExecutor();
_strategy = new ExecuteProduceConsume(producer, executor, Invocable.InvocationType.BLOCKING); _strategy = new EatWhatYouKill(producer,executor);
_lowPriorityStrategy = new LowPriorityProduceExecuteConsume(producer, executor); addBean(_strategy);
setStopTimeout(5000); setStopTimeout(5000);
} }
@ -94,29 +94,6 @@ public class ManagedSelector extends AbstractLifeCycle implements Dumpable
// The normal strategy obtains the produced task, schedules // The normal strategy obtains the produced task, schedules
// a new thread to produce more, runs the task and then exits. // a new thread to produce more, runs the task and then exits.
_selectorManager.execute(_strategy::produce); _selectorManager.execute(_strategy::produce);
// The low priority strategy knows the producer will never
// be idle, that tasks are scheduled to run in different
// threads, therefore lowPriorityProduce() never exits.
_selectorManager.execute(this::lowPriorityProduce);
}
private void lowPriorityProduce()
{
Thread current = Thread.currentThread();
String name = current.getName();
int priority = current.getPriority();
current.setPriority(Thread.MIN_PRIORITY);
current.setName(name+"-lowPrioritySelector");
try
{
_lowPriorityStrategy.produce();
}
finally
{
current.setPriority(priority);
current.setName(name);
}
} }
public int size() public int size()
@ -135,11 +112,12 @@ public class ManagedSelector extends AbstractLifeCycle implements Dumpable
CloseEndPoints close_endps = new CloseEndPoints(); CloseEndPoints close_endps = new CloseEndPoints();
submit(close_endps); submit(close_endps);
close_endps.await(getStopTimeout()); close_endps.await(getStopTimeout());
super.doStop();
CloseSelector close_selector = new CloseSelector(); CloseSelector close_selector = new CloseSelector();
submit(close_selector); submit(close_selector);
close_selector.await(getStopTimeout()); close_selector.await(getStopTimeout());
super.doStop();
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("Stopped {}", this); LOG.debug("Stopped {}", this);
} }
@ -185,42 +163,6 @@ public class ManagedSelector extends AbstractLifeCycle implements Dumpable
void updateKey(); void updateKey();
} }
private static class LowPriorityProduceExecuteConsume extends ProduceExecuteConsume
{
private LowPriorityProduceExecuteConsume(SelectorProducer producer, Executor executor)
{
super(producer, executor, InvocationType.BLOCKING);
}
@Override
protected boolean execute(Runnable task)
{
try
{
InvocationType invocation=Invocable.getInvocationType(task);
if (LOG.isDebugEnabled())
LOG.debug("Low Priority Selector executing {} {}",invocation,task);
switch (invocation)
{
case NON_BLOCKING:
task.run();
return true;
case EITHER:
Invocable.invokeNonBlocking(task);
return true;
default:
return super.execute(task);
}
}
finally
{
// Allow opportunity for main strategy to take over.
Thread.yield();
}
}
}
private class SelectorProducer implements ExecutionStrategy.Producer private class SelectorProducer implements ExecutionStrategy.Producer
{ {
@ -230,30 +172,20 @@ public class ManagedSelector extends AbstractLifeCycle implements Dumpable
@Override @Override
public Runnable produce() public Runnable produce()
{ {
// This method is called from both the while (true)
// normal and low priority strategies.
// Only one can produce at a time, so it's synchronized
// to enforce that only one strategy actually produces.
// When idle in select(), this method blocks;
// the other strategy's thread will be blocked
// waiting for this lock to be released.
synchronized (this)
{ {
while (true) Runnable task = processSelected();
{ if (task != null)
Runnable task = processSelected(); return task;
if (task != null)
return task;
Runnable action = nextAction(); Runnable action = nextAction();
if (action != null) if (action != null)
return action; return action;
update(); update();
if (!select()) if (!select())
return null; return null;
}
} }
} }

View File

@ -307,12 +307,13 @@ public class SslConnection extends AbstractConnection
b = _decryptedInput; b = _decryptedInput;
int di=b==null?-1:b.remaining(); int di=b==null?-1:b.remaining();
Connection connection = _decryptedEndPoint.getConnection();
return String.format("%s@%x{%s,eio=%d/%d,di=%d}=>%s", return String.format("%s@%x{%s,eio=%d/%d,di=%d}=>%s",
getClass().getSimpleName(), getClass().getSimpleName(),
hashCode(), hashCode(),
_sslEngine.getHandshakeStatus(), _sslEngine.getHandshakeStatus(),
ei,eo,di, ei,eo,di,
((AbstractConnection)_decryptedEndPoint.getConnection()).toConnectionString()); connection instanceof AbstractConnection ? ((AbstractConnection)connection).toConnectionString() : connection);
} }
public class DecryptedEndPoint extends AbstractEndPoint public class DecryptedEndPoint extends AbstractEndPoint

View File

@ -27,7 +27,7 @@ logs/
# jetty.requestlog.retainDays=90 # jetty.requestlog.retainDays=90
## Whether to append to existing file ## Whether to append to existing file
# jetty.requestlog.append=true # jetty.requestlog.append=false
## Whether to use the extended log output ## Whether to use the extended log output
# jetty.requestlog.extended=true # jetty.requestlog.extended=true

View File

@ -106,6 +106,11 @@ public class AsyncContextEvent extends AsyncEvent implements Runnable
_timeoutTask = task; _timeoutTask = task;
} }
public boolean hasTimeoutTask()
{
return _timeoutTask!=null;
}
public void cancelTimeoutTask() public void cancelTimeoutTask()
{ {
Scheduler.Task task=_timeoutTask; Scheduler.Task task=_timeoutTask;

View File

@ -78,7 +78,7 @@ public class HttpChannelState
ERROR_DISPATCH, // handle a normal error ERROR_DISPATCH, // handle a normal error
ASYNC_ERROR, // handle an async error ASYNC_ERROR, // handle an async error
WRITE_CALLBACK, // handle an IO write callback WRITE_CALLBACK, // handle an IO write callback
READ_PRODUCE, // Check is a read is possible by parsing/filling READ_PRODUCE, // Check is a read is possible by parsing/filling
READ_CALLBACK, // handle an IO read callback READ_CALLBACK, // handle an IO read callback
COMPLETE, // Complete the response COMPLETE, // Complete the response
TERMINATED, // No further actions TERMINATED, // No further actions
@ -102,13 +102,12 @@ public class HttpChannelState
private enum AsyncRead private enum AsyncRead
{ {
NONE, // No isReady; No data IDLE, // No isReady; No data
AVAILABLE, // No isReady; onDataAvailable REGISTER, // isReady()==false handling; No data
NEEDED, // isReady()==false handling; No data
REGISTERED, // isReady()==false !handling; No data REGISTERED, // isReady()==false !handling; No data
POSSIBLE, // isReady()==false async callback called (http/1 only) POSSIBLE, // isReady()==false async read callback called (http/1 only)
PRODUCING, // isReady()==false handling content production (http/1 only) PRODUCING, // isReady()==false READ_PRODUCE action is being handled (http/1 only)
READY // isReady() was false, data now available READY // isReady() was false, onContentAdded has been called
} }
private final Locker _locker=new Locker(); private final Locker _locker=new Locker();
@ -117,7 +116,7 @@ public class HttpChannelState
private State _state; private State _state;
private Async _async; private Async _async;
private boolean _initial; private boolean _initial;
private AsyncRead _asyncRead=AsyncRead.NONE; private AsyncRead _asyncRead=AsyncRead.IDLE;
private boolean _asyncWritePossible; private boolean _asyncWritePossible;
private long _timeoutMs=DEFAULT_TIMEOUT; private long _timeoutMs=DEFAULT_TIMEOUT;
private AsyncContextEvent _event; private AsyncContextEvent _event;
@ -237,9 +236,13 @@ public class HttpChannelState
return Action.READ_PRODUCE; return Action.READ_PRODUCE;
case READY: case READY:
_state=State.ASYNC_IO; _state=State.ASYNC_IO;
_asyncRead=AsyncRead.NONE; _asyncRead=AsyncRead.IDLE;
return Action.READ_CALLBACK; return Action.READ_CALLBACK;
default: case REGISTER:
case PRODUCING:
throw new IllegalStateException(toStringLocked());
case IDLE:
case REGISTERED:
break; break;
} }
@ -386,7 +389,6 @@ public class HttpChannelState
*/ */
protected Action unhandle() protected Action unhandle()
{ {
Action action;
boolean read_interested = false; boolean read_interested = false;
try(Locker.Lock lock= _locker.lock()) try(Locker.Lock lock= _locker.lock())
@ -414,41 +416,38 @@ public class HttpChannelState
} }
_initial=false; _initial=false;
async: switch(_async) switch(_async)
{ {
case COMPLETE: case COMPLETE:
_state=State.COMPLETING; _state=State.COMPLETING;
_async=Async.NOT_ASYNC; _async=Async.NOT_ASYNC;
action=Action.COMPLETE; return Action.COMPLETE;
break;
case DISPATCH: case DISPATCH:
_state=State.DISPATCHED; _state=State.DISPATCHED;
_async=Async.NOT_ASYNC; _async=Async.NOT_ASYNC;
action=Action.ASYNC_DISPATCH; return Action.ASYNC_DISPATCH;
break;
case STARTED: case STARTED:
switch(_asyncRead) switch(_asyncRead)
{ {
case READY: case READY:
_state=State.ASYNC_IO; _state=State.ASYNC_IO;
_asyncRead=AsyncRead.NONE; _asyncRead=AsyncRead.IDLE;
action=Action.READ_CALLBACK; return Action.READ_CALLBACK;
break async;
case POSSIBLE: case POSSIBLE:
_state=State.ASYNC_IO; _state=State.ASYNC_IO;
action=Action.READ_PRODUCE; _asyncRead=AsyncRead.PRODUCING;
break async; return Action.READ_PRODUCE;
case NEEDED: case REGISTER:
case PRODUCING: case PRODUCING:
_asyncRead=AsyncRead.REGISTERED; _asyncRead=AsyncRead.REGISTERED;
read_interested=true; read_interested=true;
break;
case NONE: case IDLE:
case AVAILABLE:
case REGISTERED: case REGISTERED:
break; break;
} }
@ -457,54 +456,50 @@ public class HttpChannelState
{ {
_state=State.ASYNC_IO; _state=State.ASYNC_IO;
_asyncWritePossible=false; _asyncWritePossible=false;
action=Action.WRITE_CALLBACK; return Action.WRITE_CALLBACK;
} }
else else
{ {
_state=State.ASYNC_WAIT; _state=State.ASYNC_WAIT;
action=Action.WAIT;
Scheduler scheduler=_channel.getScheduler(); Scheduler scheduler=_channel.getScheduler();
if (scheduler!=null && _timeoutMs>0) if (scheduler!=null && _timeoutMs>0 && !_event.hasTimeoutTask())
_event.setTimeoutTask(scheduler.schedule(_event,_timeoutMs,TimeUnit.MILLISECONDS)); _event.setTimeoutTask(scheduler.schedule(_event,_timeoutMs,TimeUnit.MILLISECONDS));
return Action.WAIT;
} }
break;
case EXPIRING: case EXPIRING:
// onTimeout callbacks still being called, so just WAIT // onTimeout callbacks still being called, so just WAIT
_state=State.ASYNC_WAIT; _state=State.ASYNC_WAIT;
action=Action.WAIT; return Action.WAIT;
break;
case EXPIRED: case EXPIRED:
// onTimeout handling is complete, but did not dispatch as // onTimeout handling is complete, but did not dispatch as
// we were handling. So do the error dispatch here // we were handling. So do the error dispatch here
_state=State.DISPATCHED; _state=State.DISPATCHED;
_async=Async.NOT_ASYNC; _async=Async.NOT_ASYNC;
action=Action.ERROR_DISPATCH; return Action.ERROR_DISPATCH;
break;
case ERRORED: case ERRORED:
_state=State.DISPATCHED; _state=State.DISPATCHED;
_async=Async.NOT_ASYNC; _async=Async.NOT_ASYNC;
action=Action.ERROR_DISPATCH; return Action.ERROR_DISPATCH;
break;
case NOT_ASYNC: case NOT_ASYNC:
_state=State.COMPLETING; _state=State.COMPLETING;
action=Action.COMPLETE; return Action.COMPLETE;
break;
default: default:
_state=State.COMPLETING; _state=State.COMPLETING;
action=Action.COMPLETE; return Action.COMPLETE;
break;
} }
} }
finally
if (read_interested) {
_channel.asyncReadFillInterested(); if (read_interested)
_channel.asyncReadFillInterested();
return action; }
} }
public void dispatch(ServletContext context, String path) public void dispatch(ServletContext context, String path)
@ -930,7 +925,7 @@ public class HttpChannelState
_state=State.IDLE; _state=State.IDLE;
_async=Async.NOT_ASYNC; _async=Async.NOT_ASYNC;
_initial=true; _initial=true;
_asyncRead=AsyncRead.NONE; _asyncRead=AsyncRead.IDLE;
_asyncWritePossible=false; _asyncWritePossible=false;
_timeoutMs=DEFAULT_TIMEOUT; _timeoutMs=DEFAULT_TIMEOUT;
_event=null; _event=null;
@ -957,7 +952,7 @@ public class HttpChannelState
_state=State.UPGRADED; _state=State.UPGRADED;
_async=Async.NOT_ASYNC; _async=Async.NOT_ASYNC;
_initial=true; _initial=true;
_asyncRead=AsyncRead.NONE; _asyncRead=AsyncRead.IDLE;
_asyncWritePossible=false; _asyncWritePossible=false;
_timeoutMs=DEFAULT_TIMEOUT; _timeoutMs=DEFAULT_TIMEOUT;
_event=null; _event=null;
@ -1148,10 +1143,8 @@ public class HttpChannelState
switch(_asyncRead) switch(_asyncRead)
{ {
case NONE: case IDLE:
case AVAILABLE:
case READY: case READY:
case NEEDED:
if (_state==State.ASYNC_WAIT) if (_state==State.ASYNC_WAIT)
{ {
interested=true; interested=true;
@ -1159,17 +1152,15 @@ public class HttpChannelState
} }
else else
{ {
_asyncRead=AsyncRead.NEEDED; _asyncRead=AsyncRead.REGISTER;
} }
break; break;
case REGISTER:
case REGISTERED: case REGISTERED:
case POSSIBLE: case POSSIBLE:
case PRODUCING: case PRODUCING:
break; break;
default:
throw new IllegalStateException(toStringLocked());
} }
} }
@ -1184,31 +1175,26 @@ public class HttpChannelState
* is returned. * is returned.
* @return True IFF the channel was unready and in ASYNC_WAIT state * @return True IFF the channel was unready and in ASYNC_WAIT state
*/ */
public boolean onDataAvailable() public boolean onContentAdded()
{ {
boolean woken=false; boolean woken=false;
try(Locker.Lock lock= _locker.lock()) try(Locker.Lock lock= _locker.lock())
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("onReadPossible {}",toStringLocked()); LOG.debug("onContentAdded {}",toStringLocked());
switch(_asyncRead) switch(_asyncRead)
{ {
case NONE: case IDLE:
_asyncRead=AsyncRead.AVAILABLE; case READY:
break;
case AVAILABLE:
break; break;
case PRODUCING: case PRODUCING:
_asyncRead=AsyncRead.READY; _asyncRead=AsyncRead.READY;
break; break;
case NEEDED: case REGISTER:
case REGISTERED: case REGISTERED:
case POSSIBLE:
case READY:
_asyncRead=AsyncRead.READY; _asyncRead=AsyncRead.READY;
if (_state==State.ASYNC_WAIT) if (_state==State.ASYNC_WAIT)
{ {
@ -1217,7 +1203,7 @@ public class HttpChannelState
} }
break; break;
default: case POSSIBLE:
throw new IllegalStateException(toStringLocked()); throw new IllegalStateException(toStringLocked());
} }
} }
@ -1227,7 +1213,7 @@ public class HttpChannelState
/** /**
* Called to signal that the channel is ready for a callback. * Called to signal that the channel is ready for a callback.
* This is similar to calling {@link #onReadUnready()} followed by * This is similar to calling {@link #onReadUnready()} followed by
* {@link #onDataAvailable()}, except that as content is already * {@link #onContentAdded()}, except that as content is already
* available, read interest is never set. * available, read interest is never set.
* @return true if woken * @return true if woken
*/ */
@ -1241,8 +1227,7 @@ public class HttpChannelState
switch(_asyncRead) switch(_asyncRead)
{ {
case NONE: case IDLE:
case AVAILABLE:
_asyncRead=AsyncRead.READY; _asyncRead=AsyncRead.READY;
if (_state==State.ASYNC_WAIT) if (_state==State.ASYNC_WAIT)
{ {
@ -1269,7 +1254,7 @@ public class HttpChannelState
try(Locker.Lock lock= _locker.lock()) try(Locker.Lock lock= _locker.lock())
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("onReadReady {}",toStringLocked()); LOG.debug("onReadPossible {}",toStringLocked());
switch(_asyncRead) switch(_asyncRead)
{ {

View File

@ -260,9 +260,9 @@ public class HttpInput extends ServletInputStream implements Runnable
int l; int l;
synchronized (_inputQ) synchronized (_inputQ)
{ {
// Setup blocking only if not async
if (!isAsync()) if (!isAsync())
{ {
// Setup blocking only if not async
if (_blockUntil == 0) if (_blockUntil == 0)
{ {
long blockingTimeout = getBlockingTimeout(); long blockingTimeout = getBlockingTimeout();
@ -306,8 +306,8 @@ public class HttpInput extends ServletInputStream implements Runnable
// Not blocking, so what should we return? // Not blocking, so what should we return?
l = _state.noContent(); l = _state.noContent();
// If EOF do we need to wake for allDataRead callback?
if (l<0) if (l<0)
// If EOF do we need to wake for allDataRead callback?
wake = _channelState.onReadEof(); wake = _channelState.onReadEof();
break; break;
} }
@ -577,7 +577,7 @@ public class HttpInput extends ServletInputStream implements Runnable
if (_listener == null) if (_listener == null)
_inputQ.notify(); _inputQ.notify();
else else
woken = _channelState.onDataAvailable(); woken = _channelState.onContentAdded();
} }
return woken; return woken;
} }
@ -612,7 +612,7 @@ public class HttpInput extends ServletInputStream implements Runnable
if (_listener == null) if (_listener == null)
_inputQ.notify(); _inputQ.notify();
else else
woken = _channelState.onDataAvailable(); woken = _channelState.onContentAdded();
} }
} }
return woken; return woken;
@ -800,7 +800,7 @@ public class HttpInput extends ServletInputStream implements Runnable
if (_listener == null) if (_listener == null)
_inputQ.notify(); _inputQ.notify();
else else
woken = _channelState.onDataAvailable(); woken = _channelState.onContentAdded();
} }
return woken; return woken;

View File

@ -27,6 +27,7 @@ import java.util.EnumSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.servlet.RequestDispatcher; import javax.servlet.RequestDispatcher;
@ -69,20 +70,11 @@ public class Response implements HttpServletResponse
{ {
private static final Logger LOG = Log.getLogger(Response.class); private static final Logger LOG = Log.getLogger(Response.class);
private static final String __COOKIE_DELIM="\",;\\ \t"; private static final String __COOKIE_DELIM="\",;\\ \t";
private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim(); private static final String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim();
private final static int __MIN_BUFFER_SIZE = 1; private static final int __MIN_BUFFER_SIZE = 1;
private final static HttpField __EXPIRES_01JAN1970 = new PreEncodedHttpField(HttpHeader.EXPIRES,DateGenerator.__01Jan1970); private static final HttpField __EXPIRES_01JAN1970 = new PreEncodedHttpField(HttpHeader.EXPIRES,DateGenerator.__01Jan1970);
// Cookie building buffer. Reduce garbage for cookie using applications // Cookie building buffer. Reduce garbage for cookie using applications
private static final ThreadLocal<StringBuilder> __cookieBuilder = new ThreadLocal<StringBuilder>() private static final ThreadLocal<StringBuilder> __cookieBuilder = ThreadLocal.withInitial(() -> new StringBuilder(128));
{
@Override
protected StringBuilder initialValue()
{
return new StringBuilder(128);
}
};
public enum OutputType public enum OutputType
{ {
@ -116,12 +108,12 @@ public class Response implements HttpServletResponse
private OutputType _outputType = OutputType.NONE; private OutputType _outputType = OutputType.NONE;
private ResponseWriter _writer; private ResponseWriter _writer;
private long _contentLength = -1; private long _contentLength = -1;
private Supplier<HttpFields> trailers;
private enum EncodingFrom { NOT_SET, INFERRED, SET_LOCALE, SET_CONTENT_TYPE, SET_CHARACTER_ENCODING }; private enum EncodingFrom { NOT_SET, INFERRED, SET_LOCALE, SET_CONTENT_TYPE, SET_CHARACTER_ENCODING }
private static final EnumSet<EncodingFrom> __localeOverride = EnumSet.of(EncodingFrom.NOT_SET,EncodingFrom.INFERRED); private static final EnumSet<EncodingFrom> __localeOverride = EnumSet.of(EncodingFrom.NOT_SET,EncodingFrom.INFERRED);
private static final EnumSet<EncodingFrom> __explicitCharset = EnumSet.of(EncodingFrom.SET_LOCALE,EncodingFrom.SET_CHARACTER_ENCODING); private static final EnumSet<EncodingFrom> __explicitCharset = EnumSet.of(EncodingFrom.SET_LOCALE,EncodingFrom.SET_CHARACTER_ENCODING);
public Response(HttpChannel channel, HttpOutput out) public Response(HttpChannel channel, HttpOutput out)
{ {
_channel = channel; _channel = channel;
@ -686,7 +678,7 @@ public class Response implements HttpServletResponse
/** /**
* Sends a response with one of the 300 series redirection codes. * Sends a response with one of the 300 series redirection codes.
* @param code the redirect status code * @param code the redirect status code
* @param location the location to send in <code>Location</code> headers * @param location the location to send in {@code Location} headers
* @throws IOException if unable to send the redirect * @throws IOException if unable to send the redirect
*/ */
public void sendRedirect(int code, String location) throws IOException public void sendRedirect(int code, String location) throws IOException
@ -765,7 +757,7 @@ public class Response implements HttpServletResponse
if (HttpHeader.CONTENT_LENGTH == name) if (HttpHeader.CONTENT_LENGTH == name)
{ {
if (value == null) if (value == null)
_contentLength = -1l; _contentLength = -1L;
else else
_contentLength = Long.parseLong(value); _contentLength = Long.parseLong(value);
} }
@ -790,7 +782,7 @@ public class Response implements HttpServletResponse
if (HttpHeader.CONTENT_LENGTH.is(name)) if (HttpHeader.CONTENT_LENGTH.is(name))
{ {
if (value == null) if (value == null)
_contentLength = -1l; _contentLength = -1L;
else else
_contentLength = Long.parseLong(value); _contentLength = Long.parseLong(value);
} }
@ -800,8 +792,7 @@ public class Response implements HttpServletResponse
@Override @Override
public Collection<String> getHeaderNames() public Collection<String> getHeaderNames()
{ {
final HttpFields fields = _fields; return _fields.getFieldNamesCollection();
return fields.getFieldNamesCollection();
} }
@Override @Override
@ -813,8 +804,7 @@ public class Response implements HttpServletResponse
@Override @Override
public Collection<String> getHeaders(String name) public Collection<String> getHeaders(String name)
{ {
final HttpFields fields = _fields; Collection<String> i = _fields.getValuesList(name);
Collection<String> i = fields.getValuesList(name);
if (i == null) if (i == null)
return Collections.emptyList(); return Collections.emptyList();
return i; return i;
@ -1290,7 +1280,7 @@ public class Response implements HttpServletResponse
} }
if (preserveCookies) if (preserveCookies)
cookies.forEach(f->_fields.add(f)); cookies.forEach(_fields::add);
else else
{ {
Request request = getHttpChannel().getRequest(); Request request = getHttpChannel().getRequest();
@ -1320,10 +1310,20 @@ public class Response implements HttpServletResponse
_out.resetBuffer(); _out.resetBuffer();
} }
public void setTrailers(Supplier<HttpFields> trailers)
{
this.trailers = trailers;
}
public Supplier<HttpFields> getTrailers()
{
return trailers;
}
protected MetaData.Response newResponseMetaData() protected MetaData.Response newResponseMetaData()
{ {
MetaData.Response info = new MetaData.Response(_channel.getRequest().getHttpVersion(), getStatus(), getReason(), _fields, getLongContentLength()); MetaData.Response info = new MetaData.Response(_channel.getRequest().getHttpVersion(), getStatus(), getReason(), _fields, getLongContentLength());
// TODO info.setTrailerSupplier(trailers); info.setTrailerSupplier(getTrailers());
return info; return info;
} }

View File

@ -120,9 +120,9 @@ public class HttpInputAsyncStateTest
} }
@Override @Override
public boolean onDataAvailable() public boolean onContentAdded()
{ {
boolean wake = super.onDataAvailable(); boolean wake = super.onContentAdded();
__history.add("onReadPossible "+wake); __history.add("onReadPossible "+wake);
return wake; return wake;
} }

View File

@ -111,10 +111,10 @@ public class HttpInputTest
} }
@Override @Override
public boolean onDataAvailable() public boolean onContentAdded()
{ {
_history.add("s.onDataAvailable"); _history.add("s.onDataAvailable");
return super.onDataAvailable(); return super.onContentAdded();
} }
@Override @Override

View File

@ -1,170 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.server;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpTester;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
public class HttpTrailersTest
{
private Server server;
private ServerConnector connector;
private void start(Handler handler) throws Exception
{
server = new Server();
connector = new ServerConnector(server);
server.addConnector(connector);
server.setHandler(handler);
server.start();
}
@After
public void dispose() throws Exception
{
if (server != null)
server.stop();
}
@Test
public void testServletRequestTrailers() throws Exception
{
String trailerName = "Trailer";
String trailerValue = "value";
start(new AbstractHandler.ErrorDispatchHandler()
{
@Override
protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
jettyRequest.setHandled(true);
// Read the content first.
ServletInputStream input = jettyRequest.getInputStream();
while (true)
{
int read = input.read();
if (read < 0)
break;
}
// Now the trailers can be accessed.
HttpFields trailers = jettyRequest.getTrailers();
Assert.assertNotNull(trailers);
Assert.assertEquals(trailerValue, trailers.get(trailerName));
}
});
try (Socket client = new Socket("localhost", connector.getLocalPort()))
{
client.setSoTimeout(5000);
String request = "" +
"GET / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"0\r\n" +
trailerName + ": " + trailerValue + "\r\n" +
"\r\n";
OutputStream output = client.getOutputStream();
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client.getInputStream()));
Assert.assertNotNull(response);
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
}
@Test
public void testHugeTrailer() throws Exception
{
start(new AbstractHandler.ErrorDispatchHandler()
{
@Override
protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
jettyRequest.setHandled(true);
try
{
// EOF will not be reached because of the huge trailer.
ServletInputStream input = jettyRequest.getInputStream();
while (true)
{
int read = input.read();
if (read < 0)
break;
}
Assert.fail();
}
catch (IOException x)
{
// Expected.
}
}
});
char[] huge = new char[1024 * 1024];
Arrays.fill(huge, 'X');
try (Socket client = new Socket("localhost", connector.getLocalPort()))
{
client.setSoTimeout(5000);
try
{
String request = "" +
"GET / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Transfer-Encoding: chunked\r\n" +
"\r\n" +
"0\r\n" +
"Trailer: " + new String(huge) + "\r\n" +
"\r\n";
OutputStream output = client.getOutputStream();
output.write(request.getBytes(StandardCharsets.UTF_8));
output.flush();
HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client.getInputStream()));
Assert.assertNotNull(response);
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
catch(Exception e)
{
// May be thrown if write fails and error handling is aborted
}
}
}
}

View File

@ -19,7 +19,7 @@ resources/
# jetty.console-capture.dir=logs # jetty.console-capture.dir=logs
## Whether to append to existing file ## Whether to append to existing file
# jetty.console-capture.append=false # jetty.console-capture.append=true
## How many days to retain old log files ## How many days to retain old log files
# jetty.console-capture.retainDays=90 # jetty.console-capture.retainDays=90

View File

@ -420,11 +420,11 @@ public class BufferUtil
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Like append, but does not throw {@link BufferOverflowException} * Like append, but does not throw {@link BufferOverflowException}
* @param to Buffer is flush mode * @param to Buffer The buffer to fill to. The buffer will be flipped to fill mode and then flipped back to flush mode.
* @param b bytes to fill * @param b bytes The bytes to fill
* @param off offset into byte * @param off offset into bytes
* @param len length to fill * @param len length to fill
* @return The position of the valid data before the flipped position. * @return the number of bytes taken from the buffer.
*/ */
public static int fill(ByteBuffer to, byte[] b, int off, int len) public static int fill(ByteBuffer to, byte[] b, int off, int len)
{ {

View File

@ -119,6 +119,11 @@ public interface Callback extends Invocable
this.callback = nested.callback; this.callback = nested.callback;
} }
public Callback getCallback()
{
return callback;
}
@Override @Override
public void succeeded() public void succeeded()
{ {

View File

@ -34,8 +34,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import javax.servlet.ServletContainerInitializer;
import org.eclipse.jetty.util.annotation.Name; import org.eclipse.jetty.util.annotation.Name;
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;
@ -723,7 +721,7 @@ public class TypeUtil
{ {
try try
{ {
return Resource.newResource(URIUtil.getJarSource(url.toString())); return Resource.newResource(URIUtil.getJarSource(url.toURI()));
} }
catch(Exception e) catch(Exception e)
{ {

View File

@ -122,4 +122,47 @@ public interface LifeCycle
public void lifeCycleStopping(LifeCycle event); public void lifeCycleStopping(LifeCycle event);
public void lifeCycleStopped(LifeCycle event); public void lifeCycleStopped(LifeCycle event);
} }
/**
* Utility to start an object if it is a LifeCycle and to convert
* any exception thrown to a {@link RuntimeException}
* @param object The instance to start.
* @throws RuntimeException if the call to start throws an exception.
*/
public static void start(Object object)
{
if (object instanceof LifeCycle)
{
try
{
((LifeCycle)object).start();
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
}
/**
* Utility to stop an object if it is a LifeCycle and to convert
* any exception thrown to a {@link RuntimeException}
* @param object The instance to stop.
* @throws RuntimeException if the call to stop throws an exception.
*/
public static void stop(Object object)
{
if (object instanceof LifeCycle)
{
try
{
((LifeCycle)object).stop();
}
catch(Exception e)
{
throw new RuntimeException(e);
}
}
}
} }

View File

@ -25,6 +25,7 @@ import java.util.concurrent.RejectedExecutionException;
import org.eclipse.jetty.util.Loader; import org.eclipse.jetty.util.Loader;
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.util.thread.strategy.EatWhatYouKill;
import org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume; import org.eclipse.jetty.util.thread.strategy.ExecuteProduceConsume;
/** /**
@ -73,55 +74,4 @@ public interface ExecutionStrategy
Runnable produce(); Runnable produce();
} }
/**
* <p>A factory for {@link ExecutionStrategy}.</p>
*/
public static interface Factory
{
/**
* <p>Creates a new {@link ExecutionStrategy}.</p>
*
* @param producer the execution strategy producer
* @param executor the execution strategy executor
* @return a new {@link ExecutionStrategy}
*/
public ExecutionStrategy newExecutionStrategy(Producer producer, Executor executor);
/**
* @return the default {@link ExecutionStrategy}
*/
public static Factory getDefault()
{
return DefaultExecutionStrategyFactory.INSTANCE;
}
}
public static class DefaultExecutionStrategyFactory implements Factory
{
private static final Logger LOG = Log.getLogger(Factory.class);
private static final Factory INSTANCE = new DefaultExecutionStrategyFactory();
@Override
public ExecutionStrategy newExecutionStrategy(Producer producer, Executor executor)
{
String strategy = System.getProperty(producer.getClass().getName() + ".ExecutionStrategy");
if (strategy != null)
{
try
{
Class<? extends ExecutionStrategy> c = Loader.loadClass(strategy);
Constructor<? extends ExecutionStrategy> m = c.getConstructor(Producer.class, Executor.class);
LOG.info("Use {} for {}", c.getSimpleName(), producer.getClass().getName());
return m.newInstance(producer, executor);
}
catch (Exception e)
{
LOG.warn(e);
}
}
return new ExecuteProduceConsume(producer, executor);
}
}
} }

View File

@ -18,7 +18,13 @@
package org.eclipse.jetty.util.thread; package org.eclipse.jetty.util.thread;
import java.io.Closeable;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/** /**
* <p>A task (typically either a {@link Runnable} or {@link Callable} * <p>A task (typically either a {@link Runnable} or {@link Callable}
@ -151,7 +157,7 @@ public interface Invocable
break; break;
case EITHER: case EITHER:
if (getInvocationType(task) == InvocationType.EITHER && preferredInvocationType == InvocationType.NON_BLOCKING) if (preferredInvocationType == InvocationType.NON_BLOCKING)
return () -> invokeNonBlocking(task); return () -> invokeNonBlocking(task);
break; break;
} }
@ -179,4 +185,85 @@ public interface Invocable
{ {
return InvocationType.BLOCKING; return InvocationType.BLOCKING;
} }
/**
* An Executor wrapper that knows about Invocable
*
*/
public static class InvocableExecutor implements Executor
{
private static final Logger LOG = Log.getLogger(InvocableExecutor.class);
private final Executor _executor;
private final InvocationType _preferredInvocationForExecute;
private final InvocationType _preferredInvocationForInvoke;
public InvocableExecutor(Executor executor,InvocationType preferred)
{
this(executor,preferred,preferred);
}
public InvocableExecutor(Executor executor,InvocationType preferredInvocationForExecute,InvocationType preferredInvocationForIvoke)
{
_executor=executor;
_preferredInvocationForExecute=preferredInvocationForExecute;
_preferredInvocationForInvoke=preferredInvocationForIvoke;
}
public Invocable.InvocationType getPreferredInvocationType()
{
return _preferredInvocationForInvoke;
}
public void invoke(Runnable task)
{
if (LOG.isDebugEnabled())
LOG.debug("{} invoke {}", this, task);
Invocable.invokePreferred(task,_preferredInvocationForInvoke);
if (LOG.isDebugEnabled())
LOG.debug("{} invoked {}", this, task);
}
public void execute(Runnable task)
{
tryExecute(task,_preferredInvocationForExecute);
}
public void execute(Runnable task, InvocationType preferred)
{
tryExecute(task,preferred);
}
public boolean tryExecute(Runnable task)
{
return tryExecute(task,_preferredInvocationForExecute);
}
public boolean tryExecute(Runnable task, InvocationType preferred)
{
try
{
_executor.execute(Invocable.asPreferred(task,preferred));
return true;
}
catch(RejectedExecutionException e)
{
// If we cannot execute, then close the task
LOG.debug(e);
LOG.warn("Rejected execution of {}",task);
try
{
if (task instanceof Closeable)
((Closeable)task).close();
}
catch (Exception x)
{
e.addSuppressed(x);
LOG.warn(e);
}
}
return false;
}
}
} }

View File

@ -0,0 +1,374 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.util.thread.strategy;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Condition;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Invocable.InvocableExecutor;
import org.eclipse.jetty.util.thread.Invocable.InvocationType;
import org.eclipse.jetty.util.thread.Locker;
import org.eclipse.jetty.util.thread.Locker.Lock;
/**
* <p>A strategy where the thread that produces will run the resulting task if it
* is possible to do so without thread starvation.</p>
*
* <p>This strategy preemptively dispatches a thread as a pending producer, so that
* when a thread produces a task it can immediately run the task and let the pending
* producer thread take over producing. If necessary another thread will be dispatched
* to replace the pending producing thread. When operating in this pattern, the
* sub-strategy is called Execute Produce Consume (EPC)
* </p>
* <p>However, if the task produced uses the {@link Invocable} API to indicate that
* it will not block, then the strategy will run it directly, regardless of the
* presence of a pending producing thread and then resume producing after the
* task has completed. This sub-strategy is also used if the strategy has been
* configured with a maximum of 0 pending threads and the thread currently producing
* does not use the {@link Invocable} API to indicate that it will not block.
* When operating in this pattern, the sub-strategy is called
* ProduceConsume (PC).
* </p>
* <p>If there is no pending producer thread available and if the task has not
* indicated it is non-blocking, then this strategy will dispatch the execution of
* the task and immediately continue producing. When operating in this pattern, the
* sub-strategy is called ProduceExecuteConsume (PEC).
* </p>
*
*/
public class EatWhatYouKill extends AbstractLifeCycle implements ExecutionStrategy, Runnable
{
private static final Logger LOG = Log.getLogger(EatWhatYouKill.class);
enum State { IDLE, PRODUCING, REPRODUCING };
private final Locker _locker = new Locker();
private State _state = State.IDLE;
private final Runnable _runProduce = new RunProduce();
private final Producer _producer;
private final InvocableExecutor _executor;
private int _pendingProducersMax;
private int _pendingProducers;
private int _pendingProducersDispatched;
private int _pendingProducersSignalled;
private Condition _produce = _locker.newCondition();
public EatWhatYouKill(Producer producer, Executor executor)
{
this(producer,executor,InvocationType.NON_BLOCKING,InvocationType.BLOCKING);
}
public EatWhatYouKill(Producer producer, Executor executor, int maxProducersPending )
{
this(producer,executor,InvocationType.NON_BLOCKING,InvocationType.BLOCKING);
}
public EatWhatYouKill(Producer producer, Executor executor, InvocationType preferredInvocationPEC, InvocationType preferredInvocationEPC)
{
this(producer,executor,preferredInvocationPEC,preferredInvocationEPC,Integer.getInteger("org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.maxProducersPending",1));
}
public EatWhatYouKill(Producer producer, Executor executor, InvocationType preferredInvocationPEC, InvocationType preferredInvocationEPC, int maxProducersPending )
{
_producer = producer;
_pendingProducersMax = maxProducersPending;
_executor = new InvocableExecutor(executor,preferredInvocationPEC,preferredInvocationEPC);
}
@Override
public void produce()
{
boolean produce;
try (Lock locked = _locker.lock())
{
switch(_state)
{
case IDLE:
_state = State.PRODUCING;
produce = true;
break;
case PRODUCING:
_state = State.REPRODUCING;
produce = false;
break;
default:
produce = false;
}
}
if (LOG.isDebugEnabled())
LOG.debug("{} execute {}", this, produce);
if (produce)
doProduce();
}
@Override
public void dispatch()
{
boolean dispatch = false;
try (Lock locked = _locker.lock())
{
switch(_state)
{
case IDLE:
dispatch = true;
break;
case PRODUCING:
_state = State.REPRODUCING;
dispatch = false;
break;
default:
dispatch = false;
}
}
if (LOG.isDebugEnabled())
LOG.debug("{} dispatch {}", this, dispatch);
if (dispatch)
_executor.execute(_runProduce,InvocationType.BLOCKING);
}
@Override
public void run()
{
if (LOG.isDebugEnabled())
LOG.debug("{} run", this);
if (!isRunning())
return;
boolean producing = false;
try (Lock locked = _locker.lock())
{
_pendingProducersDispatched--;
_pendingProducers++;
loop: while (isRunning())
{
try
{
_produce.await();
if (_pendingProducersSignalled==0)
{
// spurious wakeup!
continue loop;
}
_pendingProducersSignalled--;
if (_state == State.IDLE)
{
_state = State.PRODUCING;
producing = true;
}
}
catch (InterruptedException e)
{
LOG.debug(e);
_pendingProducers--;
}
break loop;
}
}
if (producing)
doProduce();
}
private void doProduce()
{
boolean may_block_caller = !Invocable.isNonBlockingInvocation();
if (LOG.isDebugEnabled())
LOG.debug("{} produce {}", this,may_block_caller?"non-blocking":"blocking");
producing: while (isRunning())
{
// If we got here, then we are the thread that is producing.
Runnable task = _producer.produce();
boolean produce;
boolean consume;
boolean execute_producer;
StringBuilder state = null;
try (Lock locked = _locker.lock())
{
if (LOG.isDebugEnabled())
{
state = new StringBuilder();
getString(state);
getState(state);
state.append("->");
}
// Did we produced a task?
if (task == null)
{
// There is no task.
// Could another one just have been queued with a produce call?
if (_state==State.REPRODUCING)
{
_state = State.PRODUCING;
continue producing;
}
// ... and no additional calls to execute, so we are idle
_state = State.IDLE;
break producing;
}
// Will we eat our own kill - ie consume the task we just produced?
if (Invocable.getInvocationType(task)==InvocationType.NON_BLOCKING)
{
// ProduceConsume
produce = true;
consume = true;
execute_producer = false;
}
else if (may_block_caller && (_pendingProducers>0 || _pendingProducersMax==0))
{
// ExecuteProduceConsume (eat what we kill!)
produce = false;
consume = true;
execute_producer = true;
_pendingProducersDispatched++;
_state = State.IDLE;
_pendingProducers--;
_pendingProducersSignalled++;
_produce.signal();
}
else
{
// ProduceExecuteConsume
produce = true;
consume = false;
execute_producer = (_pendingProducersDispatched + _pendingProducers)<_pendingProducersMax;
if (execute_producer)
_pendingProducersDispatched++;
}
if (LOG.isDebugEnabled())
getState(state);
}
if (LOG.isDebugEnabled())
{
LOG.debug("{} {} {}",
state,
consume?(execute_producer?"EPC!":"PC"):"PEC",
task);
}
if (execute_producer)
// Spawn a new thread to continue production by running the produce loop.
_executor.execute(this);
// Run or execute the task.
if (consume)
_executor.invoke(task);
else
_executor.execute(task);
// Once we have run the task, we can try producing again.
if (produce)
continue producing;
try (Lock locked = _locker.lock())
{
if (_state==State.IDLE)
{
_state = State.PRODUCING;
continue producing;
}
}
break producing;
}
if (LOG.isDebugEnabled())
LOG.debug("{} produce exit",this);
}
public Boolean isIdle()
{
try (Lock locked = _locker.lock())
{
return _state==State.IDLE;
}
}
@Override
protected void doStop() throws Exception
{
try (Lock locked = _locker.lock())
{
_pendingProducersSignalled=_pendingProducers+_pendingProducersDispatched;
_pendingProducers=0;
_produce.signalAll();
}
}
public String toString()
{
StringBuilder builder = new StringBuilder();
getString(builder);
try (Lock locked = _locker.lock())
{
getState(builder);
}
return builder.toString();
}
private void getString(StringBuilder builder)
{
builder.append(getClass().getSimpleName());
builder.append('@');
builder.append(Integer.toHexString(hashCode()));
builder.append('/');
builder.append(_producer);
builder.append('/');
}
private void getState(StringBuilder builder)
{
builder.append(_state);
builder.append('/');
builder.append(_pendingProducers);
builder.append('/');
builder.append(_pendingProducersMax);
}
private class RunProduce implements Runnable
{
@Override
public void run()
{
produce();
}
}
}

View File

@ -24,6 +24,7 @@ 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.util.thread.ExecutionStrategy; import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.Invocable; import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Invocable.InvocableExecutor;
import org.eclipse.jetty.util.thread.Invocable.InvocationType; import org.eclipse.jetty.util.thread.Invocable.InvocationType;
import org.eclipse.jetty.util.thread.Locker; import org.eclipse.jetty.util.thread.Locker;
import org.eclipse.jetty.util.thread.Locker.Lock; import org.eclipse.jetty.util.thread.Locker.Lock;
@ -41,13 +42,14 @@ import org.eclipse.jetty.util.thread.Locker.Lock;
* does not yet have capacity to consume, which can save memory and exert back * does not yet have capacity to consume, which can save memory and exert back
* pressure on producers.</p> * pressure on producers.</p>
*/ */
public class ExecuteProduceConsume extends ExecutingExecutionStrategy implements ExecutionStrategy, Runnable public class ExecuteProduceConsume implements ExecutionStrategy, Runnable
{ {
private static final Logger LOG = Log.getLogger(ExecuteProduceConsume.class); private static final Logger LOG = Log.getLogger(ExecuteProduceConsume.class);
private final Locker _locker = new Locker(); private final Locker _locker = new Locker();
private final Runnable _runProduce = new RunProduce(); private final Runnable _runProduce = new RunProduce();
private final Producer _producer; private final Producer _producer;
private final InvocableExecutor _executor;
private boolean _idle = true; private boolean _idle = true;
private boolean _execute; private boolean _execute;
private boolean _producing; private boolean _producing;
@ -61,8 +63,8 @@ public class ExecuteProduceConsume extends ExecutingExecutionStrategy implements
public ExecuteProduceConsume(Producer producer, Executor executor, InvocationType preferred ) public ExecuteProduceConsume(Producer producer, Executor executor, InvocationType preferred )
{ {
super(executor,preferred);
this._producer = producer; this._producer = producer;
_executor = new InvocableExecutor(executor,preferred);
} }
@Override @Override
@ -111,7 +113,7 @@ public class ExecuteProduceConsume extends ExecutingExecutionStrategy implements
_execute = true; _execute = true;
} }
if (dispatch) if (dispatch)
execute(_runProduce); _executor.execute(_runProduce);
} }
@Override @Override
@ -190,7 +192,7 @@ public class ExecuteProduceConsume extends ExecutingExecutionStrategy implements
// Spawn a new thread to continue production by running the produce loop. // Spawn a new thread to continue production by running the produce loop.
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("{} dispatch", this); LOG.debug("{} dispatch", this);
if (!execute(this)) if (!_executor.tryExecute(this))
task = null; task = null;
} }
@ -198,7 +200,7 @@ public class ExecuteProduceConsume extends ExecutingExecutionStrategy implements
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("{} run {}", this, task); LOG.debug("{} run {}", this, task);
if (task != null) if (task != null)
invoke(task); _executor.invoke(task);
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("{} ran {}", this, task); LOG.debug("{} ran {}", this, task);
@ -247,13 +249,4 @@ public class ExecuteProduceConsume extends ExecutingExecutionStrategy implements
produce(); produce();
} }
} }
public static class Factory implements ExecutionStrategy.Factory
{
@Override
public ExecutionStrategy newExecutionStrategy(Producer producer, Executor executor)
{
return new ExecuteProduceConsume(producer, executor);
}
}
} }

View File

@ -1,88 +0,0 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.util.thread.strategy;
import java.io.Closeable;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.Invocable;
/**
* <p>Base class for strategies that need to execute a task by submitting it to an {@link Executor}.</p>
* <p>If the submission to the {@code Executor} is rejected (via a {@link RejectedExecutionException}),
* the task is tested whether it implements {@link Closeable}; if it does, then {@link Closeable#close()}
* is called on the task object.</p>
*/
public abstract class ExecutingExecutionStrategy implements ExecutionStrategy
{
private static final Logger LOG = Log.getLogger(ExecutingExecutionStrategy.class);
private final Executor _executor;
private final Invocable.InvocationType _preferredInvocationType;
protected ExecutingExecutionStrategy(Executor executor,Invocable.InvocationType preferred)
{
_executor=executor;
_preferredInvocationType=preferred;
}
public Invocable.InvocationType getPreferredInvocationType()
{
return _preferredInvocationType;
}
public void invoke(Runnable task)
{
if (LOG.isDebugEnabled())
LOG.debug("{} invoke {}", this, task);
Invocable.invokePreferred(task,_preferredInvocationType);
if (LOG.isDebugEnabled())
LOG.debug("{} invoked {}", this, task);
}
protected boolean execute(Runnable task)
{
try
{
_executor.execute(Invocable.asPreferred(task,_preferredInvocationType));
return true;
}
catch(RejectedExecutionException e)
{
// If we cannot execute, then close the task and keep producing.
LOG.debug(e);
LOG.warn("Rejected execution of {}",task);
try
{
if (task instanceof Closeable)
((Closeable)task).close();
}
catch (Exception x)
{
e.addSuppressed(x);
LOG.warn(e);
}
}
return false;
}
}

View File

@ -106,15 +106,6 @@ public class ProduceConsume implements ExecutionStrategy, Runnable
produce(); produce();
} }
public static class Factory implements ExecutionStrategy.Factory
{
@Override
public ExecutionStrategy newExecutionStrategy(Producer producer, Executor executor)
{
return new ProduceConsume(producer, executor);
}
}
private enum State private enum State
{ {
IDLE, PRODUCE, EXECUTE IDLE, PRODUCE, EXECUTE

View File

@ -23,7 +23,8 @@ import java.util.concurrent.Executor;
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.util.thread.ExecutionStrategy; import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.Invocable; import org.eclipse.jetty.util.thread.Invocable.InvocationType;
import org.eclipse.jetty.util.thread.Invocable.InvocableExecutor;
import org.eclipse.jetty.util.thread.Locker; import org.eclipse.jetty.util.thread.Locker;
import org.eclipse.jetty.util.thread.Locker.Lock; import org.eclipse.jetty.util.thread.Locker.Lock;
@ -31,23 +32,24 @@ import org.eclipse.jetty.util.thread.Locker.Lock;
* <p>A strategy where the caller thread iterates over task production, submitting each * <p>A strategy where the caller thread iterates over task production, submitting each
* task to an {@link Executor} for execution.</p> * task to an {@link Executor} for execution.</p>
*/ */
public class ProduceExecuteConsume extends ExecutingExecutionStrategy implements ExecutionStrategy public class ProduceExecuteConsume implements ExecutionStrategy
{ {
private static final Logger LOG = Log.getLogger(ProduceExecuteConsume.class); private static final Logger LOG = Log.getLogger(ProduceExecuteConsume.class);
private final Locker _locker = new Locker(); private final Locker _locker = new Locker();
private final Producer _producer; private final Producer _producer;
private final InvocableExecutor _executor;
private State _state = State.IDLE; private State _state = State.IDLE;
public ProduceExecuteConsume(Producer producer, Executor executor) public ProduceExecuteConsume(Producer producer, Executor executor)
{ {
this(producer,executor,Invocable.InvocationType.NON_BLOCKING); this(producer,executor,InvocationType.NON_BLOCKING);
} }
public ProduceExecuteConsume(Producer producer, Executor executor, Invocable.InvocationType preferred) public ProduceExecuteConsume(Producer producer, Executor executor, InvocationType preferred)
{ {
super(executor,preferred); _producer = producer;
this._producer = producer; _executor = new InvocableExecutor(executor,preferred);
} }
@Override @Override
@ -95,7 +97,7 @@ public class ProduceExecuteConsume extends ExecutingExecutionStrategy implements
} }
// Execute the task. // Execute the task.
execute(task); _executor.execute(task);
} }
} }
@ -105,15 +107,6 @@ public class ProduceExecuteConsume extends ExecutingExecutionStrategy implements
produce(); produce();
} }
public static class Factory implements ExecutionStrategy.Factory
{
@Override
public ExecutionStrategy newExecutionStrategy(Producer producer, Executor executor)
{
return new ProduceExecuteConsume(producer, executor);
}
}
private enum State private enum State
{ {
IDLE, PRODUCE, EXECUTE IDLE, PRODUCE, EXECUTE

View File

@ -0,0 +1,218 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.util.thread.strategy;
import static org.hamcrest.Matchers.greaterThan;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ExecutionStrategy.Producer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ExecutionStrategyTest
{
@Parameterized.Parameters(name = "{0}")
public static Iterable<Object[]> data()
{
return Arrays.asList(new Object[][]{
{ProduceExecuteConsume.class},
{ExecuteProduceConsume.class},
{EatWhatYouKill.class}
});
}
QueuedThreadPool _threads = new QueuedThreadPool(20);
Class<? extends ExecutionStrategy> _strategyClass;
ExecutionStrategy _strategy;
public ExecutionStrategyTest(Class<? extends ExecutionStrategy> strategy)
{
_strategyClass = strategy;
}
void newExecutionStrategy(Producer producer, Executor executor) throws Exception
{
_strategy = _strategyClass.getConstructor(Producer.class,Executor.class).newInstance(producer,executor);
LifeCycle.start(_strategy);
}
@Before
public void before() throws Exception
{
_threads.start();
}
@After
public void after() throws Exception
{
LifeCycle.stop(_strategy);
_threads.stop();
}
public static abstract class TestProducer implements Producer
{
@Override
public String toString()
{
return "TestProducer";
}
}
@Test
public void idleTest() throws Exception
{
AtomicInteger count = new AtomicInteger(0);
Producer producer = new TestProducer()
{
@Override
public Runnable produce()
{
count.incrementAndGet();
return null;
}
};
newExecutionStrategy(producer,_threads);
_strategy.produce();
assertThat(count.get(),greaterThan(0));
}
@Test
public void simpleTest() throws Exception
{
final int TASKS = 3*_threads.getMaxThreads();
final CountDownLatch latch = new CountDownLatch(TASKS);
Producer producer = new TestProducer()
{
int tasks = TASKS;
@Override
public Runnable produce()
{
if (tasks-->0)
{
return new Runnable()
{
@Override
public void run()
{
latch.countDown();
}
};
}
return null;
}
};
newExecutionStrategy(producer,_threads);
for (int p=0; latch.getCount()>0 && p<TASKS; p++)
_strategy.produce();
assertTrue(latch.await(10,TimeUnit.SECONDS));
}
@Test
public void blockingProducerTest() throws Exception
{
final int TASKS = 3*_threads.getMaxThreads();
final BlockingQueue<CountDownLatch> q = new ArrayBlockingQueue<>(500);
Producer producer = new TestProducer()
{
int tasks=TASKS;
@Override
public Runnable produce()
{
if (tasks-->0)
{
try
{
final CountDownLatch latch = q.poll(10,TimeUnit.SECONDS);
if (latch!=null)
{
return new Runnable()
{
@Override
public void run()
{
latch.countDown();
}
};
}
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
return null;
}
};
newExecutionStrategy(producer,_threads);
_threads.execute(()->_strategy.produce());
final CountDownLatch latch = new CountDownLatch(TASKS);
_threads.execute(new Runnable()
{
@Override
public void run()
{
try
{
for (int t=TASKS;t-->0;)
{
Thread.sleep(20);
q.offer(latch);
_strategy.produce();
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
});
assertTrue(latch.await(10,TimeUnit.SECONDS));
}
}

View File

@ -3,3 +3,5 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
#org.eclipse.jetty.util.LEVEL=DEBUG #org.eclipse.jetty.util.LEVEL=DEBUG
#org.eclipse.jetty.util.PathWatcher.Noisy.LEVEL=OFF #org.eclipse.jetty.util.PathWatcher.Noisy.LEVEL=OFF
org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.LEVEL=DEBUG

View File

@ -179,23 +179,6 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL
"org.w3c." // javax.xml "org.w3c." // javax.xml
) ; ) ;
// Find the location of the JVM lib directory
public final static String __jvmlib;
static
{
String lib=null;
try
{
lib=TypeUtil.getLoadedFrom(System.class).getFile().getParentFile().toURI().toString();
}
catch(Exception e)
{
LOG.warn(e);
lib=null;
}
__jvmlib=lib;
}
// Server classes are classes that are hidden from being // Server classes are classes that are hidden from being
// loaded by the web application using system classloader, // loaded by the web application using system classloader,
// so if web application needs to load any of such classes, // so if web application needs to load any of such classes,

View File

@ -0,0 +1,78 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.webapp;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLStreamHandlerFactory;
import java.util.Arrays;
import java.util.Optional;
public final class URLStreamHandlerUtil
{
public static void setFactory(URLStreamHandlerFactory factory)
{
try
{
// First, reset the factory field
Field factoryField = getURLStreamHandlerFactoryField();
factoryField.setAccessible(true);
factoryField.set(null, null);
if(factory != null)
{
// Next, set the factory
URL.setURLStreamHandlerFactory(factory);
}
}
catch(Throwable ignore)
{
ignore.printStackTrace(System.err);
}
}
public static URLStreamHandlerFactory getFactory()
{
try
{
// First, reset the factory field
Field factoryField = getURLStreamHandlerFactoryField();
factoryField.setAccessible(true);
return (URLStreamHandlerFactory) factoryField.get(null);
}
catch(Throwable ignore)
{
return null;
}
}
private static Field getURLStreamHandlerFactoryField()
{
Optional<Field> optFactoryField = Arrays.stream(URL.class.getDeclaredFields())
.filter((f) -> Modifier.isStatic(f.getModifiers()) &&
f.getType().equals(URLStreamHandlerFactory.class))
.findFirst();
if(optFactoryField.isPresent())
return optFactoryField.get();
throw new RuntimeException( "Cannot find URLStreamHandlerFactory field in " + URL.class.getName() );
}
}

View File

@ -24,6 +24,7 @@ import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeThat;
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.IllegalClassFormatException;
@ -51,7 +52,7 @@ public class WebAppClassLoaderTest
private Path testWebappDir; private Path testWebappDir;
private WebAppContext _context; private WebAppContext _context;
private WebAppClassLoader _loader; protected WebAppClassLoader _loader;
@Before @Before
public void init() throws Exception public void init() throws Exception
@ -278,6 +279,9 @@ public class WebAppClassLoaderTest
@Test @Test
public void testResources() throws Exception public void testResources() throws Exception
{ {
// The existence of a URLStreamHandler changes the behavior
assumeThat("No URLStreamHandler in place", URLStreamHandlerUtil.getFactory(), nullValue());
List<URL> expected = new ArrayList<>(); List<URL> expected = new ArrayList<>();
List<URL> resources; List<URL> resources;

View File

@ -0,0 +1,113 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.webapp;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Before;
public class WebAppClassLoaderUrlStreamTest extends WebAppClassLoaderTest
{
public static class URLHandlers implements URLStreamHandlerFactory
{
private static final String[] STREAM_HANDLER_PREFIXES;
static
{
STREAM_HANDLER_PREFIXES = new String[]{
"sun.net.www.protocol",
"org.apache.harmony.luni.internal.net.www.protocol",
"javax.net.ssl"
};
}
private Map<String, URLStreamHandler> handlers = new HashMap<>();
private ClassLoader loader;
public URLHandlers(ClassLoader loader)
{
this.loader = loader;
}
private URLStreamHandler getBuiltInHandler(String protocol, ClassLoader classLoader)
{
URLStreamHandler handler = handlers.get(protocol);
if (handler == null)
{
for (String prefix : STREAM_HANDLER_PREFIXES)
{
String className = prefix + '.' + protocol + ".Handler";
try
{
Class<?> clazz = Class.forName(className, false, classLoader);
handler = (URLStreamHandler) clazz.newInstance();
break;
}
catch (Exception ignore)
{
ignore.printStackTrace(System.err);
}
}
if (handler != null)
{
handlers.put(protocol, handler);
}
}
if (handler == null)
{
throw new RuntimeException("Unable to find handler for protocol [" + protocol + "]");
}
return handler;
}
@Override
public URLStreamHandler createURLStreamHandler(String protocol)
{
try
{
return getBuiltInHandler(protocol, loader);
}
catch (Exception e)
{
throw new RuntimeException("Unable to create URLStreamHandler for protocol [" + protocol + "]");
}
}
}
@After
public void cleanupURLStreamHandlerFactory()
{
URLStreamHandlerUtil.setFactory(null);
}
@Before
public void init() throws Exception
{
super.init();
URLStreamHandlerUtil.setFactory(new URLHandlers(_loader));
}
}

View File

@ -0,0 +1,227 @@
//
// ========================================================================
// Copyright (c) 1995-2017 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.http.client;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpRequest;
import org.eclipse.jetty.client.HttpResponse;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.util.BytesContentProvider;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.junit.Assert;
import org.junit.Test;
public class HttpTrailersTest extends AbstractTest
{
public HttpTrailersTest(Transport transport)
{
super(transport == Transport.FCGI ? null : transport);
}
@Test
public void testRequestTrailersNoContent() throws Exception
{
testRequestTrailers(null);
}
@Test
public void testRequestTrailersWithContent() throws Exception
{
testRequestTrailers("abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.UTF_8));
}
private void testRequestTrailers(byte[] content) throws Exception
{
String trailerName = "Trailer";
String trailerValue = "value";
start(new AbstractHandler.ErrorDispatchHandler()
{
@Override
protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
jettyRequest.setHandled(true);
// Read the content first.
ServletInputStream input = jettyRequest.getInputStream();
while (true)
{
int read = input.read();
if (read < 0)
break;
}
// Now the trailers can be accessed.
HttpFields trailers = jettyRequest.getTrailers();
Assert.assertNotNull(trailers);
Assert.assertEquals(trailerValue, trailers.get(trailerName));
}
});
HttpFields trailers = new HttpFields();
trailers.put(trailerName, trailerValue);
HttpRequest request = (HttpRequest)client.newRequest(newURI());
request = request.trailers(() -> trailers);
if (content != null)
request.method(HttpMethod.POST).content(new BytesContentProvider(content));
ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testEmptyRequestTrailers() throws Exception
{
start(new AbstractHandler.ErrorDispatchHandler()
{
@Override
protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
jettyRequest.setHandled(true);
// Read the content first.
ServletInputStream input = jettyRequest.getInputStream();
while (true)
{
int read = input.read();
if (read < 0)
break;
}
// Now the trailers can be accessed.
HttpFields trailers = jettyRequest.getTrailers();
Assert.assertNull(trailers);
}
});
HttpFields trailers = new HttpFields();
HttpRequest request = (HttpRequest)client.newRequest(newURI());
request = request.trailers(() -> trailers);
ContentResponse response = request.timeout(5, TimeUnit.SECONDS).send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
}
@Test
public void testResponseTrailersNoContent() throws Exception
{
testResponseTrailers(null);
}
@Test
public void testResponseTrailersWithContent() throws Exception
{
testResponseTrailers("abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.UTF_8));
}
private void testResponseTrailers(byte[] content) throws Exception
{
String trailerName = "Trailer";
String trailerValue = "value";
start(new AbstractHandler.ErrorDispatchHandler()
{
@Override
protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
jettyRequest.setHandled(true);
HttpFields trailers = new HttpFields();
trailers.put(trailerName, trailerValue);
Response jettyResponse = (Response)response;
jettyResponse.setTrailers(() -> trailers);
if (content != null)
response.getOutputStream().write(content);
}
});
AtomicReference<Throwable> failure = new AtomicReference<>(new Throwable("no_success"));
ContentResponse response = client.newRequest(newURI())
.onResponseSuccess(r ->
{
try
{
HttpResponse httpResponse = (HttpResponse)r;
HttpFields trailers = httpResponse.getTrailers();
Assert.assertNotNull(trailers);
Assert.assertEquals(trailerValue, trailers.get(trailerName));
failure.set(null);
}
catch (Throwable x)
{
failure.set(x);
}
})
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertNull(failure.get());
}
@Test
public void testEmptyResponseTrailers() throws Exception
{
start(new AbstractHandler.ErrorDispatchHandler()
{
@Override
protected void doNonErrorHandle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
jettyRequest.setHandled(true);
HttpFields trailers = new HttpFields();
Response jettyResponse = (Response)response;
jettyResponse.setTrailers(() -> trailers);
}
});
AtomicReference<Throwable> failure = new AtomicReference<>(new Throwable("no_success"));
ContentResponse response = client.newRequest(newURI())
.onResponseSuccess(r ->
{
try
{
HttpResponse httpResponse = (HttpResponse)r;
HttpFields trailers = httpResponse.getTrailers();
Assert.assertNull(trailers);
failure.set(null);
}
catch (Throwable x)
{
failure.set(x);
}
})
.timeout(5, TimeUnit.SECONDS)
.send();
Assert.assertEquals(HttpStatus.OK_200, response.getStatus());
Assert.assertNull(failure.get());
}
}