From 28566c72c8bb091b272fd7b25fb7b4389363b024 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 24 Oct 2013 17:22:08 +1100 Subject: [PATCH 01/27] 420142 reimplemented graceful shutdown --- .../java/org/eclipse/jetty/server/Server.java | 7 +- .../server/handler/StatisticsHandler.java | 54 ++++++++- .../jetty/server/GracefulStopTest.java | 105 ++++++++++++++++++ .../util/statistic/CounterStatistic.java | 24 ++-- 4 files changed, 167 insertions(+), 23 deletions(-) create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index 31a67de487d..0fbc89f6f56 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -359,9 +359,10 @@ public class Server extends HandlerWrapper implements Attributes futures.add(connector.shutdown()); // Then tell the contexts that we are shutting down - Handler[] contexts = getChildHandlersByClass(Graceful.class); - for (Handler context : contexts) - futures.add(((Graceful)context).shutdown()); + + Handler[] gracefuls = getChildHandlersByClass(Graceful.class); + for (Handler graceful : gracefuls) + futures.add(((Graceful)graceful).shutdown()); // Shall we gracefully wait for zero connections? long stopTimeout = getStopTimeout(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java index 78e8eb71ef3..bc93a6501cb 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java @@ -19,8 +19,11 @@ package org.eclipse.jetty.server.handler; import java.io.IOException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; @@ -32,14 +35,16 @@ import org.eclipse.jetty.server.AsyncContextEvent; import org.eclipse.jetty.server.HttpChannelState; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedOperation; +import org.eclipse.jetty.util.component.Graceful; import org.eclipse.jetty.util.statistic.CounterStatistic; import org.eclipse.jetty.util.statistic.SampleStatistic; @ManagedObject("Request Statistics Gathering") -public class StatisticsHandler extends HandlerWrapper +public class StatisticsHandler extends HandlerWrapper implements Graceful { private final AtomicLong _statsStartedAt = new AtomicLong(); @@ -59,6 +64,8 @@ public class StatisticsHandler extends HandlerWrapper private final AtomicInteger _responses5xx = new AtomicInteger(); private final AtomicLong _responsesTotalBytes = new AtomicLong(); + private final AtomicReference _shutdown=new AtomicReference<>(); + private final AsyncListener _onCompletion = new AsyncListener() { @Override @@ -81,21 +88,27 @@ public class StatisticsHandler extends HandlerWrapper @Override public void onComplete(AsyncEvent event) throws IOException { - HttpChannelState state = ((AsyncContextEvent)event).getHttpChannelState(); Request request = state.getBaseRequest(); final long elapsed = System.currentTimeMillis()-request.getTimeStamp(); - _requestStats.decrement(); + long d=_requestStats.decrement(); _requestTimeStats.set(elapsed); updateResponse(request); if (!state.isDispatched()) _asyncWaitStats.decrement(); + + // If we have no more dispatches, should we signal shutdown? + if (d==0) + { + FutureCallback shutdown = _shutdown.get(); + if (shutdown!=null) + shutdown.succeeded(); + } } - }; /** @@ -164,9 +177,18 @@ public class StatisticsHandler extends HandlerWrapper } else if (state.isInitial()) { - _requestStats.decrement(); + long d=_requestStats.decrement(); _requestTimeStats.set(dispatched); updateResponse(request); + + // If we have no more dispatches, should we signal shutdown? + FutureCallback shutdown = _shutdown.get(); + if (shutdown!=null) + { + httpResponse.flushBuffer(); + if (d==0) + shutdown.succeeded(); + } } // else onCompletion will handle it. } @@ -207,9 +229,20 @@ public class StatisticsHandler extends HandlerWrapper @Override protected void doStart() throws Exception { + _shutdown.set(null); super.doStart(); statsReset(); } + + + @Override + protected void doStop() throws Exception + { + super.doStop(); + FutureCallback shutdown = _shutdown.get(); + if (shutdown!=null && !shutdown.isDone()) + shutdown.failed(new TimeoutException()); + } /** * @return the number of requests handled by this handler @@ -525,4 +558,15 @@ public class StatisticsHandler extends HandlerWrapper return sb.toString(); } + + @Override + public Future shutdown() + { + FutureCallback shutdown=new FutureCallback(false); + _shutdown.compareAndSet(null,shutdown); + shutdown=_shutdown.get(); + if (_dispatchedStats.getCurrent()==0) + shutdown.succeeded(); + return shutdown; + } } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java new file mode 100644 index 00000000000..0c11502ff68 --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java @@ -0,0 +1,105 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.net.Socket; +import java.util.concurrent.TimeUnit; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.server.handler.StatisticsHandler; +import org.eclipse.jetty.util.IO; +import org.eclipse.jetty.util.StringUtil; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class GracefulStopTest +{ + private Server server; + + @Before + public void setup() throws Exception + { + server = new Server(0); + StatisticsHandler stats = new StatisticsHandler(); + TestHandler test=new TestHandler(); + server.setHandler(stats); + stats.setHandler(test); + server.setStopTimeout(10 * 1000); + + server.start(); + } + + @Test + public void testGraceful() throws Exception + { + new Thread() + { + @Override + public void run() + { + try + { + TimeUnit.SECONDS.sleep(1); + server.stop(); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + }.start(); + + try(Socket socket = new Socket("localhost",server.getBean(NetworkConnector.class).getLocalPort());) + { + socket.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes(StringUtil.__ISO_8859_1_CHARSET)); + String out = IO.toString(socket.getInputStream()); + Assert.assertThat(out,Matchers.containsString("200 OK")); + } + } + + private static class TestHandler extends AbstractHandler + { + @Override + public void handle(final String s, final Request request, final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) + throws IOException, ServletException + { + try + { + TimeUnit.SECONDS.sleep(2); + } + catch (InterruptedException e) + { + } + + httpServletResponse.getWriter().write("OK"); + httpServletResponse.setStatus(200); + request.setHandled(true); + } + } + +} \ No newline at end of file diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java b/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java index 67699911c9b..51a82ea3bb4 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/statistic/CounterStatistic.java @@ -55,37 +55,31 @@ public class CounterStatistic /** * @param delta the amount to add to the count */ - public void add(final long delta) + public long add(final long delta) { long value=_curr.addAndGet(delta); if (delta > 0) + { _total.addAndGet(delta); - Atomics.updateMax(_max,value); - } - - /* ------------------------------------------------------------ */ - /** - * @param delta the amount to subtract the count by. - */ - public void subtract(final long delta) - { - add(-delta); + Atomics.updateMax(_max,value); + } + return value; } /* ------------------------------------------------------------ */ /** */ - public void increment() + public long increment() { - add(1); + return add(1); } /* ------------------------------------------------------------ */ /** */ - public void decrement() + public long decrement() { - add(-1); + return add(-1); } /* ------------------------------------------------------------ */ From 2e434ef1e8720847b743b245c574b1b6ed5a3d20 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 25 Oct 2013 13:02:18 +0200 Subject: [PATCH 02/27] Refactored HttpDestinationOverHTTP into a PoolingHttpDestination base class for reuse from other transports. --- .../jetty/client/PoolingHttpDestination.java | 189 ++++++++++++++++++ .../client/http/HttpDestinationOverHTTP.java | 160 +-------------- .../http/HttpDestinationOverHTTPTest.java | 2 +- 3 files changed, 194 insertions(+), 157 deletions(-) create mode 100644 jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java new file mode 100644 index 00000000000..4354b7b5de6 --- /dev/null +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java @@ -0,0 +1,189 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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; + +import java.io.IOException; +import java.util.Arrays; + +import org.eclipse.jetty.client.api.Connection; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.component.ContainerLifeCycle; + +public abstract class PoolingHttpDestination extends HttpDestination implements Promise +{ + private final ConnectionPool connectionPool; + + public PoolingHttpDestination(HttpClient client, Origin origin) + { + super(client, origin); + this.connectionPool = newConnectionPool(client); + } + + protected ConnectionPool newConnectionPool(HttpClient client) + { + return new ConnectionPool(this, client.getMaxConnectionsPerDestination(), this); + } + + public ConnectionPool getConnectionPool() + { + return connectionPool; + } + + @Override + @SuppressWarnings("unchecked") + public void succeeded(Connection connection) + { + process((C)connection, true); + } + + @Override + public void failed(final Throwable x) + { + getHttpClient().getExecutor().execute(new Runnable() + { + @Override + public void run() + { + abort(x); + } + }); + } + + protected void send() + { + C connection = acquire(); + if (connection != null) + process(connection, false); + } + + @SuppressWarnings("unchecked") + public C acquire() + { + return (C)connectionPool.acquire(); + } + + /** + *

Processes a new connection making it idle or active depending on whether requests are waiting to be sent.

+ *

A new connection is created when a request needs to be executed; it is possible that the request that + * triggered the request creation is executed by another connection that was just released, so the new connection + * may become idle.

+ *

If a request is waiting to be executed, it will be dequeued and executed by the new connection.

+ * + * @param connection the new connection + * @param dispatch whether to dispatch the processing to another thread + */ + public void process(final C connection, boolean dispatch) + { + HttpClient client = getHttpClient(); + final HttpExchange exchange = getHttpExchanges().poll(); + LOG.debug("Processing exchange {} on connection {}", exchange, connection); + if (exchange == null) + { + // TODO: review this part... may not be 100% correct + // TODO: e.g. is client is not running, there should be no need to close the connection + + if (!connectionPool.release(connection)) + connection.close(); + + if (!client.isRunning()) + { + LOG.debug("{} is stopping", client); + connection.close(); + } + } + else + { + final Request request = exchange.getRequest(); + Throwable cause = request.getAbortCause(); + if (cause != null) + { + abort(exchange, cause); + LOG.debug("Aborted before processing {}: {}", exchange, cause); + } + else + { + if (dispatch) + { + client.getExecutor().execute(new Runnable() + { + @Override + public void run() + { + send(connection, exchange); + } + }); + } + else + { + send(connection, exchange); + } + } + } + } + + protected abstract void send(C connection, HttpExchange exchange); + + public void release(C connection) + { + LOG.debug("{} released", connection); + HttpClient client = getHttpClient(); + if (client.isRunning()) + { + if (connectionPool.isActive(connection)) + process(connection, false); + else + LOG.debug("{} explicit", connection); + } + else + { + LOG.debug("{} is stopped", client); + close(connection); + connection.close(); + } + } + + @Override + public void close(Connection oldConnection) + { + super.close(oldConnection); + connectionPool.remove(oldConnection); + + // We need to execute queued requests even if this connection failed. + // We may create a connection that is not needed, but it will eventually + // idle timeout, so no worries + if (!getHttpExchanges().isEmpty()) + { + C newConnection = acquire(); + if (newConnection != null) + process(newConnection, false); + } + } + + public void close() + { + connectionPool.close(); + } + + @Override + public void dump(Appendable out, String indent) throws IOException + { + ContainerLifeCycle.dump(out, indent, Arrays.asList(connectionPool)); + } +} diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java index 632365c417e..6e639f5dcd3 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTP.java @@ -18,173 +18,21 @@ package org.eclipse.jetty.client.http; -import java.io.IOException; -import java.util.Arrays; - -import org.eclipse.jetty.client.ConnectionPool; import org.eclipse.jetty.client.HttpClient; -import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.Origin; -import org.eclipse.jetty.client.api.Connection; -import org.eclipse.jetty.client.api.Request; -import org.eclipse.jetty.util.Promise; -import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.client.PoolingHttpDestination; -public class HttpDestinationOverHTTP extends HttpDestination implements Promise +public class HttpDestinationOverHTTP extends PoolingHttpDestination { - private final ConnectionPool connectionPool; - public HttpDestinationOverHTTP(HttpClient client, Origin origin) { super(client, origin); - this.connectionPool = newConnectionPool(client); - } - - protected ConnectionPool newConnectionPool(HttpClient client) - { - return new ConnectionPool(this, client.getMaxConnectionsPerDestination(), this); - } - - public ConnectionPool getConnectionPool() - { - return connectionPool; } @Override - public void succeeded(Connection connection) + protected void send(HttpConnectionOverHTTP connection, HttpExchange exchange) { - process((HttpConnectionOverHTTP)connection, true); - } - - @Override - public void failed(final Throwable x) - { - getHttpClient().getExecutor().execute(new Runnable() - { - @Override - public void run() - { - abort(x); - } - }); - } - - protected void send() - { - HttpConnectionOverHTTP connection = acquire(); - if (connection != null) - process(connection, false); - } - - protected HttpConnectionOverHTTP acquire() - { - return (HttpConnectionOverHTTP)connectionPool.acquire(); - } - - /** - *

Processes a new connection making it idle or active depending on whether requests are waiting to be sent.

- *

A new connection is created when a request needs to be executed; it is possible that the request that - * triggered the request creation is executed by another connection that was just released, so the new connection - * may become idle.

- *

If a request is waiting to be executed, it will be dequeued and executed by the new connection.

- * - * @param connection the new connection - * @param dispatch whether to dispatch the processing to another thread - */ - protected void process(final HttpConnectionOverHTTP connection, boolean dispatch) - { - HttpClient client = getHttpClient(); - final HttpExchange exchange = getHttpExchanges().poll(); - LOG.debug("Processing exchange {} on connection {}", exchange, connection); - if (exchange == null) - { - // TODO: review this part... may not be 100% correct - // TODO: e.g. is client is not running, there should be no need to close the connection - - if (!connectionPool.release(connection)) - connection.close(); - - if (!client.isRunning()) - { - LOG.debug("{} is stopping", client); - connection.close(); - } - } - else - { - final Request request = exchange.getRequest(); - Throwable cause = request.getAbortCause(); - if (cause != null) - { - abort(exchange, cause); - LOG.debug("Aborted before processing {}: {}", exchange, cause); - } - else - { - if (dispatch) - { - client.getExecutor().execute(new Runnable() - { - @Override - public void run() - { - connection.send(exchange); - } - }); - } - else - { - connection.send(exchange); - } - } - } - } - - protected void release(HttpConnectionOverHTTP connection) - { - LOG.debug("{} released", connection); - HttpClient client = getHttpClient(); - if (client.isRunning()) - { - if (connectionPool.isActive(connection)) - process(connection, false); - else - LOG.debug("{} explicit", connection); - } - else - { - LOG.debug("{} is stopped", client); - close(connection); - connection.close(); - } - } - - @Override - public void close(Connection oldConnection) - { - super.close(oldConnection); - connectionPool.remove(oldConnection); - - // We need to execute queued requests even if this connection failed. - // We may create a connection that is not needed, but it will eventually - // idle timeout, so no worries - if (!getHttpExchanges().isEmpty()) - { - HttpConnectionOverHTTP newConnection = acquire(); - if (newConnection != null) - process(newConnection, false); - } - } - - public void close() - { - connectionPool.close(); - } - - @Override - public void dump(Appendable out, String indent) throws IOException - { - ContainerLifeCycle.dump(out, indent, Arrays.asList(connectionPool)); + connection.send(exchange); } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java index f7ed4c965b0..55109b2bfe3 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpDestinationOverHTTPTest.java @@ -91,7 +91,7 @@ public class HttpDestinationOverHTTPTest extends AbstractHttpClientServerTest HttpDestinationOverHTTP destination = new HttpDestinationOverHTTP(client, new Origin("http", "localhost", connector.getLocalPort())) { @Override - protected void process(HttpConnectionOverHTTP connection, boolean dispatch) + public void process(HttpConnectionOverHTTP connection, boolean dispatch) { try { From a28e4730ad48012b367900ee5a8b40e95462dde2 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 25 Oct 2013 14:08:55 +0200 Subject: [PATCH 03/27] 420362 - Response/request listeners called too many times. Wrapped on[Request|Response]XXX(XXXListener) listeners into their specific interface so that they don't get notified multiple times. --- .../org/eclipse/jetty/client/HttpRequest.java | 145 ++++++++++++++---- .../org/eclipse/jetty/client/HttpSender.java | 2 +- .../eclipse/jetty/client/HttpClientTest.java | 144 +++++++++++++++++ 3 files changed, 264 insertions(+), 27 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java index 3ea2e0e6c22..0986a6af13f 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java @@ -23,6 +23,7 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; +import java.nio.ByteBuffer; import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Path; import java.util.ArrayList; @@ -43,6 +44,7 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.client.util.PathContentProvider; +import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; @@ -295,93 +297,184 @@ public class HttpRequest implements Request } @Override - public Request onRequestQueued(QueuedListener listener) + public Request onRequestQueued(final QueuedListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new QueuedListener() + { + @Override + public void onQueued(Request request) + { + listener.onQueued(request); + } + }); return this; } @Override - public Request onRequestBegin(BeginListener listener) + public Request onRequestBegin(final BeginListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new BeginListener() + { + @Override + public void onBegin(Request request) + { + listener.onBegin(request); + } + }); return this; } @Override - public Request onRequestHeaders(HeadersListener listener) + public Request onRequestHeaders(final HeadersListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new HeadersListener() + { + @Override + public void onHeaders(Request request) + { + listener.onHeaders(request); + } + }); return this; } @Override - public Request onRequestCommit(CommitListener listener) + public Request onRequestCommit(final CommitListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new CommitListener() + { + @Override + public void onCommit(Request request) + { + listener.onCommit(request); + } + }); return this; } @Override - public Request onRequestContent(ContentListener listener) + public Request onRequestContent(final ContentListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new ContentListener() + { + @Override + public void onContent(Request request, ByteBuffer content) + { + listener.onContent(request, content); + } + }); return this; } @Override - public Request onRequestSuccess(SuccessListener listener) + public Request onRequestSuccess(final SuccessListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new SuccessListener() + { + @Override + public void onSuccess(Request request) + { + listener.onSuccess(request); + } + }); return this; } @Override - public Request onRequestFailure(FailureListener listener) + public Request onRequestFailure(final FailureListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new FailureListener() + { + @Override + public void onFailure(Request request, Throwable failure) + { + listener.onFailure(request, failure); + } + }); return this; } @Override - public Request onResponseBegin(Response.BeginListener listener) + public Request onResponseBegin(final Response.BeginListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.BeginListener() + { + @Override + public void onBegin(Response response) + { + listener.onBegin(response); + } + }); return this; } @Override - public Request onResponseHeader(Response.HeaderListener listener) + public Request onResponseHeader(final Response.HeaderListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.HeaderListener() + { + @Override + public boolean onHeader(Response response, HttpField field) + { + return listener.onHeader(response, field); + } + }); return this; } @Override - public Request onResponseHeaders(Response.HeadersListener listener) + public Request onResponseHeaders(final Response.HeadersListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.HeadersListener() + { + @Override + public void onHeaders(Response response) + { + listener.onHeaders(response); + } + }); return this; } @Override - public Request onResponseContent(Response.ContentListener listener) + public Request onResponseContent(final Response.ContentListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.ContentListener() + { + @Override + public void onContent(Response response, ByteBuffer content) + { + listener.onContent(response, content); + } + }); return this; } @Override - public Request onResponseSuccess(Response.SuccessListener listener) + public Request onResponseSuccess(final Response.SuccessListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.SuccessListener() + { + @Override + public void onSuccess(Response response) + { + listener.onSuccess(response); + } + }); return this; } @Override - public Request onResponseFailure(Response.FailureListener listener) + public Request onResponseFailure(final Response.FailureListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.FailureListener() + { + @Override + public void onFailure(Response response, Throwable failure) + { + listener.onFailure(response, failure); + } + }); return this; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java index eff0576761c..4d607b803e4 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpSender.java @@ -360,7 +360,7 @@ public class HttpSender implements AsyncContentProvider.Listener if (!commit(request)) return false; - if (content != null) + if (content != null && content.hasRemaining()) { RequestNotifier notifier = connection.getDestination().getRequestNotifier(); notifier.notifyContent(request, content); diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index 9d80ad3b722..9fc6a81630f 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -860,4 +860,148 @@ public class HttpClientTest extends AbstractHttpClientServerTest Assert.assertEquals(200, response.getStatus()); } + + @Test + public void testRequestListenerForMultipleEventsIsInvokedOncePerEvent() throws Exception + { + start(new EmptyServerHandler()); + + final AtomicInteger counter = new AtomicInteger(); + Request.Listener listener = new Request.Listener() + { + @Override + public void onQueued(Request request) + { + counter.incrementAndGet(); + } + + @Override + public void onBegin(Request request) + { + counter.incrementAndGet(); + } + + @Override + public void onHeaders(Request request) + { + counter.incrementAndGet(); + } + + @Override + public void onCommit(Request request) + { + counter.incrementAndGet(); + } + + @Override + public void onContent(Request request, ByteBuffer content) + { + // Should not be invoked + counter.incrementAndGet(); + } + + @Override + public void onFailure(Request request, Throwable failure) + { + // Should not be invoked + counter.incrementAndGet(); + } + + @Override + public void onSuccess(Request request) + { + counter.incrementAndGet(); + } + }; + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .onRequestQueued(listener) + .onRequestBegin(listener) + .onRequestHeaders(listener) + .onRequestCommit(listener) + .onRequestContent(listener) + .onRequestSuccess(listener) + .onRequestFailure(listener) + .listener(listener) + .send(); + + Assert.assertEquals(200, response.getStatus()); + int expectedEventsTriggeredByOnRequestXXXListeners = 5; + int expectedEventsTriggeredByListener = 5; + int expected = expectedEventsTriggeredByOnRequestXXXListeners + expectedEventsTriggeredByListener; + Assert.assertEquals(expected, counter.get()); + } + + @Test + public void testResponseListenerForMultipleEventsIsInvokedOncePerEvent() throws Exception + { + start(new EmptyServerHandler()); + + final AtomicInteger counter = new AtomicInteger(); + final CountDownLatch latch = new CountDownLatch(1); + Response.Listener listener = new Response.Listener() + { + @Override + public void onBegin(Response response) + { + counter.incrementAndGet(); + } + + @Override + public boolean onHeader(Response response, HttpField field) + { + // Number of header may vary, so don't count + return true; + } + + @Override + public void onHeaders(Response response) + { + counter.incrementAndGet(); + } + + @Override + public void onContent(Response response, ByteBuffer content) + { + // Should not be invoked + counter.incrementAndGet(); + } + + @Override + public void onSuccess(Response response) + { + counter.incrementAndGet(); + } + + @Override + public void onFailure(Response response, Throwable failure) + { + // Should not be invoked + counter.incrementAndGet(); + } + + @Override + public void onComplete(Result result) + { + Assert.assertEquals(200, result.getResponse().getStatus()); + counter.incrementAndGet(); + latch.countDown(); + } + }; + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .onResponseBegin(listener) + .onResponseHeader(listener) + .onResponseHeaders(listener) + .onResponseContent(listener) + .onResponseSuccess(listener) + .onResponseFailure(listener) + .send(listener); + + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + int expectedEventsTriggeredByOnResponseXXXListeners = 3; + int expectedEventsTriggeredByCompletionListener = 4; + int expected = expectedEventsTriggeredByOnResponseXXXListeners + expectedEventsTriggeredByCompletionListener; + Assert.assertEquals(expected, counter.get()); + } } From b9fcd6695ce04aac2c6777617b7e43db84b013b5 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 25 Oct 2013 14:08:55 +0200 Subject: [PATCH 04/27] Merged via cherry-pick branch 'master' into 'jetty-9.1'. --- .../org/eclipse/jetty/client/HttpRequest.java | 144 ++++++++++++++---- .../eclipse/jetty/client/HttpClientTest.java | 144 ++++++++++++++++++ 2 files changed, 262 insertions(+), 26 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java index 0efef0fd24b..c7240a9367c 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpRequest.java @@ -23,6 +23,7 @@ import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; import java.net.URLEncoder; +import java.nio.ByteBuffer; import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Path; import java.util.ArrayList; @@ -296,93 +297,184 @@ public class HttpRequest implements Request } @Override - public Request onRequestQueued(QueuedListener listener) + public Request onRequestQueued(final QueuedListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new QueuedListener() + { + @Override + public void onQueued(Request request) + { + listener.onQueued(request); + } + }); return this; } @Override - public Request onRequestBegin(BeginListener listener) + public Request onRequestBegin(final BeginListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new BeginListener() + { + @Override + public void onBegin(Request request) + { + listener.onBegin(request); + } + }); return this; } @Override - public Request onRequestHeaders(HeadersListener listener) + public Request onRequestHeaders(final HeadersListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new HeadersListener() + { + @Override + public void onHeaders(Request request) + { + listener.onHeaders(request); + } + }); return this; } @Override - public Request onRequestCommit(CommitListener listener) + public Request onRequestCommit(final CommitListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new CommitListener() + { + @Override + public void onCommit(Request request) + { + listener.onCommit(request); + } + }); return this; } @Override - public Request onRequestContent(ContentListener listener) + public Request onRequestContent(final ContentListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new ContentListener() + { + @Override + public void onContent(Request request, ByteBuffer content) + { + listener.onContent(request, content); + } + }); return this; } @Override - public Request onRequestSuccess(SuccessListener listener) + public Request onRequestSuccess(final SuccessListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new SuccessListener() + { + @Override + public void onSuccess(Request request) + { + listener.onSuccess(request); + } + }); return this; } @Override - public Request onRequestFailure(FailureListener listener) + public Request onRequestFailure(final FailureListener listener) { - this.requestListeners.add(listener); + this.requestListeners.add(new FailureListener() + { + @Override + public void onFailure(Request request, Throwable failure) + { + listener.onFailure(request, failure); + } + }); return this; } @Override - public Request onResponseBegin(Response.BeginListener listener) + public Request onResponseBegin(final Response.BeginListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.BeginListener() + { + @Override + public void onBegin(Response response) + { + listener.onBegin(response); + } + }); return this; } @Override - public Request onResponseHeader(Response.HeaderListener listener) + public Request onResponseHeader(final Response.HeaderListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.HeaderListener() + { + @Override + public boolean onHeader(Response response, HttpField field) + { + return listener.onHeader(response, field); + } + }); return this; } @Override - public Request onResponseHeaders(Response.HeadersListener listener) + public Request onResponseHeaders(final Response.HeadersListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.HeadersListener() + { + @Override + public void onHeaders(Response response) + { + listener.onHeaders(response); + } + }); return this; } @Override - public Request onResponseContent(Response.ContentListener listener) + public Request onResponseContent(final Response.ContentListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.ContentListener() + { + @Override + public void onContent(Response response, ByteBuffer content) + { + listener.onContent(response, content); + } + }); return this; } @Override - public Request onResponseSuccess(Response.SuccessListener listener) + public Request onResponseSuccess(final Response.SuccessListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.SuccessListener() + { + @Override + public void onSuccess(Response response) + { + listener.onSuccess(response); + } + }); return this; } @Override - public Request onResponseFailure(Response.FailureListener listener) + public Request onResponseFailure(final Response.FailureListener listener) { - this.responseListeners.add(listener); + this.responseListeners.add(new Response.FailureListener() + { + @Override + public void onFailure(Response response, Throwable failure) + { + listener.onFailure(response, failure); + } + }); return this; } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index 9d241dcbcd9..ef6240a206c 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -863,4 +863,148 @@ public class HttpClientTest extends AbstractHttpClientServerTest Assert.assertEquals(200, response.getStatus()); } + + @Test + public void testRequestListenerForMultipleEventsIsInvokedOncePerEvent() throws Exception + { + start(new EmptyServerHandler()); + + final AtomicInteger counter = new AtomicInteger(); + Request.Listener listener = new Request.Listener() + { + @Override + public void onQueued(Request request) + { + counter.incrementAndGet(); + } + + @Override + public void onBegin(Request request) + { + counter.incrementAndGet(); + } + + @Override + public void onHeaders(Request request) + { + counter.incrementAndGet(); + } + + @Override + public void onCommit(Request request) + { + counter.incrementAndGet(); + } + + @Override + public void onContent(Request request, ByteBuffer content) + { + // Should not be invoked + counter.incrementAndGet(); + } + + @Override + public void onFailure(Request request, Throwable failure) + { + // Should not be invoked + counter.incrementAndGet(); + } + + @Override + public void onSuccess(Request request) + { + counter.incrementAndGet(); + } + }; + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .onRequestQueued(listener) + .onRequestBegin(listener) + .onRequestHeaders(listener) + .onRequestCommit(listener) + .onRequestContent(listener) + .onRequestSuccess(listener) + .onRequestFailure(listener) + .listener(listener) + .send(); + + Assert.assertEquals(200, response.getStatus()); + int expectedEventsTriggeredByOnRequestXXXListeners = 5; + int expectedEventsTriggeredByListener = 5; + int expected = expectedEventsTriggeredByOnRequestXXXListeners + expectedEventsTriggeredByListener; + Assert.assertEquals(expected, counter.get()); + } + + @Test + public void testResponseListenerForMultipleEventsIsInvokedOncePerEvent() throws Exception + { + start(new EmptyServerHandler()); + + final AtomicInteger counter = new AtomicInteger(); + final CountDownLatch latch = new CountDownLatch(1); + Response.Listener listener = new Response.Listener() + { + @Override + public void onBegin(Response response) + { + counter.incrementAndGet(); + } + + @Override + public boolean onHeader(Response response, HttpField field) + { + // Number of header may vary, so don't count + return true; + } + + @Override + public void onHeaders(Response response) + { + counter.incrementAndGet(); + } + + @Override + public void onContent(Response response, ByteBuffer content) + { + // Should not be invoked + counter.incrementAndGet(); + } + + @Override + public void onSuccess(Response response) + { + counter.incrementAndGet(); + } + + @Override + public void onFailure(Response response, Throwable failure) + { + // Should not be invoked + counter.incrementAndGet(); + } + + @Override + public void onComplete(Result result) + { + Assert.assertEquals(200, result.getResponse().getStatus()); + counter.incrementAndGet(); + latch.countDown(); + } + }; + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .onResponseBegin(listener) + .onResponseHeader(listener) + .onResponseHeaders(listener) + .onResponseContent(listener) + .onResponseSuccess(listener) + .onResponseFailure(listener) + .send(listener); + + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + int expectedEventsTriggeredByOnResponseXXXListeners = 3; + int expectedEventsTriggeredByCompletionListener = 4; + int expected = expectedEventsTriggeredByOnResponseXXXListeners + expectedEventsTriggeredByCompletionListener; + Assert.assertEquals(expected, counter.get()); + } } From abac85e1291054f93a73f27644480dbec30bd50f Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 25 Oct 2013 14:55:48 +0200 Subject: [PATCH 05/27] 420364 - Bad synchronization in HttpConversation. --- .../main/java/org/eclipse/jetty/client/HttpConversation.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java index 6ab9a3a50d1..596e239d8f0 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpConversation.java @@ -131,8 +131,7 @@ public class HttpConversation extends AttributesMap // will notify a listener that may send a new request and trigger // another call to this method which will build different listeners // which may be iterated over when the iteration continues. - listeners = new ArrayList<>(); - + List listeners = new ArrayList<>(); HttpExchange firstExchange = exchanges.peekFirst(); HttpExchange lastExchange = exchanges.peekLast(); if (firstExchange == lastExchange) @@ -151,6 +150,7 @@ public class HttpConversation extends AttributesMap else listeners.addAll(firstExchange.getResponseListeners()); } + this.listeners = listeners; } public void complete() From c101e55c7f933bd86d65640f49e78d50e46751c5 Mon Sep 17 00:00:00 2001 From: Mikhail Mazursky Date: Sat, 26 Oct 2013 12:43:06 +0600 Subject: [PATCH 06/27] [Bug 420374] Call super.close() in a finally block Signed-off-by: Mikhail Mazursky --- .../jetty/util/MultiPartOutputStream.java | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java index 024bb6378ab..85cc700254b 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java @@ -35,12 +35,12 @@ public class MultiPartOutputStream extends FilterOutputStream private static final byte[] __CRLF={'\r','\n'}; private static final byte[] __DASHDASH={'-','-'}; - public static String MULTIPART_MIXED="multipart/mixed"; - public static String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace"; + public static final String MULTIPART_MIXED="multipart/mixed"; + public static final String MULTIPART_X_MIXED_REPLACE="multipart/x-mixed-replace"; /* ------------------------------------------------------------ */ - private String boundary; - private byte[] boundaryBytes; + private final String boundary; + private final byte[] boundaryBytes; /* ------------------------------------------------------------ */ private boolean inPart=false; @@ -54,8 +54,15 @@ public class MultiPartOutputStream extends FilterOutputStream boundary = "jetty"+System.identityHashCode(this)+ Long.toString(System.currentTimeMillis(),36); boundaryBytes=boundary.getBytes(StringUtil.__ISO_8859_1); + } - inPart=false; + public MultiPartOutputStream(OutputStream out, String boundary) + throws IOException + { + super(out); + + this.boundary = boundary; + boundaryBytes=boundary.getBytes(StringUtil.__ISO_8859_1); } /* ------------------------------------------------------------ */ @@ -66,14 +73,20 @@ public class MultiPartOutputStream extends FilterOutputStream public void close() throws IOException { - if (inPart) + try + { + if (inPart) + out.write(__CRLF); + out.write(__DASHDASH); + out.write(boundaryBytes); + out.write(__DASHDASH); out.write(__CRLF); - out.write(__DASHDASH); - out.write(boundaryBytes); - out.write(__DASHDASH); - out.write(__CRLF); - inPart=false; - super.close(); + inPart=false; + } + finally + { + super.close(); + } } /* ------------------------------------------------------------ */ From de75b82f991395525d529ef32384855b4647d601 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 28 Oct 2013 09:58:33 +1100 Subject: [PATCH 07/27] 419350 Do not borrow space from passed arrays --- .../eclipse/jetty/server/HttpConnection.java | 3 +- .../org/eclipse/jetty/server/HttpOutput.java | 3 +- .../eclipse/jetty/server/HttpOutputTest.java | 35 +++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index d56fdd3ddc3..a85f7b937f8 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -647,7 +647,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http { case NEED_HEADER: { - if (_lastContent && _content!=null && BufferUtil.space(_content)>_config.getResponseHeaderSize() && _content.hasArray() ) + // Look for optimisation to avoid allocating a _header buffer + if (_lastContent && _content!=null && !_content.isReadOnly() && _content.hasArray() && BufferUtil.space(_content)>_config.getResponseHeaderSize() ) { // use spare space in content buffer for header buffer int p=_content.position(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index bbea718ffa9..968a5a00406 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -207,7 +207,8 @@ public class HttpOutput extends ServletOutputStream // write any remaining content in the buffer directly if (len>0) - _channel.write(ByteBuffer.wrap(b, off, len), complete); + // pass as readonly to avoid space stealing optimisation in HttpConnection + _channel.write(ByteBuffer.wrap(b, off, len).asReadOnlyBuffer(), complete); else if (complete) _channel.write(BufferUtil.EMPTY_BUFFER,complete); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java index c64387b6c65..65ac35d8195 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.server; import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import java.io.FilterInputStream; @@ -26,6 +27,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; +import java.util.Arrays; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -35,6 +37,7 @@ import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.resource.Resource; import org.hamcrest.Matchers; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -78,6 +81,26 @@ public class HttpOutputTest assertThat(response,containsString("HTTP/1.1 200 OK")); } + @Test + public void testSendArray() throws Exception + { + byte[] buffer=new byte[16*1024]; + Arrays.fill(buffer,0,4*1024,(byte)0x99); + Arrays.fill(buffer,4*1024,12*1024,(byte)0x58); + Arrays.fill(buffer,12*1024,16*1024,(byte)0x66); + _handler._content=ByteBuffer.wrap(buffer); + _handler._content.limit(12*1024); + _handler._content.position(4*1024); + String response=_connector.getResponses("GET / HTTP/1.0\nHost: localhost:80\n\n"); + assertThat(response,containsString("HTTP/1.1 200 OK")); + assertThat(response,containsString("\r\nXXXXXXXXXXXXXXXXXXXXXXXXXXX")); + + for (int i=0;i<4*1024;i++) + assertEquals("i="+i,(byte)0x99,buffer[i]); + for (int i=12*1024;i<16*1024;i++) + assertEquals("i="+i,(byte)0x66,buffer[i]); + } + @Test public void testSendInputStreamSimple() throws Exception { @@ -195,6 +218,7 @@ public class HttpOutputTest { InputStream _contentInputStream; ReadableByteChannel _contentChannel; + ByteBuffer _content; @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException @@ -204,6 +228,17 @@ public class HttpOutputTest HttpOutput out = (HttpOutput) response.getOutputStream(); + if (_content!=null) + { + response.setContentLength(_content.remaining()); + if (_content.hasArray()) + out.write(_content.array(),_content.arrayOffset()+_content.position(),_content.remaining()); + else + out.sendContent(_content); + _content=null; + return; + } + if (_contentInputStream!=null) { out.sendContent(_contentInputStream); From 960c03b8acfd52083ba16632e59523e605aa12cf Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 28 Oct 2013 10:14:12 +1100 Subject: [PATCH 08/27] 419350 Do not borrow space from passed arrays --- .../src/main/java/org/eclipse/jetty/server/HttpOutput.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index 968a5a00406..5f17d0205f3 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -300,12 +300,14 @@ public class HttpOutput extends ServletOutputStream /* ------------------------------------------------------------ */ /** Blocking send of content. - * @param content The content to send + * @param content The content to send. * @throws IOException */ public void sendContent(ByteBuffer content) throws IOException { final BlockingCallback callback =_channel.getWriteBlockingCallback(); + if (content.hasArray()&&content.limit() Date: Mon, 28 Oct 2013 10:17:55 +1100 Subject: [PATCH 09/27] fixed merge --- .../eclipse/jetty/server/HttpOutputTest.java | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java index 3c9dc638340..8fcc49bb384 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java @@ -40,7 +40,6 @@ import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.resource.Resource; import org.hamcrest.Matchers; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -409,7 +408,6 @@ public class HttpOutputTest boolean _async; ByteBuffer _buffer; byte[] _bytes; - ByteBuffer _content; InputStream _contentInputStream; ReadableByteChannel _contentChannel; ByteBuffer _content; @@ -422,16 +420,6 @@ public class HttpOutputTest final HttpOutput out = (HttpOutput) response.getOutputStream(); - if (_content!=null) - { - response.setContentLength(_content.remaining()); - if (_content.hasArray()) - out.write(_content.array(),_content.arrayOffset()+_content.position(),_content.remaining()); - else - out.sendContent(_content); - _content=null; - return; - } if (_contentInputStream!=null) { @@ -545,10 +533,13 @@ public class HttpOutputTest return; } - if (_content!=null) { - out.sendContent(_content); + response.setContentLength(_content.remaining()); + if (_content.hasArray()) + out.write(_content.array(),_content.arrayOffset()+_content.position(),_content.remaining()); + else + out.sendContent(_content); _content=null; return; } From fc6493b5b1153a282f533e2deee316e4db0b40c8 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 28 Oct 2013 13:09:26 +1100 Subject: [PATCH 10/27] Remove accidental checkin of println --- .../java/org/eclipse/jetty/annotations/AnnotationParser.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java index 002877546f7..7362eaf99e3 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/AnnotationParser.java @@ -692,7 +692,6 @@ public class AnnotationParser parseDir(handlers, res, resolver); else { -System.err.println("TRYING TO SCAN "+res); //we've already verified the directories, so just verify the class file name File file = res.getFile(); if (isValidClassFileName((file==null?null:file.getName()))) From 9d756dc018de2af33dfd3fd4e307427e7b6f441e Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 28 Oct 2013 15:25:21 +1100 Subject: [PATCH 11/27] 294531 Unpacking webapp twice to the same directory name causes problems with updated jars in WEB-INF/lib --- .../jetty/ant/AntWebInfConfiguration.java | 7 +- .../maven/plugin/JettyRunForkedMojo.java | 23 +- .../plugin/MavenWebInfConfiguration.java | 3 - .../eclipse/jetty/maven/plugin/Starter.java | 5 +- .../eclipse/jetty/webapp/WebAppContext.java | 26 +- .../jetty/webapp/WebInfConfiguration.java | 238 ++++++------------ 6 files changed, 129 insertions(+), 173 deletions(-) diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java index bb3e3223778..a8535e99c72 100644 --- a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java +++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java @@ -40,12 +40,7 @@ public class AntWebInfConfiguration extends WebInfConfiguration @Override public void preConfigure(final WebAppContext context) throws Exception - { - // Look for a work directory - File work = findWorkDirectory(context); - if (work != null) - makeTempDirectory(work, context, false); - + { //Make a temp directory for the webapp if one is not already set resolveTempDirectory(context); diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java index d1dcf49320a..14265714171 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/JettyRunForkedMojo.java @@ -132,11 +132,20 @@ public class JettyRunForkedMojo extends AbstractMojo * The temporary directory to use for the webapp. * Defaults to target/tmp * - * @parameter expression="${project.build.directory}/tmp" + * @parameter alias="tmpDirectory" expression="${project.build.directory}/tmp" * @required * @readonly */ - protected File tmpDirectory; + protected File tempDirectory; + + + + /** + * Whether temporary directory contents should survive webapp restarts. + * + * @parameter default-value="false" + */ + private boolean persistTempDirectory; /** @@ -418,12 +427,14 @@ public class JettyRunForkedMojo extends AbstractMojo props.put("context.path", contextPath); //sort out the tmp directory (make it if it doesn't exist) - if (tmpDirectory != null) + if (tempDirectory != null) { - if (!tmpDirectory.exists()) - tmpDirectory.mkdirs(); - props.put("tmp.dir", tmpDirectory.getAbsolutePath()); + if (!tempDirectory.exists()) + tempDirectory.mkdirs(); + props.put("tmp.dir", tempDirectory.getAbsolutePath()); } + + props.put("tmp.dir.persist", Boolean.toString(persistTempDirectory)); //sort out base dir of webapp if (webAppSourceDirectory == null || !webAppSourceDirectory.exists()) diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java index 680f8ba93a2..bfa467b1c0d 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/MavenWebInfConfiguration.java @@ -284,9 +284,6 @@ public class MavenWebInfConfiguration extends WebInfConfiguration { LOG.debug("Unpacking overlay: " + overlay); - //resolve if not already resolved - resolveTempDirectory(context); - if (overlay.getResource() == null) return null; //nothing to unpack diff --git a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java index 87f7fe61ba1..15414f6e441 100644 --- a/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java +++ b/jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/Starter.java @@ -207,6 +207,9 @@ public class Starter if (str != null) webApp.setTempDirectory(new File(str.trim())); + str = (String)props.getProperty("tmp.dir.persist"); + if (str != null) + webApp.setPersistTempDirectory(Boolean.valueOf(str)); // - the base directory str = (String)props.getProperty("base.dir"); @@ -219,7 +222,7 @@ public class Starter // - put virtual webapp base resource first on resource path or not str = (String)props.getProperty("base.first"); if (str != null && !"".equals(str.trim())) - webApp.setBaseAppFirst(Boolean.getBoolean(str)); + webApp.setBaseAppFirst(Boolean.valueOf(str)); //For overlays diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java index 2fe990edb68..81534cb1a3c 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppContext.java @@ -154,6 +154,8 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL private String[] _contextWhiteList = null; private File _tmpDir; + private boolean _persistTmpDir = false; + private String _war; private String _extraClasspath; private Throwable _unavailableException; @@ -1222,7 +1224,7 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL LOG.warn(e); } _tmpDir=dir; - setAttribute(TEMPDIR,_tmpDir); + setAttribute(TEMPDIR,_tmpDir); } /* ------------------------------------------------------------ */ @@ -1232,6 +1234,27 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL return _tmpDir; } + /** + * If true the temp directory for this + * webapp will be kept when the webapp stops. Otherwise, + * it will be deleted. + * + * @param delete + */ + public void setPersistTempDirectory(boolean persist) + { + _persistTmpDir = persist; + } + + /** + * @return + */ + public boolean isPersistTempDirectory() + { + return _persistTmpDir; + } + + /* ------------------------------------------------------------ */ /** * @param war The war to set as a file name or URL @@ -1480,5 +1503,4 @@ public class WebAppContext extends ServletContextHandler implements WebAppClassL { return _metadata; } - } diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java index 88db011c2ea..cc3f875da79 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java @@ -27,6 +27,7 @@ import java.net.URLClassLoader; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Random; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.TimeUnit; @@ -57,17 +58,15 @@ public class WebInfConfiguration extends AbstractConfiguration * resource base as a resource collection. */ public static final String RESOURCE_DIRS = "org.eclipse.jetty.resources"; + protected Resource _preUnpackBaseResource; + + @Override public void preConfigure(final WebAppContext context) throws Exception { - // Look for a work directory - File work = findWorkDirectory(context); - if (work != null) - makeTempDirectory(work, context, false); - //Make a temp directory for the webapp if one is not already set resolveTempDirectory(context); @@ -192,20 +191,16 @@ public class WebInfConfiguration extends AbstractConfiguration @Override public void deconfigure(WebAppContext context) throws Exception { - // delete temp directory if we had to create it or if it isn't called work - Boolean tmpdirConfigured = (Boolean)context.getAttribute(TEMPDIR_CONFIGURED); - - if (context.getTempDirectory()!=null && (tmpdirConfigured == null || !tmpdirConfigured.booleanValue()) && !isTempWorkDirectory(context.getTempDirectory())) + //if we're not persisting the temp dir contents delete it + if (!context.isPersistTempDirectory()) { IO.delete(context.getTempDirectory()); - context.setTempDirectory(null); - - //clear out the context attributes for the tmp dir only if we had to - //create the tmp dir - context.setAttribute(TEMPDIR_CONFIGURED, null); - context.setAttribute(WebAppContext.TEMPDIR, null); } - + + //if it wasn't explicitly configured by the user, then unset it + Boolean tmpdirConfigured = (Boolean)context.getAttribute(TEMPDIR_CONFIGURED); + if (tmpdirConfigured != null && !tmpdirConfigured) + context.setTempDirectory(null); //reset the base resource back to what it was before we did any unpacking of resources context.setBaseResource(_preUnpackBaseResource); @@ -238,51 +233,44 @@ public class WebInfConfiguration extends AbstractConfiguration *

A. Try to use an explicit directory specifically for this webapp:

*
    *
  1. - * Iff an explicit directory is set for this webapp, use it. Do NOT set - * delete on exit. + * Iff an explicit directory is set for this webapp, use it. Set delete on + * exit depends on value of persistTempDirectory. *
  2. *
  3. * Iff javax.servlet.context.tempdir context attribute is set for - * this webapp && exists && writeable, then use it. Do NOT set delete on exit. + * this webapp && exists && writeable, then use it. Set delete on exit depends on + * value of persistTempDirectory. *
  4. *
* *

B. Create a directory based on global settings. The new directory - * will be called "Jetty_"+host+"_"+port+"__"+context+"_"+virtualhost - * Work out where to create this directory: - *

    - *
  1. - * Iff $(jetty.home)/work exists create the directory there. Do NOT - * set delete on exit. Do NOT delete contents if dir already exists. - *
  2. - *
  3. - * Iff WEB-INF/work exists create the directory there. Do NOT set - * delete on exit. Do NOT delete contents if dir already exists. - *
  4. - *
  5. - * Else create dir in $(java.io.tmpdir). Set delete on exit. Delete - * contents if dir already exists. - *
  6. - *
+ * will be called "Jetty-"+host+"-"+port+"__"+context+"-"+virtualhost+"-"+randomdigits+".dir" + *

+ *

+ * If the user has specified the context attribute org.eclipse.jetty.webapp.basetempdir, the + * directory specified by this attribute will be the parent of the temp dir created. Otherwise, + * the parent dir is $(java.io.tmpdir). Set delete on exit depends on value of persistTempDirectory. + *

*/ public void resolveTempDirectory (WebAppContext context) + throws Exception { - //If a tmp directory is already set, we're done + //If a tmp directory is already set we should use it File tmpDir = context.getTempDirectory(); - if (tmpDir != null && tmpDir.isDirectory() && tmpDir.canWrite()) + if (tmpDir != null) { - context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE); - return; // Already have a suitable tmp dir configured + configureTempDirectory(tmpDir, context); + context.setAttribute(TEMPDIR_CONFIGURED, Boolean.TRUE); //the tmp dir was set explicitly + return; } - - // No temp directory configured, try to establish one. - // First we check the context specific, javax.servlet specified, temp directory attribute + // No temp directory configured, try to establish one via the javax.servlet.context.tempdir. File servletTmpDir = asFile(context.getAttribute(WebAppContext.TEMPDIR)); - if (servletTmpDir != null && servletTmpDir.isDirectory() && servletTmpDir.canWrite()) + if (servletTmpDir != null) { // Use as tmpDir tmpDir = servletTmpDir; + configureTempDirectory(tmpDir, context); // Ensure Attribute has File object context.setAttribute(WebAppContext.TEMPDIR,tmpDir); // Set as TempDir in context. @@ -290,60 +278,25 @@ public class WebInfConfiguration extends AbstractConfiguration return; } - try + //We need to make a temp dir. Check if the user has set a directory to use instead + //of java.io.tmpdir as the parent of the dir + File baseTemp = asFile(context.getAttribute(WebAppContext.BASETEMPDIR)); + if (baseTemp != null && baseTemp.isDirectory() && baseTemp.canWrite()) { - // Put the tmp dir in the work directory if we had one - File work = new File(System.getProperty("jetty.base"),"work"); - if (work.exists() && work.canWrite() && work.isDirectory()) - { - makeTempDirectory(work, context, false); //make a tmp dir inside work, don't delete if it exists - } - else - { - File baseTemp = asFile(context.getAttribute(WebAppContext.BASETEMPDIR)); - if (baseTemp != null && baseTemp.isDirectory() && baseTemp.canWrite()) - { - // Use baseTemp directory (allow the funky Jetty_0_0_0_0.. subdirectory logic to kick in - makeTempDirectory(baseTemp,context,false); - } - else - { - makeTempDirectory(new File(System.getProperty("java.io.tmpdir")),context,true); //make a tmpdir, delete if it already exists - } - } + //Make a temp directory as a child of the given base dir + makeTempDirectory(baseTemp,context); } - catch(Exception e) + else { - tmpDir=null; - LOG.ignore(e); - } - - //Third ... Something went wrong trying to make the tmp directory, just make - //a jvm managed tmp directory - if (context.getTempDirectory() == null) - { - try - { - // Last resort - tmpDir=File.createTempFile("JettyContext",""); - if (tmpDir.exists()) - IO.delete(tmpDir); - tmpDir.mkdir(); - tmpDir.deleteOnExit(); - context.setTempDirectory(tmpDir); - } - catch(IOException e) - { - tmpDir = null; - throw new IllegalStateException("Cannot create tmp dir in "+System.getProperty("java.io.tmpdir")+ " for context "+context,e); - } + //Make a temp directory in java.io.tmpdir + makeTempDirectory(new File(System.getProperty("java.io.tmpdir")),context); } } /** * Given an Object, return File reference for object. * Typically used to convert anonymous Object from getAttribute() calls to a File object. - * @param fileattr the file attribute to analyze and return from (supports type File and type String, all others return null) + * @param fileattr the file attribute to analyze and return from (supports type File and type String, all others return null * @return the File object, null if null, or null if not a File or String */ private File asFile(Object fileattr) @@ -365,45 +318,47 @@ public class WebInfConfiguration extends AbstractConfiguration - public void makeTempDirectory (File parent, WebAppContext context, boolean deleteExisting) - throws IOException + public void makeTempDirectory (File parent, WebAppContext context) + throws Exception { - if (parent != null && parent.exists() && parent.canWrite() && parent.isDirectory()) + if (parent == null || !parent.exists() || !parent.canWrite() || !parent.isDirectory()) + throw new IllegalStateException("Parent for temp dir not configured correctly: "+(parent==null?"null":"writeable="+parent.canWrite())); + + String temp = getCanonicalNameForWebAppTmpDir(context); + File tmpDir = File.createTempFile(temp, ".dir", parent); + //delete the file that was created + tmpDir.delete(); + //and make a directory of the same name + tmpDir.mkdirs(); + configureTempDirectory(tmpDir, context); + + if(LOG.isDebugEnabled()) + LOG.debug("Set temp dir "+tmpDir); + context.setTempDirectory(tmpDir); + } + + private void configureTempDirectory (File dir, WebAppContext context) + { + if (dir == null) + throw new IllegalArgumentException("Null temp dir"); + + //if dir exists and we don't want it persisted, delete it + if (dir.exists() && !context.isPersistTempDirectory()) { - String temp = getCanonicalNameForWebAppTmpDir(context); - File tmpDir = new File(parent,temp); - - if (deleteExisting && tmpDir.exists()) - { - if (!IO.delete(tmpDir)) - { - if(LOG.isDebugEnabled())LOG.debug("Failed to delete temp dir "+tmpDir); - } - - //If we can't delete the existing tmp dir, create a new one - if (tmpDir.exists()) - { - String old=tmpDir.toString(); - tmpDir=File.createTempFile(temp+"_",""); - if (tmpDir.exists()) - IO.delete(tmpDir); - LOG.warn("Can't reuse "+old+", using "+tmpDir); - } - } - - if (!tmpDir.exists()) - tmpDir.mkdir(); - - //If the parent is not a work directory - if (!isTempWorkDirectory(tmpDir)) - { - tmpDir.deleteOnExit(); - } - - if(LOG.isDebugEnabled()) - LOG.debug("Set temp dir "+tmpDir); - context.setTempDirectory(tmpDir); + if (!IO.delete(dir)) + throw new IllegalStateException("Failed to delete temp dir "+dir); } + + //if it doesn't exist make it + if (!dir.exists()) + dir.mkdirs(); + + if (!context.isPersistTempDirectory()) + dir.deleteOnExit(); + + //is it useable + if (!dir.canWrite() || !dir.isDirectory()) + throw new IllegalStateException("Temp dir "+dir+" not useable: writeable="+dir.canWrite()+", dir="+dir.isDirectory()); } @@ -566,45 +521,17 @@ public class WebInfConfiguration extends AbstractConfiguration } - public File findWorkDirectory (WebAppContext context) throws IOException - { - if (context.getBaseResource() != null) - { - Resource web_inf = context.getWebInf(); - if (web_inf !=null && web_inf.exists()) - { - return new File(web_inf.getFile(),"work"); - } - } - return null; - } - - - /** - * Check if the tmpDir itself is called "work", or if the tmpDir - * is in a directory called "work". - * @return true if File is a temporary or work directory - */ - public boolean isTempWorkDirectory (File tmpDir) - { - if (tmpDir == null) - return false; - if (tmpDir.getName().equalsIgnoreCase("work")) - return true; - File t = tmpDir.getParentFile(); - if (t == null) - return false; - return (t.getName().equalsIgnoreCase("work")); - } /** * Create a canonical name for a webapp temp directory. * The form of the name is: - * "Jetty_"+host+"_"+port+"__"+resourceBase+"_"+context+"_"+virtualhost+base36_hashcode_of_whole_string + * "jetty-"+host+"-"+port+"-"+resourceBase+"-_"+context+"-"+virtualhost+"-"+randomdigits+".dir" * * host and port uniquely identify the server * context and virtual host uniquely identify the webapp + * randomdigits ensure every tmp directory is unique + * * @return the canonical name for the webapp temp directory */ public static String getCanonicalNameForWebAppTmpDir (WebAppContext context) @@ -697,6 +624,7 @@ public class WebInfConfiguration extends AbstractConfiguration } canonicalName.append("-"); + return canonicalName.toString(); } From f671fbaa85fcc64190fd2cf4c92a79171b97cf6f Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 28 Oct 2013 18:09:54 +1100 Subject: [PATCH 12/27] 419330 Allow access to setters on jetty-jspc-maven-plugin --- .../src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java b/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java index 1ff828a613e..817afef2d0b 100644 --- a/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java +++ b/jetty-jspc-maven-plugin/src/main/java/org/eclipse/jetty/jspc/plugin/JspcMojo.java @@ -288,6 +288,9 @@ public class JspcMojo extends AbstractMojo Thread.currentThread().setContextClassLoader(webAppClassLoader); + if (jspc == null) + jspc = new JspC(); + jspc.setWebXmlFragment(webXmlFragment); jspc.setUriroot(webAppSourceDirectory); jspc.setOutputDir(generatedClasses); From 3a10aa41648e45bb544bea2cbbeebce7229de0e3 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 28 Oct 2013 11:13:17 +0100 Subject: [PATCH 13/27] 419964 - InputStreamContentProvider does not close provided InputStream. Now closing the provided InputStream when reading -1 or when an exception is thrown. --- .../util/InputStreamContentProvider.java | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java index eacbb09168b..2382ef62787 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/InputStreamContentProvider.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.client.util; +import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.Iterator; @@ -25,6 +26,8 @@ import java.util.NoSuchElementException; import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; /** * A {@link ContentProvider} for an {@link InputStream}. @@ -33,19 +36,26 @@ import org.eclipse.jetty.util.BufferUtil; * Invocations to the {@link #iterator()} method after the first will return an "empty" iterator * because the stream has been consumed on the first invocation. *

- * It is possible to specify, at the constructor, a buffer size used to read content from the - * stream, by default 4096 bytes. - *

* However, it is possible for subclasses to override {@link #onRead(byte[], int, int)} to copy * the content read from the stream to another location (for example a file), and be able to * support multiple invocations of {@link #iterator()}, returning the iterator provided by this * class on the first invocation, and an iterator on the bytes copied to the other location * for subsequent invocations. + *

+ * It is possible to specify, at the constructor, a buffer size used to read content from the + * stream, by default 4096 bytes. + *

+ * The {@link InputStream} passed to the constructor is by default closed when is it fully + * consumed (or when an exception is thrown while reading it), unless otherwise specified + * to the {@link #InputStreamContentProvider(java.io.InputStream, int, boolean) constructor}. */ public class InputStreamContentProvider implements ContentProvider { + private static final Logger LOG = Log.getLogger(InputStreamContentProvider.class); + private final InputStream stream; private final int bufferSize; + private final boolean autoClose; public InputStreamContentProvider(InputStream stream) { @@ -53,9 +63,15 @@ public class InputStreamContentProvider implements ContentProvider } public InputStreamContentProvider(InputStream stream, int bufferSize) + { + this(stream, bufferSize, true); + } + + public InputStreamContentProvider(InputStream stream, int bufferSize, boolean autoClose) { this.stream = stream; this.bufferSize = bufferSize; + this.autoClose = autoClose; } @Override @@ -107,6 +123,7 @@ public class InputStreamContentProvider implements ContentProvider } else if (read < 0) { + close(); return false; } else @@ -122,6 +139,7 @@ public class InputStreamContentProvider implements ContentProvider failure = x; // Signal we have more content to cause a call to // next() which will throw NoSuchElementException. + close(); return true; } return false; @@ -145,6 +163,21 @@ public class InputStreamContentProvider implements ContentProvider { throw new UnsupportedOperationException(); } + + private void close() + { + if (autoClose) + { + try + { + stream.close(); + } + catch (IOException x) + { + LOG.ignore(x); + } + } + } }; } } From c822ee4f82e92a39401c4301bed173d44564f958 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 28 Oct 2013 12:19:16 +0100 Subject: [PATCH 14/27] Make sure we never exit the selector loop unless stopped. --- .../src/main/java/org/eclipse/jetty/io/SelectorManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java index 07bb5a42670..eed227e27f8 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java @@ -520,7 +520,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa } selectedKeys.clear(); } - catch (Exception x) + catch (Throwable x) { if (isRunning()) LOG.warn(x); From b6a306a6b8f443b001f477390c33f07e1ad7e6fc Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Tue, 29 Oct 2013 12:24:03 +1100 Subject: [PATCH 15/27] 420530 AbstractLoginModule never fails a login Conflicts: jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/AbstractLoginModule.java jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java --- .../jetty/jaas/spi/AbstractLoginModule.java | 30 ++++++++++++++----- .../jaas/spi/PropertyFileLoginModule.java | 7 +++-- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/AbstractLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/AbstractLoginModule.java index 20646e2e714..4e6c700b062 100644 --- a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/AbstractLoginModule.java +++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/AbstractLoginModule.java @@ -31,6 +31,7 @@ import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginException; import javax.security.auth.spi.LoginModule; @@ -199,9 +200,14 @@ public abstract class AbstractLoginModule implements LoginModule callbacks[2] = new PasswordCallback("Enter password", false); //only used if framework does not support the ObjectCallback return callbacks; } - - - + + + public boolean isIgnored () + { + return false; + } + + public abstract UserInfo getUserInfo (String username) throws Exception; @@ -214,7 +220,10 @@ public abstract class AbstractLoginModule implements LoginModule public boolean login() throws LoginException { try - { + { + if (isIgnored()) + return false; + if (callbackHandler == null) throw new LoginException ("No callback handler"); @@ -231,7 +240,7 @@ public abstract class AbstractLoginModule implements LoginModule if ((webUserName == null) || (webCredential == null)) { setAuthenticated(false); - return isAuthenticated(); + throw new FailedLoginException(); } UserInfo userInfo = getUserInfo(webUserName); @@ -239,12 +248,16 @@ public abstract class AbstractLoginModule implements LoginModule if (userInfo == null) { setAuthenticated(false); - return isAuthenticated(); + throw new FailedLoginException(); } currentUser = new JAASUserInfo(userInfo); setAuthenticated(currentUser.checkCredential(webCredential)); - return isAuthenticated(); + + if (isAuthenticated()) + return true; + else + throw new FailedLoginException(); } catch (IOException e) { @@ -256,7 +269,8 @@ public abstract class AbstractLoginModule implements LoginModule } catch (Exception e) { - e.printStackTrace(); + if (e instanceof LoginException) + throw (LoginException)e; throw new LoginException (e.toString()); } } diff --git a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java index 028e4f269dc..69ea3e9a600 100644 --- a/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java +++ b/jetty-jaas/src/main/java/org/eclipse/jetty/jaas/spi/PropertyFileLoginModule.java @@ -112,9 +112,10 @@ public class PropertyFileLoginModule extends AbstractLoginModule PropertyUserStore propertyUserStore = _propertyUserStores.get(_filename); if (propertyUserStore == null) throw new IllegalStateException("PropertyUserStore should never be null here!"); - + + LOG.debug("Checking PropertyUserStore "+_filename+" for "+userName); UserIdentity userIdentity = propertyUserStore.getUserIdentity(userName); - if(userIdentity==null) + if (userIdentity==null) return null; Set principals = userIdentity.getSubject().getPrincipals(); @@ -127,7 +128,7 @@ public class PropertyFileLoginModule extends AbstractLoginModule } Credential credential = (Credential)userIdentity.getSubject().getPrivateCredentials().iterator().next(); - LOG.debug("Found: " + userName + " in PropertyUserStore"); + LOG.debug("Found: " + userName + " in PropertyUserStore "+_filename); return new UserInfo(userName, credential, roles); } From 00867b094b2d0d272fefcd6c79d4cd07c6c75dd0 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 29 Oct 2013 09:53:18 +0100 Subject: [PATCH 16/27] Making classes implement Closeable, rather than AutoCloseable, since it is more semantically correct. --- jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java | 4 +++- .../main/java/org/eclipse/jetty/server/NetworkConnector.java | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java index 840e175ab2e..912dc2033f6 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/Connection.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.io; +import java.io.Closeable; + import org.eclipse.jetty.util.Callback; /** @@ -28,7 +30,7 @@ import org.eclipse.jetty.util.Callback; * and when the {@link EndPoint} signals read readyness, this {@link Connection} can * read bytes from the network and interpret them.

*/ -public interface Connection extends AutoCloseable +public interface Connection extends Closeable { public void addListener(Listener listener); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkConnector.java index df8ce174c62..fbbffb6d032 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkConnector.java @@ -18,12 +18,13 @@ package org.eclipse.jetty.server; +import java.io.Closeable; import java.io.IOException; /** *

A {@link Connector} for TCP/IP network connectors

*/ -public interface NetworkConnector extends Connector, AutoCloseable +public interface NetworkConnector extends Connector, Closeable { /** *

Performs the activities needed to open the network communication From edcb39cc8970a5dc7e1ddc20414ff303fd0be98c Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 29 Oct 2013 10:03:08 +0100 Subject: [PATCH 17/27] Catching Throwable and closing channels rigorously. --- .../org/eclipse/jetty/io/SelectorManager.java | 64 ++++++++----------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java index eed227e27f8..b2cc0673f90 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java @@ -25,8 +25,6 @@ import java.net.Socket; import java.net.SocketAddress; import java.net.SocketTimeoutException; import java.nio.channels.CancelledKeyException; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.ClosedSelectorException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; @@ -58,14 +56,10 @@ import org.eclipse.jetty.util.thread.Scheduler; */ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable { - protected static final Logger LOG = Log.getLogger(SelectorManager.class); - public static final String SUBMIT_KEY_UPDATES="org.eclipse.jetty.io.SelectorManager.submitKeyUpdates"; - /** - * The default connect timeout, in milliseconds - */ + public static final String SUBMIT_KEY_UPDATES = "org.eclipse.jetty.io.SelectorManager.submitKeyUpdates"; public static final int DEFAULT_CONNECT_TIMEOUT = 15000; - - private final static boolean __submitKeyUpdates=Boolean.valueOf(System.getProperty(SUBMIT_KEY_UPDATES,"FALSE")); + protected static final Logger LOG = Log.getLogger(SelectorManager.class); + private final static boolean __submitKeyUpdates = Boolean.valueOf(System.getProperty(SUBMIT_KEY_UPDATES, "false")); private final Executor executor; private final Scheduler scheduler; @@ -233,7 +227,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { connection.onOpen(); } - catch (Exception x) + catch (Throwable x) { if (isRunning()) LOG.warn("Exception while notifying connection " + connection, x); @@ -253,9 +247,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { connection.onClose(); } - catch (Exception x) + catch (Throwable x) { - LOG.info("Exception while notifying connection " + connection, x); + LOG.debug("Exception while notifying connection " + connection, x); } } @@ -368,11 +362,12 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa public void updateKey(Runnable update) { if (__submitKeyUpdates) + { submit(update); + } else { - update.run(); - + runChange(update); if (_state.compareAndSet(State.SELECT, State.WAKEUP)) wakeup(); } @@ -435,8 +430,15 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa protected void runChange(Runnable change) { - LOG.debug("Running change {}", change); - change.run(); + try + { + LOG.debug("Running change {}", change); + change.run(); + } + catch (Throwable x) + { + LOG.debug("Could not run change " + change, x); + } } @Override @@ -553,7 +555,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa if (attachment instanceof EndPoint) ((EndPoint)attachment).close(); } - catch (Exception x) + catch (Throwable x) { LOG.warn("Could not process key for channel " + key.channel(), x); if (attachment instanceof EndPoint) @@ -563,10 +565,10 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa private void processConnect(SelectionKey key, Connect connect) { - key.attach(connect.attachment); SocketChannel channel = (SocketChannel)key.channel(); try { + key.attach(connect.attachment); boolean connected = finishConnect(channel); if (connected) { @@ -580,10 +582,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa throw new ConnectException(); } } - catch (Exception x) + catch (Throwable x) { connect.failed(x); - closeNoExceptions(channel); } } @@ -593,7 +594,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { closeable.close(); } - catch (IOException x) + catch (Throwable x) { LOG.ignore(x); } @@ -740,8 +741,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa EndPoint endpoint = createEndPoint(_channel, key); key.attach(endpoint); } - catch (IOException x) + catch (Throwable x) { + closeNoExceptions(_channel); LOG.debug(x); } } @@ -768,7 +770,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { channel.register(_selector, SelectionKey.OP_CONNECT, this); } - catch (ClosedSelectorException | ClosedChannelException x) + catch (Throwable x) { failed(x); } @@ -779,22 +781,10 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa if (failed.compareAndSet(false, true)) { timeout.cancel(); - close(); + closeNoExceptions(channel); connectionFailed(channel, failure, attachment); } } - - private void close() - { - try - { - channel.close(); - } - catch (IOException x) - { - LOG.ignore(x); - } - } } private class ConnectTimeout implements Runnable @@ -878,7 +868,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { try { - endPoint.getConnection().close(); + closeNoExceptions(endPoint.getConnection()); } finally { From 44ce72164e9570aedc3f19d242597da63508a1d4 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Wed, 30 Oct 2013 10:51:24 +1100 Subject: [PATCH 18/27] 420687 XML errors in jetty-plus/src/test/resources/web-fragment-*.xml --- jetty-plus/src/test/resources/web-fragment-1.xml | 2 +- jetty-plus/src/test/resources/web-fragment-2.xml | 4 ++-- jetty-plus/src/test/resources/web-fragment-3.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/jetty-plus/src/test/resources/web-fragment-1.xml b/jetty-plus/src/test/resources/web-fragment-1.xml index b213172ef01..2c563e86eee 100644 --- a/jetty-plus/src/test/resources/web-fragment-1.xml +++ b/jetty-plus/src/test/resources/web-fragment-1.xml @@ -9,7 +9,7 @@ Fragment1 - others + diff --git a/jetty-plus/src/test/resources/web-fragment-2.xml b/jetty-plus/src/test/resources/web-fragment-2.xml index e2fff6786f1..878ec0f9ba2 100644 --- a/jetty-plus/src/test/resources/web-fragment-2.xml +++ b/jetty-plus/src/test/resources/web-fragment-2.xml @@ -9,13 +9,13 @@ Fragment2 - others + jdbc/mydatasource javax.sql.DataSource - User + Application - 300000 - 2 - false + + + diff --git a/jetty-server/src/main/config/modules/xinetd.mod b/jetty-server/src/main/config/modules/xinetd.mod index 85e6865bcc4..e53618e4890 100644 --- a/jetty-server/src/main/config/modules/xinetd.mod +++ b/jetty-server/src/main/config/modules/xinetd.mod @@ -7,3 +7,11 @@ server [xml] etc/jetty-xinetd.xml + +[ini-template] +## Xinetd Configuration +## See ${jetty.home}/etc/jetty-xinetd.xml for example service entry +jetty.xinetd.idleTimeout=300000 +jetty.xinetd.acceptors=2 +jetty.xinetd.statsOn=false + From ed4d8241caa7431135befebc19c27e0e6a314811 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 30 Oct 2013 09:29:25 -0700 Subject: [PATCH 23/27] 418923 - Missing parameterization of etc/jetty-proxy.xml --- jetty-proxy/src/main/config/etc/jetty-proxy.xml | 12 ++++++------ jetty-proxy/src/main/config/modules/proxy.mod | 9 +++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/jetty-proxy/src/main/config/etc/jetty-proxy.xml b/jetty-proxy/src/main/config/etc/jetty-proxy.xml index 8a09bba66e1..75a7671c3cd 100644 --- a/jetty-proxy/src/main/config/etc/jetty-proxy.xml +++ b/jetty-proxy/src/main/config/etc/jetty-proxy.xml @@ -9,8 +9,8 @@ - 16 - 256 + + @@ -20,7 +20,7 @@ - 300000 + @@ -34,7 +34,7 @@ / maxThreads - 128 + @@ -42,7 +42,7 @@ - true - 1000 + + diff --git a/jetty-proxy/src/main/config/modules/proxy.mod b/jetty-proxy/src/main/config/modules/proxy.mod index 9486a839430..eb1b38884aa 100644 --- a/jetty-proxy/src/main/config/modules/proxy.mod +++ b/jetty-proxy/src/main/config/modules/proxy.mod @@ -11,3 +11,12 @@ lib/jetty-proxy-${jetty.version}.jar [xml] etc/jetty-proxy.xml + +[ini-template] +## Proxy Configuration +jetty.proxy.threadpool.min=16 +jetty.proxy.threadpool.max=256 +jetty.proxy.idleTimeout=300000 +jetty.proxy.threads.max=128 +jetty.proxy.stopAtShutdown=true +jetty.proxy.stopTimeout=1000 From 1705825346d14ba4aa38e6904c668e401663932d Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 30 Oct 2013 13:11:27 -0700 Subject: [PATCH 24/27] 417932 - resources.mod should make ${jetty.base}/resources/ directory --- jetty-server/src/main/config/modules/ext.mod | 9 +++++++++ jetty-server/src/main/config/modules/resources.mod | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/jetty-server/src/main/config/modules/ext.mod b/jetty-server/src/main/config/modules/ext.mod index 3af9897c03a..329a833d442 100644 --- a/jetty-server/src/main/config/modules/ext.mod +++ b/jetty-server/src/main/config/modules/ext.mod @@ -1,2 +1,11 @@ +# +# Module to add all lib/ext/*.jar files to classpath +# + [lib] lib/ext/*.jar + +[files] +lib/ +lib/ext/ + diff --git a/jetty-server/src/main/config/modules/resources.mod b/jetty-server/src/main/config/modules/resources.mod index 6ed073a3b4b..b3a4d2d74f6 100644 --- a/jetty-server/src/main/config/modules/resources.mod +++ b/jetty-server/src/main/config/modules/resources.mod @@ -3,4 +3,8 @@ # [lib] -resources \ No newline at end of file +resources + +[files] +resources/ + From 845c08970ea59bad52b25cb8b190e24a0d6ae0b6 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 30 Oct 2013 13:14:22 -0700 Subject: [PATCH 25/27] 417933 - logging.mod ini template should include commented log.class settings --- jetty-util/src/main/config/modules/logging.mod | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/jetty-util/src/main/config/modules/logging.mod b/jetty-util/src/main/config/modules/logging.mod index f72863d1f20..ec7d2ca4ea3 100644 --- a/jetty-util/src/main/config/modules/logging.mod +++ b/jetty-util/src/main/config/modules/logging.mod @@ -6,6 +6,16 @@ etc/jetty-logging.xml [ini-template] -## STDERR / STDOUT Logging +## Logging Configuration +# Configure jetty logging for default internal behavior STDERR output +# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog + +# Configure jetty logging for slf4j +# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.Slf4jLog + +# Configure jetty logging for java.util.logging +# -Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.JavaUtilLog + +# STDERR / STDOUT Logging # Number of days to retain logs # jetty.log.retain=90 From 466725e3434ea95579224f71f1cafbbf0e3f4987 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 31 Oct 2013 13:59:37 +1100 Subject: [PATCH 26/27] 420776 complete error pages after startAsync Conflicts: jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpConnection.java jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java --- .../org/eclipse/jetty/server/HttpChannel.java | 1 + .../eclipse/jetty/servlet/ServletHandler.java | 19 +++++--- .../jetty/servlet/AsyncContextTest.java | 43 ++++++++++++++++++- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 4c2bd54bbfc..9c94256400f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -380,6 +380,7 @@ public class HttpChannel implements HttpParser.RequestHandler, Runnable boolean committed = sendResponse(info, null, true); if (!committed) LOG.warn("Could not send response error 500: "+x); + _request.getAsyncContext().complete(); } else if (isCommitted()) { diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java index 1470d34bd93..c9ccb06944e 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHandler.java @@ -569,10 +569,10 @@ public class ServletHandler extends ScopedHandler LOG.debug(request.toString()); } + request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass()); + request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th); if (!response.isCommitted()) { - request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,th.getClass()); - request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,th); if (th instanceof UnavailableException) { UnavailableException ue = (UnavailableException)th; @@ -586,6 +586,10 @@ public class ServletHandler extends ScopedHandler } else LOG.debug("Response already committed for handling "+th); + + // Complete async requests + if (request.isAsyncStarted()) + request.getAsyncContext().complete(); } catch(Error e) { @@ -596,15 +600,16 @@ public class ServletHandler extends ScopedHandler LOG.warn("Error for "+request.getRequestURI(),e); if(LOG.isDebugEnabled())LOG.debug(request.toString()); - // TODO httpResponse.getHttpConnection().forceClose(); + request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass()); + request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e); if (!response.isCommitted()) - { - request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,e.getClass()); - request.setAttribute(RequestDispatcher.ERROR_EXCEPTION,e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } else LOG.debug("Response already committed for handling ",e); + + // Complete async requests + if (request.isAsyncStarted()) + request.getAsyncContext().complete(); } finally { diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java index 121874ce762..e707884f9f5 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncContextTest.java @@ -41,6 +41,7 @@ import javax.servlet.http.HttpServletResponseWrapper; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.LocalConnector; +import org.eclipse.jetty.server.QuietServletException; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.DefaultHandler; @@ -68,7 +69,7 @@ public class AsyncContextTest _server = new Server(); _contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS); _connector = new LocalConnector(_server); - _connector.setIdleTimeout(30000); + _connector.setIdleTimeout(5000); _server.setConnectors(new Connector[] { _connector }); @@ -76,6 +77,7 @@ public class AsyncContextTest _contextHandler.addServlet(new ServletHolder(new TestServlet()),"/servletPath"); _contextHandler.addServlet(new ServletHolder(new TestServlet()),"/path with spaces/servletPath"); _contextHandler.addServlet(new ServletHolder(new TestServlet2()),"/servletPath2"); + _contextHandler.addServlet(new ServletHolder(new TestStartThrowServlet()),"/startthrow/*"); _contextHandler.addServlet(new ServletHolder(new ForwardingServlet()),"/forward"); _contextHandler.addServlet(new ServletHolder(new AsyncDispatchingServlet()),"/dispatchingServlet"); _contextHandler.addServlet(new ServletHolder(new ExpireServlet()),"/expire/*"); @@ -84,7 +86,8 @@ public class AsyncContextTest ErrorPageErrorHandler error_handler = new ErrorPageErrorHandler(); _contextHandler.setErrorHandler(error_handler); - error_handler.addErrorPage(500,"/error"); + error_handler.addErrorPage(500,"/error/500"); + error_handler.addErrorPage(IOException.class.getName(),"/error/IOE"); HandlerList handlers = new HandlerList(); handlers.setHandlers(new Handler[] @@ -116,6 +119,25 @@ public class AsyncContextTest } + @Test + public void testStartThrow() throws Exception + { + String request = "GET /ctx/startthrow HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + + "Connection: close\r\n" + "\r\n"; + String responseString = _connector.getResponses(request); + + BufferedReader br = new BufferedReader(new StringReader(responseString)); + + assertEquals("HTTP/1.1 500 Server Error",br.readLine()); + br.readLine();// connection close + br.readLine();// server + br.readLine();// empty + + Assert.assertEquals("error servlet","ERROR: /error",br.readLine()); + Assert.assertEquals("error servlet","PathInfo= /IOE",br.readLine()); + Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: Test",br.readLine()); + } + @Test public void testDispatchAsyncContext() throws Exception { @@ -327,6 +349,7 @@ public class AsyncContextTest br.readLine();// empty Assert.assertEquals("error servlet","ERROR: /error",br.readLine()); + Assert.assertEquals("error servlet","PathInfo= /500",br.readLine()); Assert.assertEquals("error servlet","EXCEPTION: java.io.IOException: TEST",br.readLine()); } @@ -365,6 +388,7 @@ public class AsyncContextTest protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getOutputStream().print("ERROR: " + request.getServletPath() + "\n"); + response.getOutputStream().print("PathInfo= " + request.getPathInfo() + "\n"); if (request.getAttribute(RequestDispatcher.ERROR_EXCEPTION)!=null) response.getOutputStream().print("EXCEPTION: " + request.getAttribute(RequestDispatcher.ERROR_EXCEPTION) + "\n"); } @@ -462,6 +486,21 @@ public class AsyncContextTest asyncContext.start(new AsyncRunnable(asyncContext)); } } + + private class TestStartThrowServlet extends HttpServlet + { + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + if (request.getDispatcherType()==DispatcherType.REQUEST) + { + request.startAsync(request, response); + throw new QuietServletException(new IOException("Test")); + } + } + } private class AsyncRunnable implements Runnable { From b1e277af21fda1ce3e64379cac9ab4c7941dad1f Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 31 Oct 2013 10:08:52 +0100 Subject: [PATCH 27/27] Refactored addition of proxy headers into two methods, to allow subclasses to override this behavior more easily. --- .../org/eclipse/jetty/proxy/ProxyServlet.java | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java index f2465145622..fcd00176d90 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java @@ -137,6 +137,11 @@ public class ProxyServlet extends HttpServlet } } + public String getViaHost() + { + return _viaHost; + } + public long getTimeout() { return _timeout; @@ -419,11 +424,8 @@ public class ProxyServlet extends HttpServlet proxyRequest.header(HttpHeader.HOST, _hostHeader); // Add proxy headers - proxyRequest.header(HttpHeader.VIA, "http/1.1 " + _viaHost); - proxyRequest.header(HttpHeader.X_FORWARDED_FOR, request.getRemoteAddr()); - proxyRequest.header(HttpHeader.X_FORWARDED_PROTO, request.getScheme()); - proxyRequest.header(HttpHeader.X_FORWARDED_HOST, request.getHeader(HttpHeader.HOST.asString())); - proxyRequest.header(HttpHeader.X_FORWARDED_SERVER, request.getLocalName()); + addViaHeader(proxyRequest); + addXForwardedHeaders(proxyRequest, request); if (hasContent) { @@ -488,6 +490,19 @@ public class ProxyServlet extends HttpServlet proxyRequest.send(new ProxyResponseListener(request, response)); } + protected Request addViaHeader(Request proxyRequest) + { + return proxyRequest.header(HttpHeader.VIA, "http/1.1 " + getViaHost()); + } + + protected void addXForwardedHeaders(Request proxyRequest, HttpServletRequest request) + { + proxyRequest.header(HttpHeader.X_FORWARDED_FOR, request.getRemoteAddr()); + proxyRequest.header(HttpHeader.X_FORWARDED_PROTO, request.getScheme()); + proxyRequest.header(HttpHeader.X_FORWARDED_HOST, request.getHeader(HttpHeader.HOST.asString())); + proxyRequest.header(HttpHeader.X_FORWARDED_SERVER, request.getLocalName()); + } + protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse) { for (HttpField field : proxyResponse.getHeaders())