From a96d7c388c19ee4965ced6f4d955391517585680 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 1 Apr 2014 15:45:52 +0200 Subject: [PATCH 001/135] Initial implementation of 431642 (async proxy servlet). --- .../org/eclipse/jetty/client/HttpRequest.java | 15 +++ .../org/eclipse/jetty/client/api/Request.java | 5 + .../eclipse/jetty/client/api/Response.java | 14 ++- .../client/util/DeferredContentProvider.java | 54 ++++++--- .../jetty/proxy/AsyncProxyServlet.java | 106 ++++++++++++++++++ .../org/eclipse/jetty/proxy/ProxyServlet.java | 51 +++++---- .../eclipse/jetty/proxy/ProxyServletTest.java | 90 +++++++++------ 7 files changed, 267 insertions(+), 68 deletions(-) create mode 100644 jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java 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 afe214f5297..0002c6b7313 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 @@ -50,6 +50,7 @@ import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Fields; public class HttpRequest implements Request @@ -460,6 +461,20 @@ public class HttpRequest implements Request return this; } + @Override + public Request onResponseContent(final Response.AsyncContentListener listener) + { + this.responseListeners.add(new Response.AsyncContentListener() + { + @Override + public void onContent(Response response, ByteBuffer content, Callback callback) + { + listener.onContent(response, content, callback); + } + }); + return this; + } + @Override public Request onResponseSuccess(final Response.SuccessListener listener) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java index cc3b5f85a68..6c9ae20acc3 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java @@ -340,9 +340,14 @@ public interface Request /** * @param listener a listener for response content events * @return this request object + * @deprecated Use {@link #onResponseContent(Response.AsyncContentListener)} instead. */ + @Deprecated Request onResponseContent(Response.ContentListener listener); + // TODO: JAVADOCS + Request onResponseContent(Response.AsyncContentListener listener); + /** * @param listener a listener for response success event * @return this request object diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java index 5f60ef80198..e92dcaa1083 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java @@ -26,6 +26,7 @@ import org.eclipse.jetty.client.util.BufferingResponseListener; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.util.Callback; /** *

{@link Response} represents a HTTP response and offers methods to retrieve status code, HTTP version @@ -152,6 +153,11 @@ public interface Response public void onContent(Response response, ByteBuffer content); } + public interface AsyncContentListener extends ResponseListener + { + public void onContent(Response response, ByteBuffer content, Callback callback); + } + /** * Listener for the response succeeded event. */ @@ -204,7 +210,7 @@ public interface Response /** * Listener for all response events. */ - public interface Listener extends BeginListener, HeaderListener, HeadersListener, ContentListener, SuccessListener, FailureListener, CompleteListener + public interface Listener extends BeginListener, HeaderListener, HeadersListener, ContentListener, AsyncContentListener, SuccessListener, FailureListener, CompleteListener { /** * An empty implementation of {@link Listener} @@ -232,6 +238,12 @@ public interface Response { } + @Override + public void onContent(Response response, ByteBuffer content, Callback callback) + { + callback.succeeded(); + } + @Override public void onSuccess(Response response) { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java index a0af29571af..73ad9614646 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java @@ -24,6 +24,7 @@ import java.io.InterruptedIOException; import java.nio.ByteBuffer; import java.util.Iterator; import java.util.NoSuchElementException; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -33,6 +34,7 @@ import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.util.ArrayQueue; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; /** @@ -83,10 +85,11 @@ import org.eclipse.jetty.util.Callback; */ public class DeferredContentProvider implements AsyncContentProvider, Closeable { - private static final ByteBuffer CLOSE = ByteBuffer.allocate(0); + private static final Callback EMPTY_CALLBACK = new Callback.Adapter(); + private static final AsyncChunk CLOSE = new AsyncChunk(BufferUtil.EMPTY_BUFFER, EMPTY_CALLBACK); private final Object lock = this; - private final Queue chunks = new ArrayQueue<>(4, 64, lock); + private final Queue chunks = new ArrayQueue<>(4, 64, lock); private final AtomicReference listener = new AtomicReference<>(); private final Iterator iterator = new DeferredContentProviderIterator(); private final AtomicBoolean closed = new AtomicBoolean(); @@ -126,12 +129,22 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable * @return true if the content was added, false otherwise */ public boolean offer(ByteBuffer buffer) + { + return offer(buffer, EMPTY_CALLBACK); + } + + public boolean offer(ByteBuffer buffer, Callback callback) + { + return offer(new AsyncChunk(buffer, callback)); + } + + private boolean offer(AsyncChunk chunk) { boolean result; synchronized (lock) { - result = chunks.offer(buffer); - if (result && buffer != CLOSE) + result = chunks.offer(chunk); + if (result && chunk != CLOSE) ++size; } if (result) @@ -186,7 +199,7 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable private class DeferredContentProviderIterator implements Iterator, Callback { - private ByteBuffer current; + private AsyncChunk current; @Override public boolean hasNext() @@ -202,10 +215,10 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable { synchronized (lock) { - ByteBuffer element = current = chunks.poll(); - if (element == CLOSE) + AsyncChunk chunk = current = chunks.poll(); + if (chunk == CLOSE) throw new NoSuchElementException(); - return element; + return chunk == null ? null : chunk.buffer; } } @@ -218,24 +231,39 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable @Override public void succeeded() { + AsyncChunk chunk; synchronized (lock) { - if (current != null) - { - --size; - lock.notify(); - } + chunk = current; + --size; + lock.notify(); } + chunk.callback.succeeded(); } @Override public void failed(Throwable x) { + AsyncChunk chunk; synchronized (lock) { + chunk = current; failure = x; lock.notify(); } + chunk.callback.failed(x); + } + } + + private static class AsyncChunk + { + private final ByteBuffer buffer; + private final Callback callback; + + private AsyncChunk(ByteBuffer buffer, Callback callback) + { + this.buffer = Objects.requireNonNull(buffer); + this.callback = Objects.requireNonNull(callback); } } } diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java new file mode 100644 index 00000000000..e9915a489c0 --- /dev/null +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java @@ -0,0 +1,106 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.proxy; + +import java.io.IOException; +import java.nio.ByteBuffer; +import javax.servlet.AsyncContext; +import javax.servlet.ReadListener; +import javax.servlet.ServletInputStream; + +import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.util.Callback; + +public class AsyncProxyServlet extends ProxyServlet +{ + @Override + protected ContentProvider proxyRequestContent(AsyncContext asyncContext, final int requestId) throws IOException + { + ServletInputStream input = asyncContext.getRequest().getInputStream(); + DeferredContentProvider provider = new DeferredContentProvider(); + input.setReadListener(new StreamReader(input, requestId, provider)); + return provider; + } + + private class StreamReader implements ReadListener, Callback + { + private final byte[] buffer = new byte[512]; + private final ServletInputStream input; + private final int requestId; + private final DeferredContentProvider provider; + + public StreamReader(ServletInputStream input, int requestId, DeferredContentProvider provider) + { + this.input = input; + this.requestId = requestId; + this.provider = provider; + } + + @Override + public void onDataAvailable() throws IOException + { + _log.debug("Asynchronous read start from {}", input); + + // First check for isReady() because it has + // side effects, and then for isFinished(). + while (input.isReady() && !input.isFinished()) + { + int read = input.read(buffer); + _log.debug("Asynchronous read {} bytes from {}", read, input); + if (read >= 0) + { + _log.debug("{} proxying content to upstream: {} bytes", requestId, read); + provider.offer(ByteBuffer.wrap(buffer, 0, read), this); + // Do not call isReady() so that we can apply backpressure. + break; + } + } + if (!input.isFinished()) + _log.debug("Asynchronous read pending from {}", input); + } + + @Override + public void onAllDataRead() throws IOException + { + _log.debug("{} proxying content to upstream completed", requestId); + provider.close(); + } + + @Override + public void onError(Throwable x) + { + failed(x); + } + + @Override + public void succeeded() + { + // Notify the container that it may call onDataAvailable() again. + input.isReady(); + } + + @Override + public void failed(Throwable x) + { + // TODO: send a response error ? + // complete the async context since we cannot throw an exception from here. + } + } +} 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 8207b986bfc..de40c8e50bd 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 @@ -19,6 +19,7 @@ package org.eclipse.jetty.proxy; import java.io.IOException; +import java.io.InputStream; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; @@ -41,6 +42,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; @@ -385,7 +387,7 @@ public class ProxyServlet extends HttpServlet if (rewrittenURI == null) { - response.sendError(HttpServletResponse.SC_FORBIDDEN); + onRewriteFailed(request, response); return; } @@ -427,25 +429,6 @@ public class ProxyServlet extends HttpServlet addViaHeader(proxyRequest); addXForwardedHeaders(proxyRequest, request); - if (hasContent) - { - proxyRequest.content(new InputStreamContentProvider(request.getInputStream()) - { - @Override - public long getLength() - { - return request.getContentLength(); - } - - @Override - protected ByteBuffer onRead(byte[] buffer, int offset, int length) - { - _log.debug("{} proxying content to upstream: {} bytes", requestId, length); - return super.onRead(buffer, offset, length); - } - }); - } - final AsyncContext asyncContext = request.startAsync(); // We do not timeout the continuation, but the proxy request asyncContext.setTimeout(0); @@ -453,6 +436,9 @@ public class ProxyServlet extends HttpServlet customizeProxyRequest(proxyRequest, request); + if (hasContent) + proxyRequest.content(proxyRequestContent(asyncContext, requestId)); + if (_log.isDebugEnabled()) { StringBuilder builder = new StringBuilder(request.getMethod()); @@ -490,6 +476,31 @@ public class ProxyServlet extends HttpServlet proxyRequest.send(new ProxyResponseListener(request, response)); } + protected ContentProvider proxyRequestContent(final AsyncContext asyncContext, final int requestId) throws IOException + { + final HttpServletRequest request = (HttpServletRequest)asyncContext.getRequest(); + return new InputStreamContentProvider(request.getInputStream()) + { + @Override + public long getLength() + { + return request.getContentLength(); + } + + @Override + protected ByteBuffer onRead(byte[] buffer, int offset, int length) + { + _log.debug("{} proxying content to upstream: {} bytes", requestId, length); + return super.onRead(buffer, offset, length); + } + }; + } + + protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException + { + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } + protected Request addViaHeader(Request proxyRequest) { return proxyRequest.header(HttpHeader.VIA, "http/1.1 " + getViaHost()); diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java index ec32b8b63f0..2e2f3c6576d 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java @@ -18,8 +18,6 @@ package org.eclipse.jetty.proxy; -import static java.nio.file.StandardOpenOption.CREATE; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -30,6 +28,7 @@ import java.net.HttpCookie; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -39,7 +38,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.zip.GZIPOutputStream; - import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; @@ -64,7 +62,6 @@ import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; -import org.eclipse.jetty.toolchain.test.AdvancedRunner; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.TestTracker; import org.eclipse.jetty.toolchain.test.annotation.Slow; @@ -78,11 +75,24 @@ import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; -@RunWith(AdvancedRunner.class) +import static java.nio.file.StandardOpenOption.CREATE; + +@RunWith(Parameterized.class) public class ProxyServletTest { private static final String PROXIED_HEADER = "X-Proxied"; + + @Parameterized.Parameters + public static Iterable data() + { + return Arrays.asList(new Object[][]{ + {new ProxyServlet()}, + {new AsyncProxyServlet()} + }); + } + @Rule public final TestTracker tracker = new TestTracker(); private HttpClient client; @@ -92,6 +102,16 @@ public class ProxyServletTest private Server server; private ServerConnector serverConnector; + public ProxyServletTest(ProxyServlet proxyServlet) + { + this.proxyServlet = proxyServlet; + } + + private void prepareProxy() throws Exception + { + prepareProxy(proxyServlet); + } + private void prepareProxy(ProxyServlet proxyServlet) throws Exception { proxy = new Server(); @@ -145,7 +165,7 @@ public class ProxyServletTest @Test public void testProxyDown() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new EmptyHttpServlet()); // Shutdown the proxy @@ -167,7 +187,7 @@ public class ProxyServletTest @Test public void testServerDown() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new EmptyHttpServlet()); // Shutdown the server @@ -187,7 +207,7 @@ public class ProxyServletTest ((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(true); try { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -198,8 +218,8 @@ public class ProxyServletTest }); ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) - .timeout(5, TimeUnit.SECONDS) - .send(); + .timeout(5, TimeUnit.SECONDS) + .send(); Assert.assertEquals(500, response.getStatus()); } @@ -212,7 +232,7 @@ public class ProxyServletTest @Test public void testProxyWithoutContent() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -234,7 +254,7 @@ public class ProxyServletTest @Test public void testProxyWithResponseContent() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); HttpClient result = new HttpClient(); result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort())); @@ -259,16 +279,16 @@ public class ProxyServletTest } }); - for ( int i = 0; i < 10; ++i ) + for (int i = 0; i < 10; ++i) { - // Request is for the target server + // Request is for the target server responses[i] = result.newRequest("localhost", serverConnector.getLocalPort()) .timeout(5, TimeUnit.SECONDS) .send(); } - for ( int i = 0; i < 10; ++i ) + for (int i = 0; i < 10; ++i) { Assert.assertEquals(200, responses[i].getStatus()); Assert.assertTrue(responses[i].getHeaders().containsKey(PROXIED_HEADER)); @@ -279,7 +299,7 @@ public class ProxyServletTest @Test public void testProxyWithRequestContentAndResponseContent() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -307,7 +327,7 @@ public class ProxyServletTest @Test public void testProxyWithBigRequestContentIgnored() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -335,7 +355,7 @@ public class ProxyServletTest final byte[] content = new byte[128 * 1024]; new Random().nextBytes(content); - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -370,7 +390,7 @@ public class ProxyServletTest @Test public void testProxyWithBigResponseContentWithSlowReader() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); // Create a 6 MiB file final int length = 6 * 1024; @@ -431,7 +451,7 @@ public class ProxyServletTest @Test public void testProxyWithQueryString() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); String query = "a=1&b=%E2%82%AC"; prepareServer(new HttpServlet() { @@ -453,7 +473,7 @@ public class ProxyServletTest @Test public void testProxyLongPoll() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); final long timeout = 1000; prepareServer(new HttpServlet() { @@ -504,7 +524,7 @@ public class ProxyServletTest @Test public void testProxyRequestExpired() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); final long timeout = 1000; proxyServlet.setTimeout(timeout); prepareServer(new HttpServlet() @@ -536,7 +556,7 @@ public class ProxyServletTest @Test(expected = TimeoutException.class) public void testClientRequestExpired() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); final long timeout = 1000; proxyServlet.setTimeout(3 * timeout); prepareServer(new HttpServlet() @@ -566,7 +586,7 @@ public class ProxyServletTest @Test public void testProxyXForwardedHostHeaderIsPresent() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -587,7 +607,7 @@ public class ProxyServletTest @Test public void testProxyWhiteList() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new EmptyHttpServlet()); int port = serverConnector.getLocalPort(); proxyServlet.getWhiteListHosts().add("127.0.0.1:" + port); @@ -608,7 +628,7 @@ public class ProxyServletTest @Test public void testProxyBlackList() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new EmptyHttpServlet()); int port = serverConnector.getLocalPort(); proxyServlet.getBlackListHosts().add("localhost:" + port); @@ -629,7 +649,7 @@ public class ProxyServletTest @Test public void testClientExcludedHosts() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -821,7 +841,7 @@ public class ProxyServletTest @Test public void testRedirectsAreProxied() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -846,7 +866,7 @@ public class ProxyServletTest public void testGZIPContentIsProxied() throws Exception { final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -873,13 +893,15 @@ public class ProxyServletTest @Test(expected = TimeoutException.class) public void shouldHandleWrongContentLength() throws Exception { - prepareProxy(new ProxyServlet()); - prepareServer(new HttpServlet() { + prepareProxy(); + prepareServer(new HttpServlet() + { @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { byte[] message = "tooshort".getBytes("ascii"); resp.setContentType("text/plain;charset=ascii"); - resp.setHeader("Content-Length", Long.toString(message.length+1)); + resp.setHeader("Content-Length", Long.toString(message.length + 1)); resp.getOutputStream().write(message); } }); @@ -895,7 +917,7 @@ public class ProxyServletTest public void testCookiesFromDifferentClientsAreNotMixed() throws Exception { final String name = "biscuit"; - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override From ce75625f0fcf594d26c84d3bf24d35b671eafdd5 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 8 Apr 2014 06:08:21 -0700 Subject: [PATCH 002/135] Preparing BaseHome for extra-start-dirs --- .../org/eclipse/jetty/start/BaseHome.java | 229 +++++++++++------- .../main/java/org/eclipse/jetty/start/FS.java | 6 + .../org/eclipse/jetty/start/BaseHomeTest.java | 4 +- 3 files changed, 146 insertions(+), 93 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java index c9211ded8ce..6299086df10 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java @@ -23,6 +23,8 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; @@ -48,29 +50,54 @@ public class BaseHome private final static EnumSet SEARCH_VISIT_OPTIONS = EnumSet.of(FileVisitOption.FOLLOW_LINKS);; private final static int MAX_SEARCH_DEPTH = 30; - private File homeDir; - private File baseDir; + private Path homeDir; + private Path baseDir; public BaseHome() { try { - this.baseDir = new File(System.getProperty("jetty.base",System.getProperty("user.dir","."))); + // find ${jetty.base} + + // default is ${user.dir} + this.baseDir = new File(System.getProperty("user.dir",".")).toPath(); + + // if ${jetty.base} declared, use it + String jettyBase = System.getProperty("jetty.base"); + if (jettyBase != null) + { + this.baseDir = new File(jettyBase).toPath(); + } + + // find ${jetty.home} + + // default location is based on lookup for BaseHome (from jetty's start.jar) URL jarfile = this.getClass().getClassLoader().getResource("org/eclipse/jetty/start/BaseHome.class"); if (jarfile != null) { Matcher m = Pattern.compile("jar:(file:.*)!/org/eclipse/jetty/start/BaseHome.class").matcher(jarfile.toString()); if (m.matches()) { - homeDir = new File(new URI(m.group(1))).getParentFile(); + // ${jetty.home} is relative to found BaseHome class + this.homeDir = new File(new URI(m.group(1))).getParentFile().toPath(); } } - homeDir = new File(System.getProperty("jetty.home",(homeDir == null?baseDir:homeDir).getAbsolutePath())); - baseDir = baseDir.getAbsoluteFile().getCanonicalFile(); - homeDir = homeDir.getAbsoluteFile().getCanonicalFile(); + // if we can't locate BaseHome, then assume home == base + this.homeDir = baseDir; + + // if ${jetty.home} declared, use it + String jettyHome = System.getProperty("jetty.home"); + if (jettyHome != null) + { + this.homeDir = new File(jettyHome).toPath(); + } + + // resolve base and home to absolute paths + baseDir = baseDir.toAbsolutePath(); + homeDir = homeDir.toAbsolutePath(); } - catch (IOException | URISyntaxException e) + catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -78,15 +105,8 @@ public class BaseHome public BaseHome(File homeDir, File baseDir) { - try - { - this.homeDir = homeDir.getCanonicalFile(); - this.baseDir = baseDir == null?this.homeDir:baseDir.getCanonicalFile(); - } - catch (IOException e) - { - throw new RuntimeException(e); - } + this.homeDir = homeDir.toPath().toAbsolutePath(); + this.baseDir = baseDir == null?this.homeDir:baseDir.toPath().toAbsolutePath(); } public String getBase() @@ -95,12 +115,13 @@ public class BaseHome { return null; } - return baseDir.getAbsolutePath(); + return baseDir.toString(); } + // TODO: change return type to Path public File getBaseDir() { - return baseDir; + return baseDir.toFile(); } /** @@ -112,7 +133,7 @@ public class BaseHome */ public File getBaseFile(String path) { - return new File(baseDir,FS.separators(path)); + return baseDir.resolve(FS.separators(path)).toFile(); } /** @@ -130,28 +151,47 @@ public class BaseHome * @return the file reference. */ public File getFile(String path) + { + return getPath(path).toAbsolutePath().toFile(); + } + + /** + * Get a specific file reference. + *

+ * File references go through 3 possibly scenarios. + *

    + *
  1. If exists relative to ${jetty.base}, return that reference
  2. + *
  3. If exists relative to ${jetty.home}, return that reference
  4. + *
  5. Otherwise return absolute path reference (standard java logic)
  6. + *
+ * + * @param path + * the path to get. + * @return the file reference. + */ + public Path getPath(String path) { String rpath = FS.separators(path); // Relative to Base Directory First if (isBaseDifferent()) { - File file = new File(baseDir,rpath); - if (file.exists()) + Path file = baseDir.resolve(rpath); + if (FS.exists(file)) { return file; } } // Then relative to Home Directory - File file = new File(homeDir,rpath); - if (file.exists()) + Path file = homeDir.resolve(rpath); + if (FS.exists(file)) { return file; } // Finally, as an absolute path - return new File(rpath); + return FileSystems.getDefault().getPath(rpath); } /** @@ -232,7 +272,7 @@ public class BaseHome finder.setIncludeDirsInResults(true); finder.setFileMatcher(matcher); - Path homePath = homeDir.toPath().resolve(relativePath); + Path homePath = homeDir.resolve(relativePath); if (FS.isValidDirectory(homePath)) { @@ -242,7 +282,7 @@ public class BaseHome if (isBaseDifferent()) { - Path basePath = baseDir.toPath().resolve(relativePath); + Path basePath = baseDir.resolve(relativePath); if (FS.isValidDirectory(basePath)) { finder.setBase(basePath); @@ -292,12 +332,13 @@ public class BaseHome public String getHome() { - return homeDir.getAbsolutePath(); + return homeDir.toString(); } + // TODO: change return type to Path public File getHomeDir() { - return homeDir; + return homeDir.toFile(); } public void initialize(StartArgs args) @@ -305,46 +346,52 @@ public class BaseHome Pattern jetty_home = Pattern.compile("(-D)?jetty.home=(.*)"); Pattern jetty_base = Pattern.compile("(-D)?jetty.base=(.*)"); - File homePath = null; - File basePath = null; + Path homePath = null; + Path basePath = null; + + FileSystem fs = FileSystems.getDefault(); for (String arg : args.getCommandLine()) { Matcher home_match = jetty_home.matcher(arg); if (home_match.matches()) { - homePath = new File(home_match.group(2)); + homePath = fs.getPath(home_match.group(2)); } Matcher base_match = jetty_base.matcher(arg); if (base_match.matches()) { - basePath = new File(base_match.group(2)); + basePath = fs.getPath(base_match.group(2)); } } if (homePath != null) { // logic if home is specified - this.homeDir = homePath.getAbsoluteFile(); + this.homeDir = homePath; if (basePath == null) { - this.baseDir = homePath.getAbsoluteFile(); + this.baseDir = homePath; args.getProperties().setProperty("jetty.base",this.baseDir.toString(),""); } else { - this.baseDir = basePath.getAbsoluteFile(); + this.baseDir = basePath; } } else if (basePath != null) { // logic if home is undeclared - this.baseDir = basePath.getAbsoluteFile(); + this.baseDir = basePath; } + // resolve base and home to absolute paths + baseDir = baseDir.toAbsolutePath(); + homeDir = homeDir.toAbsolutePath(); + // Update System Properties - args.addSystemProperty("jetty.home",this.homeDir.getAbsolutePath()); - args.addSystemProperty("jetty.base",this.baseDir.getAbsolutePath()); + args.addSystemProperty("jetty.home",this.homeDir.toString()); + args.addSystemProperty("jetty.base",this.baseDir.toString()); } public boolean isBaseDifferent() @@ -352,46 +399,28 @@ public class BaseHome return homeDir.compareTo(baseDir) != 0; } + // TODO: deprecate (in favor of Path version) public void setBaseDir(File dir) { - try - { - this.baseDir = dir.getCanonicalFile(); - System.setProperty("jetty.base",dir.getCanonicalPath()); - } - catch (IOException e) - { - e.printStackTrace(System.err); - } + setBaseDir(dir.toPath()); } + public void setBaseDir(Path dir) + { + this.baseDir = dir.toAbsolutePath(); + System.setProperty("jetty.base",dir.toString()); + } + + // TODO: deprecate (in favor of Path version) public void setHomeDir(File dir) { - try - { - this.homeDir = dir.getCanonicalFile(); - System.setProperty("jetty.home",dir.getCanonicalPath()); - } - catch (IOException e) - { - e.printStackTrace(System.err); - } + setHomeDir(dir.toPath()); } - /** - * Convenience method for toShortForm(file.getCanonicalPath()) - */ - public String toShortForm(File path) + public void setHomeDir(Path dir) { - try - { - return toShortForm(path.getCanonicalPath()); - } - catch (IOException ignore) - { - /* ignore */ - } - return toShortForm(path.getAbsolutePath()); + this.homeDir = dir.toAbsolutePath(); + System.setProperty("jetty.home",dir.toString()); } /** @@ -401,32 +430,50 @@ public class BaseHome * the path to shorten * @return the potentially shortened path */ - public String toShortForm(String path) + public String toShortForm(final Path path) + { + Path apath = path.toAbsolutePath(); + + if (isBaseDifferent()) + { + // is path part of ${jetty.base} ? + if (apath.startsWith(baseDir)) + { + return "${jetty.base}" + File.separatorChar + baseDir.relativize(apath); + } + } + + // is path part of ${jetty.home} ? + if (apath.startsWith(homeDir)) + { + return "${jetty.home}" + File.separatorChar + homeDir.relativize(apath); + } + + return apath.toString(); + } + + /** + * Convenience method for toShortForm(file.toPath()) + */ + public String toShortForm(final File path) + { + return toShortForm(path.toPath()); + } + + /** + * Replace/Shorten arbitrary path with property strings "${jetty.home}" or "${jetty.base}" where appropriate. + * + * @param path + * the path to shorten + * @return the potentially shortened path + */ + public String toShortForm(final String path) { if (path == null) { return path; } - String value; - - if (isBaseDifferent()) - { - value = baseDir.getAbsolutePath(); - if (path.startsWith(value)) - { - return "${jetty.base}" + path.substring(value.length()); - } - } - - value = homeDir.getAbsolutePath(); - - if (path.startsWith(value)) - { - return "${jetty.home}" + path.substring(value.length()); - } - - return path; + return toShortForm(FS.toPath(path)); } - } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java index de5f02676a6..270dd08f0ff 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java @@ -22,6 +22,7 @@ import java.io.Closeable; import java.io.File; import java.io.FileFilter; import java.io.IOException; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; @@ -256,4 +257,9 @@ public class FS { return Files.exists(path,new LinkOption[0]); } + + public static Path toPath(String path) + { + return FileSystems.getDefault().getPath(FS.separators(path)); + } } diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java index b2c19cbd50a..bac546a4d7c 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java @@ -98,7 +98,7 @@ public class BaseHomeTest File baseDir = null; BaseHome hb = new BaseHome(homeDir,baseDir); - File startIni = hb.getFile("/start.ini"); + File startIni = hb.getFile("start.ini"); String ref = hb.toShortForm(startIni); Assert.assertThat("Reference",ref,startsWith("${jetty.home}")); @@ -183,7 +183,7 @@ public class BaseHomeTest File baseDir = MavenTestingUtils.getTestResourceDir("hb.1/base"); BaseHome hb = new BaseHome(homeDir,baseDir); - File startIni = hb.getFile("/start.ini"); + File startIni = hb.getFile("start.ini"); String ref = hb.toShortForm(startIni); Assert.assertThat("Reference",ref,startsWith("${jetty.base}")); From 9d0143b70434674147656cc6fd3a06cf065f6a1d Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 8 Apr 2014 06:08:39 -0700 Subject: [PATCH 003/135] Making searchDepth configurable via System property --- jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java index 6299086df10..705f8bd6e97 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java @@ -48,7 +48,7 @@ import java.util.regex.Pattern; public class BaseHome { private final static EnumSet SEARCH_VISIT_OPTIONS = EnumSet.of(FileVisitOption.FOLLOW_LINKS);; - private final static int MAX_SEARCH_DEPTH = 30; + private final static int MAX_SEARCH_DEPTH = Integer.getInteger("org.eclipse.jetty.start.searchDepth",10); private Path homeDir; private Path baseDir; From 87f2deed65371d082e922ebb4a45ac355c73b64b Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 8 Apr 2014 06:11:42 -0700 Subject: [PATCH 004/135] Squelching prior notified loop detection --- .../main/java/org/eclipse/jetty/start/PathFinder.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/PathFinder.java b/jetty-start/src/main/java/org/eclipse/jetty/start/PathFinder.java index 5387959b6f0..799ccb193ea 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/PathFinder.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/PathFinder.java @@ -29,11 +29,16 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; public class PathFinder extends SimpleFileVisitor { + // internal tracking of prior notified paths (to avoid repeated notification of same ignored path) + private static Set NOTIFIED_PATHS = new HashSet<>(); + private boolean includeDirsInResults = false; private Map hits = new HashMap<>(); private Path basePath = null; @@ -149,7 +154,11 @@ public class PathFinder extends SimpleFileVisitor { if (exc instanceof FileSystemLoopException) { - StartLog.warn("skipping detected filesystem loop: " + file); + if (!NOTIFIED_PATHS.contains(file)) + { + StartLog.warn("skipping detected filesystem loop: " + file); + NOTIFIED_PATHS.add(file); + } return FileVisitResult.SKIP_SUBTREE; } else From e793c85eb445ccb24c5b25bfade44e24d892077e Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 8 Apr 2014 06:45:04 -0700 Subject: [PATCH 005/135] Cleanup and documentation of StartArgs in prep for extra-start-dirs --- .../java/org/eclipse/jetty/start/Main.java | 4 +- .../org/eclipse/jetty/start/StartArgs.java | 52 +++++++++++++------ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java index 9261397ed8b..3b9dd44623a 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java @@ -683,13 +683,13 @@ public class Main } // Initialize start.ini - for (String module : args.getModuleStartIni()) + for (String module : args.getAddToStartIni()) { moduleIni(args,module,true,true); } // Initialize start.d - for (String module : args.getModuleStartdIni()) + for (String module : args.getAddToStartdIni()) { moduleIni(args,module,true,false); } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java index 4daa0ac1af8..2419d509dc8 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java @@ -69,23 +69,41 @@ public class StartArgs private static final String SERVER_MAIN = "org.eclipse.jetty.xml.XmlConfiguration"; private List commandLine = new ArrayList<>(); + /** List of enabled modules */ private Set modules = new HashSet<>(); + /** Map of enabled modules to the source of where that activation occurred */ private Map> sources = new HashMap<>(); + /** Map of properties to where that property was declared */ + private Map propertySource = new HashMap<>(); + /** List of all active [files] sections from enabled modules */ private List files = new ArrayList<>(); + /** List of all active [lib] sectinos from enabled modules */ private Classpath classpath; - private List xmlRefs = new ArrayList<>(); + /** List of all active [xml] sections from enabled modules */ private List xmls = new ArrayList<>(); + /** JVM arguments, found via commmand line and in all active [exec] sections from enabled modules */ + private List jvmArgs = new ArrayList<>(); + + /** List of all xml references found directly on command line or start.ini */ + private List xmlRefs = new ArrayList<>(); + private Props properties = new Props(); private Set systemPropertyKeys = new HashSet<>(); - private List jvmArgs = new ArrayList<>(); private List rawLibs = new ArrayList<>(); - private List moduleStartdIni = new ArrayList<>(); - private List moduleStartIni = new ArrayList<>(); - private Map propertySource = new HashMap<>(); + + // jetty.base - build out commands + /** --add-to-startd=[module,[module]] */ + private List addToStartdIni = new ArrayList<>(); + /** --add-to-start=[module,[module]] */ + private List addToStartIni = new ArrayList<>(); + + // module inspection commands + /** --write-module-graph=[filename] */ private String moduleGraphFilename; + /** Collection of all modules */ private Modules allModules; - // Should the server be run? + /** Should the server be run? */ private boolean run = true; private boolean download = false; private boolean help = false; @@ -309,7 +327,7 @@ public class StartArgs System.setProperty(key,val); } } - + /** * Expand any command line added --lib lib references. * @@ -348,7 +366,7 @@ public class StartArgs StartLog.debug("rawlibref = " + rawlibref); String libref = properties.expand(rawlibref); StartLog.debug("expanded = " + libref); - + for (Path libpath : baseHome.getPaths(libref)) { classpath.addComponent(libpath.toFile()); @@ -357,10 +375,10 @@ public class StartArgs for (String jvmArg : module.getJvmArgs()) { - exec=true; + exec = true; jvmArgs.add(jvmArg); } - + // Find and Expand XML files for (String xmlRef : module.getXmls()) { @@ -475,14 +493,14 @@ public class StartArgs return moduleGraphFilename; } - public List getModuleStartdIni() + public List getAddToStartdIni() { - return moduleStartdIni; + return addToStartdIni; } - public List getModuleStartIni() + public List getAddToStartIni() { - return moduleStartIni; + return addToStartIni; } public Props getProperties() @@ -744,7 +762,7 @@ public class StartArgs if (arg.startsWith("--lib=")) { String cp = getValue(arg); - + if (cp != null) { StringTokenizer t = new StringTokenizer(cp,File.pathSeparator); @@ -770,7 +788,7 @@ public class StartArgs { throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source); } - moduleStartdIni.addAll(getValues(arg)); + addToStartdIni.addAll(getValues(arg)); run = false; download = true; return; @@ -782,7 +800,7 @@ public class StartArgs { throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source); } - moduleStartIni.addAll(getValues(arg)); + addToStartIni.addAll(getValues(arg)); run = false; download = true; return; From a9c01d3607517cac89718e906a8da50d8ebf91c4 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 8 Apr 2014 09:57:52 -0700 Subject: [PATCH 006/135] 432321 - jetty-start / Allow defining extra start directories for common configurations + Initial impl of BaseHome with support for lookups on extra start dirs --- .../org/eclipse/jetty/start/BaseHome.java | 195 ++++++++++++++---- .../org/eclipse/jetty/start/StartArgs.java | 2 +- .../org/eclipse/jetty/start/BaseHomeTest.java | 24 +++ .../logging/etc/jetty-logging.xml | 0 .../logging/lib/logging/logback.jar | Bin .../more-startd/start.d/more.ini | 0 6 files changed, 176 insertions(+), 45 deletions(-) create mode 100644 jetty-start/src/test/resources/extra-start-dirs/logging/etc/jetty-logging.xml create mode 100644 jetty-start/src/test/resources/extra-start-dirs/logging/lib/logging/logback.jar create mode 100644 jetty-start/src/test/resources/extra-start-dirs/more-startd/start.d/more.ini diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java index 705f8bd6e97..581bf5ad9b5 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java @@ -32,7 +32,9 @@ import java.nio.file.PathMatcher; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; +import java.util.LinkedList; import java.util.List; +import java.util.ListIterator; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -47,29 +49,94 @@ import java.util.regex.Pattern; */ public class BaseHome { + private static final String JETTY_BASE = "jetty.base"; + private static final String JETTY_HOME = "jetty.home"; + private final static EnumSet SEARCH_VISIT_OPTIONS = EnumSet.of(FileVisitOption.FOLLOW_LINKS);; private final static int MAX_SEARCH_DEPTH = Integer.getInteger("org.eclipse.jetty.start.searchDepth",10); - private Path homeDir; - private Path baseDir; + public static class SearchDir + { + private Path dir; + private String name; + + public SearchDir(String name) + { + this.name = name; + } + + public SearchDir setDir(String path) + { + if (path != null) + { + return setDir(FS.toPath(path)); + } + return this; + } + + public SearchDir setDir(File path) + { + if (path != null) + { + return setDir(path.toPath()); + } + return this; + } + + public SearchDir setDir(Path path) + { + if (path != null) + { + this.dir = path.toAbsolutePath(); + } + return this; + } + + public Path getDir() + { + return dir; + } + + public Path resolve(String subpath) + { + return dir.resolve(FS.separators(subpath)); + } + + public Path resolve(Path subpath) + { + return dir.resolve(subpath); + } + + public String toShortForm(Path path) + { + Path relative = dir.relativize(path); + return String.format("${%s}%c%s",name,File.separatorChar,relative.toString()); + } + } + + private SearchDir homeDir; + private LinkedList searchDirs = new LinkedList<>(); + private SearchDir baseDir; public BaseHome() { try { // find ${jetty.base} + this.baseDir = new SearchDir(JETTY_BASE); // default is ${user.dir} - this.baseDir = new File(System.getProperty("user.dir",".")).toPath(); + this.baseDir.setDir(System.getProperty("user.dir",".")); // if ${jetty.base} declared, use it - String jettyBase = System.getProperty("jetty.base"); + String jettyBase = System.getProperty(JETTY_BASE); if (jettyBase != null) { - this.baseDir = new File(jettyBase).toPath(); + this.baseDir.setDir(jettyBase); } // find ${jetty.home} + this.homeDir = new SearchDir(JETTY_HOME); // default location is based on lookup for BaseHome (from jetty's start.jar) URL jarfile = this.getClass().getClassLoader().getResource("org/eclipse/jetty/start/BaseHome.class"); @@ -79,23 +146,19 @@ public class BaseHome if (m.matches()) { // ${jetty.home} is relative to found BaseHome class - this.homeDir = new File(new URI(m.group(1))).getParentFile().toPath(); + this.homeDir.setDir(new File(new URI(m.group(1))).getParentFile()); } } // if we can't locate BaseHome, then assume home == base - this.homeDir = baseDir; + this.homeDir.setDir(baseDir.getDir()); // if ${jetty.home} declared, use it - String jettyHome = System.getProperty("jetty.home"); + String jettyHome = System.getProperty(JETTY_HOME); if (jettyHome != null) { - this.homeDir = new File(jettyHome).toPath(); + this.homeDir.setDir(jettyHome); } - - // resolve base and home to absolute paths - baseDir = baseDir.toAbsolutePath(); - homeDir = homeDir.toAbsolutePath(); } catch (URISyntaxException e) { @@ -105,8 +168,15 @@ public class BaseHome public BaseHome(File homeDir, File baseDir) { - this.homeDir = homeDir.toPath().toAbsolutePath(); - this.baseDir = baseDir == null?this.homeDir:baseDir.toPath().toAbsolutePath(); + this.baseDir = new SearchDir(JETTY_BASE); + this.homeDir = new SearchDir(JETTY_HOME); + + this.homeDir.setDir(homeDir); + this.baseDir.setDir(homeDir); // default + if (baseDir != null) + { + this.baseDir.setDir(baseDir); + } } public String getBase() @@ -121,7 +191,7 @@ public class BaseHome // TODO: change return type to Path public File getBaseDir() { - return baseDir.toFile(); + return baseDir.getDir().toFile(); } /** @@ -131,9 +201,10 @@ public class BaseHome * the path to reference * @return the file reference */ + // TODO: change return type to Path public File getBaseFile(String path) { - return baseDir.resolve(FS.separators(path)).toFile(); + return baseDir.resolve(path).toFile(); } /** @@ -150,6 +221,7 @@ public class BaseHome * the path to get. * @return the file reference. */ + // TODO: deprecate in favor of getPath() version public File getFile(String path) { return getPath(path).toAbsolutePath().toFile(); @@ -169,14 +241,22 @@ public class BaseHome * the path to get. * @return the file reference. */ - public Path getPath(String path) + public Path getPath(final String path) { - String rpath = FS.separators(path); - // Relative to Base Directory First if (isBaseDifferent()) { - Path file = baseDir.resolve(rpath); + Path file = baseDir.resolve(path); + if (FS.exists(file)) + { + return file; + } + } + + // Next, test for relative to all extra search paths + for (SearchDir search : searchDirs) + { + Path file = search.resolve(path); if (FS.exists(file)) { return file; @@ -184,14 +264,14 @@ public class BaseHome } // Then relative to Home Directory - Path file = homeDir.resolve(rpath); + Path file = homeDir.resolve(path); if (FS.exists(file)) { return file; } // Finally, as an absolute path - return FileSystems.getDefault().getPath(rpath); + return FS.toPath(path); } /** @@ -251,7 +331,11 @@ public class BaseHome if (PathMatchers.isAbsolute(pattern)) { + // Perform absolute path pattern search + + // The root to start search from Path root = PathMatchers.getSearchRoot(pattern); + // The matcher for file hits PathMatcher matcher = PathMatchers.getMatcher(pattern); if (FS.isValidDirectory(root)) @@ -266,6 +350,7 @@ public class BaseHome } else { + // Perform relative path pattern search Path relativePath = PathMatchers.getSearchRoot(pattern); PathMatcher matcher = PathMatchers.getMatcher(pattern); PathFinder finder = new PathFinder(); @@ -280,6 +365,18 @@ public class BaseHome Files.walkFileTree(homePath,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder); } + ListIterator iter = searchDirs.listIterator(searchDirs.size()); + while (iter.hasPrevious()) + { + SearchDir search = iter.previous(); + Path dir = search.getDir(); + if (FS.isValidDirectory(dir)) + { + finder.setBase(dir); + Files.walkFileTree(dir,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder); + } + } + if (isBaseDifferent()) { Path basePath = baseDir.resolve(relativePath); @@ -338,7 +435,7 @@ public class BaseHome // TODO: change return type to Path public File getHomeDir() { - return homeDir.toFile(); + return homeDir.getDir().toFile(); } public void initialize(StartArgs args) @@ -368,35 +465,31 @@ public class BaseHome if (homePath != null) { // logic if home is specified - this.homeDir = homePath; + this.homeDir.setDir(homePath); if (basePath == null) { - this.baseDir = homePath; - args.getProperties().setProperty("jetty.base",this.baseDir.toString(),""); + this.baseDir.setDir(homePath); + args.getProperties().setProperty(JETTY_BASE,this.baseDir.toString(),""); } else { - this.baseDir = basePath; + this.baseDir.setDir(basePath); } } else if (basePath != null) { // logic if home is undeclared - this.baseDir = basePath; + this.baseDir.setDir(basePath); } - // resolve base and home to absolute paths - baseDir = baseDir.toAbsolutePath(); - homeDir = homeDir.toAbsolutePath(); - // Update System Properties - args.addSystemProperty("jetty.home",this.homeDir.toString()); - args.addSystemProperty("jetty.base",this.baseDir.toString()); + args.addSystemProperty(JETTY_HOME,this.homeDir.toString()); + args.addSystemProperty(JETTY_BASE,this.baseDir.toString()); } public boolean isBaseDifferent() { - return homeDir.compareTo(baseDir) != 0; + return homeDir.getDir().compareTo(baseDir.getDir()) != 0; } // TODO: deprecate (in favor of Path version) @@ -407,8 +500,8 @@ public class BaseHome public void setBaseDir(Path dir) { - this.baseDir = dir.toAbsolutePath(); - System.setProperty("jetty.base",dir.toString()); + this.baseDir.setDir(dir); + System.setProperty(JETTY_BASE,dir.toString()); } // TODO: deprecate (in favor of Path version) @@ -419,8 +512,8 @@ public class BaseHome public void setHomeDir(Path dir) { - this.homeDir = dir.toAbsolutePath(); - System.setProperty("jetty.home",dir.toString()); + this.homeDir.setDir(dir); + System.setProperty(JETTY_HOME,dir.toString()); } /** @@ -437,16 +530,25 @@ public class BaseHome if (isBaseDifferent()) { // is path part of ${jetty.base} ? - if (apath.startsWith(baseDir)) + if (apath.startsWith(baseDir.getDir())) { - return "${jetty.base}" + File.separatorChar + baseDir.relativize(apath); + return baseDir.toShortForm(apath); + } + } + + // Extra search dirs + for(SearchDir search: searchDirs) + { + if(apath.startsWith(search.getDir())) + { + return search.toShortForm(apath); } } // is path part of ${jetty.home} ? - if (apath.startsWith(homeDir)) + if (apath.startsWith(homeDir.getDir())) { - return "${jetty.home}" + File.separatorChar + homeDir.relativize(apath); + return homeDir.toShortForm(apath); } return apath.toString(); @@ -476,4 +578,9 @@ public class BaseHome return toShortForm(FS.toPath(path)); } + + public void addExtraStart(String name, File dir) + { + this.searchDirs.add(new SearchDir(name).setDir(dir)); + } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java index 2419d509dc8..896298aaa95 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java @@ -86,7 +86,7 @@ public class StartArgs /** List of all xml references found directly on command line or start.ini */ private List xmlRefs = new ArrayList<>(); - + private Props properties = new Props(); private Set systemPropertyKeys = new HashSet<>(); private List rawLibs = new ArrayList<>(); diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java index bac546a4d7c..5514abd66ab 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java @@ -167,6 +167,30 @@ public class BaseHomeTest assertPathList(hb,"Paths found",expected,paths); } + + @Test + public void testGetPaths_More() throws IOException + { + File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home"); + File baseDir = MavenTestingUtils.getTestResourceDir("hb.1/base"); + File moreDir = MavenTestingUtils.getTestResourceDir("extra-start-dirs/more-startd"); + + BaseHome hb = new BaseHome(homeDir,baseDir); + hb.addExtraStart("more",moreDir); + List paths = hb.getPaths("start.d/*.ini"); + + List expected = new ArrayList<>(); + expected.add("${more}/start.d/more.ini"); + expected.add("${jetty.base}/start.d/jmx.ini"); + expected.add("${jetty.home}/start.d/jndi.ini"); + expected.add("${jetty.home}/start.d/jsp.ini"); + expected.add("${jetty.base}/start.d/logging.ini"); + expected.add("${jetty.home}/start.d/ssl.ini"); + expected.add("${jetty.base}/start.d/myapp.ini"); + FSTest.toOsSeparators(expected); + + assertPathList(hb,"Paths found",expected,paths); + } @Test public void testDefault() throws IOException diff --git a/jetty-start/src/test/resources/extra-start-dirs/logging/etc/jetty-logging.xml b/jetty-start/src/test/resources/extra-start-dirs/logging/etc/jetty-logging.xml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/jetty-start/src/test/resources/extra-start-dirs/logging/lib/logging/logback.jar b/jetty-start/src/test/resources/extra-start-dirs/logging/lib/logging/logback.jar new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/jetty-start/src/test/resources/extra-start-dirs/more-startd/start.d/more.ini b/jetty-start/src/test/resources/extra-start-dirs/more-startd/start.d/more.ini new file mode 100644 index 00000000000..e69de29bb2d From 5ecf564dfac72b675e740864289cc5336cd15110 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 9 Apr 2014 09:51:20 -0700 Subject: [PATCH 007/135] 432321 - jetty-start / Allow defining extra start directories for common configurations + Identified test cases for extra-start-dir logic + Working on layered ConfigSource approach to handling extra-start-dir --- .../jetty/start/CommandLineConfigSource.java | 49 ++ .../org/eclipse/jetty/start/ConfigSource.java | 43 ++ .../eclipse/jetty/start/DirConfigSource.java | 111 ++++ .../main/java/org/eclipse/jetty/start/FS.java | 6 + .../org/eclipse/jetty/start/StartArgs.java | 36 +- .../org/eclipse/jetty/start/StartIni.java | 49 +- .../jetty/start/ConfigurationAssert.java | 2 +- .../eclipse/jetty/start/ExtraStartTest.java | 580 ++++++++++++++++++ .../org/eclipse/jetty/start/TestUseCases.java | 6 + .../extra-start-dirs/logging/start.ini | 1 + .../assert-extra-start-dir-logging.txt | 18 + .../base.with.extra.start.dirs/start.ini | 5 + 12 files changed, 892 insertions(+), 14 deletions(-) create mode 100644 jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineConfigSource.java create mode 100644 jetty-start/src/main/java/org/eclipse/jetty/start/ConfigSource.java create mode 100644 jetty-start/src/main/java/org/eclipse/jetty/start/DirConfigSource.java create mode 100644 jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java create mode 100644 jetty-start/src/test/resources/extra-start-dirs/logging/start.ini create mode 100644 jetty-start/src/test/resources/usecases/assert-extra-start-dir-logging.txt create mode 100644 jetty-start/src/test/resources/usecases/base.with.extra.start.dirs/start.ini diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineConfigSource.java new file mode 100644 index 00000000000..4a0c8830218 --- /dev/null +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineConfigSource.java @@ -0,0 +1,49 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.start; + +import java.util.Arrays; +import java.util.List; + +/** + * Configuration Source representing the Command Line arguments. + */ +public class CommandLineConfigSource implements ConfigSource +{ + public static final String CMD_LINE_SOURCE = ""; + + private final List args; + + public CommandLineConfigSource(String rawargs[]) + { + args = Arrays.asList(rawargs); + } + + @Override + public String getId() + { + return CMD_LINE_SOURCE; + } + + @Override + public List getArgs() + { + return args; + } +} diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/ConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/ConfigSource.java new file mode 100644 index 00000000000..2809117705e --- /dev/null +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/ConfigSource.java @@ -0,0 +1,43 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.start; + +import java.util.List; + +/** + * A Configuration Source + */ +public interface ConfigSource +{ + /** + * The identifier for this source. + *

+ * Used in end-user display of the source. + * + * @return the configuration source identifier. + */ + public String getId(); + + /** + * The list of Arguments for this ConfigSource + * + * @return the list of Arguments for this ConfigSource + */ + public List getArgs(); +} diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/DirConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/DirConfigSource.java new file mode 100644 index 00000000000..366fcd61f0e --- /dev/null +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/DirConfigSource.java @@ -0,0 +1,111 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.start; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.util.ArrayList; +import java.util.List; + +/** + * A Directory based {@link ConfigSource}. + *

+ * Such as ${jetty.base} or and --extra-start-dir=[path] sources. + */ +public class DirConfigSource implements ConfigSource +{ + private final String id; + private final Path dir; + private final List args; + + /** + * Create DirConfigSource with specified identifier and directory. + * + * @param id + * the identifier for this {@link ConfigSource} + * @param dir + * the directory for this {@link ConfigSource} + * @param canHaveArgs + * true if this directory can have start.ini or start.d entries. (false for directories like ${jetty.home}, for example) + * @throws IOException + * if unable to load the configuration args + */ + public DirConfigSource(String id, Path dir, boolean canHaveArgs) throws IOException + { + this.id = id; + this.dir = dir; + + this.args = new ArrayList<>(); + + if (canHaveArgs) + { + Path iniFile = dir.resolve("start.ini"); + if (FS.canReadFile(iniFile)) + { + StartIni ini = new StartIni(iniFile); + args.addAll(ini.getArgs()); + } + + Path startDdir = dir.resolve("start.d"); + + if (FS.canReadDirectory(startDdir)) + { + DirectoryStream.Filter filter = new DirectoryStream.Filter() + { + PathMatcher iniMatcher = PathMatchers.getMatcher("glob:**/start.d/*.ini"); + + @Override + public boolean accept(Path entry) throws IOException + { + return iniMatcher.matches(entry); + } + }; + + for (Path diniFile : Files.newDirectoryStream(startDdir,filter)) + { + if (FS.canReadFile(diniFile)) + { + StartIni ini = new StartIni(diniFile); + args.addAll(ini.getArgs()); + } + } + } + } + } + + public Path getDir() + { + return dir; + } + + @Override + public String getId() + { + return id; + } + + @Override + public List getArgs() + { + return args; + } +} diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java index 270dd08f0ff..87c4a76c028 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java @@ -175,6 +175,12 @@ public class FS { return (path.exists() && path.isFile() && path.canRead()); } + + public static boolean canReadFile(Path path) + { + LinkOption lopts[] = new LinkOption[0]; + return Files.exists(path,lopts) && Files.isRegularFile(path,lopts) && Files.isReadable(path); + } public static void close(Closeable c) { diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java index 896298aaa95..09f34d8c015 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java @@ -29,6 +29,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -87,6 +88,9 @@ public class StartArgs /** List of all xml references found directly on command line or start.ini */ private List xmlRefs = new ArrayList<>(); + /** List of extra Start Directories referenced */ + private LinkedList extraStartRefs = new LinkedList<>(); + private Props properties = new Props(); private Set systemPropertyKeys = new HashSet<>(); private List rawLibs = new ArrayList<>(); @@ -410,6 +414,11 @@ public class StartArgs { return this.commandLine; } + + public LinkedList getExtraStartRefs() + { + return extraStartRefs; + } public List getFiles() { @@ -646,18 +655,19 @@ public class StartArgs return version; } - public void parse(BaseHome baseHome, TextFile file) + public void parse(BaseHome baseHome, StartIni ini) { String source; try { - source = baseHome.toShortForm(file.getFile()); + source = baseHome.toShortForm(ini.getFile()); } catch (Exception e) { - throw new UsageException(ERR_BAD_ARG,"Bad file: %s",file); + throw new UsageException(ERR_BAD_ARG,"Bad file: %s",ini); } - for (String line : file) + + for (String line : ini) { parse(line,source); } @@ -751,14 +761,22 @@ public class StartArgs return; } + // Enable forked execution of Jetty server if ("--exec".equals(arg)) { exec = true; return; } - // Arbitrary Libraries + // Add extra start dir + if (arg.startsWith("--extra-start-dir=")) + { + String dirRef = getValue(arg); + extraStartRefs.add(dirRef); + return; + } + // Arbitrary Libraries if (arg.startsWith("--lib=")) { String cp = getValue(arg); @@ -782,7 +800,8 @@ public class StartArgs return; } - if (arg.startsWith("--add-to-startd")) + // jetty.base build-out : add to ${jetty.base}/start.d/ + if (arg.startsWith("--add-to-startd=")) { if (!CMD_LINE_SOURCE.equals(source)) { @@ -794,7 +813,8 @@ public class StartArgs return; } - if (arg.startsWith("--add-to-start")) + // jetty.base build-out : add to ${jetty.base}/start.ini + if (arg.startsWith("--add-to-start=")) { if (!CMD_LINE_SOURCE.equals(source)) { @@ -806,6 +826,7 @@ public class StartArgs return; } + // Enable a module if (arg.startsWith("--module=")) { for (String moduleName : getValues(arg)) @@ -822,6 +843,7 @@ public class StartArgs return; } + // Create graphviz output of module graph if (arg.startsWith("--write-module-graph=")) { this.moduleGraphFilename = getValue(arg); diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java index 192e74adfdb..5f712d80529 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java @@ -19,21 +19,23 @@ package org.eclipse.jetty.start; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; +import java.util.List; /** * Simple Start .INI handler */ -public class StartIni extends TextFile +public class StartIni extends TextFile implements ConfigSource { - public StartIni(File file) throws FileNotFoundException, IOException + private Path basedir; + + public StartIni(File file) throws IOException { super(file); } - public StartIni(Path path) throws FileNotFoundException, IOException + public StartIni(Path path) throws IOException { this(path.toFile()); } @@ -47,12 +49,47 @@ public class StartIni extends TextFile String value = line.substring(idx + 1); for (String part : value.split(",")) { - super.addUniqueLine("--module=" + part); + super.addUniqueLine("--module=" + expandBaseDir(part)); } } else { - super.addUniqueLine(line); + super.addUniqueLine(expandBaseDir(line)); } } + + private String expandBaseDir(String line) + { + if (line == null) + { + return line; + } + + return line.replace("${start.basedir}",basedir.toString()); + } + + @Override + public void init() + { + basedir = getFile().getParentFile().toPath().toAbsolutePath(); + } + + public Path getBaseDir() + { + return basedir; + } + + @Override + public String getId() + { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getArgs() + { + // TODO Auto-generated method stub + return null; + } } diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java index f3ba3a86df0..5ad4a238170 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java @@ -206,7 +206,7 @@ public class ConfigurationAssert } } - private static void assertOrdered(String msg, List expectedList, List actualList) + public static void assertOrdered(String msg, List expectedList, List actualList) { // same size? boolean mismatch = expectedList.size() != actualList.size(); diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java new file mode 100644 index 00000000000..0656879cbd1 --- /dev/null +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java @@ -0,0 +1,580 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.start; + +import static org.hamcrest.Matchers.*; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.start.Props.Prop; +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.IO; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.OS; +import org.eclipse.jetty.toolchain.test.TestingDir; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; + +public class ExtraStartTest +{ + private static class MainResult + { + private Main main; + private StartArgs args; + + public void assertSearchOrder(List expectedSearchOrder) + { + List actualOrder = new ArrayList<>(); + actualOrder.add("${jetty.base}"); + List startRefs = args.getExtraStartRefs(); + if (startRefs.size() > 0) + { + actualOrder.addAll(startRefs); + } + actualOrder.add("${jetty.home}"); + ConfigurationAssert.assertOrdered("Search Order",expectedSearchOrder,actualOrder); + } + + public void assertProperty(String key, String expectedValue) + { + Prop prop = args.getProperties().getProp(key); + String prefix = "Prop[" + key + "]"; + Assert.assertThat(prefix + " should have a value",prop,notNullValue()); + Assert.assertThat(prefix + " value",prop.value,is(expectedValue)); + } + } + + @Rule + public TestingDir testdir = new TestingDir(); + + private void copyTestDir(String testResourceDir, File destDir) throws IOException + { + FS.ensureDirExists(destDir); + File srcDir = MavenTestingUtils.getTestResourceDir(testResourceDir); + IO.copyDir(srcDir,destDir); + } + + private void makeFile(File dir, String relFilePath, String... contents) throws IOException + { + File outputFile = new File(dir,OS.separators(relFilePath)); + FS.ensureDirExists(outputFile.getParentFile()); + try (FileWriter writer = new FileWriter(outputFile); PrintWriter out = new PrintWriter(writer)) + { + for (String content : contents) + { + out.println(content); + } + } + } + + private MainResult runMain(File baseDir, File homeDir, String... cmdLineArgs) throws Exception + { + MainResult ret = new MainResult(); + ret.main = new Main(); + List cmdLine = new ArrayList<>(); + cmdLine.add("jetty.home=" + homeDir.getAbsolutePath()); + cmdLine.add("jetty.base=" + baseDir.getAbsolutePath()); + // cmdLine.add("--debug"); + for (String arg : cmdLineArgs) + { + cmdLine.add(arg); + } + ret.args = ret.main.processCommandLine(cmdLine); + return ret; + } + + @Test + public void testNoExtras() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1"); + + // Simple command line - no reference to extra-start-dirs + MainResult result = runMain(base,home); + + List expectedSearchOrder = new ArrayList<>(); + expectedSearchOrder.add("${jetty.base}"); + expectedSearchOrder.add("${jetty.home}"); + result.assertSearchOrder(expectedSearchOrder); + + result.assertProperty("jetty.host","127.0.0.1"); + } + + @Test + public void testCommandLine_1Extra() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + makeFile(common,"start.ini","jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1"); + + // Simple command line reference to extra-start-dir + MainResult result = runMain(base,home, + // direct reference via path + "--extra-start-dir=" + common.getAbsolutePath()); + + List expectedSearchOrder = new ArrayList<>(); + expectedSearchOrder.add("${jetty.base}"); + expectedSearchOrder.add(common.getAbsolutePath()); + expectedSearchOrder.add("${jetty.home}"); + result.assertSearchOrder(expectedSearchOrder); + + result.assertProperty("jetty.host","127.0.0.1"); + result.assertProperty("jetty.port","8080"); // from 'common' + } + + @Test + public void testCommandLine_1Extra_FromSimpleProp() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + makeFile(common,"start.ini","jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1"); + + // Simple command line reference to extra-start-dir via property (also on command line) + MainResult result = runMain(base,home, + // property + "my.common=" + common.getAbsolutePath(), + // reference via property + "--extra-start-dir=${my.common}"); + + List expectedSearchOrder = new ArrayList<>(); + expectedSearchOrder.add("${jetty.base}"); + expectedSearchOrder.add("${my.common}"); // should see property use + expectedSearchOrder.add("${jetty.home}"); + result.assertSearchOrder(expectedSearchOrder); + + result.assertProperty("jetty.host","127.0.0.1"); + result.assertProperty("jetty.port","8080"); // from 'common' + } + + @Test + public void testCommandLine_1Extra_FromPropPrefix() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create opt + File opt = testdir.getFile("opt"); + FS.ensureEmpty(opt); + + // Create common + File common = new File(opt, "common"); + FS.ensureEmpty(common); + makeFile(common,"start.ini","jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1"); + + String dirRef = "${my.opt}" + File.separator + "common"; + + // Simple command line reference to extra-start-dir via property (also on command line) + MainResult result = runMain(base,home, + // property to 'opt' dir + "my.opt=" + opt.getAbsolutePath(), + // reference via property prefix + "--extra-start-dir=" + dirRef); + + List expectedSearchOrder = new ArrayList<>(); + expectedSearchOrder.add("${jetty.base}"); + expectedSearchOrder.add(dirRef); // should use property + expectedSearchOrder.add("${jetty.home}"); + result.assertSearchOrder(expectedSearchOrder); + + result.assertProperty("jetty.host","127.0.0.1"); + result.assertProperty("jetty.port","8080"); // from 'common' + } + + @Test + public void testCommandLine_1Extra_FromCompoundProp() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create opt + File opt = testdir.getFile("opt"); + FS.ensureEmpty(opt); + + // Create common + File common = new File(opt, "common"); + FS.ensureEmpty(common); + makeFile(common,"start.ini","jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1"); + + String dirRef = "${my.opt}" + File.separator + "${my.dir}"; + + // Simple command line reference to extra-start-dir via property (also on command line) + MainResult result = runMain(base,home, + // property to 'opt' dir + "my.opt=" + opt.getAbsolutePath(), + // property to commmon dir name + "my.dir=common", + // reference via property prefix + "--extra-start-dir=" + dirRef); + + List expectedSearchOrder = new ArrayList<>(); + expectedSearchOrder.add("${jetty.base}"); + expectedSearchOrder.add(dirRef); // should use property + expectedSearchOrder.add("${jetty.home}"); + result.assertSearchOrder(expectedSearchOrder); + + result.assertProperty("jetty.host","127.0.0.1"); + result.assertProperty("jetty.port","8080"); // from 'common' + } + + @Test + public void testRefCommon() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath()); + + MainResult result = runMain(base,home); + + List expectedSearchOrder = new ArrayList<>(); + expectedSearchOrder.add("${jetty.base}"); + expectedSearchOrder.add(common.getAbsolutePath()); + expectedSearchOrder.add("${jetty.home}"); + result.assertSearchOrder(expectedSearchOrder); + + result.assertProperty("jetty.host","127.0.0.1"); + result.assertProperty("jetty.port","8080"); // from 'common' + } + + @Test + public void testRefCommonAndCorp() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath(), // + "--extra-start-dir=" + corp.getAbsolutePath()); + + MainResult result = runMain(base,home); + + List expectedSearchOrder = new ArrayList<>(); + expectedSearchOrder.add("${jetty.base}"); + expectedSearchOrder.add(common.getAbsolutePath()); + expectedSearchOrder.add(corp.getAbsolutePath()); + expectedSearchOrder.add("${jetty.home}"); + result.assertSearchOrder(expectedSearchOrder); + + result.assertProperty("jetty.host","127.0.0.1"); + result.assertProperty("jetty.port","8080"); // from 'common' + } + + @Test + public void testRefCommonRefCorp() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + makeFile(corp,"start.ini", // + "jetty.port=9090"); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + makeFile(common,"start.ini", // + "--extra-start-dir=" + corp.getAbsolutePath(), // + "jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath()); + + MainResult result = runMain(base,home); + + List expectedSearchOrder = new ArrayList<>(); + expectedSearchOrder.add("${jetty.base}"); + expectedSearchOrder.add(common.getAbsolutePath()); + expectedSearchOrder.add(corp.getAbsolutePath()); + expectedSearchOrder.add("${jetty.home}"); + result.assertSearchOrder(expectedSearchOrder); + + result.assertProperty("jetty.host","127.0.0.1"); + result.assertProperty("jetty.port","8080"); // from 'common' + } + + @Test + public void testRefCommonRefCorp_FromSimpleProps() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + makeFile(corp,"start.ini", // + "jetty.port=9090"); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + makeFile(common,"start.ini", // + "my.corp=" + corp.getAbsolutePath(), // + "--extra-start-dir=${my.corp}", // + "jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "my.common="+common.getAbsolutePath(), // + "--extra-start-dir=${my.common}"); + + MainResult result = runMain(base,home); + + List expectedSearchOrder = new ArrayList<>(); + expectedSearchOrder.add("${jetty.base}"); + expectedSearchOrder.add("${my.common}"); + expectedSearchOrder.add("${my.corp}"); + expectedSearchOrder.add("${jetty.home}"); + result.assertSearchOrder(expectedSearchOrder); + + result.assertProperty("jetty.host","127.0.0.1"); + result.assertProperty("jetty.port","8080"); // from 'common' + } + + @Test + public void testRefCommonRefCorp_CmdLineRef() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create devops + File devops = testdir.getFile("devops"); + FS.ensureEmpty(devops); + makeFile(devops,"start.ini", // + "--module=logging", // + "jetty.port=2222"); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + makeFile(corp,"start.ini", // + "jetty.port=9090"); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + makeFile(common,"start.ini", // + "--extra-start-dir=" + corp.getAbsolutePath(), // + "jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath()); + + MainResult result = runMain(base,home, + // command line provided extra-start-dir ref + "--extra-start-dir=" + devops.getAbsolutePath()); + + List expectedSearchOrder = new ArrayList<>(); + expectedSearchOrder.add("${jetty.base}"); + expectedSearchOrder.add(devops.getAbsolutePath()); + expectedSearchOrder.add(common.getAbsolutePath()); + expectedSearchOrder.add(corp.getAbsolutePath()); + expectedSearchOrder.add("${jetty.home}"); + result.assertSearchOrder(expectedSearchOrder); + + result.assertProperty("jetty.host","127.0.0.1"); + result.assertProperty("jetty.port","2222"); // from 'devops' + } + + @Test + public void testRefCommonRefCorp_CmdLineProp() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + makeFile(corp,"start.ini", // + "jetty.port=9090"); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + makeFile(common,"start.ini", // + "--extra-start-dir=" + corp.getAbsolutePath(), // + "jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath()); + + MainResult result = runMain(base,home, + // command line property should override all others + "jetty.port=7070"); + + List expectedSearchOrder = new ArrayList<>(); + expectedSearchOrder.add("${jetty.base}"); + expectedSearchOrder.add(common.getAbsolutePath()); + expectedSearchOrder.add(corp.getAbsolutePath()); + expectedSearchOrder.add("${jetty.home}"); + result.assertSearchOrder(expectedSearchOrder); + + result.assertProperty("jetty.host","127.0.0.1"); + result.assertProperty("jetty.port","7070"); // from command line + } + + @Test + public void testBadDoubleRef() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + copyTestDir("usecases/home",home); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + makeFile(corp,"start.ini", + // standard property + "jetty.port=9090", + // INTENTIONAL BAD Reference (duplicate) + "--extra-start-dir=" + common.getAbsolutePath()); + + // Populate common + makeFile(common,"start.ini", + // standard property + "jetty.port=8080", + // reference to corp + "--extra-start-dir=" + corp.getAbsolutePath()); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath()); + + try + { + runMain(base,home); + Assert.fail("Should have thrown a UsageException"); + } + catch (UsageException e) + { + Assert.assertThat("UsageException",e.getMessage(),containsString("Duplicate")); + } + } +} diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java b/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java index 28ce62f71c0..5e0e3c83c37 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/TestUseCases.java @@ -60,6 +60,12 @@ public class TestUseCases { assertUseCase("home","base.jmx","assert-jmx.txt"); } + + @Test + public void testWithExtraStartDir_Logging() throws Exception + { + assertUseCase("home","base.with.extra.start.dirs","assert-extra-start-dir-logging.txt"); + } @Test public void testWithMissingNpnVersion() throws Exception diff --git a/jetty-start/src/test/resources/extra-start-dirs/logging/start.ini b/jetty-start/src/test/resources/extra-start-dirs/logging/start.ini new file mode 100644 index 00000000000..905d6db4bb0 --- /dev/null +++ b/jetty-start/src/test/resources/extra-start-dirs/logging/start.ini @@ -0,0 +1 @@ +--module=logging \ No newline at end of file diff --git a/jetty-start/src/test/resources/usecases/assert-extra-start-dir-logging.txt b/jetty-start/src/test/resources/usecases/assert-extra-start-dir-logging.txt new file mode 100644 index 00000000000..def27d7da7f --- /dev/null +++ b/jetty-start/src/test/resources/usecases/assert-extra-start-dir-logging.txt @@ -0,0 +1,18 @@ +# The XMLs we expect (order is important) +XML|${jetty.home}/etc/jetty-jmx.xml +XML|${jetty.home}/etc/jetty.xml +XML|${jetty.home}/etc/jetty-http.xml + +# The LIBs we expect (order is irrelevant) +LIB|${jetty.home}/lib/jetty-continuation-TEST.jar +LIB|${jetty.home}/lib/jetty-http-TEST.jar +LIB|${jetty.home}/lib/jetty-io-TEST.jar +LIB|${jetty.home}/lib/jetty-jmx-TEST.jar +LIB|${jetty.home}/lib/jetty-schemas-3.1.jar +LIB|${jetty.home}/lib/jetty-server-TEST.jar +LIB|${jetty.home}/lib/jetty-util-TEST.jar +LIB|${jetty.home}/lib/jetty-xml-TEST.jar +LIB|${jetty.home}/lib/servlet-api-3.1.jar + +# The Properties we expect (order is irrelevant) +PROP|jetty.port=9090 diff --git a/jetty-start/src/test/resources/usecases/base.with.extra.start.dirs/start.ini b/jetty-start/src/test/resources/usecases/base.with.extra.start.dirs/start.ini new file mode 100644 index 00000000000..81e6d724e6a --- /dev/null +++ b/jetty-start/src/test/resources/usecases/base.with.extra.start.dirs/start.ini @@ -0,0 +1,5 @@ + +--extra-start-dir=${start.basedir}/../../extra-start-dirs/logging +--module=server,http,jmx + +jetty.port=9090 From 1b9f0cb945a98c3d88d8020278edae5a55e3fbf2 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 9 Apr 2014 10:02:28 -0700 Subject: [PATCH 008/135] Cleanup of Main.moduleIni() to .buildIni() with better documentation --- .../java/org/eclipse/jetty/start/Main.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java index 3b9dd44623a..20b9fc8f6c0 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java @@ -334,7 +334,20 @@ public class Main modules.dumpEnabledTree(); } - private void moduleIni(StartArgs args, String name, boolean topLevel, boolean appendStartIni) throws IOException + /** + * Build out INI file. + *

+ * This applies equally for either ${jetty.base}/start.ini or + * ${jetty.base}/start.d/${name}.ini + * + * @param args the arguments of what modules are enabled + * @param name the name of the module to based the build of the ini + * @param topLevel + * @param appendStartIni true to append to ${jetty.base}/start.ini, + * false to create a ${jetty.base}/start.d/${name}.ini entry instead. + * @throws IOException + */ + private void buildIni(StartArgs args, String name, boolean topLevel, boolean appendStartIni) throws IOException { // Find the start.d relative to the base directory only. File start_d = baseHome.getBaseFile("start.d"); @@ -460,7 +473,9 @@ public class Main StartLog.info("%-15s initialised in %s",name,short_ini); } else + { StartLog.info("%-15s initialised transitively",name); + } // Also list other places this module is enabled for (String source : module.getSources()) @@ -508,7 +523,7 @@ public class Main if (!done.contains(m.getName())) { complete=false; - moduleIni(args,m.getName(),false,appendStartIni); + buildIni(args,m.getName(),false,appendStartIni); done.add(m.getName()); } } @@ -685,13 +700,13 @@ public class Main // Initialize start.ini for (String module : args.getAddToStartIni()) { - moduleIni(args,module,true,true); + buildIni(args,module,true,true); } // Initialize start.d for (String module : args.getAddToStartdIni()) { - moduleIni(args,module,true,false); + buildIni(args,module,true,false); } // Check ini files for download possibilities From 8593ce58206cee0e8ccaa613aec96cad2f2df175 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Thu, 10 Apr 2014 09:34:39 +1000 Subject: [PATCH 009/135] 432473 web.xml declaration order of filters not preserved on calls to init() --- .../webapp/StandardDescriptorProcessor.java | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java index 43c33480374..06cd46f9ad5 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java @@ -70,9 +70,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor public static final String STANDARD_PROCESSOR = "org.eclipse.jetty.standardDescriptorProcessor"; - final Map _filterHolders = new HashMap<>(); + final Map _filterHolderMap = new HashMap<>(); + final List _filterHolders = new ArrayList<>(); final List _filterMappings = new ArrayList<>(); - final Map _servletHolders = new HashMap<>(); + final Map _servletHolderMap = new HashMap<>(); + final List _servletHolders = new ArrayList<>(); final List _servletMappings = new ArrayList<>(); public StandardDescriptorProcessor () @@ -113,11 +115,17 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor public void start(WebAppContext context, Descriptor descriptor) { for (FilterHolder h : context.getServletHandler().getFilters()) - _filterHolders.put(h.getName(),h); + { + _filterHolderMap.put(h.getName(),h); + _filterHolders.add(h); + } if (context.getServletHandler().getFilterMappings()!=null) _filterMappings.addAll(Arrays.asList(context.getServletHandler().getFilterMappings())); for (ServletHolder h : context.getServletHandler().getServlets()) - _servletHolders.put(h.getName(),h); + { + _servletHolderMap.put(h.getName(),h); + _servletHolders.add(h); + } if (context.getServletHandler().getServletMappings()!=null) _servletMappings.addAll(Arrays.asList(context.getServletHandler().getServletMappings())); } @@ -128,14 +136,16 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor */ public void end(WebAppContext context, Descriptor descriptor) { - context.getServletHandler().setFilters(_filterHolders.values().toArray(new FilterHolder[_filterHolders.size()])); - context.getServletHandler().setServlets(_servletHolders.values().toArray(new ServletHolder[_servletHolders.size()])); + context.getServletHandler().setFilters(_filterHolders.toArray(new FilterHolder[_filterHolderMap.size()])); + context.getServletHandler().setServlets(_servletHolders.toArray(new ServletHolder[_servletHolderMap.size()])); context.getServletHandler().setFilterMappings(_filterMappings.toArray(new FilterMapping[_filterMappings.size()])); context.getServletHandler().setServletMappings(_servletMappings.toArray(new ServletMapping[_servletMappings.size()])); + _filterHolderMap.clear(); _filterHolders.clear(); _filterMappings.clear(); + _servletHolderMap.clear(); _servletHolders.clear(); _servletMappings.clear(); } @@ -217,14 +227,15 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor // initialize holder String name = node.getString("servlet-name", false, true); - ServletHolder holder = _servletHolders.get(name); + ServletHolder holder = _servletHolderMap.get(name); //If servlet of that name does not already exist, create it. if (holder == null) { holder = context.getServletHandler().newServletHolder(Source.DESCRIPTOR); holder.setName(name); - _servletHolders.put(name,holder); + _servletHolderMap.put(name,holder); + _servletHolders.add(holder); } // init params @@ -1401,11 +1412,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor if (paths.size() > 0) { ServletHandler handler = context.getServletHandler(); - ServletHolder jsp_pg_servlet = _servletHolders.get(JspPropertyGroupServlet.NAME); + ServletHolder jsp_pg_servlet = _servletHolderMap.get(JspPropertyGroupServlet.NAME); if (jsp_pg_servlet==null) { jsp_pg_servlet=new ServletHolder(JspPropertyGroupServlet.NAME,new JspPropertyGroupServlet(context,handler)); - _servletHolders.put(JspPropertyGroupServlet.NAME,jsp_pg_servlet); + _servletHolderMap.put(JspPropertyGroupServlet.NAME,jsp_pg_servlet); } ServletMapping mapping = new ServletMapping(); @@ -1721,12 +1732,13 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor protected void visitFilter(WebAppContext context, Descriptor descriptor, XmlParser.Node node) { String name = node.getString("filter-name", false, true); - FilterHolder holder = _filterHolders.get(name); + FilterHolder holder = _filterHolderMap.get(name); if (holder == null) { holder = context.getServletHandler().newFilterHolder(Source.DESCRIPTOR); holder.setName(name); - _filterHolders.put(name,holder); + _filterHolderMap.put(name,holder); + _filterHolders.add(holder); } String filter_class = node.getString("filter-class", false, true); From 268ca92ce2e8135c02edb9170e7bc9e9946fe89e Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Thu, 10 Apr 2014 09:34:39 +1000 Subject: [PATCH 010/135] 432473 web.xml declaration order of filters not preserved on calls to init() --- .../webapp/StandardDescriptorProcessor.java | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java index 43c33480374..06cd46f9ad5 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java @@ -70,9 +70,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor public static final String STANDARD_PROCESSOR = "org.eclipse.jetty.standardDescriptorProcessor"; - final Map _filterHolders = new HashMap<>(); + final Map _filterHolderMap = new HashMap<>(); + final List _filterHolders = new ArrayList<>(); final List _filterMappings = new ArrayList<>(); - final Map _servletHolders = new HashMap<>(); + final Map _servletHolderMap = new HashMap<>(); + final List _servletHolders = new ArrayList<>(); final List _servletMappings = new ArrayList<>(); public StandardDescriptorProcessor () @@ -113,11 +115,17 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor public void start(WebAppContext context, Descriptor descriptor) { for (FilterHolder h : context.getServletHandler().getFilters()) - _filterHolders.put(h.getName(),h); + { + _filterHolderMap.put(h.getName(),h); + _filterHolders.add(h); + } if (context.getServletHandler().getFilterMappings()!=null) _filterMappings.addAll(Arrays.asList(context.getServletHandler().getFilterMappings())); for (ServletHolder h : context.getServletHandler().getServlets()) - _servletHolders.put(h.getName(),h); + { + _servletHolderMap.put(h.getName(),h); + _servletHolders.add(h); + } if (context.getServletHandler().getServletMappings()!=null) _servletMappings.addAll(Arrays.asList(context.getServletHandler().getServletMappings())); } @@ -128,14 +136,16 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor */ public void end(WebAppContext context, Descriptor descriptor) { - context.getServletHandler().setFilters(_filterHolders.values().toArray(new FilterHolder[_filterHolders.size()])); - context.getServletHandler().setServlets(_servletHolders.values().toArray(new ServletHolder[_servletHolders.size()])); + context.getServletHandler().setFilters(_filterHolders.toArray(new FilterHolder[_filterHolderMap.size()])); + context.getServletHandler().setServlets(_servletHolders.toArray(new ServletHolder[_servletHolderMap.size()])); context.getServletHandler().setFilterMappings(_filterMappings.toArray(new FilterMapping[_filterMappings.size()])); context.getServletHandler().setServletMappings(_servletMappings.toArray(new ServletMapping[_servletMappings.size()])); + _filterHolderMap.clear(); _filterHolders.clear(); _filterMappings.clear(); + _servletHolderMap.clear(); _servletHolders.clear(); _servletMappings.clear(); } @@ -217,14 +227,15 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor // initialize holder String name = node.getString("servlet-name", false, true); - ServletHolder holder = _servletHolders.get(name); + ServletHolder holder = _servletHolderMap.get(name); //If servlet of that name does not already exist, create it. if (holder == null) { holder = context.getServletHandler().newServletHolder(Source.DESCRIPTOR); holder.setName(name); - _servletHolders.put(name,holder); + _servletHolderMap.put(name,holder); + _servletHolders.add(holder); } // init params @@ -1401,11 +1412,11 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor if (paths.size() > 0) { ServletHandler handler = context.getServletHandler(); - ServletHolder jsp_pg_servlet = _servletHolders.get(JspPropertyGroupServlet.NAME); + ServletHolder jsp_pg_servlet = _servletHolderMap.get(JspPropertyGroupServlet.NAME); if (jsp_pg_servlet==null) { jsp_pg_servlet=new ServletHolder(JspPropertyGroupServlet.NAME,new JspPropertyGroupServlet(context,handler)); - _servletHolders.put(JspPropertyGroupServlet.NAME,jsp_pg_servlet); + _servletHolderMap.put(JspPropertyGroupServlet.NAME,jsp_pg_servlet); } ServletMapping mapping = new ServletMapping(); @@ -1721,12 +1732,13 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor protected void visitFilter(WebAppContext context, Descriptor descriptor, XmlParser.Node node) { String name = node.getString("filter-name", false, true); - FilterHolder holder = _filterHolders.get(name); + FilterHolder holder = _filterHolderMap.get(name); if (holder == null) { holder = context.getServletHandler().newFilterHolder(Source.DESCRIPTOR); holder.setName(name); - _filterHolders.put(name,holder); + _filterHolderMap.put(name,holder); + _filterHolders.add(holder); } String filter_class = node.getString("filter-class", false, true); From a5bd46fd57ef717c816cddeb769606d7e93f0f5b Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Thu, 10 Apr 2014 11:23:13 +1000 Subject: [PATCH 011/135] 409105 Upgrade jetty-osgi build/test to use more recent pax junit test framework --- jetty-osgi/jetty-osgi-httpservice/pom.xml | 1 + jetty-osgi/test-jetty-osgi-context/pom.xml | 2 + jetty-osgi/test-jetty-osgi-webapp/pom.xml | 2 + jetty-osgi/test-jetty-osgi/pom.xml | 210 ++++++++---------- .../TestJettyOSGiBootContextAsService.java | 38 +--- .../osgi/test/TestJettyOSGiBootCore.java | 26 ++- .../osgi/test/TestJettyOSGiBootSpdy.java | 40 +--- .../TestJettyOSGiBootWebAppAsService.java | 30 +-- .../osgi/test/TestJettyOSGiBootWithJsp.java | 51 +---- .../eclipse/jetty/osgi/test/TestOSGiUtil.java | 19 +- 10 files changed, 154 insertions(+), 265 deletions(-) diff --git a/jetty-osgi/jetty-osgi-httpservice/pom.xml b/jetty-osgi/jetty-osgi-httpservice/pom.xml index 3749cab43e7..91087d7bcca 100644 --- a/jetty-osgi/jetty-osgi-httpservice/pom.xml +++ b/jetty-osgi/jetty-osgi-httpservice/pom.xml @@ -28,6 +28,7 @@ org.eclipse.osgi org.eclipse.osgi + provided javax.servlet diff --git a/jetty-osgi/test-jetty-osgi-context/pom.xml b/jetty-osgi/test-jetty-osgi-context/pom.xml index 4e1c28a342b..941b13bfb3c 100644 --- a/jetty-osgi/test-jetty-osgi-context/pom.xml +++ b/jetty-osgi/test-jetty-osgi-context/pom.xml @@ -21,10 +21,12 @@ org.eclipse.osgi org.eclipse.osgi + provided org.eclipse.osgi org.eclipse.osgi.services + provided org.eclipse.jetty.toolchain diff --git a/jetty-osgi/test-jetty-osgi-webapp/pom.xml b/jetty-osgi/test-jetty-osgi-webapp/pom.xml index 00697afd6dd..1a0f37b6655 100644 --- a/jetty-osgi/test-jetty-osgi-webapp/pom.xml +++ b/jetty-osgi/test-jetty-osgi-webapp/pom.xml @@ -21,10 +21,12 @@ org.eclipse.osgi org.eclipse.osgi + provided org.eclipse.osgi org.eclipse.osgi.services + provided diff --git a/jetty-osgi/test-jetty-osgi/pom.xml b/jetty-osgi/test-jetty-osgi/pom.xml index 5cc1640678c..cf6810a052a 100644 --- a/jetty-osgi/test-jetty-osgi/pom.xml +++ b/jetty-osgi/test-jetty-osgi/pom.xml @@ -14,102 +14,36 @@ ${project.groupId}.boot.test.spdy http://download.eclipse.org/jetty/orbit/ target/distribution - 2.6.0 - 1.4.0 - 1.5.1 - 4.0.3 + 3.4.0 + 1.5.2 1.0 - 1.7.6 + 1.8.5 - - - org.ops4j.pax.swissbox - pax-swissbox-core - ${paxswissbox.version} - test - - - org.ops4j.pax.swissbox - pax-swissbox-extender - ${paxswissbox.version} - test - - - org.ops4j.pax.swissbox - pax-swissbox-lifecycle - ${paxswissbox.version} - test - - - org.ops4j.pax.swissbox - pax-swissbox-framework - ${paxswissbox.version} - test - + org.ops4j.pax.exam pax-exam ${exam.version} test - - org.apache.geronimo.specs - geronimo-atinject_1.0_spec - ${injection.bundle.version} - test - org.ops4j.pax.exam pax-exam-inject ${exam.version} test - - org.apache.aries.spifly - org.apache.aries.spifly.dynamic.bundle - 1.0.0 - test - - - - - org.ops4j.pax.exam pax-exam-container-forked ${exam.version} test - --> - - - org.ops4j.pax.exam - pax-exam-container-paxrunner - ${exam.version} - test - - - - org.ops4j.pax.runner - pax-runner-no-jcl - ${runner.version} - test - - + org.ops4j.pax.exam pax-exam-junit4 @@ -134,28 +68,81 @@ ${url.version} test + + - + + org.eclipse + osgi + 3.9.1-v20140110-1610 + test + + + org.eclipse.osgi + org.eclipse.osgi.services + test + + + + + + org.eclipse.jetty.osgi + jetty-osgi-boot + ${project.version} + test + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + + + org.eclipse.jetty.osgi + jetty-osgi-boot-jsp + ${project.version} + test + + + org.eclipse.osgi + org.eclipse.osgi + + + org.eclipse.osgi + org.eclipse.osgi.services + + + + + org.eclipse.jetty.toolchain + jetty-jsp-fragment + 2.3.3 + test + + + org.eclipse.jetty.osgi + jetty-httpservice + ${project.version} + test + + javax.servlet javax.servlet-api @@ -167,47 +154,34 @@ 1.1.1 test - - - org.eclipse.jetty.osgi - jetty-osgi-boot - ${project.version} - provided - - - org.eclipse.jetty.osgi - jetty-osgi-boot-jsp - ${project.version} - provided - - - org.eclipse.jetty.toolchain - jetty-jsp-fragment - 2.3.3 - provided - - - org.eclipse.jetty.osgi - jetty-httpservice - ${project.version} - provided - - + + org.apache.geronimo.specs + geronimo-atinject_1.0_spec + ${injection.bundle.version} + test + + + org.apache.aries.spifly + org.apache.aries.spifly.dynamic.bundle + 1.0.0 + test + + org.ow2.asm asm 4.1 - - + + org.ow2.asm asm-commons 4.1 - - + + org.ow2.asm asm-tree 4.1 - + @@ -386,7 +360,9 @@ ${project.version} runtime + + javax.servlet servlet-api @@ -409,6 +384,9 @@ servlet runtime + +--> + org.eclipse.jetty test-jetty-webapp diff --git a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java index 94ecd39df48..0dfc1bb0e18 100644 --- a/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java +++ b/jetty-osgi/test-jetty-osgi/src/test/java/org/eclipse/jetty/osgi/test/TestJettyOSGiBootContextAsService.java @@ -37,12 +37,13 @@ import org.eclipse.jetty.client.api.ContentResponse; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.osgi.boot.OSGiServerConstants; import org.eclipse.jetty.server.handler.ContextHandler; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.CoreOptions; import org.ops4j.pax.exam.Option; -import org.ops4j.pax.exam.junit.Configuration; -import org.ops4j.pax.exam.junit.JUnit4TestRunner; +import org.ops4j.pax.exam.Configuration; +import org.ops4j.pax.exam.junit.PaxExam; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; @@ -55,12 +56,11 @@ import org.osgi.framework.ServiceReference; * Tests the ServiceContextProvider. * */ -@RunWith(JUnit4TestRunner.class) +@RunWith(PaxExam.class) public class TestJettyOSGiBootContextAsService { - private static final boolean LOGGING_ENABLED = false; + private static final String LOG_LEVEL = "WARN"; - private static final boolean REMOTE_DEBUGGING = false; @Inject BundleContext bundleContext = null; @@ -69,7 +69,6 @@ public class TestJettyOSGiBootContextAsService public static Option[] configure() { ArrayList

+ * Used in end-user display of the source. + * + * @return the configuration source identifier. + */ + public String getId(); + + /** + * The weight of this source, used for proper ordering of the config source search order. + *

+ * Recommended Weights: + *

+     *           -1 = the command line
+     *            0 = the ${jetty.base} source
+     *       [1..n] = extra-start-dir entries from command line
+     *     [n+1..n] = extra-start-dir entries from start.ini (or start.d/*.ini) 
+     *      9999999 = the ${jetty.home} source
+     * 
+ * + * @return the weight of the config source. (lower value is more important) + */ + public int getWeight(); + + /** + * The list of Arguments for this ConfigSource + * + * @return the list of Arguments for this ConfigSource + */ + public List getArgs(); + + /** + * The properties for this ConfigSource + * + * @return the properties for this ConfigSource + */ + public Props getProps(); + + /** + * Return the value of the specified property. + * + * @param key the key to lookup + * @return the value of the property, or null if not found + */ + public String getProperty(String key); +} diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSources.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSources.java new file mode 100644 index 00000000000..d1cbbe1e206 --- /dev/null +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSources.java @@ -0,0 +1,126 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.start.config; + +import static org.eclipse.jetty.start.UsageException.*; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.ListIterator; +import java.util.concurrent.atomic.AtomicInteger; + +import org.eclipse.jetty.start.FS; +import org.eclipse.jetty.start.Props; +import org.eclipse.jetty.start.Props.Prop; +import org.eclipse.jetty.start.UsageException; + +/** + * Weighted List of ConfigSources. + *

+ */ +public class ConfigSources implements Iterable +{ + private static class WeightedConfigSourceComparator implements Comparator + { + @Override + public int compare(ConfigSource o1, ConfigSource o2) + { + return o1.getWeight() - o2.getWeight(); + } + } + + private LinkedList sources = new LinkedList<>(); + private Props props = new Props(); + private AtomicInteger xtraSourceWeight = new AtomicInteger(1); + + public void add(ConfigSource source) throws IOException + { + if (sources.contains(source)) + { + // TODO: needs a better/more clear error message + throw new UsageException(ERR_BAD_ARG,"Duplicate Configuration Source Reference: " + source); + } + sources.add(source); + + Collections.sort(sources,new WeightedConfigSourceComparator()); + + updateProps(); + + // look for --extra-start-dir entries + for (String arg : source.getArgs()) + { + if (arg.startsWith("--extra-start-dir")) + { + String ref = getValue(arg); + String dirName = props.expand(ref); + Path dir = FS.toPath(dirName); + DirConfigSource dirsource = new DirConfigSource(ref,dir,xtraSourceWeight.incrementAndGet(),true); + add(dirsource); + } + } + } + + public Prop getProp(String key) + { + return props.getProp(key); + } + + public Props getProps() + { + return props; + } + + private String getValue(String arg) + { + int idx = arg.indexOf('='); + if (idx == (-1)) + { + throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg); + } + String value = arg.substring(idx + 1).trim(); + if (value.length() <= 0) + { + throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg); + } + return value; + } + + @Override + public Iterator iterator() + { + return sources.iterator(); + } + + private void updateProps() + { + props.reset(); + + // add all properties from config sources (in reverse order) + ListIterator iter = sources.listIterator(sources.size()); + while (iter.hasPrevious()) + { + ConfigSource source = iter.previous(); + props.addAll(source.getProps()); + } + } +} diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/DirConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/DirConfigSource.java similarity index 60% rename from jetty-start/src/main/java/org/eclipse/jetty/start/DirConfigSource.java rename to jetty-start/src/main/java/org/eclipse/jetty/start/config/DirConfigSource.java index 366fcd61f0e..ee6224aedf5 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/DirConfigSource.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/DirConfigSource.java @@ -16,7 +16,7 @@ // ======================================================================== // -package org.eclipse.jetty.start; +package org.eclipse.jetty.start.config; import java.io.IOException; import java.nio.file.DirectoryStream; @@ -26,6 +26,12 @@ import java.nio.file.PathMatcher; import java.util.ArrayList; import java.util.List; +import org.eclipse.jetty.start.FS; +import org.eclipse.jetty.start.PathMatchers; +import org.eclipse.jetty.start.Props; +import org.eclipse.jetty.start.Props.Prop; +import org.eclipse.jetty.start.StartIni; + /** * A Directory based {@link ConfigSource}. *

@@ -35,7 +41,9 @@ public class DirConfigSource implements ConfigSource { private final String id; private final Path dir; + private final int weight; private final List args; + private final Props props; /** * Create DirConfigSource with specified identifier and directory. @@ -44,15 +52,19 @@ public class DirConfigSource implements ConfigSource * the identifier for this {@link ConfigSource} * @param dir * the directory for this {@link ConfigSource} + * @param weight + * the configuration weight (used for search order) * @param canHaveArgs * true if this directory can have start.ini or start.d entries. (false for directories like ${jetty.home}, for example) * @throws IOException * if unable to load the configuration args */ - public DirConfigSource(String id, Path dir, boolean canHaveArgs) throws IOException + public DirConfigSource(String id, Path dir, int weight, boolean canHaveArgs) throws IOException { this.id = id; this.dir = dir; + this.weight = weight; + this.props = new Props(); this.args = new ArrayList<>(); @@ -62,7 +74,8 @@ public class DirConfigSource implements ConfigSource if (FS.canReadFile(iniFile)) { StartIni ini = new StartIni(iniFile); - args.addAll(ini.getArgs()); + args.addAll(ini.getLines()); + this.props.addAllProperties(ini.getLines(),iniFile.toString()); } Path startDdir = dir.resolve("start.d"); @@ -85,13 +98,49 @@ public class DirConfigSource implements ConfigSource if (FS.canReadFile(diniFile)) { StartIni ini = new StartIni(diniFile); - args.addAll(ini.getArgs()); + args.addAll(ini.getLines()); + this.props.addAllProperties(ini.getLines(),diniFile.toString()); } } } } } + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((dir == null)?0:dir.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + DirConfigSource other = (DirConfigSource)obj; + if (dir == null) + { + if (other.dir != null) + return false; + } + else if (!dir.equals(other.dir)) + return false; + return true; + } + + @Override + public List getArgs() + { + return args; + } + public Path getDir() { return dir; @@ -104,8 +153,31 @@ public class DirConfigSource implements ConfigSource } @Override - public List getArgs() + public int getWeight() { - return args; + return weight; + } + + @Override + public Props getProps() + { + return props; + } + + @Override + public String getProperty(String key) + { + Prop prop = props.getProp(key,false); + if (prop == null) + { + return null; + } + return prop.value; + } + + @Override + public String toString() + { + return String.format("%s[%s,%s,args.length=%d]",this.getClass().getSimpleName(),id,dir,getArgs().size()); } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/ConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyBaseConfigSource.java similarity index 62% rename from jetty-start/src/main/java/org/eclipse/jetty/start/ConfigSource.java rename to jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyBaseConfigSource.java index 2809117705e..ebac6011c61 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/ConfigSource.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyBaseConfigSource.java @@ -16,28 +16,21 @@ // ======================================================================== // -package org.eclipse.jetty.start; +package org.eclipse.jetty.start.config; -import java.util.List; +import java.io.IOException; +import java.nio.file.Path; /** - * A Configuration Source + * ${jetty.base} specific ConfigSource */ -public interface ConfigSource +public class JettyBaseConfigSource extends DirConfigSource { - /** - * The identifier for this source. - *

- * Used in end-user display of the source. - * - * @return the configuration source identifier. - */ - public String getId(); + // Standard weight for ${jetty.base}, so that it comes after command line, and before everything else + private final static int WEIGHT = 0; - /** - * The list of Arguments for this ConfigSource - * - * @return the list of Arguments for this ConfigSource - */ - public List getArgs(); + public JettyBaseConfigSource(Path dir) throws IOException + { + super("${jetty.base}",dir,WEIGHT,true); + } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyHomeConfigSource.java similarity index 59% rename from jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineConfigSource.java rename to jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyHomeConfigSource.java index 4a0c8830218..8ebe0bb4b01 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/CommandLineConfigSource.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/JettyHomeConfigSource.java @@ -16,34 +16,21 @@ // ======================================================================== // -package org.eclipse.jetty.start; +package org.eclipse.jetty.start.config; -import java.util.Arrays; -import java.util.List; +import java.io.IOException; +import java.nio.file.Path; /** - * Configuration Source representing the Command Line arguments. + * ${jetty.home} specific ConfigSource */ -public class CommandLineConfigSource implements ConfigSource +public class JettyHomeConfigSource extends DirConfigSource { - public static final String CMD_LINE_SOURCE = ""; + // Standard weight for ${jetty.home}, so that it comes after everything else + private final static int WEIGHT = 9999999; - private final List args; - - public CommandLineConfigSource(String rawargs[]) + public JettyHomeConfigSource(Path dir) throws IOException { - args = Arrays.asList(rawargs); - } - - @Override - public String getId() - { - return CMD_LINE_SOURCE; - } - - @Override - public List getArgs() - { - return args; + super("${jetty.home}",dir,WEIGHT,false); } } diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java index 0656879cbd1..c217e53d02e 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java @@ -21,17 +21,11 @@ package org.eclipse.jetty.start; import static org.hamcrest.Matchers.*; import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import org.eclipse.jetty.start.Props.Prop; import org.eclipse.jetty.toolchain.test.FS; -import org.eclipse.jetty.toolchain.test.IO; -import org.eclipse.jetty.toolchain.test.MavenTestingUtils; -import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.toolchain.test.TestingDir; import org.junit.Assert; import org.junit.Rule; @@ -69,26 +63,6 @@ public class ExtraStartTest @Rule public TestingDir testdir = new TestingDir(); - private void copyTestDir(String testResourceDir, File destDir) throws IOException - { - FS.ensureDirExists(destDir); - File srcDir = MavenTestingUtils.getTestResourceDir(testResourceDir); - IO.copyDir(srcDir,destDir); - } - - private void makeFile(File dir, String relFilePath, String... contents) throws IOException - { - File outputFile = new File(dir,OS.separators(relFilePath)); - FS.ensureDirExists(outputFile.getParentFile()); - try (FileWriter writer = new FileWriter(outputFile); PrintWriter out = new PrintWriter(writer)) - { - for (String content : contents) - { - out.println(content); - } - } - } - private MainResult runMain(File baseDir, File homeDir, String... cmdLineArgs) throws Exception { MainResult ret = new MainResult(); @@ -108,15 +82,15 @@ public class ExtraStartTest @Test public void testNoExtras() throws Exception { - // Create home + // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1"); // Simple command line - no reference to extra-start-dirs @@ -136,17 +110,17 @@ public class ExtraStartTest // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create common File common = testdir.getFile("common"); FS.ensureEmpty(common); - makeFile(common,"start.ini","jetty.port=8080"); + TestEnv.makeFile(common,"start.ini","jetty.port=8080"); // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1"); // Simple command line reference to extra-start-dir @@ -170,17 +144,17 @@ public class ExtraStartTest // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create common File common = testdir.getFile("common"); FS.ensureEmpty(common); - makeFile(common,"start.ini","jetty.port=8080"); + TestEnv.makeFile(common,"start.ini","jetty.port=8080"); // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1"); // Simple command line reference to extra-start-dir via property (also on command line) @@ -206,7 +180,7 @@ public class ExtraStartTest // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create opt File opt = testdir.getFile("opt"); @@ -215,12 +189,12 @@ public class ExtraStartTest // Create common File common = new File(opt, "common"); FS.ensureEmpty(common); - makeFile(common,"start.ini","jetty.port=8080"); + TestEnv.makeFile(common,"start.ini","jetty.port=8080"); // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1"); String dirRef = "${my.opt}" + File.separator + "common"; @@ -248,7 +222,7 @@ public class ExtraStartTest // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create opt File opt = testdir.getFile("opt"); @@ -257,17 +231,17 @@ public class ExtraStartTest // Create common File common = new File(opt, "common"); FS.ensureEmpty(common); - makeFile(common,"start.ini","jetty.port=8080"); + TestEnv.makeFile(common,"start.ini","jetty.port=8080"); // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1"); String dirRef = "${my.opt}" + File.separator + "${my.dir}"; - // Simple command line reference to extra-start-dir via property (also on command line) + // Simple command line reference to extra-start-dir via property (also on command line) MainResult result = runMain(base,home, // property to 'opt' dir "my.opt=" + opt.getAbsolutePath(), @@ -292,7 +266,7 @@ public class ExtraStartTest // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create common File common = testdir.getFile("common"); @@ -301,7 +275,7 @@ public class ExtraStartTest // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1",// "--extra-start-dir=" + common.getAbsolutePath()); @@ -323,7 +297,7 @@ public class ExtraStartTest // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create common File common = testdir.getFile("common"); @@ -336,7 +310,7 @@ public class ExtraStartTest // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1",// "--extra-start-dir=" + common.getAbsolutePath(), // "--extra-start-dir=" + corp.getAbsolutePath()); @@ -360,25 +334,25 @@ public class ExtraStartTest // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create corp File corp = testdir.getFile("corp"); FS.ensureEmpty(corp); - makeFile(corp,"start.ini", // + TestEnv.makeFile(corp,"start.ini", // "jetty.port=9090"); // Create common File common = testdir.getFile("common"); FS.ensureEmpty(common); - makeFile(common,"start.ini", // + TestEnv.makeFile(common,"start.ini", // "--extra-start-dir=" + corp.getAbsolutePath(), // "jetty.port=8080"); // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1",// "--extra-start-dir=" + common.getAbsolutePath()); @@ -401,18 +375,18 @@ public class ExtraStartTest // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create corp File corp = testdir.getFile("corp"); FS.ensureEmpty(corp); - makeFile(corp,"start.ini", // + TestEnv.makeFile(corp,"start.ini", // "jetty.port=9090"); // Create common File common = testdir.getFile("common"); FS.ensureEmpty(common); - makeFile(common,"start.ini", // + TestEnv.makeFile(common,"start.ini", // "my.corp=" + corp.getAbsolutePath(), // "--extra-start-dir=${my.corp}", // "jetty.port=8080"); @@ -420,7 +394,7 @@ public class ExtraStartTest // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1",// "my.common="+common.getAbsolutePath(), // "--extra-start-dir=${my.common}"); @@ -444,32 +418,32 @@ public class ExtraStartTest // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create devops File devops = testdir.getFile("devops"); FS.ensureEmpty(devops); - makeFile(devops,"start.ini", // + TestEnv.makeFile(devops,"start.ini", // "--module=logging", // "jetty.port=2222"); // Create corp File corp = testdir.getFile("corp"); FS.ensureEmpty(corp); - makeFile(corp,"start.ini", // + TestEnv.makeFile(corp,"start.ini", // "jetty.port=9090"); // Create common File common = testdir.getFile("common"); FS.ensureEmpty(common); - makeFile(common,"start.ini", // + TestEnv.makeFile(common,"start.ini", // "--extra-start-dir=" + corp.getAbsolutePath(), // "jetty.port=8080"); // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1",// "--extra-start-dir=" + common.getAbsolutePath()); @@ -495,25 +469,25 @@ public class ExtraStartTest // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create corp File corp = testdir.getFile("corp"); FS.ensureEmpty(corp); - makeFile(corp,"start.ini", // + TestEnv.makeFile(corp,"start.ini", // "jetty.port=9090"); // Create common File common = testdir.getFile("common"); FS.ensureEmpty(common); - makeFile(common,"start.ini", // + TestEnv.makeFile(common,"start.ini", // "--extra-start-dir=" + corp.getAbsolutePath(), // "jetty.port=8080"); // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1",// "--extra-start-dir=" + common.getAbsolutePath()); @@ -538,7 +512,7 @@ public class ExtraStartTest // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); - copyTestDir("usecases/home",home); + TestEnv.copyTestDir("usecases/home",home); // Create common File common = testdir.getFile("common"); @@ -547,14 +521,14 @@ public class ExtraStartTest // Create corp File corp = testdir.getFile("corp"); FS.ensureEmpty(corp); - makeFile(corp,"start.ini", + TestEnv.makeFile(corp,"start.ini", // standard property "jetty.port=9090", // INTENTIONAL BAD Reference (duplicate) "--extra-start-dir=" + common.getAbsolutePath()); // Populate common - makeFile(common,"start.ini", + TestEnv.makeFile(common,"start.ini", // standard property "jetty.port=8080", // reference to corp @@ -563,7 +537,7 @@ public class ExtraStartTest // Create base File base = testdir.getFile("base"); FS.ensureEmpty(base); - makeFile(base,"start.ini", // + TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1",// "--extra-start-dir=" + common.getAbsolutePath()); diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/TestEnv.java b/jetty-start/src/test/java/org/eclipse/jetty/start/TestEnv.java new file mode 100644 index 00000000000..84659c24f3c --- /dev/null +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/TestEnv.java @@ -0,0 +1,52 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.start; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.IO; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.OS; + +public class TestEnv +{ + public static void copyTestDir(String testResourceDir, File destDir) throws IOException + { + FS.ensureDirExists(destDir); + File srcDir = MavenTestingUtils.getTestResourceDir(testResourceDir); + IO.copyDir(srcDir,destDir); + } + + public static void makeFile(File dir, String relFilePath, String... contents) throws IOException + { + File outputFile = new File(dir,OS.separators(relFilePath)); + FS.ensureDirExists(outputFile.getParentFile()); + try (FileWriter writer = new FileWriter(outputFile); PrintWriter out = new PrintWriter(writer)) + { + for (String content : contents) + { + out.println(content); + } + } + } +} diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/config/ConfigSourcesTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/config/ConfigSourcesTest.java new file mode 100644 index 00000000000..bc55cb746ca --- /dev/null +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/config/ConfigSourcesTest.java @@ -0,0 +1,599 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.start.config; + +import static org.hamcrest.Matchers.*; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jetty.start.ConfigurationAssert; +import org.eclipse.jetty.start.Props.Prop; +import org.eclipse.jetty.start.TestEnv; +import org.eclipse.jetty.start.UsageException; +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.TestingDir; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; + +public class ConfigSourcesTest +{ + @Rule + public TestingDir testdir = new TestingDir(); + + private void assertIdOrder(ConfigSources sources, String... expectedOrder) + { + List actualList = new ArrayList<>(); + for (ConfigSource source : sources) + { + actualList.add(source.getId()); + } + List expectedList = Arrays.asList(expectedOrder); + ConfigurationAssert.assertOrdered("ConfigSources.id order",expectedList,actualList); + } + + private void assertDirOrder(ConfigSources sources, File... expectedDirOrder) + { + List actualList = new ArrayList<>(); + for (ConfigSource source : sources) + { + if (source instanceof DirConfigSource) + { + actualList.add(((DirConfigSource)source).getDir().toString()); + } + } + List expectedList = new ArrayList<>(); + for (File path : expectedDirOrder) + { + expectedList.add(path.getAbsolutePath()); + } + ConfigurationAssert.assertOrdered("ConfigSources.dir order",expectedList,actualList); + } + + private void assertProperty(ConfigSources sources, String key, String expectedValue) + { + Prop prop = sources.getProp(key); + Assert.assertThat("getProp('" + key + "') should not be null",prop,notNullValue()); + Assert.assertThat("getProp('" + key + "')",prop.value,is(expectedValue)); + } + + @Test + public void testOrder_BasicConfig() throws IOException + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1"); + + ConfigSources sources = new ConfigSources(); + + String[] cmdLine = new String[0]; + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyBaseConfigSource(base.toPath())); + sources.add(new JettyHomeConfigSource(home.toPath())); + + assertIdOrder(sources,"","${jetty.base}","${jetty.home}"); + } + + @Test + public void testOrder_With1ExtraConfig() throws IOException + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath()); + + ConfigSources sources = new ConfigSources(); + + String[] cmdLine = new String[0]; + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyHomeConfigSource(home.toPath())); + sources.add(new JettyBaseConfigSource(base.toPath())); + + assertIdOrder(sources,"","${jetty.base}",common.getAbsolutePath(),"${jetty.home}"); + } + + @Test + public void testCommandLine_1Extra_FromSimpleProp() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + TestEnv.makeFile(common,"start.ini","jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1"); + + ConfigSources sources = new ConfigSources(); + + // Simple command line reference to extra-start-dir via property (also on command line) + + String[] cmdLine = new String[] { + // property + "my.common=" + common.getAbsolutePath(), + // reference via property + "--extra-start-dir=${my.common}" }; + + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyHomeConfigSource(home.toPath())); + sources.add(new JettyBaseConfigSource(base.toPath())); + + assertIdOrder(sources,"","${jetty.base}","${my.common}","${jetty.home}"); + + assertDirOrder(sources,base,common,home); + + assertProperty(sources,"jetty.host","127.0.0.1"); + assertProperty(sources,"jetty.port","8080"); // from 'common' + } + + @Test + public void testCommandLine_1Extra_FromPropPrefix() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create opt + File opt = testdir.getFile("opt"); + FS.ensureEmpty(opt); + + // Create common + File common = new File(opt,"common"); + FS.ensureEmpty(common); + TestEnv.makeFile(common,"start.ini","jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1"); + + String dirRef = "${my.opt}" + File.separator + "common"; + + ConfigSources sources = new ConfigSources(); + + // Simple command line reference to extra-start-dir via property (also on command line) + String[] cmdLine = new String[] { + // property to 'opt' dir + "my.opt=" + opt.getAbsolutePath(), + // reference via property prefix + "--extra-start-dir=" + dirRef }; + + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyHomeConfigSource(home.toPath())); + sources.add(new JettyBaseConfigSource(base.toPath())); + + assertIdOrder(sources,"","${jetty.base}",dirRef,"${jetty.home}"); + + assertDirOrder(sources,base,common,home); + + assertProperty(sources,"jetty.host","127.0.0.1"); + assertProperty(sources,"jetty.port","8080"); // from 'common' + } + + @Test + public void testCommandLine_1Extra_FromCompoundProp() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create opt + File opt = testdir.getFile("opt"); + FS.ensureEmpty(opt); + + // Create common + File common = new File(opt,"common"); + FS.ensureEmpty(common); + TestEnv.makeFile(common,"start.ini","jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1"); + + String dirRef = "${my.opt}" + File.separator + "${my.dir}"; + + ConfigSources sources = new ConfigSources(); + + // Simple command line reference to extra-start-dir via property (also on command line) + + String[] cmdLine = new String[] { + // property to 'opt' dir + "my.opt=" + opt.getAbsolutePath(), + // property to commmon dir name + "my.dir=common", + // reference via property prefix + "--extra-start-dir=" + dirRef }; + + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyHomeConfigSource(home.toPath())); + sources.add(new JettyBaseConfigSource(base.toPath())); + + assertIdOrder(sources,"","${jetty.base}",dirRef,"${jetty.home}"); + + assertDirOrder(sources,base,common,home); + + assertProperty(sources,"jetty.host","127.0.0.1"); + assertProperty(sources,"jetty.port","8080"); // from 'common' + } + + @Test + public void testRefCommon() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + TestEnv.makeFile(common,"start.ini","jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath()); + + ConfigSources sources = new ConfigSources(); + + String cmdLine[] = new String[0]; + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyHomeConfigSource(home.toPath())); + sources.add(new JettyBaseConfigSource(base.toPath())); + + assertIdOrder(sources,"","${jetty.base}",common.getAbsolutePath(),"${jetty.home}"); + + assertDirOrder(sources,base,common,home); + + assertProperty(sources,"jetty.host","127.0.0.1"); + assertProperty(sources,"jetty.port","8080"); // from 'common' + } + + @Test + public void testRefCommonAndCorp() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + TestEnv.makeFile(common,"start.ini","jetty.port=8080"); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath(), // + "--extra-start-dir=" + corp.getAbsolutePath()); + + ConfigSources sources = new ConfigSources(); + + String cmdLine[] = new String[0]; + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyHomeConfigSource(home.toPath())); + sources.add(new JettyBaseConfigSource(base.toPath())); + + assertIdOrder(sources,"","${jetty.base}", + common.getAbsolutePath(), + corp.getAbsolutePath(), + "${jetty.home}"); + + assertDirOrder(sources,base,common,corp,home); + + assertProperty(sources,"jetty.host","127.0.0.1"); + assertProperty(sources,"jetty.port","8080"); // from 'common' + } + + @Test + public void testRefCommonRefCorp() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + TestEnv.makeFile(corp,"start.ini", // + "jetty.port=9090"); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + TestEnv.makeFile(common,"start.ini", // + "--extra-start-dir=" + corp.getAbsolutePath(), // + "jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath()); + + ConfigSources sources = new ConfigSources(); + + String cmdLine[] = new String[0]; + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyHomeConfigSource(home.toPath())); + sources.add(new JettyBaseConfigSource(base.toPath())); + + assertIdOrder(sources,"","${jetty.base}", + common.getAbsolutePath(), + corp.getAbsolutePath(), + "${jetty.home}"); + + assertDirOrder(sources,base,common,corp,home); + + assertProperty(sources,"jetty.host","127.0.0.1"); + assertProperty(sources,"jetty.port","8080"); // from 'common' + } + + @Test + public void testRefCommonRefCorp_FromSimpleProps() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + TestEnv.makeFile(corp,"start.ini", // + "jetty.port=9090"); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + TestEnv.makeFile(common,"start.ini", // + "my.corp=" + corp.getAbsolutePath(), // + "--extra-start-dir=${my.corp}", // + "jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "my.common="+common.getAbsolutePath(), // + "--extra-start-dir=${my.common}"); + + ConfigSources sources = new ConfigSources(); + + String cmdLine[] = new String[0]; + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyHomeConfigSource(home.toPath())); + sources.add(new JettyBaseConfigSource(base.toPath())); + + assertIdOrder(sources,"", + "${jetty.base}", + "${my.common}", + "${my.corp}", + "${jetty.home}"); + + assertDirOrder(sources,base,common,corp,home); + + assertProperty(sources,"jetty.host","127.0.0.1"); + assertProperty(sources,"jetty.port","8080"); // from 'common' + } + + @Test + public void testRefCommonRefCorp_CmdLineRef() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create devops + File devops = testdir.getFile("devops"); + FS.ensureEmpty(devops); + TestEnv.makeFile(devops,"start.ini", // + "--module=logging", // + "jetty.port=2222"); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + TestEnv.makeFile(corp,"start.ini", // + "jetty.port=9090"); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + TestEnv.makeFile(common,"start.ini", // + "--extra-start-dir=" + corp.getAbsolutePath(), // + "jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath()); + + ConfigSources sources = new ConfigSources(); + + String cmdLine[] = new String[]{ + // command line provided extra-start-dir ref + "--extra-start-dir=" + devops.getAbsolutePath()}; + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyHomeConfigSource(home.toPath())); + sources.add(new JettyBaseConfigSource(base.toPath())); + + assertIdOrder(sources,"", + "${jetty.base}", + devops.getAbsolutePath(), + common.getAbsolutePath(), + corp.getAbsolutePath(), + "${jetty.home}"); + + assertDirOrder(sources,base,devops,common,corp,home); + + assertProperty(sources,"jetty.host","127.0.0.1"); + assertProperty(sources,"jetty.port","2222"); // from 'common' + } + + @Test + public void testRefCommonRefCorp_CmdLineProp() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + TestEnv.makeFile(corp,"start.ini", // + "jetty.port=9090"); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + TestEnv.makeFile(common,"start.ini", // + "--extra-start-dir=" + corp.getAbsolutePath(), // + "jetty.port=8080"); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath()); + + ConfigSources sources = new ConfigSources(); + + String cmdLine[] = new String[]{ + // command line property should override all others + "jetty.port=7070" + }; + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyHomeConfigSource(home.toPath())); + sources.add(new JettyBaseConfigSource(base.toPath())); + + assertIdOrder(sources,"","${jetty.base}", + common.getAbsolutePath(), + corp.getAbsolutePath(), + "${jetty.home}"); + + assertDirOrder(sources,base,common,corp,home); + + assertProperty(sources,"jetty.host","127.0.0.1"); + assertProperty(sources,"jetty.port","7070"); // from + } + + @Test + public void testBadDoubleRef() throws Exception + { + // Create home + File home = testdir.getFile("home"); + FS.ensureEmpty(home); + TestEnv.copyTestDir("usecases/home",home); + + // Create common + File common = testdir.getFile("common"); + FS.ensureEmpty(common); + + // Create corp + File corp = testdir.getFile("corp"); + FS.ensureEmpty(corp); + TestEnv.makeFile(corp,"start.ini", + // standard property + "jetty.port=9090", + // INTENTIONAL BAD Reference (duplicate) + "--extra-start-dir=" + common.getAbsolutePath()); + + // Populate common + TestEnv.makeFile(common,"start.ini", + // standard property + "jetty.port=8080", + // reference to corp + "--extra-start-dir=" + corp.getAbsolutePath()); + + // Create base + File base = testdir.getFile("base"); + FS.ensureEmpty(base); + TestEnv.makeFile(base,"start.ini", // + "jetty.host=127.0.0.1",// + "--extra-start-dir=" + common.getAbsolutePath()); + + ConfigSources sources = new ConfigSources(); + + try + { + String cmdLine[] = new String[0]; + sources.add(new CommandLineConfigSource(cmdLine)); + sources.add(new JettyHomeConfigSource(home.toPath())); + sources.add(new JettyBaseConfigSource(base.toPath())); + + Assert.fail("Should have thrown a UsageException"); + } + catch (UsageException e) + { + Assert.assertThat("UsageException",e.getMessage(),containsString("Duplicate")); + } + } +} From eff78efe3cb65a495e013ba7ebf0ac7e083c2cc6 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 10 Apr 2014 16:45:11 -0700 Subject: [PATCH 027/135] 432321 - jetty-start / Allow defining extra start directories for common configurations + Integration of ConfigSources with rest of startup. --- .../org/eclipse/jetty/start/BaseHome.java | 449 +++++++++--------- .../main/java/org/eclipse/jetty/start/FS.java | 173 ++++--- .../java/org/eclipse/jetty/start/Main.java | 132 +++-- .../java/org/eclipse/jetty/start/Module.java | 110 +++-- .../jetty/start/ModuleGraphWriter.java | 16 +- .../java/org/eclipse/jetty/start/Modules.java | 9 +- .../java/org/eclipse/jetty/start/Props.java | 112 +++-- .../org/eclipse/jetty/start/StartArgs.java | 173 ++----- .../org/eclipse/jetty/start/StartIni.java | 3 - .../org/eclipse/jetty/start/StartLog.java | 100 ++-- .../start/config/CommandLineConfigSource.java | 73 ++- .../jetty/start/config/ConfigSources.java | 37 ++ .../jetty/start/config/DirConfigSource.java | 118 ++++- .../org/eclipse/jetty/start/BaseHomeTest.java | 61 +-- .../jetty/start/ConfigurationAssert.java | 7 +- .../eclipse/jetty/start/ExtraStartTest.java | 74 +-- .../jetty/start/ModuleGraphWriterTest.java | 37 +- .../org/eclipse/jetty/start/ModuleTest.java | 2 +- .../org/eclipse/jetty/start/ModulesTest.java | 96 +++- 19 files changed, 1011 insertions(+), 771 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java index 581bf5ad9b5..68c121ed92e 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java @@ -23,8 +23,6 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; @@ -32,12 +30,17 @@ import java.nio.file.PathMatcher; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; -import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.jetty.start.config.CommandLineConfigSource; +import org.eclipse.jetty.start.config.ConfigSource; +import org.eclipse.jetty.start.config.ConfigSources; +import org.eclipse.jetty.start.config.DirConfigSource; + /** * File access for ${jetty.home}, ${jetty.base}, directories. *

@@ -49,12 +52,6 @@ import java.util.regex.Pattern; */ public class BaseHome { - private static final String JETTY_BASE = "jetty.base"; - private static final String JETTY_HOME = "jetty.home"; - - private final static EnumSet SEARCH_VISIT_OPTIONS = EnumSet.of(FileVisitOption.FOLLOW_LINKS);; - private final static int MAX_SEARCH_DEPTH = Integer.getInteger("org.eclipse.jetty.start.searchDepth",10); - public static class SearchDir { private Path dir; @@ -65,13 +62,19 @@ public class BaseHome this.name = name; } - public SearchDir setDir(String path) + public Path getDir() { - if (path != null) - { - return setDir(FS.toPath(path)); - } - return this; + return dir; + } + + public Path resolve(Path subpath) + { + return dir.resolve(subpath); + } + + public Path resolve(String subpath) + { + return dir.resolve(FS.separators(subpath)); } public SearchDir setDir(File path) @@ -92,19 +95,13 @@ public class BaseHome return this; } - public Path getDir() + public SearchDir setDir(String path) { - return dir; - } - - public Path resolve(String subpath) - { - return dir.resolve(FS.separators(subpath)); - } - - public Path resolve(Path subpath) - { - return dir.resolve(subpath); + if (path != null) + { + return setDir(FS.toPath(path)); + } + return this; } public String toShortForm(Path path) @@ -113,30 +110,37 @@ public class BaseHome return String.format("${%s}%c%s",name,File.separatorChar,relative.toString()); } } + private static final String JETTY_BASE = "jetty.base"; - private SearchDir homeDir; - private LinkedList searchDirs = new LinkedList<>(); - private SearchDir baseDir; + private static final String JETTY_HOME = "jetty.home";; + private final static EnumSet SEARCH_VISIT_OPTIONS = EnumSet.of(FileVisitOption.FOLLOW_LINKS); + + private final static int MAX_SEARCH_DEPTH = Integer.getInteger("org.eclipse.jetty.start.searchDepth",10); + + private Path homeDir; + private ConfigSources sources; + private Path baseDir; public BaseHome() { try { - // find ${jetty.base} - this.baseDir = new SearchDir(JETTY_BASE); + // find ${jetty.base} and ${jetty.home} from environment. + + // overrides from command line (and the like) come later. + // in the .initialize() step // default is ${user.dir} - this.baseDir.setDir(System.getProperty("user.dir",".")); + this.baseDir = FS.toPath(System.getProperty("user.dir",".")); // if ${jetty.base} declared, use it String jettyBase = System.getProperty(JETTY_BASE); if (jettyBase != null) { - this.baseDir.setDir(jettyBase); + this.baseDir = FS.toPath(jettyBase); } // find ${jetty.home} - this.homeDir = new SearchDir(JETTY_HOME); // default location is based on lookup for BaseHome (from jetty's start.jar) URL jarfile = this.getClass().getClassLoader().getResource("org/eclipse/jetty/start/BaseHome.class"); @@ -146,19 +150,26 @@ public class BaseHome if (m.matches()) { // ${jetty.home} is relative to found BaseHome class - this.homeDir.setDir(new File(new URI(m.group(1))).getParentFile()); + this.homeDir = new File(new URI(m.group(1))).getParentFile().toPath(); } } // if we can't locate BaseHome, then assume home == base - this.homeDir.setDir(baseDir.getDir()); + if (this.homeDir == null) + { + this.homeDir = baseDir.toAbsolutePath(); + } // if ${jetty.home} declared, use it String jettyHome = System.getProperty(JETTY_HOME); if (jettyHome != null) { - this.homeDir.setDir(jettyHome); + this.homeDir = FS.toPath(jettyHome); } + + // Resolve to absolute paths + this.homeDir = this.homeDir.toAbsolutePath(); + this.baseDir = this.baseDir.toAbsolutePath(); } catch (URISyntaxException e) { @@ -168,15 +179,18 @@ public class BaseHome public BaseHome(File homeDir, File baseDir) { - this.baseDir = new SearchDir(JETTY_BASE); - this.homeDir = new SearchDir(JETTY_HOME); + Objects.requireNonNull(homeDir,"Home Dir cannot be null"); - this.homeDir.setDir(homeDir); - this.baseDir.setDir(homeDir); // default + this.homeDir = homeDir.toPath(); + this.baseDir = homeDir.toPath(); // default if (baseDir != null) { - this.baseDir.setDir(baseDir); + this.baseDir = baseDir.toPath(); } + + // Resolve to absolute paths + this.homeDir = this.homeDir.toAbsolutePath(); + this.baseDir = this.baseDir.toAbsolutePath(); } public String getBase() @@ -188,10 +202,13 @@ public class BaseHome return baseDir.toString(); } - // TODO: change return type to Path + /** + * @deprecated use {@link #getBasePath()} + */ + @Deprecated public File getBaseDir() { - return baseDir.getDir().toFile(); + return baseDir.toFile(); } /** @@ -200,31 +217,27 @@ public class BaseHome * @param path * the path to reference * @return the file reference + * @deprecated use {@link #getBasePath(String)} */ - // TODO: change return type to Path + @Deprecated public File getBaseFile(String path) { return baseDir.resolve(path).toFile(); } - /** - * Get a specific file reference. - *

- * File references go through 3 possibly scenarios. - *

    - *
  1. If exists relative to ${jetty.base}, return that reference
  2. - *
  3. If exists relative to ${jetty.home}, return that reference
  4. - *
  5. Otherwise return absolute path reference (standard java logic)
  6. - *
- * - * @param path - * the path to get. - * @return the file reference. - */ - // TODO: deprecate in favor of getPath() version - public File getFile(String path) + public Path getBasePath() { - return getPath(path).toAbsolutePath().toFile(); + return baseDir; + } + + public Path getBasePath(String path) + { + return baseDir.resolve(path); + } + + public ConfigSources getConfigSources() + { + return this.sources; } /** @@ -240,40 +253,112 @@ public class BaseHome * @param path * the path to get. * @return the file reference. + * @deprecated use {@link #getPath(String)} + */ + @Deprecated + public File getFile(String path) + { + return getPath(path).toAbsolutePath().toFile(); + } + + public String getHome() + { + return homeDir.toString(); + } + + /** + * @deprecated use {@link #getHomePath()} + */ + @Deprecated + public File getHomeDir() + { + return homeDir.toFile(); + } + + public Path getHomePath() + { + return homeDir; + } + + /** + * Get a specific path reference. + *

+ * Path references are searched based on the config source search order. + *

    + *
  1. If provided path is an absolute reference., and exists, return that reference
  2. + *
  3. If exists relative to ${jetty.base}, return that reference
  4. + *
  5. If exists relative to and extra-start-dir locations, return that reference
  6. + *
  7. If exists relative to ${jetty.home}, return that reference
  8. + *
  9. Return standard {@link Path} reference obtained from {@link java.nio.file.FileSystem#getPath(String, String...)} (no exists check performed)
  10. + *
+ * + * @param path + * the path to get. + * @return the path reference. */ public Path getPath(final String path) { - // Relative to Base Directory First - if (isBaseDifferent()) + Path apath = FS.toPath(path); + + if (apath.isAbsolute()) { - Path file = baseDir.resolve(path); - if (FS.exists(file)) + if (FS.exists(apath)) { - return file; + return apath; } } - // Next, test for relative to all extra search paths - for (SearchDir search : searchDirs) + for (ConfigSource source : sources) { - Path file = search.resolve(path); - if (FS.exists(file)) + if (source instanceof DirConfigSource) { - return file; + DirConfigSource dirsource = (DirConfigSource)source; + Path file = dirsource.getDir().resolve(apath); + if (FS.exists(file)) + { + return file; + } } } - // Then relative to Home Directory - Path file = homeDir.resolve(path); - if (FS.exists(file)) - { - return file; - } - - // Finally, as an absolute path + // Finally, as an anonymous path return FS.toPath(path); } + /** + * Search specified Path with pattern and return hits + * + * @param dir + * the path to a directory to start search from + * @param searchDepth + * the number of directories deep to perform the search + * @param pattern + * the raw pattern to use for the search (must be relative) + * @return the list of Paths found + * @throws IOException + * if unable to search the path + */ + public List getPaths(Path dir, int searchDepth, String pattern) throws IOException + { + if (PathMatchers.isAbsolute(pattern)) + { + throw new RuntimeException("Pattern cannot be absolute: " + pattern); + } + + List hits = new ArrayList<>(); + if (FS.isValidDirectory(dir)) + { + PathMatcher matcher = PathMatchers.getMatcher(pattern); + PathFinder finder = new PathFinder(); + finder.setFileMatcher(matcher); + finder.setBase(dir); + Files.walkFileTree(dir,SEARCH_VISIT_OPTIONS,searchDepth,finder); + hits.addAll(finder.getHits()); + Collections.sort(hits,new NaturalSort.Paths()); + } + return hits; + } + /** * Get a List of {@link Path}s from a provided pattern. *

@@ -327,6 +412,7 @@ public class BaseHome */ public List getPaths(String pattern) throws IOException { + StartLog.debug("getPaths('%s')",pattern); List hits = new ArrayList<>(); if (PathMatchers.isAbsolute(pattern)) @@ -357,35 +443,24 @@ public class BaseHome finder.setIncludeDirsInResults(true); finder.setFileMatcher(matcher); - Path homePath = homeDir.resolve(relativePath); - - if (FS.isValidDirectory(homePath)) - { - finder.setBase(homePath); - Files.walkFileTree(homePath,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder); - } - - ListIterator iter = searchDirs.listIterator(searchDirs.size()); + // walk config sources backwards ... + ListIterator iter = sources.reverseListIterator(); while (iter.hasPrevious()) { - SearchDir search = iter.previous(); - Path dir = search.getDir(); - if (FS.isValidDirectory(dir)) + ConfigSource source = iter.previous(); + if (source instanceof DirConfigSource) { - finder.setBase(dir); - Files.walkFileTree(dir,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder); + DirConfigSource dirsource = (DirConfigSource)source; + Path dir = dirsource.getDir(); + Path deepDir = dir.resolve(relativePath); + if (FS.isValidDirectory(deepDir)) + { + finder.setBase(dir); + Files.walkFileTree(deepDir,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder); + } } } - if (isBaseDifferent()) - { - Path basePath = baseDir.resolve(relativePath); - if (FS.isValidDirectory(basePath)) - { - finder.setBase(basePath); - Files.walkFileTree(basePath,SEARCH_VISIT_OPTIONS,MAX_SEARCH_DEPTH,finder); - } - } hits.addAll(finder.getHits()); } @@ -393,106 +468,27 @@ public class BaseHome return hits; } - /** - * Search specified Path with pattern and return hits - * - * @param dir - * the path to a directory to start search from - * @param searchDepth - * the number of directories deep to perform the search - * @param pattern - * the raw pattern to use for the search (must be relative) - * @return the list of Paths found - * @throws IOException - * if unable to search the path - */ - public List getPaths(Path dir, int searchDepth, String pattern) throws IOException + public void initialize(ConfigSources config) { - if (PathMatchers.isAbsolute(pattern)) + CommandLineConfigSource cmdLine = config.getCommandLineSource(); + if (cmdLine != null) { - throw new RuntimeException("Pattern cannot be absolute: " + pattern); + this.homeDir = cmdLine.getHomePath(); + this.baseDir = cmdLine.getBasePath(); } - List hits = new ArrayList<>(); - if (FS.isValidDirectory(dir)) - { - PathMatcher matcher = PathMatchers.getMatcher(pattern); - PathFinder finder = new PathFinder(); - finder.setFileMatcher(matcher); - finder.setBase(dir); - Files.walkFileTree(dir,SEARCH_VISIT_OPTIONS,searchDepth,finder); - hits.addAll(finder.getHits()); - Collections.sort(hits,new NaturalSort.Paths()); - } - return hits; - } - - public String getHome() - { - return homeDir.toString(); - } - - // TODO: change return type to Path - public File getHomeDir() - { - return homeDir.getDir().toFile(); - } - - public void initialize(StartArgs args) - { - Pattern jetty_home = Pattern.compile("(-D)?jetty.home=(.*)"); - Pattern jetty_base = Pattern.compile("(-D)?jetty.base=(.*)"); - - Path homePath = null; - Path basePath = null; - - FileSystem fs = FileSystems.getDefault(); - - for (String arg : args.getCommandLine()) - { - Matcher home_match = jetty_home.matcher(arg); - if (home_match.matches()) - { - homePath = fs.getPath(home_match.group(2)); - } - Matcher base_match = jetty_base.matcher(arg); - if (base_match.matches()) - { - basePath = fs.getPath(base_match.group(2)); - } - } - - if (homePath != null) - { - // logic if home is specified - this.homeDir.setDir(homePath); - if (basePath == null) - { - this.baseDir.setDir(homePath); - args.getProperties().setProperty(JETTY_BASE,this.baseDir.toString(),""); - } - else - { - this.baseDir.setDir(basePath); - } - } - else if (basePath != null) - { - // logic if home is undeclared - this.baseDir.setDir(basePath); - } - - // Update System Properties - args.addSystemProperty(JETTY_HOME,this.homeDir.toString()); - args.addSystemProperty(JETTY_BASE,this.baseDir.toString()); + this.sources = config; } public boolean isBaseDifferent() { - return homeDir.getDir().compareTo(baseDir.getDir()) != 0; + return homeDir.compareTo(baseDir) != 0; } - // TODO: deprecate (in favor of Path version) + /** + * @deprecated use {@link #setBaseDir(Path)} + */ + @Deprecated public void setBaseDir(File dir) { setBaseDir(dir.toPath()); @@ -500,11 +496,14 @@ public class BaseHome public void setBaseDir(Path dir) { - this.baseDir.setDir(dir); + this.baseDir = dir.toAbsolutePath(); System.setProperty(JETTY_BASE,dir.toString()); } - // TODO: deprecate (in favor of Path version) + /** + * @deprecated use {@link #setHomeDir(Path)} + */ + @Deprecated public void setHomeDir(File dir) { setHomeDir(dir.toPath()); @@ -512,10 +511,18 @@ public class BaseHome public void setHomeDir(Path dir) { - this.homeDir.setDir(dir); + this.homeDir = dir.toAbsolutePath(); System.setProperty(JETTY_HOME,dir.toString()); } + /** + * Convenience method for toShortForm(file.toPath()) + */ + public String toShortForm(final File path) + { + return toShortForm(path.toPath()); + } + /** * Replace/Shorten arbitrary path with property strings "${jetty.home}" or "${jetty.base}" where appropriate. * @@ -527,41 +534,30 @@ public class BaseHome { Path apath = path.toAbsolutePath(); - if (isBaseDifferent()) + for (ConfigSource source : sources) { - // is path part of ${jetty.base} ? - if (apath.startsWith(baseDir.getDir())) + if (source instanceof DirConfigSource) { - return baseDir.toShortForm(apath); + DirConfigSource dirsource = (DirConfigSource)source; + Path dir = dirsource.getDir(); + if (apath.startsWith(dir)) + { + if (dirsource.isPropertyBased()) + { + Path relative = dir.relativize(apath); + return String.format("%s%c%s",dirsource.getId(),File.separatorChar,relative.toString()); + } + else + { + return apath.toString(); + } + } } } - - // Extra search dirs - for(SearchDir search: searchDirs) - { - if(apath.startsWith(search.getDir())) - { - return search.toShortForm(apath); - } - } - - // is path part of ${jetty.home} ? - if (apath.startsWith(homeDir.getDir())) - { - return homeDir.toShortForm(apath); - } return apath.toString(); } - /** - * Convenience method for toShortForm(file.toPath()) - */ - public String toShortForm(final File path) - { - return toShortForm(path.toPath()); - } - /** * Replace/Shorten arbitrary path with property strings "${jetty.home}" or "${jetty.base}" where appropriate. * @@ -578,9 +574,4 @@ public class BaseHome return toShortForm(FS.toPath(path)); } - - public void addExtraStart(String name, File dir) - { - this.searchDirs.add(new SearchDir(name).setDir(dir)); - } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java index 87c4a76c028..abedea9c2a4 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java @@ -26,6 +26,7 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; +import java.nio.file.attribute.FileTime; import java.util.Locale; import java.util.regex.Pattern; @@ -55,29 +56,6 @@ public class FS } } - @Deprecated - public static class RelativeRegexFilter implements FileFilter - { - private final File baseDir; - private final Pattern pattern; - - public RelativeRegexFilter(File baseDir, Pattern pattern) - { - this.baseDir = baseDir; - this.pattern = pattern; - } - - @Override - public boolean accept(File path) - { - // get relative path - String relativePath = FS.toRelativePath(baseDir,path); - - // see if it matches - return (pattern.matcher(relativePath).matches()); - } - } - @Deprecated public static class FilenameRegexFilter implements FileFilter { @@ -132,6 +110,29 @@ public class FS } } + @Deprecated + public static class RelativeRegexFilter implements FileFilter + { + private final File baseDir; + private final Pattern pattern; + + public RelativeRegexFilter(File baseDir, Pattern pattern) + { + this.baseDir = baseDir; + this.pattern = pattern; + } + + @Override + public boolean accept(File path) + { + // get relative path + String relativePath = FS.toRelativePath(baseDir,path); + + // see if it matches + return (pattern.matcher(relativePath).matches()); + } + } + @Deprecated public static class XmlFilter extends FilenameRegexFilter { @@ -141,24 +142,8 @@ public class FS } } - public static boolean isValidDirectory(Path path) - { - LinkOption lopts[] = new LinkOption[0]; - if (!Files.exists(path,lopts)) - { - // doesn't exist, not a valid directory - return false; - } - - if (!Files.isDirectory(path,lopts)) - { - // not a directory (as expected) - StartLog.warn("Not a directory: " + path); - return false; - } - - return true; - } + // Default Link Options + private static final LinkOption[] NO_LINK_OPTIONS = new LinkOption[0]; public static boolean canReadDirectory(File path) { @@ -167,19 +152,22 @@ public class FS public static boolean canReadDirectory(Path path) { - LinkOption lopts[] = new LinkOption[0]; - return Files.exists(path,lopts) && Files.isDirectory(path,lopts) && Files.isReadable(path); + return Files.exists(path,NO_LINK_OPTIONS) && Files.isDirectory(path,NO_LINK_OPTIONS) && Files.isReadable(path); } public static boolean canReadFile(File path) { return (path.exists() && path.isFile() && path.canRead()); } - + public static boolean canReadFile(Path path) { - LinkOption lopts[] = new LinkOption[0]; - return Files.exists(path,lopts) && Files.isRegularFile(path,lopts) && Files.isReadable(path); + return Files.exists(path,NO_LINK_OPTIONS) && Files.isRegularFile(path,NO_LINK_OPTIONS) && Files.isReadable(path); + } + + public static boolean canWrite(Path path) + { + return Files.isWritable(path); } public static void close(Closeable c) @@ -199,6 +187,16 @@ public class FS } } + public static boolean createNewFile(Path path) throws IOException + { + Path ret = Files.createFile(path); + return Files.exists(ret,NO_LINK_OPTIONS); + } + + /** + * @deprecated use {@link #ensureDirectoryExists(Path)} instead + */ + @Deprecated public static void ensureDirectoryExists(File dir) throws IOException { if (dir.exists()) @@ -210,7 +208,21 @@ public class FS throw new IOException("Unable to create directory: " + dir.getAbsolutePath()); } } + + public static void ensureDirectoryExists(Path dir) throws IOException + { + if (exists(dir)) + { + // exists already, nothing to do + return; + } + Files.createDirectories(dir); + } + /** + * @deprecated use {@link #ensureDirectoryWritable(Path)} instead + */ + @Deprecated public static void ensureDirectoryWritable(File dir) throws IOException { if (!dir.exists()) @@ -223,6 +235,27 @@ public class FS } } + public static void ensureDirectoryWritable(Path dir) throws IOException + { + if (!Files.exists(dir,NO_LINK_OPTIONS)) + { + throw new IOException("Path does not exist: " + dir.toAbsolutePath()); + } + if (!Files.isDirectory(dir,NO_LINK_OPTIONS)) + { + throw new IOException("Directory does not exist: " + dir.toAbsolutePath()); + } + if (!Files.isWritable(dir)) + { + throw new IOException("Unable to write to directory: " + dir.toAbsolutePath()); + } + } + + public static boolean exists(Path path) + { + return Files.exists(path,NO_LINK_OPTIONS); + } + public static boolean isFile(File file) { if (file == null) @@ -232,16 +265,30 @@ public class FS return file.exists() && file.isFile(); } + public static boolean isValidDirectory(Path path) + { + LinkOption lopts[] = NO_LINK_OPTIONS; + if (!Files.exists(path,lopts)) + { + // doesn't exist, not a valid directory + return false; + } + + if (!Files.isDirectory(path,lopts)) + { + // not a directory (as expected) + StartLog.warn("Not a directory: " + path); + return false; + } + + return true; + } + public static boolean isXml(String filename) { return filename.toLowerCase(Locale.ENGLISH).endsWith(".xml"); } - public static String toRelativePath(File baseDir, File path) - { - return baseDir.toURI().relativize(path.toURI()).toASCIIString(); - } - public static String separators(String path) { StringBuilder ret = new StringBuilder(); @@ -259,13 +306,33 @@ public class FS return ret.toString(); } - public static boolean exists(Path path) + public static Path toOptionalPath(String path) { - return Files.exists(path,new LinkOption[0]); + if (path == null) + { + return null; + } + return toPath(path); } public static Path toPath(String path) { return FileSystems.getDefault().getPath(FS.separators(path)); } + + public static String toRelativePath(File baseDir, File path) + { + return baseDir.toURI().relativize(path.toURI()).toASCIIString(); + } + + public static void touch(Path path) throws IOException + { + FileTime now = FileTime.fromMillis(System.currentTimeMillis()); + Files.setLastModifiedTime(path,now); + } + + public static Path toRealPath(Path path) throws IOException + { + return path.toRealPath(NO_LINK_OPTIONS); + } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java index 0a4da40bab2..33f0c23b09b 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java @@ -21,9 +21,8 @@ package org.eclipse.jetty.start; import static org.eclipse.jetty.start.UsageException.*; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; -import java.io.FileOutputStream; -import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -37,7 +36,10 @@ import java.net.InetAddress; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -48,6 +50,11 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.jetty.start.config.CommandLineConfigSource; +import org.eclipse.jetty.start.config.ConfigSources; +import org.eclipse.jetty.start.config.JettyBaseConfigSource; +import org.eclipse.jetty.start.config.JettyHomeConfigSource; + /** * Main start class. *

@@ -168,11 +175,12 @@ public class Main { try { - File file = baseHome.getBaseFile(arg.location); + Path file = baseHome.getBasePath(arg.location); - StartLog.debug("Module file %s %s",file.getAbsolutePath(),(file.exists()?"[Exists!]":"")); - if (file.exists()) + StartLog.debug("Module file %s %s",file.toAbsolutePath(),(FS.exists(file)?"[Exists!]":"")); + if (FS.exists(file)) { + // file already initialized / downloaded, skip it return; } @@ -182,10 +190,11 @@ public class Main System.err.println("DOWNLOAD: " + url + " to " + arg.location); - FS.ensureDirectoryExists(file.getParentFile()); + FS.ensureDirectoryExists(file.getParent()); byte[] buf = new byte[8192]; - try (InputStream in = url.openStream(); OutputStream out = new FileOutputStream(file);) + try (InputStream in = url.openStream(); + OutputStream out = Files.newOutputStream(file,StandardOpenOption.CREATE_NEW,StandardOpenOption.WRITE)) { while (true) { @@ -205,11 +214,12 @@ public class Main else if (arg.location.endsWith("/")) { System.err.println("MKDIR: " + baseHome.toShortForm(file)); - file.mkdirs(); + FS.ensureDirectoryExists(file); } else + { StartLog.warn("MISSING: required file "+ baseHome.toShortForm(file)); - + } } catch (Exception e) { @@ -350,7 +360,7 @@ public class Main private void buildIni(StartArgs args, String name, boolean topLevel, boolean appendStartIni) throws IOException { // Find the start.d relative to the base directory only. - File start_d = baseHome.getBaseFile("start.d"); + Path start_d = baseHome.getBasePath("start.d"); // Is this a module? Modules modules = args.getAllModules(); @@ -362,12 +372,12 @@ public class Main } // Find any named ini file and check it follows the convention - File start_ini = baseHome.getBaseFile("start.ini"); + Path start_ini = baseHome.getBasePath("start.ini"); String short_start_ini = baseHome.toShortForm(start_ini); - File ini = new File(start_d,name + ".ini"); + Path ini = start_d.resolve(name + ".ini"); String short_ini = baseHome.toShortForm(ini); StartIni module_ini = null; - if (ini.exists()) + if (FS.exists(ini)) { module_ini = new StartIni(ini); if (module_ini.getLineMatches(Pattern.compile("--module=(.*, *)*" + name)).size() == 0) @@ -381,46 +391,30 @@ public class Main boolean has_ini_lines = module.getInitialise().size() > 0; // If it is not enabled or is transitive with ini template lines or toplevel and doesn't exist - if (!module.isEnabled() || (transitive && has_ini_lines) || (topLevel && !ini.exists() && !appendStartIni)) + if (!module.isEnabled() || (transitive && has_ini_lines) || (topLevel && !FS.exists(ini) && !appendStartIni)) { + // File BufferedWriter + BufferedWriter writer = null; String source = null; PrintWriter out = null; try { if (appendStartIni) { - if ((!start_ini.exists() && !start_ini.createNewFile()) || !start_ini.canWrite()) - { - StartLog.warn("ERROR: Bad %s! ",start_ini); - return; - } source = short_start_ini; StartLog.info("%-15s initialised in %s (appended)",name,source); - out = new PrintWriter(new FileWriter(start_ini,true)); + writer = Files.newBufferedWriter(start_ini,StandardCharsets.UTF_8,StandardOpenOption.CREATE,StandardOpenOption.APPEND); + out = new PrintWriter(writer); } else { // Create the directory if needed FS.ensureDirectoryExists(start_d); FS.ensureDirectoryWritable(start_d); - try - { - // Create a new ini file for it - if (!ini.createNewFile()) - { - StartLog.warn("ERROR: %s cannot be initialised in %s! ",name,short_ini); - return; - } - } - catch (IOException e) - { - StartLog.warn("ERROR: Unable to create %s!",ini); - StartLog.warn(e); - return; - } source = short_ini; StartLog.info("%-15s initialised in %s (created)",name,source); - out = new PrintWriter(ini); + writer = Files.newBufferedWriter(ini,StandardCharsets.UTF_8,StandardOpenOption.CREATE_NEW,StandardOpenOption.WRITE); + out = new PrintWriter(writer); } if (appendStartIni) @@ -468,7 +462,7 @@ public class Main } } } - else if (ini.exists()) + else if (FS.exists(ini)) { StartLog.info("%-15s initialised in %s",name,short_ini); } @@ -557,61 +551,49 @@ public class Main public StartArgs processCommandLine(String[] cmdLine) throws Exception { - StartArgs args = new StartArgs(cmdLine); + ConfigSources sources = new ConfigSources(); // Processing Order is important! // ------------------------------------------------------------ // 1) Directory Locations + + CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine); + sources.add(cmdLineSource); + sources.add(new JettyBaseConfigSource(cmdLineSource.getBasePath())); + sources.add(new JettyHomeConfigSource(cmdLineSource.getHomePath())); // Set Home and Base at the start, as many other paths encountered // will be based off of them. - baseHome.initialize(args); - + baseHome.initialize(sources); + // ------------------------------------------------------------ // 2) Start Logging - StartLog.getInstance().initialize(baseHome,args); + StartLog.getInstance().initialize(baseHome,cmdLineSource); StartLog.debug("jetty.home=%s",baseHome.getHome()); StartLog.debug("jetty.base=%s",baseHome.getBase()); // ------------------------------------------------------------ - // 3) Load Inis - File start_ini = baseHome.getBaseFile("start.ini"); - if (FS.canReadFile(start_ini)) - { - StartLog.debug("Reading ${jetty.base}/start.ini - %s",start_ini); - args.parse(baseHome,new StartIni(start_ini)); - } - - File start_d = baseHome.getBaseFile("start.d"); - if (FS.canReadDirectory(start_d)) - { - List paths = baseHome.getPaths(start_d.toPath(),1,"*.ini"); - Collections.sort(paths,new NaturalSort.Paths()); - for (Path path: paths) - { - StartLog.debug("Reading ${jetty.base}/start.d/%s - %s",path.getFileName(),path); - args.parse(baseHome,new StartIni(path)); - } - } - - // 4) Parse everything provided. + // 3) Parse everything provided. // This would be the directory information + // the various start inis // and then the raw command line arguments StartLog.debug("Parsing collected arguments"); - args.parseCommandLine(); + StartArgs args = new StartArgs(); + args.parse(sources); - // 5) Module Registration + // ------------------------------------------------------------ + // 4) Module Registration Modules modules = new Modules(); StartLog.debug("Registering all modules"); modules.registerAll(baseHome, args); - // 6) Active Module Resolution + // ------------------------------------------------------------ + // 5) Active Module Resolution for (String enabledModule : args.getEnabledModules()) { - List sources = args.getSources(enabledModule); - modules.enable(enabledModule,sources); + List msources = args.getSources(enabledModule); + modules.enable(enabledModule,msources); } StartLog.debug("Building Module Graph"); @@ -620,11 +602,13 @@ public class Main args.setAllModules(modules); List activeModules = modules.resolveEnabled(); - // 7) Lib & XML Expansion / Resolution + // ------------------------------------------------------------ + // 6) Lib & XML Expansion / Resolution args.expandLibs(baseHome); args.expandModules(baseHome,activeModules); - // 8) Resolve Extra XMLs + // ------------------------------------------------------------ + // 7) Resolve Extra XMLs args.resolveExtraXmls(baseHome); return args; @@ -666,7 +650,7 @@ public class Main // Generate Module Graph File if (args.getModuleGraphFilename() != null) { - File outputFile = baseHome.getBaseFile(args.getModuleGraphFilename()); + Path outputFile = baseHome.getBasePath(args.getModuleGraphFilename()); System.out.printf("Generating GraphViz Graph of Jetty Modules at %s%n",baseHome.toShortForm(outputFile)); ModuleGraphWriter writer = new ModuleGraphWriter(); writer.config(args.getProperties()); @@ -712,11 +696,13 @@ public class Main // Check ini files for download possibilities for (FileArg arg : args.getFiles()) { - File file = baseHome.getBaseFile(arg.location); - if (!file.exists() && args.isDownload()) + Path file = baseHome.getBasePath(arg.location); + if (!FS.exists(file) && args.isDownload()) + { initFile(arg); + } - if (!file.exists()) + if (!FS.exists(file)) { /* Startup should NEVER fail to run on missing content. * See Bug #427204 diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java index f995be6039d..2df1b05e64b 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Module.java @@ -19,10 +19,11 @@ package org.eclipse.jetty.start; import java.io.BufferedReader; -import java.io.File; import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.CollationKey; import java.text.Collator; import java.util.ArrayList; @@ -75,7 +76,7 @@ public class Module } /** The file of the module */ - private File file; + private Path file; /** The name of this Module (as a filesystem reference) */ private String fileRef; /** @@ -108,12 +109,12 @@ public class Module /** List of sources that enabled this module */ private final Set sources = new HashSet<>(); - public Module(BaseHome basehome, File file) throws FileNotFoundException, IOException + public Module(BaseHome basehome, Path file) throws FileNotFoundException, IOException { this.file = file; // Strip .mod - this.fileRef = Pattern.compile(".mod$",Pattern.CASE_INSENSITIVE).matcher(file.getName()).replaceFirst(""); + this.fileRef = Pattern.compile(".mod$",Pattern.CASE_INSENSITIVE).matcher(file.getFileName().toString()).replaceFirst(""); this.logicalName = fileRef; init(basehome); @@ -306,66 +307,63 @@ public class Module return; } - try (FileReader reader = new FileReader(file)) + try (BufferedReader buf = Files.newBufferedReader(file,StandardCharsets.UTF_8)) { - try (BufferedReader buf = new BufferedReader(reader)) + String sectionType = ""; + String line; + while ((line = buf.readLine()) != null) { - String sectionType = ""; - String line; - while ((line = buf.readLine()) != null) + line = line.trim(); + + Matcher sectionMatcher = section.matcher(line); + + if (sectionMatcher.matches()) { - line = line.trim(); - - Matcher sectionMatcher = section.matcher(line); - - if (sectionMatcher.matches()) + sectionType = sectionMatcher.group(1).trim().toUpperCase(Locale.ENGLISH); + } + else + { + // blank lines and comments are valid for ini-template section + if ((line.length() == 0) || line.startsWith("#")) { - sectionType = sectionMatcher.group(1).trim().toUpperCase(Locale.ENGLISH); + if ("INI-TEMPLATE".equals(sectionType)) + { + initialise.add(line); + } } else { - // blank lines and comments are valid for ini-template section - if ((line.length() == 0) || line.startsWith("#")) + switch (sectionType) { - if ("INI-TEMPLATE".equals(sectionType)) - { + case "": + // ignore (this would be entries before first section) + break; + case "DEPEND": + parentNames.add(line); + break; + case "FILES": + files.add(line); + break; + case "INI-TEMPLATE": initialise.add(line); - } - } - else - { - switch (sectionType) - { - case "": - // ignore (this would be entries before first section) - break; - case "DEPEND": - parentNames.add(line); - break; - case "FILES": - files.add(line); - break; - case "INI-TEMPLATE": - initialise.add(line); - break; - case "LIB": - libs.add(line); - break; - case "NAME": - logicalName = line; - break; - case "OPTIONAL": - optionalParentNames.add(line); - break; - case "EXEC": - jvmArgs.add(line); - break; - case "XML": - xmls.add(line); - break; - default: - throw new IOException("Unrecognized Module section: [" + sectionType + "]"); - } + break; + case "LIB": + libs.add(line); + break; + case "NAME": + logicalName = line; + break; + case "OPTIONAL": + optionalParentNames.add(line); + break; + case "EXEC": + jvmArgs.add(line); + break; + case "XML": + xmls.add(line); + break; + default: + throw new IOException("Unrecognized Module section: [" + sectionType + "]"); } } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java b/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java index b078f51ab3d..7c612a71400 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/ModuleGraphWriter.java @@ -18,10 +18,13 @@ package org.eclipse.jetty.start; -import java.io.File; -import java.io.FileWriter; +import java.io.BufferedWriter; import java.io.IOException; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.Collection; import java.util.List; @@ -73,9 +76,10 @@ public class ModuleGraphWriter return val; } - public void write(Modules modules, File outputFile) throws IOException + public void write(Modules modules, Path outputFile) throws IOException { - try (FileWriter writer = new FileWriter(outputFile,false); PrintWriter out = new PrintWriter(writer);) + try (BufferedWriter writer = Files.newBufferedWriter(outputFile,StandardCharsets.UTF_8,StandardOpenOption.CREATE_NEW,StandardOpenOption.WRITE); + PrintWriter out = new PrintWriter(writer);) { writeHeaderMessage(out,outputFile); @@ -112,7 +116,7 @@ public class ModuleGraphWriter } } - private void writeHeaderMessage(PrintWriter out, File outputFile) + private void writeHeaderMessage(PrintWriter out, Path outputFile) { out.println("/*"); out.println(" * GraphViz Graph of Jetty Modules"); @@ -121,7 +125,7 @@ public class ModuleGraphWriter out.println(" * GraphViz: http://graphviz.org/"); out.println(" * "); out.println(" * To Generate Graph image using graphviz:"); - String filename = outputFile.getName(); + String filename = outputFile.getFileName().toString(); String basename = filename.substring(0,filename.indexOf('.')); out.printf(" * $ dot -Tpng -Goverlap=false -o %s.png %s%n",basename,filename); out.println(" */"); diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java index ab263cef4ba..61330ac7546 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Modules.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.start; -import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; @@ -380,7 +379,7 @@ public class Modules implements Iterable { if (!modules.containsKey(name)) { - File file = basehome.getFile("modules/" + name + ".mod"); + Path file = basehome.getPath("modules/" + name + ".mod"); if (FS.canReadFile(file)) { Module parent = registerModule(basehome,args,file); @@ -395,7 +394,7 @@ public class Modules implements Iterable { for (Path path : basehome.getPaths("modules/*.mod")) { - registerModule(basehome,args,path.toFile()); + registerModule(basehome,args,path); } // load missing post-expanded dependent modules @@ -420,7 +419,7 @@ public class Modules implements Iterable for (String missingParent : missingParents) { - File file = basehome.getFile("modules/" + missingParent + ".mod"); + Path file = basehome.getPath("modules/" + missingParent + ".mod"); if (FS.canReadFile(file)) { Module module = registerModule(basehome,args,file); @@ -435,7 +434,7 @@ public class Modules implements Iterable } } - private Module registerModule(BaseHome basehome, StartArgs args, File file) throws FileNotFoundException, IOException + private Module registerModule(BaseHome basehome, StartArgs args, Path file) throws FileNotFoundException, IOException { if (!FS.canReadFile(file)) { diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Props.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Props.java index e9ebc26d214..f760f6e8e20 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Props.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Props.java @@ -18,8 +18,11 @@ package org.eclipse.jetty.start; +import static org.eclipse.jetty.start.UsageException.*; + import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -61,49 +64,90 @@ public final class Props implements Iterable } public static final String ORIGIN_SYSPROP = ""; + + public static String getValue(String arg) + { + int idx = arg.indexOf('='); + if (idx == (-1)) + { + throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg); + } + String value = arg.substring(idx + 1).trim(); + if (value.length() <= 0) + { + throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg); + } + return value; + } + + public static List getValues(String arg) + { + String v = getValue(arg); + ArrayList l = new ArrayList<>(); + for (String s : v.split(",")) + { + if (s != null) + { + s = s.trim(); + if (s.length() > 0) + { + l.add(s); + } + } + } + return l; + } private Map props = new HashMap<>(); + private List sysPropTracking = new ArrayList<>(); public void addAll(Props other) { this.props.putAll(other.props); + this.sysPropTracking.addAll(other.sysPropTracking); } - - public void addAllProperties(List args, String source) + + /** + * Add a potential argument as a property. + *

+ * If arg is not a property, ignore it. + * @param arg the argument to parse for a potential property + * @param source the source for this argument (to track origin of property from) + */ + public void addPossibleProperty(String arg, String source) { - for (String arg : args) + // Start property (syntax similar to System property) + if (arg.startsWith("-D")) { - // Start property (syntax similar to System property) - if (arg.startsWith("-D")) + String[] assign = arg.substring(2).split("=",2); + switch (assign.length) { - String[] assign = arg.substring(2).split("=",2); - switch (assign.length) - { - case 2: - setProperty(assign[0],assign[1],source); - break; - case 1: - setProperty(assign[0],"",source); - break; - default: - break; - } - continue; + case 2: + setSystemProperty(assign[0],assign[1]); + setProperty(assign[0],assign[1],source); + break; + case 1: + setSystemProperty(assign[0],""); + setProperty(assign[0],"",source); + break; + default: + break; } - - // Is this a raw property declaration? - int idx = arg.indexOf('='); - if (idx >= 0) - { - String key = arg.substring(0,idx); - String value = arg.substring(idx + 1); - - setProperty(key,value,source); - continue; - } - - // All other strings are ignored + return; } + + // Is this a raw property declaration? + int idx = arg.indexOf('='); + if (idx >= 0) + { + String key = arg.substring(0,idx); + String value = arg.substring(idx + 1); + + setProperty(key,value,source); + return; + } + + // All other strings are ignored } public String cleanReference(String property) @@ -302,4 +346,10 @@ public final class Props implements Iterable // write normal properties file props.store(stream,comments); } + + public void setSystemProperty(String key, String value) + { + System.setProperty(key,value); + sysPropTracking.add(key); + } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java index 140d84cdda7..46399e6be0e 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java @@ -25,24 +25,22 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.eclipse.jetty.start.Props.Prop; +import org.eclipse.jetty.start.config.ConfigSources; /** * The Arguments required to start Jetty. */ public class StartArgs { - public static final String CMD_LINE_SOURCE = ""; public static final String VERSION; static @@ -69,8 +67,6 @@ public class StartArgs private static final String SERVER_MAIN = "org.eclipse.jetty.xml.XmlConfiguration"; - private List commandLine = new ArrayList<>(); - /** List of enabled modules */ private Set modules = new HashSet<>(); /** Map of enabled modules to the source of where that activation occurred */ @@ -82,16 +78,13 @@ public class StartArgs /** List of all active [lib] sectinos from enabled modules */ private Classpath classpath; /** List of all active [xml] sections from enabled modules */ - private List xmls = new ArrayList<>(); + private List xmls = new ArrayList<>(); /** JVM arguments, found via commmand line and in all active [exec] sections from enabled modules */ private List jvmArgs = new ArrayList<>(); /** List of all xml references found directly on command line or start.ini */ private List xmlRefs = new ArrayList<>(); - /** List of extra Start Directories referenced */ - private LinkedList extraStartRefs = new LinkedList<>(); - private Props properties = new Props(); private Set systemPropertyKeys = new HashSet<>(); private List rawLibs = new ArrayList<>(); @@ -121,9 +114,8 @@ public class StartArgs private boolean exec = false; - public StartArgs(String[] commandLineArgs) + public StartArgs() { - commandLine.addAll(Arrays.asList(commandLineArgs)); classpath = new Classpath(); } @@ -142,13 +134,13 @@ public class StartArgs System.setProperty(key,value); } - private void addUniqueXmlFile(String xmlRef, File xmlfile) throws IOException + private void addUniqueXmlFile(String xmlRef, Path xmlfile) throws IOException { if (!FS.canReadFile(xmlfile)) { throw new IOException("Cannot read file: " + xmlRef); } - xmlfile = xmlfile.getCanonicalFile(); + xmlfile = FS.toRealPath(xmlfile); if (!xmls.contains(xmlfile)) { xmls.add(xmlfile); @@ -166,9 +158,9 @@ public class StartArgs return; } - for (File xml : xmls) + for (Path xml : xmls) { - System.out.printf(" %s%n",baseHome.toShortForm(xml.getAbsolutePath())); + System.out.printf(" %s%n",baseHome.toShortForm(xml.toAbsolutePath())); } } @@ -388,7 +380,7 @@ public class StartArgs for (String xmlRef : module.getXmls()) { // Straight Reference - File xmlfile = baseHome.getFile(xmlRef); + Path xmlfile = baseHome.getPath(xmlRef); addUniqueXmlFile(xmlRef,xmlfile); } @@ -411,16 +403,6 @@ public class StartArgs return classpath; } - public List getCommandLine() - { - return this.commandLine; - } - - public LinkedList getExtraStartRefs() - { - return extraStartRefs; - } - public List getFiles() { return files; @@ -484,9 +466,9 @@ public class StartArgs cmd.addRawArg(prop_file.getAbsolutePath()); } - for (File xml : xmls) + for (Path xml : xmls) { - cmd.addRawArg(xml.getAbsolutePath()); + cmd.addRawArg(xml.toAbsolutePath().toString()); } return cmd; @@ -523,40 +505,7 @@ public class StartArgs return sources.get(module); } - private String getValue(String arg) - { - int idx = arg.indexOf('='); - if (idx == (-1)) - { - throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg); - } - String value = arg.substring(idx + 1).trim(); - if (value.length() <= 0) - { - throw new UsageException(ERR_BAD_ARG,"Argument is missing a required value: %s",arg); - } - return value; - } - - private List getValues(String arg) - { - String v = getValue(arg); - ArrayList l = new ArrayList<>(); - for (String s : v.split(",")) - { - if (s != null) - { - s = s.trim(); - if (s.length() > 0) - { - l.add(s); - } - } - } - return l; - } - - public List getXmlFiles() + public List getXmlFiles() { return xmls; } @@ -656,24 +605,6 @@ public class StartArgs return version; } - public void parse(BaseHome baseHome, StartIni ini) - { - String source; - try - { - source = baseHome.toShortForm(ini.getFile()); - } - catch (Exception e) - { - throw new UsageException(ERR_BAD_ARG,"Bad file: %s",ini); - } - - for (String line : ini) - { - parse(line,source); - } - } - public void parse(final String rawarg, String source) { if (rawarg == null) @@ -695,28 +626,25 @@ public class StartArgs if ("--help".equals(arg) || "-?".equals(arg)) { - if (!CMD_LINE_SOURCE.equals(source)) - { - throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source); - } - help = true; run = false; return; } - if ("--debug".equals(arg)) + if ("--debug".equals(arg) || arg.startsWith("--start-log-file")) { // valid, but handled in StartLog instead return; } + if (arg.startsWith("--extra-start-dir=")) + { + // valid, but handled in ConfigSources instead + return; + } + if ("--stop".equals(arg)) { - if (!CMD_LINE_SOURCE.equals(source)) - { - throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source); - } stopCommand = true; run = false; return; @@ -724,7 +652,7 @@ public class StartArgs if (arg.startsWith("--download=")) { - addFile(getValue(arg)); + addFile(Props.getValue(arg)); run = false; download = true; return; @@ -753,10 +681,6 @@ public class StartArgs if ("--dry-run".equals(arg) || "--exec-print".equals(arg)) { - if (!CMD_LINE_SOURCE.equals(source)) - { - throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source); - } dryRun = true; run = false; return; @@ -769,18 +693,10 @@ public class StartArgs return; } - // Add extra start dir - if (arg.startsWith("--extra-start-dir=")) - { - String dirRef = getValue(arg); - extraStartRefs.add(dirRef); - return; - } - // Arbitrary Libraries if (arg.startsWith("--lib=")) { - String cp = getValue(arg); + String cp = Props.getValue(arg); if (cp != null) { @@ -804,11 +720,7 @@ public class StartArgs // jetty.base build-out : add to ${jetty.base}/start.d/ if (arg.startsWith("--add-to-startd=")) { - if (!CMD_LINE_SOURCE.equals(source)) - { - throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source); - } - addToStartdIni.addAll(getValues(arg)); + addToStartdIni.addAll(Props.getValues(arg)); run = false; download = true; return; @@ -817,11 +729,7 @@ public class StartArgs // jetty.base build-out : add to ${jetty.base}/start.ini if (arg.startsWith("--add-to-start=")) { - if (!CMD_LINE_SOURCE.equals(source)) - { - throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,source); - } - addToStartIni.addAll(getValues(arg)); + addToStartIni.addAll(Props.getValues(arg)); run = false; download = true; return; @@ -830,7 +738,7 @@ public class StartArgs // Enable a module if (arg.startsWith("--module=")) { - for (String moduleName : getValues(arg)) + for (String moduleName : Props.getValues(arg)) { modules.add(moduleName); List list = sources.get(moduleName); @@ -847,7 +755,7 @@ public class StartArgs // Create graphviz output of module graph if (arg.startsWith("--write-module-graph=")) { - this.moduleGraphFilename = getValue(arg); + this.moduleGraphFilename = Props.getValue(arg); run = false; return; } @@ -891,14 +799,11 @@ public class StartArgs String key = arg.substring(0,idx); String value = arg.substring(idx + 1); - if (source != CMD_LINE_SOURCE) + if (propertySource.containsKey(key)) { - if (propertySource.containsKey(key)) - { - throw new UsageException(ERR_BAD_ARG,"Property %s in %s already set in %s",key,source,propertySource.get(key)); - } - propertySource.put(key,source); + StartLog.warn("Property %s in %s already set in %s",key,source,propertySource.get(key)); } + propertySource.put(key,source); if ("OPTION".equals(key) || "OPTIONS".equals(key)) { @@ -930,26 +835,16 @@ public class StartArgs throw new UsageException(ERR_BAD_ARG,"Unrecognized argument: \"%s\" in %s",arg,source); } - public StartArgs parseCommandLine() - { - for (String line : commandLine) - { - parse(line,StartArgs.CMD_LINE_SOURCE); - } - - return this; - } - public void resolveExtraXmls(BaseHome baseHome) throws IOException { // Find and Expand XML files for (String xmlRef : xmlRefs) { // Straight Reference - File xmlfile = baseHome.getFile(xmlRef); - if (!xmlfile.exists()) + Path xmlfile = baseHome.getPath(xmlRef); + if (!FS.exists(xmlfile)) { - xmlfile = baseHome.getFile("etc/" + xmlRef); + xmlfile = baseHome.getPath("etc/" + xmlRef); } addUniqueXmlFile(xmlRef,xmlfile); } @@ -964,9 +859,7 @@ public class StartArgs public String toString() { StringBuilder builder = new StringBuilder(); - builder.append("StartArgs [commandLine="); - builder.append(commandLine); - builder.append(", enabledModules="); + builder.append("StartArgs [enabledModules="); builder.append(modules); builder.append(", xmlRefs="); builder.append(xmlRefs); @@ -978,4 +871,8 @@ public class StartArgs return builder.toString(); } + public void parse(ConfigSources sources) + { + // TODO Auto-generated method stub + } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java index afc2ed1e1bb..79b079cd712 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java @@ -21,9 +21,6 @@ package org.eclipse.jetty.start; import java.io.File; import java.io.IOException; import java.nio.file.Path; -import java.util.List; - -import org.eclipse.jetty.start.config.ConfigSource; /** * Simple Start .INI handler diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartLog.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartLog.java index c411d0dae87..1f4105d7058 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartLog.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartLog.java @@ -18,13 +18,15 @@ package org.eclipse.jetty.start; -import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.Date; -import java.util.regex.Matcher; -import java.util.regex.Pattern; + +import org.eclipse.jetty.start.config.CommandLineConfigSource; /** * Centralized Place for logging. @@ -62,7 +64,7 @@ public class StartLog { System.err.printf("INFO: " + format + "%n",args); } - + public static void warn(String format, Object... args) { System.err.printf("WARNING: " + format + "%n",args); @@ -72,7 +74,7 @@ public class StartLog { t.printStackTrace(System.err); } - + public static boolean isDebugEnabled() { return INSTANCE.debug; @@ -80,17 +82,17 @@ public class StartLog private boolean debug = false; - public void initialize(BaseHome baseHome, StartArgs args) throws IOException + public void initialize(BaseHome baseHome, CommandLineConfigSource cmdLineSource) throws IOException { - // Debug with boolean - Pattern debugBoolPat = Pattern.compile("(-D)?debug=(.*)"); - // Log file name - Pattern logFilePat = Pattern.compile("(-D)?start-log-file=(.*)"); + String dbgProp = cmdLineSource.getProperty("debug"); + if (dbgProp != null) + { + debug = Boolean.parseBoolean(dbgProp); + } - // TODO: support backward compatible --daemon argument ?? + String logFileName = cmdLineSource.getProperty("start-log-file"); - Matcher matcher; - for (String arg : args.getCommandLine()) + for (String arg : cmdLineSource.getArgs()) { if ("--debug".equals(arg)) { @@ -98,55 +100,55 @@ public class StartLog continue; } - matcher = debugBoolPat.matcher(arg); - if (matcher.matches()) + if (arg.startsWith("--start-log-file")) { - debug = Boolean.parseBoolean(matcher.group(2)); + logFileName = Props.getValue(arg); continue; } + } - matcher = logFilePat.matcher(arg); - if (matcher.matches()) - { - String filename = matcher.group(2); - File logfile = baseHome.getBaseFile(filename); - initLogFile(logfile); - } + if (logFileName != null) + { + Path logfile = baseHome.getBasePath(logFileName); + initLogFile(logfile); } } - public void initLogFile(File logfile) throws IOException + public void initLogFile(Path logfile) throws IOException { if (logfile != null) { - File logDir = logfile.getParentFile(); - if (!logDir.exists() || !logDir.canWrite()) + try { - String err = String.format("Cannot write %s to directory %s [directory doesn't exist or is read-only]",logfile.getName(), - logDir.getAbsolutePath()); - throw new UsageException(UsageException.ERR_LOGGING,new IOException(err)); + Path logDir = logfile.getParent(); + FS.ensureDirectoryWritable(logDir); + + Path startLog = logfile; + + if (!FS.exists(startLog) && !FS.createNewFile(startLog)) + { + // Output about error is lost in majority of cases. + throw new UsageException(UsageException.ERR_LOGGING,new IOException("Unable to create: " + startLog.toAbsolutePath())); + } + + if (!FS.canWrite(startLog)) + { + // Output about error is lost in majority of cases. + throw new UsageException(UsageException.ERR_LOGGING,new IOException("Unable to write to: " + startLog.toAbsolutePath())); + } + + System.out.println("Logging to " + logfile); + + OutputStream out = Files.newOutputStream(startLog,StandardOpenOption.CREATE,StandardOpenOption.APPEND); + PrintStream logger = new PrintStream(out); + System.setOut(logger); + System.setErr(logger); + System.out.println("Establishing " + logfile + " on " + new Date()); } - - File startLog = logfile; - - if (!startLog.exists() && !startLog.createNewFile()) + catch (IOException e) { - // Output about error is lost in majority of cases. - throw new UsageException(UsageException.ERR_LOGGING,new IOException("Unable to create: " + startLog.getAbsolutePath())); + throw new UsageException(UsageException.ERR_LOGGING,e); } - - if (!startLog.canWrite()) - { - // Output about error is lost in majority of cases. - throw new UsageException(UsageException.ERR_LOGGING,new IOException("Unable to write to: " + startLog.getAbsolutePath())); - } - - System.out.println("Logging to " + logfile); - - PrintStream logger = new PrintStream(new FileOutputStream(startLog,false)); - System.setOut(logger); - System.setErr(logger); - System.out.println("Establishing " + logfile + " on " + new Date()); } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java index f38c10c36a4..6b9cba7afd0 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java @@ -18,9 +18,11 @@ package org.eclipse.jetty.start.config; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; +import org.eclipse.jetty.start.FS; import org.eclipse.jetty.start.Props; /** @@ -32,24 +34,37 @@ public class CommandLineConfigSource implements ConfigSource private final List args; private final Props props; + private final Path homePath; + private final Path basePath; public CommandLineConfigSource(String rawargs[]) { this.args = Arrays.asList(rawargs); this.props = new Props(); - this.props.addAllProperties(args, CMD_LINE_SOURCE); - } - - @Override - public Props getProps() - { - return props; - } - - @Override - public String getProperty(String key) - { - return props.getString(key); + for (String arg : args) + { + this.props.addPossibleProperty(arg,CMD_LINE_SOURCE); + } + + Path home = FS.toOptionalPath(getProperty("jetty.home")); + Path base = FS.toOptionalPath(getProperty("jetty.base")); + + if (home != null) + { + // logic if home is specified + if (base == null) + { + base = home; + setProperty("jetty.base",base.toString(),""); + } + } + + this.homePath = home; + this.basePath = base; + + // Update System Properties + setSystemProperty("jetty.home",homePath.toAbsolutePath().toString()); + setSystemProperty("jetty.base",basePath.toAbsolutePath().toString()); } @Override @@ -88,12 +103,34 @@ public class CommandLineConfigSource implements ConfigSource return args; } + public Path getBasePath() + { + return basePath; + } + + public Path getHomePath() + { + return homePath; + } + @Override public String getId() { return CMD_LINE_SOURCE; } + @Override + public String getProperty(String key) + { + return props.getString(key); + } + + @Override + public Props getProps() + { + return props; + } + @Override public int getWeight() { @@ -109,6 +146,16 @@ public class CommandLineConfigSource implements ConfigSource return result; } + public void setProperty(String key, String value, String origin) + { + this.props.setProperty(key,value,origin); + } + + public void setSystemProperty(String key, String value) + { + this.props.setSystemProperty(key,value); + } + @Override public String toString() { diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSources.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSources.java index d1cbbe1e206..5f5843e7d2c 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSources.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/ConfigSources.java @@ -80,6 +80,18 @@ public class ConfigSources implements Iterable } } + public CommandLineConfigSource getCommandLineSource() + { + for (ConfigSource source : sources) + { + if (source instanceof CommandLineConfigSource) + { + return (CommandLineConfigSource)source; + } + } + return null; + } + public Prop getProp(String key) { return props.getProp(key); @@ -111,6 +123,31 @@ public class ConfigSources implements Iterable return sources.iterator(); } + public ListIterator reverseListIterator() + { + return sources.listIterator(sources.size()); + } + + @Override + public String toString() + { + StringBuilder str = new StringBuilder(); + str.append(this.getClass().getSimpleName()); + str.append('['); + boolean delim = false; + for (ConfigSource source : sources) + { + if (delim) + { + str.append(','); + } + str.append(source.getId()); + delim = true; + } + str.append(']'); + return str.toString(); + } + private void updateProps() { props.reset(); diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/DirConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/DirConfigSource.java index ee6224aedf5..b3a818c3bf0 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/config/DirConfigSource.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/DirConfigSource.java @@ -18,19 +18,25 @@ package org.eclipse.jetty.start.config; +import static org.eclipse.jetty.start.UsageException.*; + import java.io.IOException; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.eclipse.jetty.start.FS; +import org.eclipse.jetty.start.NaturalSort; import org.eclipse.jetty.start.PathMatchers; import org.eclipse.jetty.start.Props; +import org.eclipse.jetty.start.UsageException; import org.eclipse.jetty.start.Props.Prop; import org.eclipse.jetty.start.StartIni; +import org.eclipse.jetty.start.StartLog; /** * A Directory based {@link ConfigSource}. @@ -39,6 +45,29 @@ import org.eclipse.jetty.start.StartIni; */ public class DirConfigSource implements ConfigSource { + private static final List BANNED_ARGS; + + static + { + // Arguments that are not allowed to be in start.ini or start.d/{name}.ini files + BANNED_ARGS = new ArrayList<>(); + BANNED_ARGS.add("--help"); + BANNED_ARGS.add("-?"); + BANNED_ARGS.add("--stop"); + BANNED_ARGS.add("--dry-run"); + BANNED_ARGS.add("--exec-print"); + BANNED_ARGS.add("--list-config"); + BANNED_ARGS.add("--list-classpath"); + BANNED_ARGS.add("--list-modules"); + BANNED_ARGS.add("--write-module-graph"); + BANNED_ARGS.add("--version"); + BANNED_ARGS.add("-v"); + BANNED_ARGS.add("--download"); + BANNED_ARGS.add("--create-files"); + BANNED_ARGS.add("--add-to-startd"); + BANNED_ARGS.add("--add-to-start"); + } + private final String id; private final Path dir; private final int weight; @@ -62,7 +91,7 @@ public class DirConfigSource implements ConfigSource public DirConfigSource(String id, Path dir, int weight, boolean canHaveArgs) throws IOException { this.id = id; - this.dir = dir; + this.dir = dir.toAbsolutePath(); this.weight = weight; this.props = new Props(); @@ -75,7 +104,7 @@ public class DirConfigSource implements ConfigSource { StartIni ini = new StartIni(iniFile); args.addAll(ini.getLines()); - this.props.addAllProperties(ini.getLines(),iniFile.toString()); + parseAllArgs(ini.getLines(),iniFile.toString()); } Path startDdir = dir.resolve("start.d"); @@ -93,45 +122,74 @@ public class DirConfigSource implements ConfigSource } }; + List paths = new ArrayList<>(); + for (Path diniFile : Files.newDirectoryStream(startDdir,filter)) { if (FS.canReadFile(diniFile)) { - StartIni ini = new StartIni(diniFile); - args.addAll(ini.getLines()); - this.props.addAllProperties(ini.getLines(),diniFile.toString()); + paths.add(diniFile); } } + + Collections.sort(paths,new NaturalSort.Paths()); + + for (Path diniFile : paths) + { + StartLog.debug("Reading %s/start.d/%s - %s",id,diniFile.getFileName(),diniFile); + StartIni ini = new StartIni(diniFile); + args.addAll(ini.getLines()); + parseAllArgs(ini.getLines(),diniFile.toString()); + } } } } - @Override - public int hashCode() + private void parseAllArgs(List lines, String origin) { - final int prime = 31; - int result = 1; - result = prime * result + ((dir == null)?0:dir.hashCode()); - return result; + for (String line : lines) + { + String arg = line; + int idx = line.indexOf('='); + if (idx > 0) + { + arg = line.substring(0,idx); + } + if (BANNED_ARGS.contains(arg)) + { + throw new UsageException(ERR_BAD_ARG,"%s not allowed in %s",arg,origin); + } + this.props.addPossibleProperty(line,origin); + } } @Override public boolean equals(Object obj) { if (this == obj) + { return true; + } if (obj == null) + { return false; + } if (getClass() != obj.getClass()) + { return false; + } DirConfigSource other = (DirConfigSource)obj; if (dir == null) { if (other.dir != null) + { return false; + } } else if (!dir.equals(other.dir)) + { return false; + } return true; } @@ -152,18 +210,6 @@ public class DirConfigSource implements ConfigSource return id; } - @Override - public int getWeight() - { - return weight; - } - - @Override - public Props getProps() - { - return props; - } - @Override public String getProperty(String key) { @@ -175,6 +221,32 @@ public class DirConfigSource implements ConfigSource return prop.value; } + @Override + public Props getProps() + { + return props; + } + + @Override + public int getWeight() + { + return weight; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = (prime * result) + ((dir == null)?0:dir.hashCode()); + return result; + } + + public boolean isPropertyBased() + { + return id.contains("${"); + } + @Override public String toString() { diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java index 5514abd66ab..368668bfd9b 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java @@ -26,6 +26,9 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import org.eclipse.jetty.start.config.ConfigSources; +import org.eclipse.jetty.start.config.JettyBaseConfigSource; +import org.eclipse.jetty.start.config.JettyHomeConfigSource; import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.junit.Assert; @@ -92,18 +95,22 @@ public class BaseHomeTest } @Test - public void testGetFile_OnlyHome() throws IOException + public void testGetPath_OnlyHome() throws IOException { File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home"); File baseDir = null; + + ConfigSources config = new ConfigSources(); + config.add(new JettyHomeConfigSource(homeDir.toPath())); BaseHome hb = new BaseHome(homeDir,baseDir); - File startIni = hb.getFile("start.ini"); + hb.initialize(config); + Path startIni = hb.getPath("start.ini"); String ref = hb.toShortForm(startIni); Assert.assertThat("Reference",ref,startsWith("${jetty.home}")); - String contents = IO.readToString(startIni); + String contents = IO.readToString(startIni.toFile()); Assert.assertThat("Contents",contents,containsString("Home Ini")); } @@ -113,7 +120,11 @@ public class BaseHomeTest File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home"); File baseDir = null; + ConfigSources config = new ConfigSources(); + config.add(new JettyHomeConfigSource(homeDir.toPath())); + BaseHome hb = new BaseHome(homeDir,baseDir); + hb.initialize(config); List paths = hb.getPaths("start.d/*"); List expected = new ArrayList<>(); @@ -133,7 +144,11 @@ public class BaseHomeTest File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home"); File baseDir = null; + ConfigSources config = new ConfigSources(); + config.add(new JettyHomeConfigSource(homeDir.toPath())); + BaseHome hb = new BaseHome(homeDir,baseDir); + hb.initialize(config); List paths = hb.getPaths("start.d/*.ini"); List expected = new ArrayList<>(); @@ -153,7 +168,12 @@ public class BaseHomeTest File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home"); File baseDir = MavenTestingUtils.getTestResourceDir("hb.1/base"); + ConfigSources config = new ConfigSources(); + config.add(new JettyBaseConfigSource(baseDir.toPath())); + config.add(new JettyHomeConfigSource(homeDir.toPath())); + BaseHome hb = new BaseHome(homeDir,baseDir); + hb.initialize(config); List paths = hb.getPaths("start.d/*.ini"); List expected = new ArrayList<>(); @@ -167,30 +187,6 @@ public class BaseHomeTest assertPathList(hb,"Paths found",expected,paths); } - - @Test - public void testGetPaths_More() throws IOException - { - File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home"); - File baseDir = MavenTestingUtils.getTestResourceDir("hb.1/base"); - File moreDir = MavenTestingUtils.getTestResourceDir("extra-start-dirs/more-startd"); - - BaseHome hb = new BaseHome(homeDir,baseDir); - hb.addExtraStart("more",moreDir); - List paths = hb.getPaths("start.d/*.ini"); - - List expected = new ArrayList<>(); - expected.add("${more}/start.d/more.ini"); - expected.add("${jetty.base}/start.d/jmx.ini"); - expected.add("${jetty.home}/start.d/jndi.ini"); - expected.add("${jetty.home}/start.d/jsp.ini"); - expected.add("${jetty.base}/start.d/logging.ini"); - expected.add("${jetty.home}/start.d/ssl.ini"); - expected.add("${jetty.base}/start.d/myapp.ini"); - FSTest.toOsSeparators(expected); - - assertPathList(hb,"Paths found",expected,paths); - } @Test public void testDefault() throws IOException @@ -201,18 +197,23 @@ public class BaseHomeTest } @Test - public void testGetFile_Both() throws IOException + public void testGetPath_Both() throws IOException { File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home"); File baseDir = MavenTestingUtils.getTestResourceDir("hb.1/base"); + ConfigSources config = new ConfigSources(); + config.add(new JettyBaseConfigSource(baseDir.toPath())); + config.add(new JettyHomeConfigSource(homeDir.toPath())); + BaseHome hb = new BaseHome(homeDir,baseDir); - File startIni = hb.getFile("start.ini"); + hb.initialize(config); + Path startIni = hb.getPath("start.ini"); String ref = hb.toShortForm(startIni); Assert.assertThat("Reference",ref,startsWith("${jetty.base}")); - String contents = IO.readToString(startIni); + String contents = IO.readToString(startIni.toFile()); Assert.assertThat("Contents",contents,containsString("Base Ini")); } } diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java index 5ad4a238170..dcd9baf221d 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java @@ -25,6 +25,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -64,7 +65,7 @@ public class ConfigurationAssert } } List actualXmls = new ArrayList<>(); - for (File xml : args.getXmlFiles()) + for (Path xml : args.getXmlFiles()) { actualXmls.add(shorten(baseHome,xml,testResourcesDir)); } @@ -82,7 +83,7 @@ public class ConfigurationAssert List actualLibs = new ArrayList<>(); for (File path : args.getClasspath()) { - actualLibs.add(shorten(baseHome,path,testResourcesDir)); + actualLibs.add(shorten(baseHome,path.toPath(),testResourcesDir)); } assertContainsUnordered("Libs",expectedLibs,actualLibs); @@ -147,7 +148,7 @@ public class ConfigurationAssert assertContainsUnordered("Files/Dirs",expectedFiles,actualFiles); } - private static String shorten(BaseHome baseHome, File path, File testResourcesDir) + private static String shorten(BaseHome baseHome, Path path, File testResourcesDir) { String value = baseHome.toShortForm(path); if (value.startsWith(testResourcesDir.getAbsolutePath())) diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java index c217e53d02e..f22c37b3fd6 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java @@ -25,6 +25,9 @@ import java.util.ArrayList; import java.util.List; import org.eclipse.jetty.start.Props.Prop; +import org.eclipse.jetty.start.config.ConfigSource; +import org.eclipse.jetty.start.config.ConfigSources; +import org.eclipse.jetty.start.config.DirConfigSource; import org.eclipse.jetty.toolchain.test.FS; import org.eclipse.jetty.toolchain.test.TestingDir; import org.junit.Assert; @@ -40,14 +43,15 @@ public class ExtraStartTest public void assertSearchOrder(List expectedSearchOrder) { + ConfigSources sources = main.getBaseHome().getConfigSources(); List actualOrder = new ArrayList<>(); - actualOrder.add("${jetty.base}"); - List startRefs = args.getExtraStartRefs(); - if (startRefs.size() > 0) + for (ConfigSource source : sources) { - actualOrder.addAll(startRefs); + if (source instanceof DirConfigSource) + { + actualOrder.add(source.getId()); + } } - actualOrder.add("${jetty.home}"); ConfigurationAssert.assertOrdered("Search Order",expectedSearchOrder,actualOrder); } @@ -92,7 +96,7 @@ public class ExtraStartTest FS.ensureEmpty(base); TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1"); - + // Simple command line - no reference to extra-start-dirs MainResult result = runMain(base,home); @@ -125,7 +129,7 @@ public class ExtraStartTest // Simple command line reference to extra-start-dir MainResult result = runMain(base,home, - // direct reference via path + // direct reference via path "--extra-start-dir=" + common.getAbsolutePath()); List expectedSearchOrder = new ArrayList<>(); @@ -158,9 +162,9 @@ public class ExtraStartTest "jetty.host=127.0.0.1"); // Simple command line reference to extra-start-dir via property (also on command line) - MainResult result = runMain(base,home, - // property - "my.common=" + common.getAbsolutePath(), + MainResult result = runMain(base,home, + // property + "my.common=" + common.getAbsolutePath(), // reference via property "--extra-start-dir=${my.common}"); @@ -185,9 +189,9 @@ public class ExtraStartTest // Create opt File opt = testdir.getFile("opt"); FS.ensureEmpty(opt); - + // Create common - File common = new File(opt, "common"); + File common = new File(opt,"common"); FS.ensureEmpty(common); TestEnv.makeFile(common,"start.ini","jetty.port=8080"); @@ -196,13 +200,13 @@ public class ExtraStartTest FS.ensureEmpty(base); TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1"); - + String dirRef = "${my.opt}" + File.separator + "common"; // Simple command line reference to extra-start-dir via property (also on command line) - MainResult result = runMain(base,home, - // property to 'opt' dir - "my.opt=" + opt.getAbsolutePath(), + MainResult result = runMain(base,home, + // property to 'opt' dir + "my.opt=" + opt.getAbsolutePath(), // reference via property prefix "--extra-start-dir=" + dirRef); @@ -219,7 +223,7 @@ public class ExtraStartTest @Test public void testCommandLine_1Extra_FromCompoundProp() throws Exception { - // Create home + // Create home File home = testdir.getFile("home"); FS.ensureEmpty(home); TestEnv.copyTestDir("usecases/home",home); @@ -227,9 +231,9 @@ public class ExtraStartTest // Create opt File opt = testdir.getFile("opt"); FS.ensureEmpty(opt); - + // Create common - File common = new File(opt, "common"); + File common = new File(opt,"common"); FS.ensureEmpty(common); TestEnv.makeFile(common,"start.ini","jetty.port=8080"); @@ -238,12 +242,12 @@ public class ExtraStartTest FS.ensureEmpty(base); TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1"); - + String dirRef = "${my.opt}" + File.separator + "${my.dir}"; - + // Simple command line reference to extra-start-dir via property (also on command line) - MainResult result = runMain(base,home, - // property to 'opt' dir + MainResult result = runMain(base,home, + // property to 'opt' dir "my.opt=" + opt.getAbsolutePath(), // property to commmon dir name "my.dir=common", @@ -327,7 +331,7 @@ public class ExtraStartTest result.assertProperty("jetty.host","127.0.0.1"); result.assertProperty("jetty.port","8080"); // from 'common' } - + @Test public void testRefCommonRefCorp() throws Exception { @@ -368,7 +372,7 @@ public class ExtraStartTest result.assertProperty("jetty.host","127.0.0.1"); result.assertProperty("jetty.port","8080"); // from 'common' } - + @Test public void testRefCommonRefCorp_FromSimpleProps() throws Exception { @@ -396,7 +400,7 @@ public class ExtraStartTest FS.ensureEmpty(base); TestEnv.makeFile(base,"start.ini", // "jetty.host=127.0.0.1",// - "my.common="+common.getAbsolutePath(), // + "my.common=" + common.getAbsolutePath(), // "--extra-start-dir=${my.common}"); MainResult result = runMain(base,home); @@ -411,7 +415,7 @@ public class ExtraStartTest result.assertProperty("jetty.host","127.0.0.1"); result.assertProperty("jetty.port","8080"); // from 'common' } - + @Test public void testRefCommonRefCorp_CmdLineRef() throws Exception { @@ -448,7 +452,7 @@ public class ExtraStartTest "--extra-start-dir=" + common.getAbsolutePath()); MainResult result = runMain(base,home, - // command line provided extra-start-dir ref + // command line provided extra-start-dir ref "--extra-start-dir=" + devops.getAbsolutePath()); List expectedSearchOrder = new ArrayList<>(); @@ -462,7 +466,7 @@ public class ExtraStartTest result.assertProperty("jetty.host","127.0.0.1"); result.assertProperty("jetty.port","2222"); // from 'devops' } - + @Test public void testRefCommonRefCorp_CmdLineProp() throws Exception { @@ -492,7 +496,7 @@ public class ExtraStartTest "--extra-start-dir=" + common.getAbsolutePath()); MainResult result = runMain(base,home, - // command line property should override all others + // command line property should override all others "jetty.port=7070"); List expectedSearchOrder = new ArrayList<>(); @@ -505,7 +509,7 @@ public class ExtraStartTest result.assertProperty("jetty.host","127.0.0.1"); result.assertProperty("jetty.port","7070"); // from command line } - + @Test public void testBadDoubleRef() throws Exception { @@ -521,15 +525,15 @@ public class ExtraStartTest // Create corp File corp = testdir.getFile("corp"); FS.ensureEmpty(corp); - TestEnv.makeFile(corp,"start.ini", - // standard property + TestEnv.makeFile(corp,"start.ini", + // standard property "jetty.port=9090", // INTENTIONAL BAD Reference (duplicate) "--extra-start-dir=" + common.getAbsolutePath()); // Populate common - TestEnv.makeFile(common,"start.ini", - // standard property + TestEnv.makeFile(common,"start.ini", + // standard property "jetty.port=8080", // reference to corp "--extra-start-dir=" + corp.getAbsolutePath()); diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java index 92ad50363a3..c4aeb53c282 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java @@ -18,13 +18,16 @@ package org.eclipse.jetty.start; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import java.io.File; import java.io.IOException; -import java.util.Collections; -import java.util.List; +import java.nio.file.Path; +import org.eclipse.jetty.start.config.CommandLineConfigSource; +import org.eclipse.jetty.start.config.ConfigSources; +import org.eclipse.jetty.start.config.JettyBaseConfigSource; +import org.eclipse.jetty.start.config.JettyHomeConfigSource; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.TestingDir; import org.junit.Assert; @@ -33,30 +36,40 @@ import org.junit.Test; public class ModuleGraphWriterTest { - @SuppressWarnings("unused") - private final static List TEST_SOURCE = Collections.singletonList(""); - - private StartArgs DEFAULT_ARGS = new StartArgs(new String[]{"jetty.version=TEST"}).parseCommandLine(); - @Rule public TestingDir testdir = new TestingDir(); @Test public void testGenerate_NothingEnabled() throws IOException { + // Test Env File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home"); File baseDir = testdir.getEmptyDir(); - BaseHome basehome = new BaseHome(homeDir,baseDir); + String cmdLine[] = new String[] {"jetty.version=TEST"}; + + // Configuration + CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine); + ConfigSources config = new ConfigSources(); + config.add(cmdLineSource); + config.add(new JettyHomeConfigSource(homeDir.toPath())); + config.add(new JettyBaseConfigSource(baseDir.toPath())); + + // Initialize + BaseHome basehome = new BaseHome(); + basehome.initialize(config); + + StartArgs args = new StartArgs(); + args.parse(config); Modules modules = new Modules(); - modules.registerAll(basehome, DEFAULT_ARGS); + modules.registerAll(basehome, args); modules.buildGraph(); - File outputFile = new File(baseDir,"graph.dot"); + Path outputFile = basehome.getBasePath("graph.dot"); ModuleGraphWriter writer = new ModuleGraphWriter(); writer.write(modules,outputFile); - Assert.assertThat("Output File Exists",outputFile.exists(),is(true)); + Assert.assertThat("Output File Exists",FS.exists(outputFile),is(true)); } } diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java index 31b2e4d299e..c49ee3a1068 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java @@ -34,7 +34,7 @@ public class ModuleTest private Module loadTestHomeModule(String moduleFileName) throws IOException { File file = MavenTestingUtils.getTestResourceFile("usecases/home/modules/" + moduleFileName); - return new Module(new BaseHome(),file); + return new Module(new BaseHome(),file.toPath()); } @Test diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java index 6ce160cd9d1..744a3bdc66e 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java @@ -27,23 +27,48 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.eclipse.jetty.start.config.CommandLineConfigSource; +import org.eclipse.jetty.start.config.ConfigSources; +import org.eclipse.jetty.start.config.JettyBaseConfigSource; +import org.eclipse.jetty.start.config.JettyHomeConfigSource; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.TestingDir; import org.junit.Assert; +import org.junit.Rule; import org.junit.Test; public class ModulesTest { private final static List TEST_SOURCE = Collections.singletonList(""); - private StartArgs DEFAULT_ARGS = new StartArgs(new String[] { "jetty.version=TEST" }).parseCommandLine(); + + @Rule + public TestingDir testdir = new TestingDir(); @Test public void testLoadAllModules() throws IOException { + // Test Env File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home"); - BaseHome basehome = new BaseHome(homeDir,homeDir); + File baseDir = testdir.getEmptyDir(); + String cmdLine[] = new String[] {"jetty.version=TEST"}; + + // Configuration + CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine); + ConfigSources config = new ConfigSources(); + config.add(cmdLineSource); + config.add(new JettyHomeConfigSource(homeDir.toPath())); + config.add(new JettyBaseConfigSource(baseDir.toPath())); + + // Initialize + BaseHome basehome = new BaseHome(); + basehome.initialize(config); + + StartArgs args = new StartArgs(); + args.parse(config); + // Test Modules Modules modules = new Modules(); - modules.registerAll(basehome,DEFAULT_ARGS); + modules.registerAll(basehome,args); List moduleNames = new ArrayList<>(); for (Module mod : modules) @@ -66,11 +91,28 @@ public class ModulesTest @Test public void testEnableRegexSimple() throws IOException { + // Test Env File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home"); - BaseHome basehome = new BaseHome(homeDir,homeDir); + File baseDir = testdir.getEmptyDir(); + String cmdLine[] = new String[] {"jetty.version=TEST"}; + + // Configuration + CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine); + ConfigSources config = new ConfigSources(); + config.add(cmdLineSource); + config.add(new JettyHomeConfigSource(homeDir.toPath())); + config.add(new JettyBaseConfigSource(baseDir.toPath())); + + // Initialize + BaseHome basehome = new BaseHome(); + basehome.initialize(config); + + StartArgs args = new StartArgs(); + args.parse(config); + // Test Modules Modules modules = new Modules(); - modules.registerAll(basehome,DEFAULT_ARGS); + modules.registerAll(basehome,args); modules.enable("[sj]{1}.*",TEST_SOURCE); String expected[] = { "jmx", "stats", "spdy", "security", "jndi", "jsp", "servlet", "jaas", "server" }; @@ -81,12 +123,28 @@ public class ModulesTest @Test public void testResolve_ServerHttp() throws IOException { + // Test Env File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home"); - BaseHome basehome = new BaseHome(homeDir,homeDir); + File baseDir = testdir.getEmptyDir(); + String cmdLine[] = new String[] {"jetty.version=TEST"}; + + // Configuration + CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine); + ConfigSources config = new ConfigSources(); + config.add(cmdLineSource); + config.add(new JettyHomeConfigSource(homeDir.toPath())); + config.add(new JettyBaseConfigSource(baseDir.toPath())); + + // Initialize + BaseHome basehome = new BaseHome(); + basehome.initialize(config); + + StartArgs args = new StartArgs(); + args.parse(config); - // Register modules + // Test Modules Modules modules = new Modules(); - modules.registerAll(basehome,DEFAULT_ARGS); + modules.registerAll(basehome,args); modules.buildGraph(); // Enable 2 modules @@ -137,12 +195,28 @@ public class ModulesTest @Test public void testResolve_WebSocket() throws IOException { + // Test Env File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home"); - BaseHome basehome = new BaseHome(homeDir,homeDir); + File baseDir = testdir.getEmptyDir(); + String cmdLine[] = new String[] {"jetty.version=TEST"}; + + // Configuration + CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine); + ConfigSources config = new ConfigSources(); + config.add(cmdLineSource); + config.add(new JettyHomeConfigSource(homeDir.toPath())); + config.add(new JettyBaseConfigSource(baseDir.toPath())); + + // Initialize + BaseHome basehome = new BaseHome(); + basehome.initialize(config); + + StartArgs args = new StartArgs(); + args.parse(config); - // Register modules + // Test Modules Modules modules = new Modules(); - modules.registerAll(basehome,DEFAULT_ARGS); + modules.registerAll(basehome,args); modules.buildGraph(); // modules.dump(); From 5a0811b328031c4da35c060bbcf33c5bdf3f04c5 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 11 Apr 2014 10:48:24 +1000 Subject: [PATCH 028/135] 432468 Improve command CGI path handling --- .../java/org/eclipse/jetty/servlets/CGI.java | 198 +++++++++++------- 1 file changed, 119 insertions(+), 79 deletions(-) diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java index dc2a9c65599..ba56bc7d90c 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java @@ -47,32 +47,33 @@ import org.eclipse.jetty.util.log.Logger; //----------------------------------------------------------------------------- /** * CGI Servlet. - *

- * The cgi bin directory can be set with the "cgibinResourceBase" init parameter or it will default to the resource base of the context. If the - * "cgibinResourceBaseIsRelative" init parameter is set the resource base is relative to the webapp. For example "WEB-INF/cgi" would work. - *
- * Not that this only works for extracted war files as "jar cf" will not reserve the execute permissions on the cgi files. - *

- * The "commandPrefix" init parameter may be used to set a prefix to all commands passed to exec. This can be used on systems that need assistance to execute a - * particular file type. For example on windows this can be set to "perl" so that perl scripts are executed. - *

- * The "Path" init param is passed to the exec environment as PATH. Note: Must be run unpacked somewhere in the filesystem. - *

- * Any initParameter that starts with ENV_ is used to set an environment variable with the name stripped of the leading ENV_ and using the init parameter value. + *

+ * + * The following init parameters are used to configure this servlet: + *

+ *
cgibinResourceBase
Path to the cgi bin directory if set or it will default to the resource base of the context.
+ *
resourceBase
An alias for cgibinResourceBase.
+ *
cgibinResourceBaseIsRelative
If true then cgibinResourceBase is relative to the webapp (eg "WEB-INF/cgi")
+ *
commandPrefix
may be used to set a prefix to all commands passed to exec. This can be used on systems that need assistance to execute a + * particular file type. For example on windows this can be set to "perl" so that perl scripts are executed.
+ *
Path
passed to the exec environment as PATH.
+ *
ENV_*
used to set an arbitrary environment variable with the name stripped of the leading ENV_ and using the init parameter value
+ *
useFullPath
If true, the full URI path within the context is used for the exec command, otherwise a search is done for a partial URL that matches an exec Command
+ *
+ * */ public class CGI extends HttpServlet { - /** - * - */ - private static final long serialVersionUID = -6182088932884791073L; + private static final long serialVersionUID = -6182088932884791074L; private static final Logger LOG = Log.getLogger(CGI.class); private boolean _ok; private File _docRoot; + private boolean _cgiBinProvided; private String _path; private String _cmdPrefix; + private boolean _useFullPath; private EnvList _env; private boolean _ignoreExitState; private boolean _relative; @@ -83,16 +84,22 @@ public class CGI extends HttpServlet { _env = new EnvList(); _cmdPrefix = getInitParameter("commandPrefix"); + _useFullPath = Boolean.parseBoolean(getInitParameter("useFullPath")); _relative = Boolean.parseBoolean(getInitParameter("cgibinResourceBaseIsRelative")); String tmp = getInitParameter("cgibinResourceBase"); - if (tmp == null) + if (tmp != null) + _cgiBinProvided = true; + else { tmp = getInitParameter("resourceBase"); - if (tmp == null) + if (tmp != null) + _cgiBinProvided = true; + else tmp = getServletContext().getRealPath("/"); } - else if (_relative) + + if (_relative && _cgiBinProvided) { tmp = getServletContext().getRealPath(tmp); } @@ -137,10 +144,10 @@ public class CGI extends HttpServlet _env.set("PATH",_path); _ignoreExitState = "true".equalsIgnoreCase(getInitParameter("ignoreExitState")); - Enumeration e = getInitParameterNames(); + Enumeration e = getInitParameterNames(); while (e.hasMoreElements()) { - String n = (String)e.nextElement(); + String n = e.nextElement(); if (n != null && n.startsWith("ENV_")) _env.set(n.substring(4),getInitParameter(n)); } @@ -166,7 +173,6 @@ public class CGI extends HttpServlet return; } - String pathInContext = (_relative?"":StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo()); if (LOG.isDebugEnabled()) { LOG.debug("CGI: ContextPath : " + req.getContextPath()); @@ -180,63 +186,69 @@ public class CGI extends HttpServlet // pathInContext may actually comprises scriptName/pathInfo...We will // walk backwards up it until we find the script - the rest must // be the pathInfo; + String pathInContext = (_relative ? "" : StringUtil.nonNull(req.getServletPath())) + StringUtil.nonNull(req.getPathInfo()); + File execCmd = new File(_docRoot, pathInContext); + String pathInfo = pathInContext; - String both = pathInContext; - String first = both; - String last = ""; - - File exe = new File(_docRoot,first); - - while ((first.endsWith("/") || !exe.exists()) && first.length() >= 0) + if(!_useFullPath) { - int index = first.lastIndexOf('/'); + String path = pathInContext; + String info = ""; - first = first.substring(0,index); - last = both.substring(index,both.length()); - exe = new File(_docRoot,first); - } - - if (first.length() == 0 || !exe.exists() || exe.isDirectory() || !exe.getCanonicalPath().equals(exe.getAbsolutePath())) - { - res.sendError(404); - } - else - { - if (LOG.isDebugEnabled()) + // Search docroot for a matching execCmd + while (path.endsWith("/") && path.length() >= 0) { - LOG.debug("CGI: script is " + exe); - LOG.debug("CGI: pathInfo is " + last); + if(!execCmd.exists()) + break; + + int index = path.lastIndexOf('/'); + + path = path.substring(0,index); + info = pathInContext.substring(index,pathInContext.length()); + execCmd = new File(_docRoot,path); } - exec(exe,last,req,res); + + if (path.length() == 0 || !execCmd.exists() || execCmd.isDirectory() || !execCmd.getCanonicalPath().equals(execCmd.getAbsolutePath())) + { + res.sendError(404); + } + + pathInfo = info; } + exec(execCmd,pathInfo,req,res); } - /* ------------------------------------------------------------ */ + /** executes the CGI process /* - * @param root @param path @param req @param res @exception IOException + * @param command the command to execute, this command is prefixed by + * the context parameter "commandPrefix". + * @param pathInfo The PATH_INFO to process, + * see http://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletRequest.html#getPathInfo%28%29. Cannot be null + * @param req + * @param res + * @exception IOException */ private void exec(File command, String pathInfo, HttpServletRequest req, HttpServletResponse res) throws IOException { - String path = command.getAbsolutePath(); - File dir = command.getParentFile(); - String scriptName = req.getRequestURI().substring(0,req.getRequestURI().length() - pathInfo.length()); - String scriptPath = getServletContext().getRealPath(scriptName); - String pathTranslated = req.getPathTranslated(); + assert req != null; + assert res != null; + assert pathInfo != null; + assert command != null; - int len = req.getContentLength(); - if (len < 0) - len = 0; - if ((pathTranslated == null) || (pathTranslated.length() == 0)) - pathTranslated = path; + if (LOG.isDebugEnabled()) + { + LOG.debug("CGI: script is " + command); + LOG.debug("CGI: pathInfo is " + pathInfo); + } String bodyFormEncoded = null; if ((HttpMethod.POST.equals(req.getMethod()) || HttpMethod.PUT.equals(req.getMethod())) && "application/x-www-form-urlencoded".equals(req.getContentType())) { MultiMap parameterMap = new MultiMap(); - Enumeration names = req.getParameterNames(); + Enumeration names = req.getParameterNames(); while (names.hasMoreElements()) { - String parameterName = (String)names.nextElement(); + String parameterName = names.nextElement(); parameterMap.addValues(parameterName, req.getParameterValues(parameterName)); } bodyFormEncoded = UrlEncoded.encode(parameterMap, Charset.forName(req.getCharacterEncoding()), true); @@ -247,24 +259,33 @@ public class CGI extends HttpServlet // look at : // http://Web.Golux.Com/coar/cgi/draft-coar-cgi-v11-03-clean.html#6.1.1 env.set("AUTH_TYPE", req.getAuthType()); + + int contentLen = req.getContentLength(); + if (contentLen < 0) + contentLen = 0; if (bodyFormEncoded != null) { env.set("CONTENT_LENGTH", Integer.toString(bodyFormEncoded.length())); } else { - env.set("CONTENT_LENGTH", Integer.toString(len)); + env.set("CONTENT_LENGTH", Integer.toString(contentLen)); } env.set("CONTENT_TYPE", req.getContentType()); env.set("GATEWAY_INTERFACE", "CGI/1.1"); - if ((pathInfo != null) && (pathInfo.length() > 0)) + if (pathInfo.length() > 0) { env.set("PATH_INFO", pathInfo); } + + String pathTranslated = req.getPathTranslated(); + if ((pathTranslated == null) || (pathTranslated.length() == 0)) + pathTranslated = pathInfo; env.set("PATH_TRANSLATED", pathTranslated); env.set("QUERY_STRING", req.getQueryString()); env.set("REMOTE_ADDR", req.getRemoteAddr()); env.set("REMOTE_HOST", req.getRemoteHost()); + // The identity information reported about the connection by a // RFC 1413 [11] request to the remote agent, if // available. Servers MAY choose not to support this feature, or @@ -272,17 +293,33 @@ public class CGI extends HttpServlet // "REMOTE_IDENT" => "NYI" env.set("REMOTE_USER", req.getRemoteUser()); env.set("REQUEST_METHOD", req.getMethod()); - env.set("SCRIPT_NAME", scriptName); + + String scriptPath; + String scriptName; + // use docRoot for scriptPath, too + if(_cgiBinProvided) + { + scriptPath = command.getAbsolutePath(); + scriptName = scriptPath.substring(_docRoot.getAbsolutePath().length()); + } + else + { + String requestURI = req.getRequestURI(); + scriptName = requestURI.substring(0,requestURI.length() - pathInfo.length()); + scriptPath = getServletContext().getRealPath(scriptName); + } env.set("SCRIPT_FILENAME", scriptPath); + env.set("SCRIPT_NAME", scriptName); + env.set("SERVER_NAME", req.getServerName()); env.set("SERVER_PORT", Integer.toString(req.getServerPort())); env.set("SERVER_PROTOCOL", req.getProtocol()); env.set("SERVER_SOFTWARE", getServletContext().getServerInfo()); - Enumeration enm = req.getHeaderNames(); + Enumeration enm = req.getHeaderNames(); while (enm.hasMoreElements()) { - String name = (String)enm.nextElement(); + String name = enm.nextElement(); String value = req.getHeader(name); env.set("HTTP_" + name.toUpperCase(Locale.ENGLISH).replace('-','_'),value); } @@ -293,29 +330,30 @@ public class CGI extends HttpServlet // "SERVER_URL" => "NYI - http://us0245", // "TZ" => System.getProperty("user.timezone"), - // are we meant to decode args here ? or does the script get them - // via PATH_INFO ? if we are, they should be decoded and passed + // are we meant to decode args here? or does the script get them + // via PATH_INFO? if we are, they should be decoded and passed // into exec here... - String execCmd = path; - if ((execCmd.charAt(0) != '"') && (execCmd.indexOf(" ") >= 0)) + String absolutePath = command.getAbsolutePath(); + String execCmd = absolutePath; + + // escape the execCommand + if (execCmd.length() > 0 && execCmd.charAt(0) != '"' && execCmd.indexOf(" ") >= 0) execCmd = "\"" + execCmd + "\""; + if (_cmdPrefix != null) execCmd = _cmdPrefix + " " + execCmd; + assert execCmd != null; LOG.debug("Environment: " + env.getExportString()); LOG.debug("Command: " + execCmd); - final Process p; - if (dir == null) - p = Runtime.getRuntime().exec(execCmd, env.getEnvArray()); - else - p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), dir); + final Process p = Runtime.getRuntime().exec(execCmd, env.getEnvArray(), _docRoot); // hook processes input to browser's output (async) if (bodyFormEncoded != null) writeProcessInput(p, bodyFormEncoded); - else if (len > 0) - writeProcessInput(p, req.getInputStream(), len); + else if (contentLen > 0) + writeProcessInput(p, req.getInputStream(), contentLen); // hook processes output to browser's input (sync) // if browser closes stream, we should detect it and kill process... @@ -336,9 +374,9 @@ public class CGI extends HttpServlet { LOG.warn(e); } - } + } }); - + // read any headers off the top of our input stream // NOTE: Multiline header items not supported! String line = null; @@ -383,7 +421,7 @@ public class CGI extends HttpServlet int exitValue = p.exitValue(); if (0 != exitValue) { - LOG.warn("Non-zero exit status (" + exitValue + ") from CGI program: " + path); + LOG.warn("Non-zero exit status (" + exitValue + ") from CGI program: " + absolutePath); if (!res.isCommitted()) res.sendError(500,"Failed to exec CGI"); } @@ -393,7 +431,7 @@ public class CGI extends HttpServlet { // browser has probably closed its input stream - we // terminate and clean up... - LOG.debug("CGI: Client closed connection!"); + LOG.debug("CGI: Client closed connection!", e); } catch (InterruptedException ie) { @@ -422,6 +460,7 @@ public class CGI extends HttpServlet { new Thread(new Runnable() { + @Override public void run() { try @@ -445,6 +484,7 @@ public class CGI extends HttpServlet new Thread(new Runnable() { + @Override public void run() { try From deb7102e0edc5904dac5577a1904a7af1c4aa940 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 11 Apr 2014 10:57:39 +1000 Subject: [PATCH 029/135] 404511 removed deprecated StringMap --- .../org/eclipse/jetty/util/StringMap.java | 196 ---------------- .../org/eclipse/jetty/util/StringMapTest.java | 209 ------------------ 2 files changed, 405 deletions(-) delete mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java delete mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java deleted file mode 100644 index 63f5cd7d9ca..00000000000 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringMap.java +++ /dev/null @@ -1,196 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - -package org.eclipse.jetty.util; - -import java.nio.ByteBuffer; -import java.util.AbstractMap; -import java.util.Collections; -import java.util.Comparator; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -/* ------------------------------------------------------------ */ -/** Map implementation Optimized for Strings keys.. - * This String Map has been optimized for mapping small sets of - * Strings where the most frequently accessed Strings have been put to - * the map first. - * - * It also has the benefit that it can look up entries by substring or - * sections of char and byte arrays. This can prevent many String - * objects from being created just to look up in the map. - * - * This map is NOT synchronized. - * @deprecated Use {@link Trie} - */ -public class StringMap extends AbstractMap -{ - private final TreeMap _map; - - - public static final boolean CASE_INSENSTIVE=true; - - /* ------------------------------------------------------------ */ - - private final boolean _caseInsensitive; - - - /* ------------------------------------------------------------ */ - /** Constructor. - */ - public StringMap() - { - this(false); - } - - /* ------------------------------------------------------------ */ - /** Constructor. - * @param ignoreCase - */ - public StringMap(final boolean ignoreCase) - { - _caseInsensitive=ignoreCase; - _map = new TreeMap(new Comparator() - { - @Override - public int compare(Object o1, Object o2) - { - String s1=(o1 instanceof String)?(String)o1:null; - ByteBuffer b1=(o1 instanceof ByteBuffer)?(ByteBuffer)o1:null; - if (s1==null && b1==null) - s1=o1.toString(); - String s2=(String)o2; - - int n1 = s1==null?b1.remaining():s1.length(); - int n2 = s2.length(); - int min = Math.min(n1, n2); - for (int i = 0; i < min; i++) { - char c1 = s1==null?(char)b1.get(b1.position()+i):s1.charAt(i); - char c2 = s2.charAt(i); - if (c1 != c2) { - if (ignoreCase) - { - c1 = Character.toUpperCase(c1); - c2 = Character.toUpperCase(c2); - if (c1 != c2) { - c1 = Character.toLowerCase(c1); - c2 = Character.toLowerCase(c2); - if (c1 != c2) { - // No overflow because of numeric promotion - return c1 - c2; - } - } - } - else - return c1 - c2; - } - } - return n1 - n2; - } - }); - } - - /* ------------------------------------------------------------ */ - public boolean isIgnoreCase() - { - return _caseInsensitive; - } - - /* ------------------------------------------------------------ */ - @Override - public O put(String key, O value) - { - return _map.put(key,value); - } - - /* ------------------------------------------------------------ */ - @Override - public O get(Object key) - { - return _map.get(key); - } - - /* ------------------------------------------------------------ */ - public O get(String key) - { - return _map.get(key); - } - - /* ------------------------------------------------------------ */ - public O get(String key,int offset,int length) - { - return _map.get(key.substring(offset,offset+length)); - } - - /* ------------------------------------------------------------ */ - public O get(ByteBuffer buffer) - { - return _map.get(buffer); - } - - /* ------------------------------------------------------------ */ - @Override - public O remove(Object key) - { - return _map.remove(key); - } - - /* ------------------------------------------------------------ */ - public O remove(String key) - { - return _map.remove(key); - } - - /* ------------------------------------------------------------ */ - @Override - public Set> entrySet() - { - Object o=_map.entrySet(); - return Collections.unmodifiableSet((Set>)o); - } - - /* ------------------------------------------------------------ */ - @Override - public int size() - { - return _map.size(); - } - - /* ------------------------------------------------------------ */ - @Override - public boolean isEmpty() - { - return _map.isEmpty(); - } - - /* ------------------------------------------------------------ */ - @Override - public boolean containsKey(Object key) - { - return _map.containsKey(key); - } - - /* ------------------------------------------------------------ */ - @Override - public void clear() - { - _map.clear(); - } - -} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java deleted file mode 100644 index c20e9f16269..00000000000 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/StringMapTest.java +++ /dev/null @@ -1,209 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. -// ------------------------------------------------------------------------ -// All rights reserved. This program and the accompanying materials -// are made available under the terms of the Eclipse Public License v1.0 -// and Apache License v2.0 which accompanies this distribution. -// -// The Eclipse Public License is available at -// http://www.eclipse.org/legal/epl-v10.html -// -// The Apache License v2.0 is available at -// http://www.opensource.org/licenses/apache2.0.php -// -// You may elect to redistribute this code under either of these licenses. -// ======================================================================== -// - -package org.eclipse.jetty.util; - -import java.util.Set; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -public class StringMapTest -{ - StringMap m0; - StringMap m1; - StringMap m5; - StringMap m5i; - - /* - * @see TestCase#setUp() - */ - - @Before - public void setUp() throws Exception - { - m0=new StringMap<>(); - m1=new StringMap<>(false); - m1.put("abc", "0"); - - m5=new StringMap<>(false); - m5.put("a", "0"); - m5.put("ab", "1"); - m5.put("abc", "2"); - m5.put("abb", "3"); - m5.put("bbb", "4"); - - m5i=new StringMap<>(true); - m5i.put("ab", "1"); - m5i.put("abc", "2"); - m5i.put("abb", "3"); - } - - @Test - public void testSize() - { - Assert.assertEquals(0, m0.size()); - Assert.assertEquals(1, m1.size()); - Assert.assertEquals(5, m5.size()); - Assert.assertEquals(3, m5i.size()); - - m1.remove("abc"); - m5.remove("abc"); - m5.put("bbb","x"); - m5i.put("ABC", "x"); - Assert.assertEquals(0, m0.size()); - Assert.assertEquals(0, m1.size()); - Assert.assertEquals(4, m5.size()); - Assert.assertEquals(3, m5i.size()); - } - - @Test - public void testIsEmpty() - { - Assert.assertTrue(m0.isEmpty()); - Assert.assertFalse(m1.isEmpty()); - Assert.assertFalse(m5.isEmpty()); - Assert.assertFalse(m5i.isEmpty()); - } - - @Test - public void testClear() - { - m0.clear(); - m1.clear(); - m5.clear(); - m5i.clear(); - Assert.assertTrue(m0.isEmpty()); - Assert.assertTrue(m1.isEmpty()); - Assert.assertTrue(m5.isEmpty()); - Assert.assertTrue(m5i.isEmpty()); - Assert.assertEquals(null, m1.get("abc")); - Assert.assertEquals(null, m5.get("abc")); - Assert.assertEquals(null, m5i.get("abc")); - } - - - /* - * Test for Object put(Object, Object) - */ - @Test - public void testPutGet() - { - Assert.assertEquals("2", m5.get("abc")); - Assert.assertEquals(null, m5.get("aBc")); - Assert.assertEquals("2", m5i.get("abc")); - Assert.assertEquals("2", m5i.get("aBc")); - - m5.put("aBc", "x"); - m5i.put("AbC", "x"); - - StringBuilder buffer=new StringBuilder(); - buffer.append("aBc"); - Assert.assertEquals("2", m5.get("abc")); - Assert.assertEquals("x", m5.get(buffer)); - Assert.assertEquals("x", m5i.get((Object)"abc")); - Assert.assertEquals("x", m5i.get("aBc")); - - - } - - /* - * Test for Object remove(Object) - */ - @Test - public void testRemove() - { - m0.remove("abc"); - m1.remove("abc"); - m5.remove("aBc"); - m5.remove("bbb"); - m5i.remove("aBc"); - - Assert.assertEquals(0, m0.size()); - Assert.assertEquals(0, m1.size()); - Assert.assertEquals(4, m5.size()); - Assert.assertEquals(2, m5i.size()); - - Assert.assertEquals("2", m5.get("abc")); - Assert.assertEquals(null, m5.get("bbb")); - Assert.assertEquals(null, m5i.get("AbC")); - } - - /* - * Test for Set entrySet() - */ - @Test - public void testEntrySet() - { - Set es0=m0.entrySet(); - Set es1=m1.entrySet(); - Set es5=m5.entrySet(); - Assert.assertEquals(0, es0.size()); - Assert.assertEquals(1, es1.size()); - Assert.assertEquals(5, es5.size()); - } - - /* - * Test for boolean containsKey(Object) - */ - @Test - public void testContainsKey() - { - Assert.assertTrue(m5.containsKey("abc")); - Assert.assertTrue(!m5.containsKey("aBc")); - Assert.assertTrue(m5.containsKey("bbb")); - Assert.assertTrue(!m5.containsKey("xyz")); - - Assert.assertTrue(m5i.containsKey("abc")); - Assert.assertTrue(m5i.containsKey("aBc")); - Assert.assertTrue(m5i.containsKey("ABC")); - } - - @Test - public void testToString() - { - Assert.assertEquals("{}", m0.toString()); - Assert.assertEquals("{abc=0}", m1.toString()); - Assert.assertTrue(m5.toString().indexOf("abc=2") > 0); - } - - @Test - public void testIgnoreCase() - { - StringMap map = new StringMap<>(true); - map.put("POST","1"); - map.put("HEAD","2"); - map.put("PUT","3"); - map.put("OPTIONS","4"); - map.put("DELETE","5"); - map.put("TRACE","6"); - map.put("CONNECT","7"); - map.put("Upgrade","8"); - - Assert.assertEquals("1", map.get("POST")); - Assert.assertEquals("1", map.get("pOST")); - Assert.assertEquals("1", map.get("Post")); - - Assert.assertEquals("8", map.get("UPGRADE")); - Assert.assertEquals("8", map.get("Upgrade")); - Assert.assertEquals("8", map.get("upgrade")); - - } - -} From f2f5353a29846d560f018c5491ee2240407a9f13 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Fri, 11 Apr 2014 17:23:42 +1000 Subject: [PATCH 030/135] 432483 jetty-annotations bundle does not start if there are no bundles that provide a service javax.servlet.ServletContainerInitializer --- jetty-osgi/jetty-osgi-boot/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 5a02af5fdbf..bd5486a5936 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -102,6 +102,7 @@ org.eclipse.jetty.osgi.boot;singleton:=true org.eclipse.jetty.osgi.boot.JettyBootstrapActivator + osgi.serviceloader; osgi.serviceloader=javax.servlet.ServletContainerInitializer org.eclipse.jetty.*;version="[9.1,10.0)" javax.mail;version="1.4.0";resolution:=optional, javax.mail.event;version="1.4.0";resolution:=optional, From eb45d45dbf607172bdb70c9d1f6fb8a8798eb6a5 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Fri, 11 Apr 2014 17:23:42 +1000 Subject: [PATCH 031/135] 432483 jetty-annotations bundle does not start if there are no bundles that provide a service javax.servlet.ServletContainerInitializer (cherry picked from commit f2f5353a29846d560f018c5491ee2240407a9f13) --- jetty-osgi/jetty-osgi-boot/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index a52ebfabcd2..0c1dbf56790 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -102,6 +102,7 @@ org.eclipse.jetty.osgi.boot;singleton:=true org.eclipse.jetty.osgi.boot.JettyBootstrapActivator + osgi.serviceloader; osgi.serviceloader=javax.servlet.ServletContainerInitializer org.eclipse.jetty.*;version="[9.1,10.0)" javax.mail;version="1.4.0";resolution:=optional, javax.mail.event;version="1.4.0";resolution:=optional, From 4975cae8811423152262e14b8b6243f56ad25e1a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 11 Apr 2014 09:45:21 +0200 Subject: [PATCH 032/135] Further progress on 431642 (async proxy servlet). --- .../org/eclipse/jetty/client/HttpRequest.java | 16 +++- .../jetty/client/ResponseNotifier.java | 4 + .../org/eclipse/jetty/client/api/Request.java | 11 +-- .../eclipse/jetty/client/api/Response.java | 10 ++- .../client/http/HttpReceiverOverHTTP.java | 5 ++ .../eclipse/jetty/client/HttpClientTest.java | 9 ++ .../jetty/proxy/AsyncProxyServlet.java | 86 ++++++++++++++++++- .../org/eclipse/jetty/proxy/ProxyServlet.java | 8 +- .../eclipse/jetty/proxy/ProxyServletTest.java | 5 +- 9 files changed, 137 insertions(+), 17 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 0002c6b7313..9b6eec6a57e 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 @@ -450,19 +450,27 @@ public class HttpRequest implements Request @Override public Request onResponseContent(final Response.ContentListener listener) { - this.responseListeners.add(new Response.ContentListener() + this.responseListeners.add(new Response.AsyncContentListener() { @Override - public void onContent(Response response, ByteBuffer content) + public void onContent(Response response, ByteBuffer content, Callback callback) { - listener.onContent(response, content); + try + { + listener.onContent(response, content); + callback.succeeded(); + } + catch (Exception x) + { + callback.failed(x); + } } }); return this; } @Override - public Request onResponseContent(final Response.AsyncContentListener listener) + public Request onResponseContentAsync(final Response.AsyncContentListener listener) { this.responseListeners.add(new Response.AsyncContentListener() { diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java index 6fa640cce9d..4b824058e89 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java @@ -108,6 +108,10 @@ public class ResponseNotifier public void notifyContent(List listeners, Response response, ByteBuffer buffer) { + // TODO: we need to create a "cumulative" callback that keeps track of how many listeners + // TODO: are invoked, and how many of these actually invoked the callback, and eventually + // TODO: call the callback passed to this method. + // Slice the buffer to avoid that listeners peek into data they should not look at. buffer = buffer.slice(); if (!buffer.hasRemaining()) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java index 6c9ae20acc3..b7caf08b8a2 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Request.java @@ -338,15 +338,16 @@ public interface Request Request onResponseHeaders(Response.HeadersListener listener); /** - * @param listener a listener for response content events + * @param listener a consuming listener for response content events * @return this request object - * @deprecated Use {@link #onResponseContent(Response.AsyncContentListener)} instead. */ - @Deprecated Request onResponseContent(Response.ContentListener listener); - // TODO: JAVADOCS - Request onResponseContent(Response.AsyncContentListener listener); + /** + * @param listener an asynchronous listener for response content events + * @return this request object + */ + Request onResponseContentAsync(Response.AsyncContentListener listener); /** * @param listener a listener for response success event diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java index e92dcaa1083..f1ef5c5fe23 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/api/Response.java @@ -241,7 +241,15 @@ public interface Response @Override public void onContent(Response response, ByteBuffer content, Callback callback) { - callback.succeeded(); + try + { + onContent(response, content); + callback.succeeded(); + } + catch (Exception x) + { + callback.failed(x); + } } @Override diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java index f8894f8ab85..6659352b318 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java @@ -182,6 +182,11 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res if (exchange == null) return false; + // TODO: need to create the callback here, then check whether it has completed + // TODO: after the call to responseContent. If it has, return false. + // TODO: if it has not, return true, and when will be invoked, we need to + // TODO: proceed with parsing. + responseContent(exchange, buffer); return false; } 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 1b7e3ee42ca..a43e680a077 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 @@ -68,6 +68,7 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.TestingDir; import org.eclipse.jetty.toolchain.test.annotation.Slow; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -983,6 +984,13 @@ public class HttpClientTest extends AbstractHttpClientServerTest counter.incrementAndGet(); } + @Override + public void onContent(Response response, ByteBuffer content, Callback callback) + { + // Should not be invoked + counter.incrementAndGet(); + } + @Override public void onSuccess(Response response) { @@ -1010,6 +1018,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest .onResponseHeader(listener) .onResponseHeaders(listener) .onResponseContent(listener) + .onResponseContentAsync(listener) .onResponseSuccess(listener) .onResponseFailure(listener) .send(listener); diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java index e9915a489c0..b59dff351ff 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java @@ -23,13 +23,20 @@ import java.nio.ByteBuffer; import javax.servlet.AsyncContext; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; +import javax.servlet.ServletOutputStream; +import javax.servlet.WriteListener; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.util.Callback; public class AsyncProxyServlet extends ProxyServlet { + private static final String WRITE_LISTENER_ATTRIBUTE = AsyncProxyServlet.class.getName() + ".writeListener"; + @Override protected ContentProvider proxyRequestContent(AsyncContext asyncContext, final int requestId) throws IOException { @@ -39,6 +46,23 @@ public class AsyncProxyServlet extends ProxyServlet return provider; } + @Override + protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) throws IOException + { + StreamWriter writeListener = (StreamWriter)request.getAttribute(WRITE_LISTENER_ATTRIBUTE); + if (writeListener == null) + { + writeListener = new StreamWriter(request.getAsyncContext()); + request.setAttribute(WRITE_LISTENER_ATTRIBUTE, writeListener); + response.getOutputStream().setWriteListener(writeListener); + } + _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length); + if (writeListener.data(buffer, offset, length, callback)) + writeListener.onWritePossible(); + else + ;// TODO: fail callback + } + private class StreamReader implements ReadListener, Callback { private final byte[] buffer = new byte[512]; @@ -64,7 +88,7 @@ public class AsyncProxyServlet extends ProxyServlet { int read = input.read(buffer); _log.debug("Asynchronous read {} bytes from {}", read, input); - if (read >= 0) + if (read > 0) { _log.debug("{} proxying content to upstream: {} bytes", requestId, read); provider.offer(ByteBuffer.wrap(buffer, 0, read), this); @@ -103,4 +127,64 @@ public class AsyncProxyServlet extends ProxyServlet // complete the async context since we cannot throw an exception from here. } } + + private class StreamWriter implements WriteListener + { + private final AsyncContext asyncContext; + private byte[] buffer; + private int offset; + private int length; + private volatile Callback callback; + + private StreamWriter(AsyncContext asyncContext) + { + this.asyncContext = asyncContext; + } + + private boolean data(byte[] bytes, int offset, int length, Callback callback) + { + if (this.callback != null) + return false; + + this.buffer = bytes; + this.offset = offset; + this.length = length; + this.callback = callback; + return true; + } + + @Override + public void onWritePossible() throws IOException + { + if (callback == null) + { + ServletOutputStream output = asyncContext.getResponse().getOutputStream(); + output.write(buffer, offset, length); + if (output.isReady()) + complete(); + } + else + { + // If we have a pending callback, it means + // that the write blocked but is now complete. + complete(); + } + } + + private void complete() + { + this.buffer = null; + this.offset = 0; + this.length = 0; + Callback callback = this.callback; + this.callback = null; + callback.succeeded(); + } + + @Override + public void onError(Throwable t) + { + // TODO: + } + } } 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 de40c8e50bd..85d692126da 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 @@ -19,7 +19,6 @@ package org.eclipse.jetty.proxy; import java.io.IOException; -import java.io.InputStream; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; @@ -51,6 +50,7 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.HttpCookieStore; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -531,7 +531,7 @@ public class ProxyServlet extends HttpServlet } } - protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length) throws IOException + protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) throws IOException { response.getOutputStream().write(buffer, offset, length); _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length); @@ -732,7 +732,7 @@ public class ProxyServlet extends HttpServlet } @Override - public void onContent(Response proxyResponse, ByteBuffer content) + public void onContent(Response proxyResponse, ByteBuffer content, Callback callback) { byte[] buffer; int offset; @@ -751,7 +751,7 @@ public class ProxyServlet extends HttpServlet try { - onResponseContent(request, response, proxyResponse, buffer, offset, length); + onResponseContent(request, response, proxyResponse, buffer, offset, length, callback); } catch (IOException x) { diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java index 2e2f3c6576d..5a2ddce8e49 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java @@ -65,6 +65,7 @@ import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.TestTracker; import org.eclipse.jetty.toolchain.test.annotation.Slow; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.StdErrLog; @@ -797,7 +798,7 @@ public class ProxyServletTest } @Override - protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length) throws IOException + protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) throws IOException { // Accumulate the response content ByteArrayOutputStream baos = temp.get(request.getRequestURI()); @@ -807,7 +808,7 @@ public class ProxyServletTest temp.put(request.getRequestURI(), baos); } baos.write(buffer, offset, length); - super.onResponseContent(request, response, proxyResponse, buffer, offset, length); + super.onResponseContent(request, response, proxyResponse, buffer, offset, length, callback); } @Override From 5eeda38f0af1ba5606465a1899c21c91a20eacc1 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 11 Apr 2014 15:57:26 +0200 Subject: [PATCH 033/135] 432270 - Slow requests with response content delimited by EOF fail. Fixed also in the FastCGI module. --- .../eclipse/jetty/client/HttpDestination.java | 4 + .../jetty/client/PoolingHttpDestination.java | 5 +- .../fcgi/client/http/HttpChannelOverFCGI.java | 50 ++++++------ .../http/HttpClientTransportOverFCGI.java | 2 +- .../client/http/HttpConnectionOverFCGI.java | 69 +++++++++++++---- .../jetty/fcgi/generator/ServerGenerator.java | 41 ++++++---- .../jetty/fcgi/parser/ClientParser.java | 8 ++ .../fcgi/parser/EndRequestContentParser.java | 8 +- .../org/eclipse/jetty/fcgi/parser/Parser.java | 8 ++ .../jetty/fcgi/parser/ClientParserTest.java | 6 +- .../fcgi/server/HttpTransportOverFCGI.java | 31 +++++--- .../fcgi/server/ServerFCGIConnection.java | 12 +++ .../jetty/fcgi/server/HttpClientTest.java | 76 +++++++++++++++++++ 13 files changed, 249 insertions(+), 71 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java index 5a40a2c4061..cc83372c437 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpDestination.java @@ -215,6 +215,10 @@ public abstract class HttpDestination implements Destination, Closeable, Dumpabl LOG.debug("Closed {}", this); } + public void release(Connection connection) + { + } + public void close(Connection connection) { } 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 index 6bba6a58205..73c13ad45da 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/PoolingHttpDestination.java @@ -140,8 +140,11 @@ public abstract class PoolingHttpDestination extends HttpD protected abstract void send(C connection, HttpExchange exchange); - public void release(C connection) + @Override + public void release(Connection c) { + @SuppressWarnings("unchecked") + C connection = (C)c; LOG.debug("{} released", connection); HttpClient client = getHttpClient(); if (client.isRunning()) diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index a6f7de6ec3b..1c33cf0518d 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -28,8 +28,6 @@ import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.IdleTimeout; @@ -83,42 +81,43 @@ public class HttpChannelOverFCGI extends HttpChannel return receiver.abort(cause); } - protected void responseBegin(int code, String reason) + protected boolean responseBegin(int code, String reason) { HttpExchange exchange = getHttpExchange(); - if (exchange != null) - { - exchange.getResponse().version(version).status(code).reason(reason); - receiver.responseBegin(exchange); - } + if (exchange == null) + return false; + exchange.getResponse().version(version).status(code).reason(reason); + return receiver.responseBegin(exchange); } - protected void responseHeader(HttpField field) + protected boolean responseHeader(HttpField field) { HttpExchange exchange = getHttpExchange(); - if (exchange != null) - receiver.responseHeader(exchange, field); + return exchange != null && receiver.responseHeader(exchange, field); } - protected void responseHeaders() + protected boolean responseHeaders() { HttpExchange exchange = getHttpExchange(); - if (exchange != null) - receiver.responseHeaders(exchange); + return exchange != null && receiver.responseHeaders(exchange); } - protected void content(ByteBuffer buffer) + protected boolean content(ByteBuffer buffer) { HttpExchange exchange = getHttpExchange(); - if (exchange != null) - receiver.responseContent(exchange, buffer); + return exchange != null && receiver.responseContent(exchange, buffer); } - protected void responseSuccess() + protected boolean responseSuccess() { HttpExchange exchange = getHttpExchange(); - if (exchange != null) - receiver.responseSuccess(exchange); + return exchange != null && receiver.responseSuccess(exchange); + } + + protected boolean responseFailure(Throwable failure) + { + HttpExchange exchange = getHttpExchange(); + return exchange != null && receiver.responseFailure(failure); } @Override @@ -126,12 +125,10 @@ public class HttpChannelOverFCGI extends HttpChannel { super.exchangeTerminated(result); idle.onClose(); - boolean close = result.isFailed(); HttpFields responseHeaders = result.getResponse().getHeaders(); - close |= responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); - if (close) - connection.close(); - else + if (result.isFailed()) + connection.close(result.getFailure()); + else if (!connection.closeByHTTP(responseHeaders)) connection.release(this); } @@ -154,7 +151,8 @@ public class HttpChannelOverFCGI extends HttpChannel @Override protected void onIdleExpired(TimeoutException timeout) { - LOG.debug("Idle timeout for request {}", request); + if (LOG.isDebugEnabled()) + LOG.debug("Idle timeout for request {}", request); connection.abort(timeout); } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java index eed8c892a60..00c6778d5e3 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpClientTransportOverFCGI.java @@ -69,7 +69,7 @@ public class HttpClientTransportOverFCGI extends AbstractHttpClientTransport public org.eclipse.jetty.io.Connection newConnection(EndPoint endPoint, Map context) throws IOException { HttpDestination destination = (HttpDestination)context.get(HTTP_DESTINATION_CONTEXT_KEY); - HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination); + HttpConnectionOverFCGI connection = new HttpConnectionOverFCGI(endPoint, destination, isMultiplexed()); LOG.debug("Created {}", connection); @SuppressWarnings("unchecked") Promise promise = (Promise)context.get(HTTP_CONNECTION_PROMISE_CONTEXT_KEY); diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index 262a7c498c9..f14143bfb0c 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -31,7 +31,6 @@ import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpConnection; import org.eclipse.jetty.client.HttpDestination; import org.eclipse.jetty.client.HttpExchange; -import org.eclipse.jetty.client.PoolingHttpDestination; import org.eclipse.jetty.client.api.Connection; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; @@ -39,6 +38,9 @@ import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.parser.ClientParser; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; @@ -55,14 +57,16 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec private final AtomicBoolean closed = new AtomicBoolean(); private final Flusher flusher; private final HttpDestination destination; + private final boolean multiplexed; private final Delegate delegate; private final ClientParser parser; - public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination) + public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination, boolean multiplexed) { super(endPoint, destination.getHttpClient().getExecutor(), destination.getHttpClient().isDispatchIO()); - this.flusher = new Flusher(endPoint); this.destination = destination; + this.multiplexed = multiplexed; + this.flusher = new Flusher(endPoint); this.delegate = new Delegate(destination); this.parser = new ClientParser(new ResponseListener()); requests.addLast(0); @@ -103,7 +107,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec while (true) { int read = endPoint.fill(buffer); - if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read' + if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'. LOG.debug("Read {} bytes from {}", read, endPoint); if (read > 0) { @@ -124,7 +128,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec catch (Exception x) { LOG.debug(x); - // TODO: fail and close ? + close(x); } finally { @@ -140,7 +144,12 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec private void shutdown() { - close(new EOFException()); + // Close explicitly only if we are idle, since the request may still + // be in progress, otherwise close only if we can fail the responses. + if (channels.isEmpty()) + close(); + else + failAndClose(new EOFException()); } @Override @@ -153,13 +162,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec protected void release(HttpChannelOverFCGI channel) { channels.remove(channel.getRequest()); - if (destination instanceof PoolingHttpDestination) - { - @SuppressWarnings("unchecked") - PoolingHttpDestination fcgiDestination = - (PoolingHttpDestination)destination; - fcgiDestination.release(this); - } + destination.release(this); } @Override @@ -168,7 +171,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec close(new AsynchronousCloseException()); } - private void close(Throwable failure) + protected void close(Throwable failure) { if (closed.compareAndSet(false, true)) { @@ -184,6 +187,16 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec } } + protected boolean closeByHTTP(HttpFields fields) + { + if (multiplexed) + return false; + if (!fields.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())) + return false; + close(); + return true; + } + protected void abort(Throwable failure) { for (HttpChannelOverFCGI channel : channels.values()) @@ -195,6 +208,15 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec channels.clear(); } + private void failAndClose(Throwable failure) + { + boolean result = false; + for (HttpChannelOverFCGI channel : channels.values()) + result |= channel.responseFailure(failure); + if (result) + close(failure); + } + private int acquireRequest() { synchronized (requests) @@ -322,8 +344,23 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec HttpChannelOverFCGI channel = channels.get(request); if (channel != null) { - channel.responseSuccess(); - releaseRequest(request); + if (channel.responseSuccess()) + releaseRequest(request); + } + else + { + noChannel(request); + } + } + + @Override + public void onFailure(int request, Throwable failure) + { + HttpChannelOverFCGI channel = channels.get(request); + if (channel != null) + { + if (channel.responseFailure(failure)) + releaseRequest(request); } else { diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java index 39619268a7b..e113dd29b78 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/generator/ServerGenerator.java @@ -88,21 +88,36 @@ public class ServerGenerator extends Generator return generateContent(request, buffer, true, false, callback, FCGI.FrameType.STDOUT); } - public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, Callback callback) + public Result generateResponseContent(int request, ByteBuffer content, boolean lastContent, boolean aborted, Callback callback) { - Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT); - if (lastContent) + if (aborted) { - // Generate the FCGI_END_REQUEST - request &= 0xFF_FF; - ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false); - BufferUtil.clearToFill(endRequestBuffer); - endRequestBuffer.putInt(0x01_03_00_00 + request); - endRequestBuffer.putInt(0x00_08_00_00); - endRequestBuffer.putLong(0x00L); - endRequestBuffer.flip(); - result = result.append(endRequestBuffer, true); + Result result = new Result(byteBufferPool, callback); + if (lastContent) + result.append(generateEndRequest(request, true), true); + else + result.append(BufferUtil.EMPTY_BUFFER, false); + return result; } - return result; + else + { + Result result = generateContent(request, content, false, lastContent, callback, FCGI.FrameType.STDOUT); + if (lastContent) + result.append(generateEndRequest(request, false), true); + return result; + } + } + + private ByteBuffer generateEndRequest(int request, boolean aborted) + { + request &= 0xFF_FF; + ByteBuffer endRequestBuffer = byteBufferPool.acquire(8, false); + BufferUtil.clearToFill(endRequestBuffer); + endRequestBuffer.putInt(0x01_03_00_00 + request); + endRequestBuffer.putInt(0x00_08_00_00); + endRequestBuffer.putInt(aborted ? 1 : 0); + endRequestBuffer.putInt(0); + endRequestBuffer.flip(); + return endRequestBuffer; } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java index 4ba70b44e22..d7a7dc6604d 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java @@ -98,5 +98,13 @@ public class ClientParser extends Parser for (StreamContentParser streamParser : streamParsers) streamParser.end(request); } + + @Override + public void onFailure(int request, Throwable failure) + { + listener.onFailure(request, failure); + for (StreamContentParser streamParser : streamParsers) + streamParser.end(request); + } } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java index 419536af77d..1f77eaf0ea6 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java @@ -107,8 +107,12 @@ public class EndRequestContentParser extends ContentParser private void onEnd() { - // TODO: if protocol != 0, invoke an error callback - listener.onEnd(getRequest()); + if (application != 0) + listener.onFailure(getRequest(), new Exception("FastCGI application returned code " + application)); + else if (protocol != 0) + listener.onFailure(getRequest(), new Exception("FastCGI server returned code " + protocol)); + else + listener.onEnd(getRequest()); } private void reset() diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java index 577219c301c..5739ef32012 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java @@ -100,6 +100,8 @@ public abstract class Parser public void onEnd(int request); + public void onFailure(int request, Throwable failure); + public static class Adapter implements Listener { @Override @@ -121,6 +123,12 @@ public abstract class Parser public void onEnd(int request) { } + + @Override + public void onFailure(int request, Throwable failure) + { + + } } } diff --git a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java index 5f80ed2827c..826ed84b43e 100644 --- a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java +++ b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java @@ -111,7 +111,7 @@ public class ClientParserTest ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ServerGenerator generator = new ServerGenerator(byteBufferPool); Generator.Result result1 = generator.generateResponseHeaders(id, 200, "OK", fields, null); - Generator.Result result2 = generator.generateResponseContent(id, null, true, null); + Generator.Result result2 = generator.generateResponseContent(id, null, true, false, null); final AtomicInteger verifier = new AtomicInteger(); ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() @@ -162,7 +162,7 @@ public class ClientParserTest ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ServerGenerator generator = new ServerGenerator(byteBufferPool); Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); - Generator.Result result2 = generator.generateResponseContent(id, content, true, null); + Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null); final AtomicInteger verifier = new AtomicInteger(); ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() @@ -214,7 +214,7 @@ public class ClientParserTest ByteBufferPool byteBufferPool = new MappedByteBufferPool(); ServerGenerator generator = new ServerGenerator(byteBufferPool); Generator.Result result1 = generator.generateResponseHeaders(id, code, "OK", fields, null); - Generator.Result result2 = generator.generateResponseContent(id, content, true, null); + Generator.Result result2 = generator.generateResponseContent(id, content, true, false, null); final AtomicInteger totalLength = new AtomicInteger(); final AtomicBoolean verifier = new AtomicBoolean(); diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java index fd62656164e..91459ed7798 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpTransportOverFCGI.java @@ -24,6 +24,8 @@ import org.eclipse.jetty.fcgi.generator.Flusher; import org.eclipse.jetty.fcgi.generator.Generator; import org.eclipse.jetty.fcgi.generator.ServerGenerator; import org.eclipse.jetty.http.HttpGenerator; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.server.HttpTransport; import org.eclipse.jetty.util.BufferUtil; @@ -35,6 +37,8 @@ public class HttpTransportOverFCGI implements HttpTransport private final Flusher flusher; private final int request; private volatile boolean head; + private volatile boolean shutdown; + private volatile boolean aborted; public HttpTransportOverFCGI(ByteBufferPool byteBufferPool, Flusher flusher, int request) { @@ -47,13 +51,15 @@ public class HttpTransportOverFCGI implements HttpTransport public void send(HttpGenerator.ResponseInfo info, ByteBuffer content, boolean lastContent, Callback callback) { boolean head = this.head = info.isHead(); + boolean shutdown = this.shutdown = info.getHttpFields().contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); + if (head) { if (lastContent) { Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), info.getHttpFields(), new Callback.Adapter()); - Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback); + Generator.Result contentResult = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback); flusher.flush(headersResult, contentResult); } else @@ -67,9 +73,12 @@ public class HttpTransportOverFCGI implements HttpTransport { Generator.Result headersResult = generator.generateResponseHeaders(request, info.getStatus(), info.getReason(), info.getHttpFields(), new Callback.Adapter()); - Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, callback); + Generator.Result contentResult = generator.generateResponseContent(request, content, lastContent, aborted, callback); flusher.flush(headersResult, contentResult); } + + if (lastContent && shutdown) + flusher.shutdown(); } @Override @@ -79,7 +88,7 @@ public class HttpTransportOverFCGI implements HttpTransport { if (lastContent) { - Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, callback); + Generator.Result result = generator.generateResponseContent(request, BufferUtil.EMPTY_BUFFER, lastContent, aborted, callback); flusher.flush(result); } else @@ -90,18 +99,22 @@ public class HttpTransportOverFCGI implements HttpTransport } else { - Generator.Result result = generator.generateResponseContent(request, content, lastContent, callback); + Generator.Result result = generator.generateResponseContent(request, content, lastContent, aborted, callback); flusher.flush(result); } + + if (lastContent && shutdown) + flusher.shutdown(); + } + + @Override + public void abort() + { + aborted = true; } @Override public void completed() { } - - @Override - public void abort() - { - } } diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java index c69253e4ded..2e8187966e0 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java @@ -175,5 +175,17 @@ public class ServerFCGIConnection extends AbstractConnection channel.dispatch(); } } + + @Override + public void onFailure(int request, Throwable failure) + { + HttpChannelOverFCGI channel = channels.remove(request); + if (LOG.isDebugEnabled()) + LOG.debug("Request {} failure on {}: {}", request, channel, failure); + if (channel != null) + { + channel.badMessage(400, failure.toString()); + } + } } } diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java index 103a326eabb..2146f6305be 100644 --- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java @@ -24,6 +24,7 @@ import java.net.URI; import java.net.URLEncoder; import java.nio.ByteBuffer; import java.util.Arrays; +import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -40,6 +41,8 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.DeferredContentProvider; +import org.eclipse.jetty.client.util.FutureResponseListener; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.IO; @@ -551,4 +554,77 @@ public class HttpClientTest extends AbstractHttpClientServerTest Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); } + + @Test + public void testEarlyEOF() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + // Promise some content, then flush the headers, then fail to send the content. + response.setContentLength(16); + response.flushBuffer(); + throw new NullPointerException(); + } + }); + + try + { + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .timeout(5, TimeUnit.SECONDS) + .send(); + Assert.fail(); + } + catch (ExecutionException x) + { + // Expected. + } + } + + @Test + public void testSmallContentDelimitedByEOFWithSlowRequest() throws Exception + { + testContentDelimitedByEOFWithSlowRequest(1024); + } + + @Test + public void testBigContentDelimitedByEOFWithSlowRequest() throws Exception + { + testContentDelimitedByEOFWithSlowRequest(128 * 1024); + } + + private void testContentDelimitedByEOFWithSlowRequest(int length) throws Exception + { + final byte[] data = new byte[length]; + new Random().nextBytes(data); + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.setHeader("Connection", "close"); + response.getOutputStream().write(data); + } + }); + + DeferredContentProvider content = new DeferredContentProvider(ByteBuffer.wrap(new byte[]{0})); + Request request = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .content(content); + FutureResponseListener listener = new FutureResponseListener(request); + request.send(listener); + // Wait some time to simulate a slow request. + Thread.sleep(1000); + content.close(); + + ContentResponse response = listener.get(5, TimeUnit.SECONDS); + + Assert.assertEquals(200, response.getStatus()); + Assert.assertArrayEquals(data, response.getContent()); + } } From 8f733169f5113cad1032a204f2b4422b0ba75d8b Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 11 Apr 2014 14:26:35 -0700 Subject: [PATCH 034/135] 432321 - jetty-start / Allow defining extra start directories for common configurations + Final work for unit testing happiness + BaseHome is now configured via ConfigSources (as intended) + StartArgs now parses the ConfigSources list (in reverse) + CommandLineConfigSource now does all of the ${jetty.base} and ${jetty.home} determination (from properties and env, with fallbacks) --- .../org/eclipse/jetty/start/BaseHome.java | 167 ++++++------------ .../main/java/org/eclipse/jetty/start/FS.java | 9 - .../java/org/eclipse/jetty/start/Main.java | 35 +--- .../org/eclipse/jetty/start/PathFinder.java | 3 +- .../org/eclipse/jetty/start/StartArgs.java | 154 ++++++++-------- .../start/config/CommandLineConfigSource.java | 111 ++++++++++-- .../org/eclipse/jetty/start/BaseHomeTest.java | 18 +- .../eclipse/jetty/start/ExtraStartTest.java | 5 +- .../org/eclipse/jetty/start/MainTest.java | 31 ++-- .../jetty/start/ModuleGraphWriterTest.java | 3 +- .../org/eclipse/jetty/start/ModuleTest.java | 53 ++++-- .../org/eclipse/jetty/start/ModulesTest.java | 12 +- .../eclipse/jetty/start/PathFinderTest.java | 7 +- .../test/resources/assert-home-with-jvm.txt | 64 +++---- .../resources/assert-home-with-spaces.txt | 2 +- .../src/test/resources/assert-home.txt | 62 +++---- 16 files changed, 378 insertions(+), 358 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java index 68c121ed92e..35485ad7c61 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java @@ -20,9 +20,6 @@ package org.eclipse.jetty.start; import java.io.File; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; @@ -33,13 +30,13 @@ import java.util.EnumSet; import java.util.List; import java.util.ListIterator; import java.util.Objects; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.eclipse.jetty.start.config.CommandLineConfigSource; import org.eclipse.jetty.start.config.ConfigSource; import org.eclipse.jetty.start.config.ConfigSources; import org.eclipse.jetty.start.config.DirConfigSource; +import org.eclipse.jetty.start.config.JettyBaseConfigSource; +import org.eclipse.jetty.start.config.JettyHomeConfigSource; /** * File access for ${jetty.home}, ${jetty.base}, directories. @@ -110,87 +107,71 @@ public class BaseHome return String.format("${%s}%c%s",name,File.separatorChar,relative.toString()); } } - private static final String JETTY_BASE = "jetty.base"; - private static final String JETTY_HOME = "jetty.home";; + public static final String JETTY_BASE = "jetty.base"; + public static final String JETTY_HOME = "jetty.home"; private final static EnumSet SEARCH_VISIT_OPTIONS = EnumSet.of(FileVisitOption.FOLLOW_LINKS); private final static int MAX_SEARCH_DEPTH = Integer.getInteger("org.eclipse.jetty.start.searchDepth",10); - private Path homeDir; - private ConfigSources sources; - private Path baseDir; + private final ConfigSources sources; + private final Path homeDir; + private final Path baseDir; - public BaseHome() + public BaseHome() throws IOException { - try - { - // find ${jetty.base} and ${jetty.home} from environment. - - // overrides from command line (and the like) come later. - // in the .initialize() step - - // default is ${user.dir} - this.baseDir = FS.toPath(System.getProperty("user.dir",".")); - - // if ${jetty.base} declared, use it - String jettyBase = System.getProperty(JETTY_BASE); - if (jettyBase != null) - { - this.baseDir = FS.toPath(jettyBase); - } - - // find ${jetty.home} - - // default location is based on lookup for BaseHome (from jetty's start.jar) - URL jarfile = this.getClass().getClassLoader().getResource("org/eclipse/jetty/start/BaseHome.class"); - if (jarfile != null) - { - Matcher m = Pattern.compile("jar:(file:.*)!/org/eclipse/jetty/start/BaseHome.class").matcher(jarfile.toString()); - if (m.matches()) - { - // ${jetty.home} is relative to found BaseHome class - this.homeDir = new File(new URI(m.group(1))).getParentFile().toPath(); - } - } - - // if we can't locate BaseHome, then assume home == base - if (this.homeDir == null) - { - this.homeDir = baseDir.toAbsolutePath(); - } - - // if ${jetty.home} declared, use it - String jettyHome = System.getProperty(JETTY_HOME); - if (jettyHome != null) - { - this.homeDir = FS.toPath(jettyHome); - } - - // Resolve to absolute paths - this.homeDir = this.homeDir.toAbsolutePath(); - this.baseDir = this.baseDir.toAbsolutePath(); - } - catch (URISyntaxException e) - { - throw new RuntimeException(e); - } + this(new String[0]); } - public BaseHome(File homeDir, File baseDir) + public BaseHome(String cmdLine[]) throws IOException { - Objects.requireNonNull(homeDir,"Home Dir cannot be null"); + this(new CommandLineConfigSource(cmdLine)); + } - this.homeDir = homeDir.toPath(); - this.baseDir = homeDir.toPath(); // default - if (baseDir != null) + public BaseHome(CommandLineConfigSource cmdLineSource) throws IOException + { + StartLog.getInstance().initialize(this,cmdLineSource); + + sources = new ConfigSources(); + sources.add(cmdLineSource); + this.homeDir = cmdLineSource.getHomePath(); + this.baseDir = cmdLineSource.getBasePath(); + sources.add(new JettyBaseConfigSource(cmdLineSource.getBasePath())); + sources.add(new JettyHomeConfigSource(cmdLineSource.getHomePath())); + + System.setProperty(JETTY_HOME,homeDir.toAbsolutePath().toString()); + System.setProperty(JETTY_BASE,baseDir.toAbsolutePath().toString()); + } + + public BaseHome(ConfigSources sources) + { + this.sources = sources; + Path home = null; + Path base = null; + for (ConfigSource source : sources) { - this.baseDir = baseDir.toPath(); + if (source instanceof CommandLineConfigSource) + { + CommandLineConfigSource cmdline = (CommandLineConfigSource)source; + home = cmdline.getHomePath(); + base = cmdline.getBasePath(); + } + else if (source instanceof JettyBaseConfigSource) + { + base = ((JettyBaseConfigSource)source).getDir(); + } + else if (source instanceof JettyHomeConfigSource) + { + home = ((JettyHomeConfigSource)source).getDir(); + } } - // Resolve to absolute paths - this.homeDir = this.homeDir.toAbsolutePath(); - this.baseDir = this.baseDir.toAbsolutePath(); + Objects.requireNonNull(home,"jetty.home cannot be null"); + this.homeDir = home; + this.baseDir = (base != null)?base:home; + + System.setProperty(JETTY_HOME,homeDir.toAbsolutePath().toString()); + System.setProperty(JETTY_BASE,baseDir.toAbsolutePath().toString()); } public String getBase() @@ -468,53 +449,11 @@ public class BaseHome return hits; } - public void initialize(ConfigSources config) - { - CommandLineConfigSource cmdLine = config.getCommandLineSource(); - if (cmdLine != null) - { - this.homeDir = cmdLine.getHomePath(); - this.baseDir = cmdLine.getBasePath(); - } - - this.sources = config; - } - public boolean isBaseDifferent() { return homeDir.compareTo(baseDir) != 0; } - /** - * @deprecated use {@link #setBaseDir(Path)} - */ - @Deprecated - public void setBaseDir(File dir) - { - setBaseDir(dir.toPath()); - } - - public void setBaseDir(Path dir) - { - this.baseDir = dir.toAbsolutePath(); - System.setProperty(JETTY_BASE,dir.toString()); - } - - /** - * @deprecated use {@link #setHomeDir(Path)} - */ - @Deprecated - public void setHomeDir(File dir) - { - setHomeDir(dir.toPath()); - } - - public void setHomeDir(Path dir) - { - this.homeDir = dir.toAbsolutePath(); - System.setProperty(JETTY_HOME,dir.toString()); - } - /** * Convenience method for toShortForm(file.toPath()) */ diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java index abedea9c2a4..ce5efb88ed4 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java @@ -306,15 +306,6 @@ public class FS return ret.toString(); } - public static Path toOptionalPath(String path) - { - if (path == null) - { - return null; - } - return toPath(path); - } - public static Path toPath(String path) { return FileSystems.getDefault().getPath(FS.separators(path)); diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java index 33f0c23b09b..97734a91ffd 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java @@ -51,9 +51,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jetty.start.config.CommandLineConfigSource; -import org.eclipse.jetty.start.config.ConfigSources; -import org.eclipse.jetty.start.config.JettyBaseConfigSource; -import org.eclipse.jetty.start.config.JettyHomeConfigSource; /** * Main start class. @@ -138,11 +135,10 @@ public class Main System.exit(exit); } - private final BaseHome baseHome; + private BaseHome baseHome; Main() throws IOException { - baseHome = new BaseHome(); } private void copyInThread(final InputStream in, final OutputStream out) @@ -551,45 +547,32 @@ public class Main public StartArgs processCommandLine(String[] cmdLine) throws Exception { - ConfigSources sources = new ConfigSources(); - // Processing Order is important! // ------------------------------------------------------------ - // 1) Directory Locations - + // 1) Configuration Locations CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine); - sources.add(cmdLineSource); - sources.add(new JettyBaseConfigSource(cmdLineSource.getBasePath())); - sources.add(new JettyHomeConfigSource(cmdLineSource.getHomePath())); - - // Set Home and Base at the start, as many other paths encountered - // will be based off of them. - baseHome.initialize(sources); - - // ------------------------------------------------------------ - // 2) Start Logging - StartLog.getInstance().initialize(baseHome,cmdLineSource); + baseHome = new BaseHome(cmdLineSource); StartLog.debug("jetty.home=%s",baseHome.getHome()); StartLog.debug("jetty.base=%s",baseHome.getBase()); // ------------------------------------------------------------ - // 3) Parse everything provided. + // 2) Parse everything provided. // This would be the directory information + // the various start inis // and then the raw command line arguments StartLog.debug("Parsing collected arguments"); StartArgs args = new StartArgs(); - args.parse(sources); + args.parse(baseHome.getConfigSources()); // ------------------------------------------------------------ - // 4) Module Registration + // 3) Module Registration Modules modules = new Modules(); StartLog.debug("Registering all modules"); modules.registerAll(baseHome, args); // ------------------------------------------------------------ - // 5) Active Module Resolution + // 4) Active Module Resolution for (String enabledModule : args.getEnabledModules()) { List msources = args.getSources(enabledModule); @@ -603,12 +586,12 @@ public class Main List activeModules = modules.resolveEnabled(); // ------------------------------------------------------------ - // 6) Lib & XML Expansion / Resolution + // 5) Lib & XML Expansion / Resolution args.expandLibs(baseHome); args.expandModules(baseHome,activeModules); // ------------------------------------------------------------ - // 7) Resolve Extra XMLs + // 6) Resolve Extra XMLs args.resolveExtraXmls(baseHome); return args; diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/PathFinder.java b/jetty-start/src/main/java/org/eclipse/jetty/start/PathFinder.java index 799ccb193ea..1f06f06bc4c 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/PathFinder.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/PathFinder.java @@ -48,7 +48,7 @@ public class PathFinder extends SimpleFileVisitor private void addHit(Path path) { String relPath = basePath.relativize(path).toString(); - StartLog.debug("addHit(" + path + ") = [" + relPath + "," + path + "]"); + StartLog.debug("Found [" + relPath + "] " + path); hits.put(relPath,path); } @@ -139,7 +139,6 @@ public class PathFinder extends SimpleFileVisitor { if (fileMatcher.matches(file)) { - StartLog.debug("Found file: " + file); addHit(file); } else diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java index 46399e6be0e..34b5aa3458b 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java @@ -29,11 +29,13 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.eclipse.jetty.start.Props.Prop; +import org.eclipse.jetty.start.config.ConfigSource; import org.eclipse.jetty.start.config.ConfigSources; /** @@ -66,7 +68,7 @@ public class StartArgs } private static final String SERVER_MAIN = "org.eclipse.jetty.xml.XmlConfiguration"; - + /** List of enabled modules */ private Set modules = new HashSet<>(); /** Map of enabled modules to the source of where that activation occurred */ @@ -84,7 +86,7 @@ public class StartArgs /** List of all xml references found directly on command line or start.ini */ private List xmlRefs = new ArrayList<>(); - + private Props properties = new Props(); private Set systemPropertyKeys = new HashSet<>(); private List rawLibs = new ArrayList<>(); @@ -247,6 +249,30 @@ public class StartArgs } } + private void dumpProperty(String key) + { + Prop prop = properties.getProp(key); + if (prop == null) + { + System.out.printf(" %s (not defined)%n",key); + } + else + { + System.out.printf(" %s = %s%n",key,properties.expand(prop.value)); + if (StartLog.isDebugEnabled()) + { + System.out.printf(" origin: %s%n",prop.origin); + while (prop.overrides != null) + { + prop = prop.overrides; + System.out.printf(" (overrides)%n"); + System.out.printf(" %s = %s%n",key,properties.expand(prop.value)); + System.out.printf(" origin: %s%n",prop.origin); + } + } + } + } + public void dumpSystemProperties() { System.out.println(); @@ -275,30 +301,6 @@ public class StartArgs System.out.printf(" %s = %s%n",key,System.getProperty(key)); } - private void dumpProperty(String key) - { - Prop prop = properties.getProp(key); - if (prop == null) - { - System.out.printf(" %s (not defined)%n",key); - } - else - { - System.out.printf(" %s = %s%n",key,properties.expand(prop.value)); - if (StartLog.isDebugEnabled()) - { - System.out.printf(" origin: %s%n",prop.origin); - while (prop.overrides != null) - { - prop = prop.overrides; - System.out.printf(" (overrides)%n"); - System.out.printf(" %s = %s%n",key,properties.expand(prop.value)); - System.out.printf(" origin: %s%n",prop.origin); - } - } - } - } - /** * Ensure that the System Properties are set (if defined as a System property, or start.config property, or start.ini property) * @@ -393,6 +395,16 @@ public class StartArgs } } + public List getAddToStartdIni() + { + return addToStartdIni; + } + + public List getAddToStartIni() + { + return addToStartIni; + } + public Modules getAllModules() { return allModules; @@ -403,16 +415,16 @@ public class StartArgs return classpath; } - public List getFiles() - { - return files; - } - public Set getEnabledModules() { return this.modules; } + public List getFiles() + { + return files; + } + public List getJvmArgs() { return jvmArgs; @@ -485,16 +497,6 @@ public class StartArgs return moduleGraphFilename; } - public List getAddToStartdIni() - { - return addToStartdIni; - } - - public List getAddToStartIni() - { - return addToStartIni; - } - public Props getProperties() { return properties; @@ -565,31 +567,6 @@ public class StartArgs return listModules; } - private void setProperty(String key, String value, String source) - { - // Special / Prevent override from start.ini's - if (key.equals("jetty.home")) - { - properties.setProperty("jetty.home",System.getProperty("jetty.home"),source); - return; - } - - // Special / Prevent override from start.ini's - if (key.equals("jetty.base")) - { - properties.setProperty("jetty.base",System.getProperty("jetty.base"),source); - return; - } - - // Normal - properties.setProperty(key,value,source); - } - - public void setRun(boolean run) - { - this.run = run; - } - public boolean isRun() { return run; @@ -605,6 +582,19 @@ public class StartArgs return version; } + public void parse(ConfigSources sources) + { + ListIterator iter = sources.reverseListIterator(); + while (iter.hasPrevious()) + { + ConfigSource source = iter.previous(); + for (String arg : source.getArgs()) + { + parse(arg,source.getId()); + } + } + } + public void parse(final String rawarg, String source) { if (rawarg == null) @@ -639,7 +629,7 @@ public class StartArgs if (arg.startsWith("--extra-start-dir=")) { - // valid, but handled in ConfigSources instead + // valid, but handled in ConfigSources instead return; } @@ -855,6 +845,31 @@ public class StartArgs this.allModules = allModules; } + private void setProperty(String key, String value, String source) + { + // Special / Prevent override from start.ini's + if (key.equals("jetty.home")) + { + properties.setProperty("jetty.home",System.getProperty("jetty.home"),source); + return; + } + + // Special / Prevent override from start.ini's + if (key.equals("jetty.base")) + { + properties.setProperty("jetty.base",System.getProperty("jetty.base"),source); + return; + } + + // Normal + properties.setProperty(key,value,source); + } + + public void setRun(boolean run) + { + this.run = run; + } + @Override public String toString() { @@ -870,9 +885,4 @@ public class StartArgs builder.append("]"); return builder.toString(); } - - public void parse(ConfigSources sources) - { - // TODO Auto-generated method stub - } } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java index 6b9cba7afd0..df6db187c18 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java @@ -18,19 +18,29 @@ package org.eclipse.jetty.start.config; +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; import java.nio.file.Path; import java.util.Arrays; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.eclipse.jetty.start.BaseHome; import org.eclipse.jetty.start.FS; import org.eclipse.jetty.start.Props; +import org.eclipse.jetty.start.Props.Prop; +import org.eclipse.jetty.start.UsageException; /** * Configuration Source representing the Command Line arguments. */ public class CommandLineConfigSource implements ConfigSource { - public static final String CMD_LINE_SOURCE = ""; + public static final String ORIGIN_INTERNAL_FALLBACK = ""; + public static final String ORIGIN_CMD_LINE = ""; private final List args; private final Props props; @@ -43,28 +53,99 @@ public class CommandLineConfigSource implements ConfigSource this.props = new Props(); for (String arg : args) { - this.props.addPossibleProperty(arg,CMD_LINE_SOURCE); + this.props.addPossibleProperty(arg,ORIGIN_CMD_LINE); } - Path home = FS.toOptionalPath(getProperty("jetty.home")); - Path base = FS.toOptionalPath(getProperty("jetty.base")); + // Setup ${jetty.base} and ${jetty.home} + this.homePath = findJettyHomePath().toAbsolutePath(); + this.basePath = findJettyBasePath().toAbsolutePath(); - if (home != null) + // Update System Properties + setSystemProperty(BaseHome.JETTY_HOME,homePath.toAbsolutePath().toString()); + setSystemProperty(BaseHome.JETTY_BASE,basePath.toAbsolutePath().toString()); + } + + private final Path findJettyBasePath() + { + // If a jetty property is defined, use it + Prop prop = this.props.getProp(BaseHome.JETTY_BASE,false); + if (prop != null && !isEmpty(prop.value)) { - // logic if home is specified - if (base == null) + return FS.toPath(prop.value); + } + + // If a system property is defined, use it + String val = System.getProperty(BaseHome.JETTY_BASE); + if (!isEmpty(val)) + { + return FS.toPath(val); + } + + // Lastly, fall back to base == home + Path base = this.homePath.toAbsolutePath(); + setProperty(BaseHome.JETTY_BASE,base.toString(),ORIGIN_INTERNAL_FALLBACK); + return base; + } + + private final Path findJettyHomePath() + { + // If a jetty property is defined, use it + Prop prop = this.props.getProp(BaseHome.JETTY_HOME,false); + if (prop != null && !isEmpty(prop.value)) + { + return FS.toPath(prop.value); + } + + // If a system property is defined, use it + String val = System.getProperty(BaseHome.JETTY_HOME); + if (!isEmpty(val)) + { + return FS.toPath(val); + } + + // Attempt to find path relative to content in jetty's start.jar + // based on lookup for the Main class (from jetty's start.jar) + String classRef = "org/eclipse/jetty/start/Main.class"; + URL jarfile = this.getClass().getClassLoader().getResource(classRef); + if (jarfile != null) + { + Matcher m = Pattern.compile("jar:(file:.*)!/" + classRef).matcher(jarfile.toString()); + if (m.matches()) { - base = home; - setProperty("jetty.base",base.toString(),""); + // ${jetty.home} is relative to found BaseHome class + try + { + return new File(new URI(m.group(1))).getParentFile().toPath(); + } + catch (URISyntaxException e) + { + throw new UsageException(UsageException.ERR_UNKNOWN,e); + } } } - this.homePath = home; - this.basePath = base; + // Lastly, fall back to ${user.dir} default + Path home = FS.toPath(System.getProperty("user.dir",".")); + setProperty(BaseHome.JETTY_HOME,home.toString(),ORIGIN_INTERNAL_FALLBACK); + return home; + } - // Update System Properties - setSystemProperty("jetty.home",homePath.toAbsolutePath().toString()); - setSystemProperty("jetty.base",basePath.toAbsolutePath().toString()); + private boolean isEmpty(String value) + { + if (value == null) + { + return true; + } + int len = value.length(); + for (int i = 0; i < len; i++) + { + int c = value.codePointAt(i); + if (!Character.isWhitespace(c)) + { + return false; + } + } + return true; } @Override @@ -116,7 +197,7 @@ public class CommandLineConfigSource implements ConfigSource @Override public String getId() { - return CMD_LINE_SOURCE; + return ORIGIN_CMD_LINE; } @Override diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java index 368668bfd9b..861972c78ca 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/BaseHomeTest.java @@ -98,13 +98,11 @@ public class BaseHomeTest public void testGetPath_OnlyHome() throws IOException { File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home"); - File baseDir = null; ConfigSources config = new ConfigSources(); config.add(new JettyHomeConfigSource(homeDir.toPath())); - BaseHome hb = new BaseHome(homeDir,baseDir); - hb.initialize(config); + BaseHome hb = new BaseHome(config); Path startIni = hb.getPath("start.ini"); String ref = hb.toShortForm(startIni); @@ -118,13 +116,11 @@ public class BaseHomeTest public void testGetPaths_OnlyHome() throws IOException { File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home"); - File baseDir = null; ConfigSources config = new ConfigSources(); config.add(new JettyHomeConfigSource(homeDir.toPath())); - BaseHome hb = new BaseHome(homeDir,baseDir); - hb.initialize(config); + BaseHome hb = new BaseHome(config); List paths = hb.getPaths("start.d/*"); List expected = new ArrayList<>(); @@ -142,13 +138,11 @@ public class BaseHomeTest public void testGetPaths_OnlyHome_InisOnly() throws IOException { File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home"); - File baseDir = null; ConfigSources config = new ConfigSources(); config.add(new JettyHomeConfigSource(homeDir.toPath())); - BaseHome hb = new BaseHome(homeDir,baseDir); - hb.initialize(config); + BaseHome hb = new BaseHome(config); List paths = hb.getPaths("start.d/*.ini"); List expected = new ArrayList<>(); @@ -172,8 +166,7 @@ public class BaseHomeTest config.add(new JettyBaseConfigSource(baseDir.toPath())); config.add(new JettyHomeConfigSource(homeDir.toPath())); - BaseHome hb = new BaseHome(homeDir,baseDir); - hb.initialize(config); + BaseHome hb = new BaseHome(config); List paths = hb.getPaths("start.d/*.ini"); List expected = new ArrayList<>(); @@ -206,8 +199,7 @@ public class BaseHomeTest config.add(new JettyBaseConfigSource(baseDir.toPath())); config.add(new JettyHomeConfigSource(homeDir.toPath())); - BaseHome hb = new BaseHome(homeDir,baseDir); - hb.initialize(config); + BaseHome hb = new BaseHome(config); Path startIni = hb.getPath("start.ini"); String ref = hb.toShortForm(startIni); diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java index f22c37b3fd6..0b41045d493 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ExtraStartTest.java @@ -275,6 +275,7 @@ public class ExtraStartTest // Create common File common = testdir.getFile("common"); FS.ensureEmpty(common); + TestEnv.makeFile(common,"start.ini","jetty.port=8080"); // Create base File base = testdir.getFile("base"); @@ -306,6 +307,7 @@ public class ExtraStartTest // Create common File common = testdir.getFile("common"); FS.ensureEmpty(common); + TestEnv.makeFile(common,"start.ini","jetty.port=8080"); // Create corp File corp = testdir.getFile("corp"); @@ -343,8 +345,7 @@ public class ExtraStartTest // Create corp File corp = testdir.getFile("corp"); FS.ensureEmpty(corp); - TestEnv.makeFile(corp,"start.ini", // - "jetty.port=9090"); + TestEnv.makeFile(corp,"start.ini","jetty.port=9090"); // Create common File common = testdir.getFile("common"); diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java index 5fe9b1a363b..561a013dfa9 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java @@ -18,28 +18,33 @@ package org.eclipse.jetty.start; +import static org.hamcrest.Matchers.*; + import java.io.File; import java.util.ArrayList; import java.util.List; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.junit.Assert; +import org.junit.Before; import org.junit.Ignore; import org.junit.Test; public class MainTest { - private void addUseCasesHome(List cmdLineArgs) + @Before + public void clearSystemProperties() { - File testJettyHome = MavenTestingUtils.getTestResourceDir("usecases/home"); - cmdLineArgs.add("jetty.home=" + testJettyHome); + System.setProperty("jetty.home",""); + System.setProperty("jetty.base",""); } - + @Test public void testBasicProcessing() throws Exception { List cmdLineArgs = new ArrayList<>(); - addUseCasesHome(cmdLineArgs); + File testJettyHome = MavenTestingUtils.getTestResourceDir("usecases/home"); + cmdLineArgs.add("jetty.home=" + testJettyHome); cmdLineArgs.add("jetty.port=9090"); Main main = new Main(); @@ -74,7 +79,8 @@ public class MainTest public void testListConfig() throws Exception { List cmdLineArgs = new ArrayList<>(); - addUseCasesHome(cmdLineArgs); + File testJettyHome = MavenTestingUtils.getTestResourceDir("usecases/home"); + cmdLineArgs.add("jetty.home=" + testJettyHome); cmdLineArgs.add("jetty.port=9090"); cmdLineArgs.add("--list-config"); // cmdLineArgs.add("--debug"); @@ -97,7 +103,8 @@ public class MainTest { List cmdLineArgs = new ArrayList<>(); - addUseCasesHome(cmdLineArgs); + File homePath = MavenTestingUtils.getTestResourceDir("usecases/home"); + cmdLineArgs.add("jetty.home=" + homePath); // JVM args cmdLineArgs.add("--exec"); @@ -118,7 +125,9 @@ public class MainTest StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()])); BaseHome baseHome = main.getBaseHome(); - System.err.println(args); + + Assert.assertThat("jetty.home", baseHome.getHome(), is(homePath.getAbsolutePath())); + Assert.assertThat("jetty.base", baseHome.getBase(), is(homePath.getAbsolutePath())); ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home-with-jvm.txt"); } @@ -134,8 +143,10 @@ public class MainTest Main main = new Main(); StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()])); BaseHome baseHome = main.getBaseHome(); - System.err.println(args); - + + Assert.assertThat("jetty.home", baseHome.getHome(), is(homePath.getAbsolutePath())); + Assert.assertThat("jetty.base", baseHome.getBase(), is(homePath.getAbsolutePath())); + ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home-with-spaces.txt"); } } diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java index c4aeb53c282..82db04cf9cb 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleGraphWriterTest.java @@ -55,8 +55,7 @@ public class ModuleGraphWriterTest config.add(new JettyBaseConfigSource(baseDir.toPath())); // Initialize - BaseHome basehome = new BaseHome(); - basehome.initialize(config); + BaseHome basehome = new BaseHome(config); StartArgs args = new StartArgs(); args.parse(config); diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java index c49ee3a1068..ced2d38632b 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModuleTest.java @@ -18,36 +18,53 @@ package org.eclipse.jetty.start; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; import java.io.File; import java.io.IOException; +import org.eclipse.jetty.start.config.CommandLineConfigSource; +import org.eclipse.jetty.start.config.ConfigSources; +import org.eclipse.jetty.start.config.JettyBaseConfigSource; +import org.eclipse.jetty.start.config.JettyHomeConfigSource; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.TestingDir; import org.junit.Assert; +import org.junit.Rule; import org.junit.Test; public class ModuleTest { - private Module loadTestHomeModule(String moduleFileName) throws IOException - { - File file = MavenTestingUtils.getTestResourceFile("usecases/home/modules/" + moduleFileName); - return new Module(new BaseHome(),file.toPath()); - } - + @Rule + public TestingDir testdir = new TestingDir(); + @Test public void testLoadWebSocket() throws IOException { - Module Module = loadTestHomeModule("websocket.mod"); - - Assert.assertThat("Module Name",Module.getName(),is("websocket")); - Assert.assertThat("Module Parents Size",Module.getParentNames().size(),is(2)); - Assert.assertThat("Module Parents",Module.getParentNames(),containsInAnyOrder("annotations","server")); - Assert.assertThat("Module Xmls Size",Module.getXmls().size(),is(1)); - Assert.assertThat("Module Xmls",Module.getXmls(),contains("etc/jetty-websockets.xml")); - Assert.assertThat("Module Options Size",Module.getLibs().size(),is(1)); - Assert.assertThat("Module Options",Module.getLibs(),contains("lib/websocket/*.jar")); + // Test Env + File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home"); + File baseDir = testdir.getEmptyDir(); + String cmdLine[] = new String[] {"jetty.version=TEST"}; + + // Configuration + CommandLineConfigSource cmdLineSource = new CommandLineConfigSource(cmdLine); + ConfigSources config = new ConfigSources(); + config.add(cmdLineSource); + config.add(new JettyHomeConfigSource(homeDir.toPath())); + config.add(new JettyBaseConfigSource(baseDir.toPath())); + + // Initialize + BaseHome basehome = new BaseHome(config); + + File file = MavenTestingUtils.getTestResourceFile("usecases/home/modules/websocket.mod"); + Module module = new Module(basehome,file.toPath()); + + Assert.assertThat("Module Name",module.getName(),is("websocket")); + Assert.assertThat("Module Parents Size",module.getParentNames().size(),is(2)); + Assert.assertThat("Module Parents",module.getParentNames(),containsInAnyOrder("annotations","server")); + Assert.assertThat("Module Xmls Size",module.getXmls().size(),is(1)); + Assert.assertThat("Module Xmls",module.getXmls(),contains("etc/jetty-websockets.xml")); + Assert.assertThat("Module Options Size",module.getLibs().size(),is(1)); + Assert.assertThat("Module Options",module.getLibs(),contains("lib/websocket/*.jar")); } } diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java index 744a3bdc66e..4b2fd2390bf 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ModulesTest.java @@ -60,8 +60,7 @@ public class ModulesTest config.add(new JettyBaseConfigSource(baseDir.toPath())); // Initialize - BaseHome basehome = new BaseHome(); - basehome.initialize(config); + BaseHome basehome = new BaseHome(config); StartArgs args = new StartArgs(); args.parse(config); @@ -104,8 +103,7 @@ public class ModulesTest config.add(new JettyBaseConfigSource(baseDir.toPath())); // Initialize - BaseHome basehome = new BaseHome(); - basehome.initialize(config); + BaseHome basehome = new BaseHome(config); StartArgs args = new StartArgs(); args.parse(config); @@ -136,8 +134,7 @@ public class ModulesTest config.add(new JettyBaseConfigSource(baseDir.toPath())); // Initialize - BaseHome basehome = new BaseHome(); - basehome.initialize(config); + BaseHome basehome = new BaseHome(config); StartArgs args = new StartArgs(); args.parse(config); @@ -208,8 +205,7 @@ public class ModulesTest config.add(new JettyBaseConfigSource(baseDir.toPath())); // Initialize - BaseHome basehome = new BaseHome(); - basehome.initialize(config); + BaseHome basehome = new BaseHome(config); StartArgs args = new StartArgs(); args.parse(config); diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PathFinderTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PathFinderTest.java index bc14d322001..18b05cd7bd4 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/PathFinderTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/PathFinderTest.java @@ -36,7 +36,7 @@ public class PathFinderTest public void testFindInis() throws IOException { File homeDir = MavenTestingUtils.getTestResourceDir("hb.1/home"); - Path homePath = homeDir.toPath(); + Path homePath = homeDir.toPath().toAbsolutePath(); PathFinder finder = new PathFinder(); finder.setFileMatcher("glob:**/*.ini"); @@ -53,7 +53,7 @@ public class PathFinderTest expected.add("${jetty.home}/start.ini"); FSTest.toOsSeparators(expected); - BaseHome hb = new BaseHome(homeDir,null); + BaseHome hb = new BaseHome(new String[] { "jetty.home=" + homePath.toString() }); BaseHomeTest.assertPathList(hb,"Files found",expected,finder); } @@ -61,6 +61,7 @@ public class PathFinderTest public void testFindMods() throws IOException { File homeDir = MavenTestingUtils.getTestResourceDir("usecases/home"); + Path homePath = homeDir.toPath().toAbsolutePath(); List expected = new ArrayList<>(); File modulesDir = new File(homeDir,"modules"); @@ -81,7 +82,7 @@ public class PathFinderTest Files.walkFileTree(modulesPath,EnumSet.of(FileVisitOption.FOLLOW_LINKS),1,finder); - BaseHome hb = new BaseHome(homeDir,null); + BaseHome hb = new BaseHome(new String[] { "jetty.home=" + homePath.toString() }); BaseHomeTest.assertPathList(hb,"Files found",expected,finder); } } diff --git a/jetty-start/src/test/resources/assert-home-with-jvm.txt b/jetty-start/src/test/resources/assert-home-with-jvm.txt index b73061bba42..9774d97e425 100644 --- a/jetty-start/src/test/resources/assert-home-with-jvm.txt +++ b/jetty-start/src/test/resources/assert-home-with-jvm.txt @@ -1,38 +1,38 @@ # The XMLs we expect (order is important) -XML|${jetty.home}/etc/jetty-jmx.xml -XML|${jetty.home}/etc/jetty.xml -XML|${jetty.home}/etc/jetty-http.xml -XML|${jetty.home}/etc/jetty-plus.xml -XML|${jetty.home}/etc/jetty-annotations.xml -XML|${jetty.home}/etc/jetty-websockets.xml -XML|${jetty.home}/etc/jetty-logging.xml +XML|${jetty.base}/etc/jetty-jmx.xml +XML|${jetty.base}/etc/jetty.xml +XML|${jetty.base}/etc/jetty-http.xml +XML|${jetty.base}/etc/jetty-plus.xml +XML|${jetty.base}/etc/jetty-annotations.xml +XML|${jetty.base}/etc/jetty-websockets.xml +XML|${jetty.base}/etc/jetty-logging.xml # The LIBs we expect (order is irrelevant) -LIB|${jetty.home}/lib/annotations/javax.annotation-api-1.2.jar -LIB|${jetty.home}/lib/annotations/org.objectweb.asm-TEST.jar -LIB|${jetty.home}/lib/jetty-annotations-TEST.jar -LIB|${jetty.home}/lib/jetty-continuation-TEST.jar -LIB|${jetty.home}/lib/jetty-http-TEST.jar -LIB|${jetty.home}/lib/jetty-io-TEST.jar -LIB|${jetty.home}/lib/jetty-jmx-TEST.jar -LIB|${jetty.home}/lib/jetty-jndi-TEST.jar -LIB|${jetty.home}/lib/jetty-plus-TEST.jar -LIB|${jetty.home}/lib/jetty-schemas-3.1.jar -LIB|${jetty.home}/lib/jetty-security-TEST.jar -LIB|${jetty.home}/lib/jetty-server-TEST.jar -LIB|${jetty.home}/lib/jetty-util-TEST.jar -LIB|${jetty.home}/lib/jetty-xml-TEST.jar -LIB|${jetty.home}/lib/jndi/javax.activation-1.1.jar -LIB|${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar -LIB|${jetty.home}/lib/servlet-api-3.1.jar -LIB|${jetty.home}/lib/websocket/javax.websocket-api-1.0.jar -LIB|${jetty.home}/lib/websocket/javax-websocket-client-impl-TEST.jar -LIB|${jetty.home}/lib/websocket/javax-websocket-server-impl-TEST.jar -LIB|${jetty.home}/lib/websocket/websocket-api-TEST.jar -LIB|${jetty.home}/lib/websocket/websocket-client-TEST.jar -LIB|${jetty.home}/lib/websocket/websocket-common-TEST.jar -LIB|${jetty.home}/lib/websocket/websocket-server-TEST.jar -LIB|${jetty.home}/lib/websocket/websocket-servlet-TEST.jar +LIB|${jetty.base}/lib/annotations/javax.annotation-api-1.2.jar +LIB|${jetty.base}/lib/annotations/org.objectweb.asm-TEST.jar +LIB|${jetty.base}/lib/jetty-annotations-TEST.jar +LIB|${jetty.base}/lib/jetty-continuation-TEST.jar +LIB|${jetty.base}/lib/jetty-http-TEST.jar +LIB|${jetty.base}/lib/jetty-io-TEST.jar +LIB|${jetty.base}/lib/jetty-jmx-TEST.jar +LIB|${jetty.base}/lib/jetty-jndi-TEST.jar +LIB|${jetty.base}/lib/jetty-plus-TEST.jar +LIB|${jetty.base}/lib/jetty-schemas-3.1.jar +LIB|${jetty.base}/lib/jetty-security-TEST.jar +LIB|${jetty.base}/lib/jetty-server-TEST.jar +LIB|${jetty.base}/lib/jetty-util-TEST.jar +LIB|${jetty.base}/lib/jetty-xml-TEST.jar +LIB|${jetty.base}/lib/jndi/javax.activation-1.1.jar +LIB|${jetty.base}/lib/jndi/javax.transaction-api-1.2.jar +LIB|${jetty.base}/lib/servlet-api-3.1.jar +LIB|${jetty.base}/lib/websocket/javax.websocket-api-1.0.jar +LIB|${jetty.base}/lib/websocket/javax-websocket-client-impl-TEST.jar +LIB|${jetty.base}/lib/websocket/javax-websocket-server-impl-TEST.jar +LIB|${jetty.base}/lib/websocket/websocket-api-TEST.jar +LIB|${jetty.base}/lib/websocket/websocket-client-TEST.jar +LIB|${jetty.base}/lib/websocket/websocket-common-TEST.jar +LIB|${jetty.base}/lib/websocket/websocket-server-TEST.jar +LIB|${jetty.base}/lib/websocket/websocket-servlet-TEST.jar LIB|${maven-test-resources}/extra-resources LIB|${maven-test-resources}/extra-libs/example.jar diff --git a/jetty-start/src/test/resources/assert-home-with-spaces.txt b/jetty-start/src/test/resources/assert-home-with-spaces.txt index 01d429aada4..d63fcce9526 100644 --- a/jetty-start/src/test/resources/assert-home-with-spaces.txt +++ b/jetty-start/src/test/resources/assert-home-with-spaces.txt @@ -2,7 +2,7 @@ # No XMLs in this home # The LIBs we expect (order is irrelevant) -LIB|${jetty.home}/lib/example of a library with spaces.jar +LIB|${jetty.base}/lib/example of a library with spaces.jar # The Properties we expect (order is irrelevant) PROP|test.message=Hello diff --git a/jetty-start/src/test/resources/assert-home.txt b/jetty-start/src/test/resources/assert-home.txt index 327b770420a..5be8d46d67a 100644 --- a/jetty-start/src/test/resources/assert-home.txt +++ b/jetty-start/src/test/resources/assert-home.txt @@ -1,37 +1,37 @@ # The XMLs we expect (order is important) -XML|${jetty.home}/etc/jetty-jmx.xml -XML|${jetty.home}/etc/jetty.xml -XML|${jetty.home}/etc/jetty-http.xml -XML|${jetty.home}/etc/jetty-plus.xml -XML|${jetty.home}/etc/jetty-annotations.xml -XML|${jetty.home}/etc/jetty-websockets.xml +XML|${jetty.base}/etc/jetty-jmx.xml +XML|${jetty.base}/etc/jetty.xml +XML|${jetty.base}/etc/jetty-http.xml +XML|${jetty.base}/etc/jetty-plus.xml +XML|${jetty.base}/etc/jetty-annotations.xml +XML|${jetty.base}/etc/jetty-websockets.xml # The LIBs we expect (order is irrelevant) -LIB|${jetty.home}/lib/annotations/javax.annotation-api-1.2.jar -LIB|${jetty.home}/lib/annotations/org.objectweb.asm-TEST.jar -LIB|${jetty.home}/lib/jetty-annotations-TEST.jar -LIB|${jetty.home}/lib/jetty-continuation-TEST.jar -LIB|${jetty.home}/lib/jetty-http-TEST.jar -LIB|${jetty.home}/lib/jetty-io-TEST.jar -LIB|${jetty.home}/lib/jetty-jmx-TEST.jar -LIB|${jetty.home}/lib/jetty-jndi-TEST.jar -LIB|${jetty.home}/lib/jetty-plus-TEST.jar -LIB|${jetty.home}/lib/jetty-schemas-3.1.jar -LIB|${jetty.home}/lib/jetty-security-TEST.jar -LIB|${jetty.home}/lib/jetty-server-TEST.jar -LIB|${jetty.home}/lib/jetty-util-TEST.jar -LIB|${jetty.home}/lib/jetty-xml-TEST.jar -LIB|${jetty.home}/lib/jndi/javax.activation-1.1.jar -LIB|${jetty.home}/lib/jndi/javax.transaction-api-1.2.jar -LIB|${jetty.home}/lib/servlet-api-3.1.jar -LIB|${jetty.home}/lib/websocket/javax.websocket-api-1.0.jar -LIB|${jetty.home}/lib/websocket/javax-websocket-client-impl-TEST.jar -LIB|${jetty.home}/lib/websocket/javax-websocket-server-impl-TEST.jar -LIB|${jetty.home}/lib/websocket/websocket-api-TEST.jar -LIB|${jetty.home}/lib/websocket/websocket-client-TEST.jar -LIB|${jetty.home}/lib/websocket/websocket-common-TEST.jar -LIB|${jetty.home}/lib/websocket/websocket-server-TEST.jar -LIB|${jetty.home}/lib/websocket/websocket-servlet-TEST.jar +LIB|${jetty.base}/lib/annotations/javax.annotation-api-1.2.jar +LIB|${jetty.base}/lib/annotations/org.objectweb.asm-TEST.jar +LIB|${jetty.base}/lib/jetty-annotations-TEST.jar +LIB|${jetty.base}/lib/jetty-continuation-TEST.jar +LIB|${jetty.base}/lib/jetty-http-TEST.jar +LIB|${jetty.base}/lib/jetty-io-TEST.jar +LIB|${jetty.base}/lib/jetty-jmx-TEST.jar +LIB|${jetty.base}/lib/jetty-jndi-TEST.jar +LIB|${jetty.base}/lib/jetty-plus-TEST.jar +LIB|${jetty.base}/lib/jetty-schemas-3.1.jar +LIB|${jetty.base}/lib/jetty-security-TEST.jar +LIB|${jetty.base}/lib/jetty-server-TEST.jar +LIB|${jetty.base}/lib/jetty-util-TEST.jar +LIB|${jetty.base}/lib/jetty-xml-TEST.jar +LIB|${jetty.base}/lib/jndi/javax.activation-1.1.jar +LIB|${jetty.base}/lib/jndi/javax.transaction-api-1.2.jar +LIB|${jetty.base}/lib/servlet-api-3.1.jar +LIB|${jetty.base}/lib/websocket/javax.websocket-api-1.0.jar +LIB|${jetty.base}/lib/websocket/javax-websocket-client-impl-TEST.jar +LIB|${jetty.base}/lib/websocket/javax-websocket-server-impl-TEST.jar +LIB|${jetty.base}/lib/websocket/websocket-api-TEST.jar +LIB|${jetty.base}/lib/websocket/websocket-client-TEST.jar +LIB|${jetty.base}/lib/websocket/websocket-common-TEST.jar +LIB|${jetty.base}/lib/websocket/websocket-server-TEST.jar +LIB|${jetty.base}/lib/websocket/websocket-servlet-TEST.jar # The Properties we expect (order is irrelevant) PROP|jetty.port=9090 From 7cd3f9983f0848c97505806e2d84c75a023067aa Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 11 Apr 2014 16:03:49 -0700 Subject: [PATCH 035/135] Compile fix --- .../org/eclipse/jetty/spdy/server/NPNModuleTest.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java index 8ccfcb3d17b..1afb8e396cc 100644 --- a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java +++ b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java @@ -29,6 +29,7 @@ import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; @@ -85,12 +86,14 @@ public class NPNModuleTest private static BaseHome basehome; @BeforeClass - public static void initBaseHome() + public static void initBaseHome() throws IOException { File homeDir = MavenTestingUtils.getProjectDir("../spdy-http-server/src/main/config"); File baseDir = MavenTestingUtils.getTargetTestingDir(NPNModuleTest.class.getName()); FS.ensureEmpty(baseDir); - basehome = new BaseHome(homeDir,baseDir); + + String cmdLine[] = { "jetty.home="+homeDir.getAbsolutePath(),"jetty.base="+baseDir.getAbsolutePath() }; + basehome = new BaseHome(cmdLine); } /** @@ -99,7 +102,7 @@ public class NPNModuleTest @Test public void testModuleValues() throws IOException { - File modFile = basehome.getFile("modules/protonego-impl/" + modBootFile); + Path modFile = basehome.getPath("modules/protonego-impl/" + modBootFile); Module mod = new Module(basehome,modFile); assertNotNull("module",mod); @@ -128,7 +131,7 @@ public class NPNModuleTest StringBuilder err = new StringBuilder(); err.append("XBootClasspath mismatch between [files] and [exec]"); err.append("\nThe following are inferred from your [files] definition in "); - err.append(modFile.getAbsolutePath()); + err.append(modFile.toAbsolutePath().toString()); err.append("\nbut are not referenced in your [exec] section"); for (String entry : expectedBootClasspath) { From 31043d25708edbea9ef31948093f4eaf2247919b Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Sat, 12 Apr 2014 09:11:04 +1000 Subject: [PATCH 036/135] 432483 make osgi.serviceloader support for javax.servlet.ServletContainerInitializer optional --- jetty-annotations/pom.xml | 2 +- jetty-osgi/jetty-osgi-boot/pom.xml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml index 0fc2b9f229d..f07812ffce3 100644 --- a/jetty-annotations/pom.xml +++ b/jetty-annotations/pom.xml @@ -44,7 +44,7 @@ javax.servlet.*;version="[2.6.0,3.2)",org.objectweb.asm.*;version=4,* - osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)" + osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)" diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index bd5486a5936..5a02af5fdbf 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -102,7 +102,6 @@ org.eclipse.jetty.osgi.boot;singleton:=true org.eclipse.jetty.osgi.boot.JettyBootstrapActivator - osgi.serviceloader; osgi.serviceloader=javax.servlet.ServletContainerInitializer org.eclipse.jetty.*;version="[9.1,10.0)" javax.mail;version="1.4.0";resolution:=optional, javax.mail.event;version="1.4.0";resolution:=optional, From e252b52eb349eb010b62016b82739bdd8e06c49f Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Sat, 12 Apr 2014 09:11:04 +1000 Subject: [PATCH 037/135] 432483 make osgi.serviceloader support for javax.servlet.ServletContainerInitializer optional (cherry picked from commit 31043d25708edbea9ef31948093f4eaf2247919b) Conflicts: jetty-annotations/pom.xml --- jetty-annotations/pom.xml | 4 ++-- jetty-osgi/jetty-osgi-boot/pom.xml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/jetty-annotations/pom.xml b/jetty-annotations/pom.xml index 80ba3aa246a..bb6d838321c 100644 --- a/jetty-annotations/pom.xml +++ b/jetty-annotations/pom.xml @@ -43,8 +43,8 @@ - javax.servlet.*;version="[2.6.0,3.2)",* - osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)" + javax.servlet.*;version="[2.6.0,3.2)",org.objectweb.asm.*;version=4,* + osgi.serviceloader; filter:="(osgi.serviceloader=javax.servlet.ServletContainerInitializer)";resolution:=optional;cardinality:=multiple, osgi.extender; filter:="(osgi.extender=osgi.serviceloader.processor)" diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 0c1dbf56790..a52ebfabcd2 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -102,7 +102,6 @@ org.eclipse.jetty.osgi.boot;singleton:=true org.eclipse.jetty.osgi.boot.JettyBootstrapActivator - osgi.serviceloader; osgi.serviceloader=javax.servlet.ServletContainerInitializer org.eclipse.jetty.*;version="[9.1,10.0)" javax.mail;version="1.4.0";resolution:=optional, javax.mail.event;version="1.4.0";resolution:=optional, From d6e538a9d2cd4a53d4a5d23e707d388de5f54000 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 15 Apr 2014 08:49:58 +0200 Subject: [PATCH 038/135] Avoid duplicating existing headers when copying requests. --- .../org/eclipse/jetty/client/HttpClient.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 79c59629ba7..15cd719d983 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -391,26 +391,29 @@ public class HttpClient extends ContainerLifeCycle .idleTimeout(oldRequest.getIdleTimeout(), TimeUnit.MILLISECONDS) .timeout(oldRequest.getTimeout(), TimeUnit.MILLISECONDS) .followRedirects(oldRequest.isFollowRedirects()); - for (HttpField header : oldRequest.getHeaders()) + for (HttpField field : oldRequest.getHeaders()) { - // We have a new URI, so skip the host header if present - if (HttpHeader.HOST == header.getHeader()) + HttpHeader header = field.getHeader(); + // We have a new URI, so skip the host header if present. + if (HttpHeader.HOST == header) continue; - // Remove expectation headers - if (HttpHeader.EXPECT == header.getHeader()) + // Remove expectation headers. + if (HttpHeader.EXPECT == header) continue; - // Remove cookies - if (HttpHeader.COOKIE == header.getHeader()) + // Remove cookies. + if (HttpHeader.COOKIE == header) continue; - // Remove authorization headers - if (HttpHeader.AUTHORIZATION == header.getHeader() || - HttpHeader.PROXY_AUTHORIZATION == header.getHeader()) + // Remove authorization headers. + if (HttpHeader.AUTHORIZATION == header || + HttpHeader.PROXY_AUTHORIZATION == header) continue; - newRequest.header(header.getName(), header.getValue()); + String value = field.getValue(); + if (!newRequest.getHeaders().contains(header, value)) + newRequest.header(field.getName(), value); } return newRequest; } From 361d8e5bd21de3f5d92641ec00164f1a334ea4fc Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 15 Apr 2014 09:03:01 +0200 Subject: [PATCH 039/135] 432528 - IllegalStateException when using DeferredContentProvider. Fixed by using the correct idiom for calling the IteratingCallback. --- .../org/eclipse/jetty/client/HttpSender.java | 121 +++++------------- 1 file changed, 32 insertions(+), 89 deletions(-) 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 0e39d1e05ce..d2e26bdafd2 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 @@ -62,7 +62,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener private final AtomicReference requestState = new AtomicReference<>(RequestState.QUEUED); private final AtomicReference senderState = new AtomicReference<>(SenderState.IDLE); private final Callback commitCallback = new CommitCallback(); - private final Callback contentCallback = new ContentCallback(); + private final IteratingCallback contentCallback = new ContentCallback(); private final Callback lastCallback = new LastContentCallback(); private final HttpChannel channel; private volatile HttpContent content; @@ -100,14 +100,7 @@ public abstract class HttpSender implements AsyncContentProvider.Listener if (updateSenderState(current, newSenderState)) { LOG.debug("Deferred content available, {} -> {}", current, newSenderState); - // TODO should just call contentCallback.iterate() here. - HttpContent content = this.content; - if (content.advance()) - sendContent(exchange, content, contentCallback); // TODO old style usage! - else if (content.isConsumed()) - sendContent(exchange, content, lastCallback); - else - throw new IllegalStateException(); + contentCallback.iterate(); return; } break; @@ -456,25 +449,11 @@ public abstract class HttpSender implements AsyncContentProvider.Listener case WAITING: { // We received the 100 Continue, now send the content if any. - HttpContent content = this.content; - // TODO should just call contentCallback.iterate() here. - if (content.advance()) - { - // There is content to send. - if (!updateSenderState(current, SenderState.SENDING)) - throw illegalSenderState(current); - LOG.debug("Proceeding while waiting"); - sendContent(exchange, content, contentCallback); // TODO old style usage! - return; - } - else - { - // No content to send yet - it's deferred. - if (!updateSenderState(current, SenderState.IDLE)) - throw illegalSenderState(current); - LOG.debug("Proceeding deferred"); - return; - } + if (!updateSenderState(current, SenderState.SENDING)) + throw illegalSenderState(current); + LOG.debug("Proceeding while waiting"); + contentCallback.iterate(); + return; } default: { @@ -665,30 +644,8 @@ public abstract class HttpSender implements AsyncContentProvider.Listener { case SENDING: { - // TODO should just call contentCallback.iterate() here. - // We have content to send ? - if (content.advance()) - { - sendContent(exchange, content, contentCallback); // TODO old style usage! - return; - } - else - { - if (content.isConsumed()) - { - sendContent(exchange, content, lastCallback); - return; - } - else - { - if (updateSenderState(current, SenderState.IDLE)) - { - LOG.debug("Waiting for deferred content for {}", request); - return; - } - break; - } - } + contentCallback.iterate(); + return; } case SENDING_WITH_CONTENT: { @@ -745,59 +702,43 @@ public abstract class HttpSender implements AsyncContentProvider.Listener if (exchange == null) return Action.IDLE; - Request request = exchange.getRequest(); HttpContent content = HttpSender.this.content; - - ByteBuffer contentBuffer = content.getContent(); - if (contentBuffer != null) - { - if (!someToContent(request, contentBuffer)) - return Action.IDLE; - } - while (true) { boolean advanced = content.advance(); boolean consumed = content.isConsumed(); + if (LOG.isDebugEnabled()) + LOG.debug("Content {} consumed {} for {}", advanced, consumed, exchange.getRequest()); - SenderState current = senderState.get(); + if (advanced) + { + sendContent(exchange, content, this); + return Action.SCHEDULED; + } + + if (consumed) + { + sendContent(exchange, content, lastCallback); + return Action.IDLE; + } + + SenderState current = HttpSender.this.senderState.get(); switch (current) { case SENDING: { - if (advanced) + if (updateSenderState(current, SenderState.IDLE)) { - // There is more content to send - sendContent(exchange, content, this); - return Action.SCHEDULED; - } - else if (consumed) - { - sendContent(exchange, content, lastCallback); + if (LOG.isDebugEnabled()) + LOG.debug("Content is deferred for {}", exchange.getRequest()); return Action.IDLE; } - else - { - if (updateSenderState(current, SenderState.IDLE)) - { - LOG.debug("Waiting for deferred content for {}", request); - return Action.IDLE; - } - break; - } + break; } case SENDING_WITH_CONTENT: { - if (updateSenderState(current, SenderState.SENDING)) - { - LOG.debug("Deferred content available for {}", request); - if (advanced) - { - sendContent(exchange, content, this); - return Action.SCHEDULED; - } - } - throw illegalSenderState(current); + updateSenderState(current, SenderState.SENDING); + break; } default: { @@ -810,6 +751,8 @@ public abstract class HttpSender implements AsyncContentProvider.Listener @Override public void succeeded() { + ByteBuffer buffer = content.getContent(); + someToContent(getHttpExchange().getRequest(), buffer); content.succeeded(); super.succeeded(); } From cad7d3f5ed9d2652050f710fde0a96af6564795e Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 15 Apr 2014 10:14:03 -0700 Subject: [PATCH 040/135] Attempt to work out websocket close notification issue that simone raised --- .../websocket/common/WebSocketSession.java | 17 +++++------------ .../common/events/AbstractEventDriver.java | 3 --- .../common/io/AbstractWebSocketConnection.java | 18 ++++++++++++------ 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java index e038e4d9109..c9eeedb6f90 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java @@ -342,7 +342,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc { switch (state) { - case CLOSING: + case CLOSED: // notify session listeners for (SessionListener listener : sessionListeners) { @@ -355,18 +355,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc LOG.ignore(t); } } - break; - case CLOSED: + IOState ioState = this.connection.getIOState(); - // The session only cares about abnormal close, as we need to notify - // the endpoint of this close scenario. - if (ioState.wasAbnormalClose()) - { - CloseInfo close = ioState.getCloseInfo(); - LOG.debug("Detected abnormal close: {}", close); - // notify local endpoint - notifyClose(close.getStatusCode(), close.getReason()); - } + CloseInfo close = ioState.getCloseInfo(); + // confirmed close of local endpoint + notifyClose(close.getStatusCode(), close.getReason()); break; case OPEN: // notify session listeners diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java index 46a6612add0..8cc60f06c43 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java @@ -121,9 +121,6 @@ public abstract class AbstractEventDriver implements IncomingFrames, EventDriver CloseFrame closeframe = (CloseFrame)frame; CloseInfo close = new CloseInfo(closeframe,validate); - // notify user websocket pojo - onClose(close); - // process handshake session.getConnection().getIOState().onCloseRemote(close); diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java index 15763393095..c9f98bb9d57 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java @@ -114,16 +114,22 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp public class OnDisconnectCallback implements WriteCallback { + private final boolean outputOnly; + + public OnDisconnectCallback(boolean outputOnly) { + this.outputOnly = outputOnly; + } + @Override public void writeFailed(Throwable x) { - disconnect(); + disconnect(outputOnly); } @Override public void writeSuccess() { - disconnect(); + disconnect(outputOnly); } } @@ -379,18 +385,18 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp { // Fire out a close frame, indicating abnormal shutdown, then disconnect CloseInfo abnormal = new CloseInfo(StatusCode.SHUTDOWN,"Abnormal Close - " + ioState.getCloseInfo().getReason()); - outgoingFrame(abnormal.asFrame(),new OnDisconnectCallback(), BatchMode.OFF); + outgoingFrame(abnormal.asFrame(),new OnDisconnectCallback(false), BatchMode.OFF); } else { // Just disconnect - this.disconnect(); + this.disconnect(false); } break; case CLOSING: CloseInfo close = ioState.getCloseInfo(); - // append close frame - outgoingFrame(close.asFrame(),new OnDisconnectCallback(), BatchMode.OFF); + // reply to close handshake from remote + outgoingFrame(close.asFrame(),new OnDisconnectCallback(true), BatchMode.OFF); default: break; } From db2c6c20b96a45eb59a639246216a8a296153d47 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 15 Apr 2014 10:16:52 -0700 Subject: [PATCH 041/135] Test failure fix --- .../org/eclipse/jetty/websocket/common/WebSocketSession.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java index c9eeedb6f90..460d1d73b54 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java @@ -342,7 +342,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc { switch (state) { - case CLOSED: + case CLOSING: // notify session listeners for (SessionListener listener : sessionListeners) { @@ -355,7 +355,8 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc LOG.ignore(t); } } - + break; + case CLOSED: IOState ioState = this.connection.getIOState(); CloseInfo close = ioState.getCloseInfo(); // confirmed close of local endpoint From 599595f54484208b069e85ec188a4c57e7067167 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 15 Apr 2014 10:31:27 -0700 Subject: [PATCH 042/135] FS utility class cleanup of unused code --- .../main/java/org/eclipse/jetty/start/FS.java | 135 ++---------------- 1 file changed, 9 insertions(+), 126 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java index ce5efb88ed4..29ee31def20 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java @@ -20,131 +20,15 @@ package org.eclipse.jetty.start; import java.io.Closeable; import java.io.File; -import java.io.FileFilter; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; -import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.attribute.FileTime; import java.util.Locale; -import java.util.regex.Pattern; public class FS { - @Deprecated - public static class AllFilter implements FileFilter - { - public static final AllFilter INSTANCE = new AllFilter(); - - @Override - public boolean accept(File pathname) - { - return true; - } - } - - @Deprecated - public static class DirFilter implements FileFilter - { - public static final DirFilter INSTANCE = new DirFilter(); - - @Override - public boolean accept(File path) - { - return path.isDirectory(); - } - } - - @Deprecated - public static class FilenameRegexFilter implements FileFilter - { - private final Pattern pattern; - - public FilenameRegexFilter(String regex) - { - pattern = Pattern.compile(regex,Pattern.CASE_INSENSITIVE); - } - - @Override - public boolean accept(File path) - { - return path.isFile() && pattern.matcher(path.getName()).matches(); - } - } - - @Deprecated - public static class FileNamesFilter implements FileFilter - { - private final String filenames[]; - - public FileNamesFilter(String... names) - { - this.filenames = names; - } - - @Override - public boolean accept(File path) - { - if (!path.isFile()) - { - return false; - } - for (String name : filenames) - { - if (name.equalsIgnoreCase(path.getName())) - { - return true; - } - } - return false; - } - } - - @Deprecated - public static class IniFilter extends FilenameRegexFilter - { - public IniFilter() - { - super("^.*\\.ini$"); - } - } - - @Deprecated - public static class RelativeRegexFilter implements FileFilter - { - private final File baseDir; - private final Pattern pattern; - - public RelativeRegexFilter(File baseDir, Pattern pattern) - { - this.baseDir = baseDir; - this.pattern = pattern; - } - - @Override - public boolean accept(File path) - { - // get relative path - String relativePath = FS.toRelativePath(baseDir,path); - - // see if it matches - return (pattern.matcher(relativePath).matches()); - } - } - - @Deprecated - public static class XmlFilter extends FilenameRegexFilter - { - public XmlFilter() - { - super("^.*\\.xml$"); - } - } - - // Default Link Options - private static final LinkOption[] NO_LINK_OPTIONS = new LinkOption[0]; - public static boolean canReadDirectory(File path) { return (path.exists() && path.isDirectory() && path.canRead()); @@ -152,7 +36,7 @@ public class FS public static boolean canReadDirectory(Path path) { - return Files.exists(path,NO_LINK_OPTIONS) && Files.isDirectory(path,NO_LINK_OPTIONS) && Files.isReadable(path); + return Files.exists(path) && Files.isDirectory(path) && Files.isReadable(path); } public static boolean canReadFile(File path) @@ -162,7 +46,7 @@ public class FS public static boolean canReadFile(Path path) { - return Files.exists(path,NO_LINK_OPTIONS) && Files.isRegularFile(path,NO_LINK_OPTIONS) && Files.isReadable(path); + return Files.exists(path) && Files.isRegularFile(path) && Files.isReadable(path); } public static boolean canWrite(Path path) @@ -190,7 +74,7 @@ public class FS public static boolean createNewFile(Path path) throws IOException { Path ret = Files.createFile(path); - return Files.exists(ret,NO_LINK_OPTIONS); + return Files.exists(ret); } /** @@ -237,11 +121,11 @@ public class FS public static void ensureDirectoryWritable(Path dir) throws IOException { - if (!Files.exists(dir,NO_LINK_OPTIONS)) + if (!Files.exists(dir)) { throw new IOException("Path does not exist: " + dir.toAbsolutePath()); } - if (!Files.isDirectory(dir,NO_LINK_OPTIONS)) + if (!Files.isDirectory(dir)) { throw new IOException("Directory does not exist: " + dir.toAbsolutePath()); } @@ -253,7 +137,7 @@ public class FS public static boolean exists(Path path) { - return Files.exists(path,NO_LINK_OPTIONS); + return Files.exists(path); } public static boolean isFile(File file) @@ -267,14 +151,13 @@ public class FS public static boolean isValidDirectory(Path path) { - LinkOption lopts[] = NO_LINK_OPTIONS; - if (!Files.exists(path,lopts)) + if (!Files.exists(path)) { // doesn't exist, not a valid directory return false; } - if (!Files.isDirectory(path,lopts)) + if (!Files.isDirectory(path)) { // not a directory (as expected) StartLog.warn("Not a directory: " + path); @@ -324,6 +207,6 @@ public class FS public static Path toRealPath(Path path) throws IOException { - return path.toRealPath(NO_LINK_OPTIONS); + return path.toRealPath(); } } From 41ab91988b7770aea374556e08e20bb2ec984135 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 15 Apr 2014 10:32:28 -0700 Subject: [PATCH 043/135] 432321 - jetty-start / Allow defining extra start directories for common configurations + Fixing jetty.base default detection to use user.dir instead of jetty.home --- .../jetty/start/config/CommandLineConfigSource.java | 4 ++-- .../org/eclipse/jetty/start/ConfigurationAssert.java | 3 ++- .../src/test/java/org/eclipse/jetty/start/MainTest.java | 9 ++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java b/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java index df6db187c18..61f9f643f13 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/config/CommandLineConfigSource.java @@ -81,8 +81,8 @@ public class CommandLineConfigSource implements ConfigSource return FS.toPath(val); } - // Lastly, fall back to base == home - Path base = this.homePath.toAbsolutePath(); + // Lastly, fall back to base == ${user.dir} + Path base = FS.toPath(this.props.getString("user.dir",".")); setProperty(BaseHome.JETTY_BASE,base.toString(),ORIGIN_INTERNAL_FALLBACK); return base; } diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java index dcd9baf221d..36c11743cb6 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java @@ -100,7 +100,8 @@ public class ConfigurationAssert for (Prop prop : args.getProperties()) { String name = prop.key; - if ("jetty.home".equals(name) || "jetty.base".equals(name) || prop.origin.equals(Props.ORIGIN_SYSPROP)) + if ("jetty.home".equals(name) || "jetty.base".equals(name) || + "user.dir".equals(name) || prop.origin.equals(Props.ORIGIN_SYSPROP)) { // strip these out from assertion, to make assertions easier. continue; diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java index 561a013dfa9..a8003c3bdf7 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java @@ -43,7 +43,8 @@ public class MainTest public void testBasicProcessing() throws Exception { List cmdLineArgs = new ArrayList<>(); - File testJettyHome = MavenTestingUtils.getTestResourceDir("usecases/home"); + File testJettyHome = MavenTestingUtils.getTestResourceDir("usecases/home").getAbsoluteFile(); + cmdLineArgs.add("user.dir=" + testJettyHome); cmdLineArgs.add("jetty.home=" + testJettyHome); cmdLineArgs.add("jetty.port=9090"); @@ -103,8 +104,9 @@ public class MainTest { List cmdLineArgs = new ArrayList<>(); - File homePath = MavenTestingUtils.getTestResourceDir("usecases/home"); + File homePath = MavenTestingUtils.getTestResourceDir("usecases/home").getAbsoluteFile(); cmdLineArgs.add("jetty.home=" + homePath); + cmdLineArgs.add("user.dir=" + homePath); // JVM args cmdLineArgs.add("--exec"); @@ -137,7 +139,8 @@ public class MainTest { List cmdLineArgs = new ArrayList<>(); - File homePath = MavenTestingUtils.getTestResourceDir("jetty home with spaces"); + File homePath = MavenTestingUtils.getTestResourceDir("jetty home with spaces").getAbsoluteFile(); + cmdLineArgs.add("user.dir=" + homePath); cmdLineArgs.add("jetty.home=" + homePath); Main main = new Main(); From dc3362ba3c1dded62f8434d1418f0c6bbcc8ef17 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 15 Apr 2014 12:22:53 -0700 Subject: [PATCH 044/135] Removing deprecated methods --- .../main/java/org/eclipse/jetty/start/FS.java | 56 ------------------- .../org/eclipse/jetty/start/StartIni.java | 10 +--- .../org/eclipse/jetty/start/TextFile.java | 42 +++++++------- .../jetty/start/ConfigurationAssert.java | 2 +- .../java/org/eclipse/jetty/start/FSTest.java | 6 +- 5 files changed, 26 insertions(+), 90 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java index 29ee31def20..9b55b45c0ae 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/FS.java @@ -29,21 +29,11 @@ import java.util.Locale; public class FS { - public static boolean canReadDirectory(File path) - { - return (path.exists() && path.isDirectory() && path.canRead()); - } - public static boolean canReadDirectory(Path path) { return Files.exists(path) && Files.isDirectory(path) && Files.isReadable(path); } - public static boolean canReadFile(File path) - { - return (path.exists() && path.isFile() && path.canRead()); - } - public static boolean canReadFile(Path path) { return Files.exists(path) && Files.isRegularFile(path) && Files.isReadable(path); @@ -77,22 +67,6 @@ public class FS return Files.exists(ret); } - /** - * @deprecated use {@link #ensureDirectoryExists(Path)} instead - */ - @Deprecated - public static void ensureDirectoryExists(File dir) throws IOException - { - if (dir.exists()) - { - return; - } - if (!dir.mkdirs()) - { - throw new IOException("Unable to create directory: " + dir.getAbsolutePath()); - } - } - public static void ensureDirectoryExists(Path dir) throws IOException { if (exists(dir)) @@ -103,22 +77,6 @@ public class FS Files.createDirectories(dir); } - /** - * @deprecated use {@link #ensureDirectoryWritable(Path)} instead - */ - @Deprecated - public static void ensureDirectoryWritable(File dir) throws IOException - { - if (!dir.exists()) - { - throw new IOException("Directory does not exist: " + dir.getAbsolutePath()); - } - if (!dir.canWrite()) - { - throw new IOException("Unable to write to directory: " + dir.getAbsolutePath()); - } - } - public static void ensureDirectoryWritable(Path dir) throws IOException { if (!Files.exists(dir)) @@ -140,15 +98,6 @@ public class FS return Files.exists(path); } - public static boolean isFile(File file) - { - if (file == null) - { - return false; - } - return file.exists() && file.isFile(); - } - public static boolean isValidDirectory(Path path) { if (!Files.exists(path)) @@ -194,11 +143,6 @@ public class FS return FileSystems.getDefault().getPath(FS.separators(path)); } - public static String toRelativePath(File baseDir, File path) - { - return baseDir.toURI().relativize(path.toURI()).toASCIIString(); - } - public static void touch(Path path) throws IOException { FileTime now = FileTime.fromMillis(System.currentTimeMillis()); diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java index 79b079cd712..524f958bd42 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartIni.java @@ -18,7 +18,6 @@ package org.eclipse.jetty.start; -import java.io.File; import java.io.IOException; import java.nio.file.Path; @@ -29,16 +28,11 @@ public class StartIni extends TextFile { private Path basedir; - public StartIni(File file) throws IOException + public StartIni(Path file) throws IOException { super(file); } - public StartIni(Path path) throws IOException - { - this(path.toFile()); - } - @Override public void addUniqueLine(String line) { @@ -70,7 +64,7 @@ public class StartIni extends TextFile @Override public void init() { - basedir = getFile().getParentFile().toPath().toAbsolutePath(); + basedir = getFile().getParent().toAbsolutePath(); } public Path getBaseDir() diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/TextFile.java b/jetty-start/src/main/java/org/eclipse/jetty/start/TextFile.java index fccc76209a9..862422f94f7 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/TextFile.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/TextFile.java @@ -19,10 +19,11 @@ package org.eclipse.jetty.start; import java.io.BufferedReader; -import java.io.File; import java.io.FileNotFoundException; -import java.io.FileReader; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -36,40 +37,37 @@ import java.util.regex.Pattern; */ public class TextFile implements Iterable { - private final File file; + private final Path file; private final List lines = new ArrayList<>(); - public TextFile(File file) throws FileNotFoundException, IOException + public TextFile(Path file) throws FileNotFoundException, IOException { this.file = file; init(); if (!FS.canReadFile(file)) { - StartLog.debug("Skipping read of missing file: %s",file.getAbsolutePath()); + StartLog.debug("Skipping read of missing file: %s",file.toAbsolutePath()); return; } - try (FileReader reader = new FileReader(file)) + try (BufferedReader buf = Files.newBufferedReader(file,StandardCharsets.UTF_8)) { - try (BufferedReader buf = new BufferedReader(reader)) + String line; + while ((line = buf.readLine()) != null) { - String line; - while ((line = buf.readLine()) != null) + if (line.length() == 0) { - if (line.length() == 0) - { - continue; - } - - if (line.charAt(0) == '#') - { - continue; - } - - // TODO - bad form calling derived method from base class constructor - process(line.trim()); + continue; } + + if (line.charAt(0) == '#') + { + continue; + } + + // TODO - bad form calling derived method from base class constructor + process(line.trim()); } } } @@ -84,7 +82,7 @@ public class TextFile implements Iterable lines.add(line); } - public File getFile() + public Path getFile() { return file; } diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java index 36c11743cb6..ced1889b5c8 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/ConfigurationAssert.java @@ -53,7 +53,7 @@ public class ConfigurationAssert { File testResourcesDir = MavenTestingUtils.getTestResourcesDir(); File file = MavenTestingUtils.getTestResourceFile(filename); - TextFile textFile = new TextFile(file); + TextFile textFile = new TextFile(file.toPath()); // Validate XMLs (order is important) List expectedXmls = new ArrayList<>(); diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/FSTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/FSTest.java index e44fb5cbe65..4061851b187 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/FSTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/FSTest.java @@ -31,21 +31,21 @@ public class FSTest public void testCanReadDirectory() { File targetDir = MavenTestingUtils.getTargetDir(); - Assert.assertTrue("Can read dir: " + targetDir,FS.canReadDirectory(targetDir)); + Assert.assertTrue("Can read dir: " + targetDir,FS.canReadDirectory(targetDir.toPath())); } @Test public void testCanReadDirectory_NotDir() { File bogusFile = MavenTestingUtils.getTestResourceFile("bogus.xml"); - Assert.assertFalse("Can read dir: " + bogusFile,FS.canReadDirectory(bogusFile)); + Assert.assertFalse("Can read dir: " + bogusFile,FS.canReadDirectory(bogusFile.toPath())); } @Test public void testCanReadFile() { File pom = MavenTestingUtils.getProjectFile("pom.xml"); - Assert.assertTrue("Can read file: " + pom,FS.canReadFile(pom)); + Assert.assertTrue("Can read file: " + pom,FS.canReadFile(pom.toPath())); } /** From e5314d4a3e0df85a786bcddb3927a0793f32f52e Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 15 Apr 2014 13:40:16 -0700 Subject: [PATCH 045/135] 432321 - jetty-start / Allow defining extra start directories for common configurations + Adding Config Search order output to --list-config --- .../java/org/eclipse/jetty/start/Main.java | 2 +- .../org/eclipse/jetty/start/StartArgs.java | 27 ++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java index 97734a91ffd..9e46100f912 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java @@ -307,7 +307,7 @@ public class Main public void listConfig(StartArgs args) { // Dump Jetty Home / Base - args.dumpEnvironment(); + args.dumpEnvironment(baseHome); // Dump JVM Args args.dumpJvmArgs(); diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java index 34b5aa3458b..cde9f562f36 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java @@ -37,6 +37,7 @@ import java.util.StringTokenizer; import org.eclipse.jetty.start.Props.Prop; import org.eclipse.jetty.start.config.ConfigSource; import org.eclipse.jetty.start.config.ConfigSources; +import org.eclipse.jetty.start.config.DirConfigSource; /** * The Arguments required to start Jetty. @@ -166,7 +167,7 @@ public class StartArgs } } - public void dumpEnvironment() + public void dumpEnvironment(BaseHome baseHome) { // Java Details System.out.println(); @@ -188,10 +189,30 @@ public class StartArgs System.out.println(); System.out.println("Jetty Environment:"); System.out.println("-----------------"); - + dumpProperty("jetty.version"); dumpProperty("jetty.home"); dumpProperty("jetty.base"); - dumpProperty("jetty.version"); + + // Jetty Configuration Environment + System.out.println(); + System.out.println("Config Search Order:"); + System.out.println("--------------------"); + for (ConfigSource config : baseHome.getConfigSources()) + { + System.out.printf(" %s",config.getId()); + if (config instanceof DirConfigSource) + { + DirConfigSource dirsource = (DirConfigSource)config; + if (dirsource.isPropertyBased()) + { + System.out.printf(" -> %s",dirsource.getDir()); + } + } + System.out.println(); + } + + // Jetty Se + System.out.println(); } public void dumpJvmArgs() From 348cd406c8cda54a1c6d1934dbb1b6138a92cbe0 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 15 Apr 2014 14:54:19 -0700 Subject: [PATCH 046/135] Removing deprecated methods --- .../org/eclipse/jetty/start/BaseHome.java | 60 +++---------------- 1 file changed, 7 insertions(+), 53 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java index 35485ad7c61..70ca6bebd56 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/BaseHome.java @@ -183,34 +183,18 @@ public class BaseHome return baseDir.toString(); } - /** - * @deprecated use {@link #getBasePath()} - */ - @Deprecated - public File getBaseDir() - { - return baseDir.toFile(); - } - - /** - * Create a file reference to some content in "${jetty.base}" - * - * @param path - * the path to reference - * @return the file reference - * @deprecated use {@link #getBasePath(String)} - */ - @Deprecated - public File getBaseFile(String path) - { - return baseDir.resolve(path).toFile(); - } - public Path getBasePath() { return baseDir; } + /** + * Create a {@link Path} reference to some content in "${jetty.base}" + * + * @param path + * the path to reference + * @return the file reference + */ public Path getBasePath(String path) { return baseDir.resolve(path); @@ -221,41 +205,11 @@ public class BaseHome return this.sources; } - /** - * Get a specific file reference. - *

- * File references go through 3 possibly scenarios. - *

    - *
  1. If exists relative to ${jetty.base}, return that reference
  2. - *
  3. If exists relative to ${jetty.home}, return that reference
  4. - *
  5. Otherwise return absolute path reference (standard java logic)
  6. - *
- * - * @param path - * the path to get. - * @return the file reference. - * @deprecated use {@link #getPath(String)} - */ - @Deprecated - public File getFile(String path) - { - return getPath(path).toAbsolutePath().toFile(); - } - public String getHome() { return homeDir.toString(); } - /** - * @deprecated use {@link #getHomePath()} - */ - @Deprecated - public File getHomeDir() - { - return homeDir.toFile(); - } - public Path getHomePath() { return homeDir; From 40c82a99abf5cd336a66699b3f9297674450b762 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 16 Apr 2014 11:06:34 +0200 Subject: [PATCH 047/135] Added profile in the main pom.xml to build with JDK 7u55. Added new NPN module files for JDK 7u55, referencing the new NPN version required by JDK 7u55. --- .../src/main/config/modules/npn/npn-1.7.0_55.mod | 9 +++++++++ .../usecases/home/modules/npn/npn-1.7.0_55.mod | 9 +++++++++ pom.xml | 12 ++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 jetty-spdy/spdy-http-server/src/main/config/modules/npn/npn-1.7.0_55.mod create mode 100644 jetty-start/src/test/resources/usecases/home/modules/npn/npn-1.7.0_55.mod diff --git a/jetty-spdy/spdy-http-server/src/main/config/modules/npn/npn-1.7.0_55.mod b/jetty-spdy/spdy-http-server/src/main/config/modules/npn/npn-1.7.0_55.mod new file mode 100644 index 00000000000..06387a2bbf6 --- /dev/null +++ b/jetty-spdy/spdy-http-server/src/main/config/modules/npn/npn-1.7.0_55.mod @@ -0,0 +1,9 @@ +[name] +npn-boot + +[files] +http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.7.v20140316/npn-boot-1.1.7.v20140316.jar:lib/npn/npn-boot-1.1.7.v20140316.jar + +[ini-template] +--exec +-Xbootclasspath/p:lib/npn/npn-boot-1.1.7.v20140316.jar diff --git a/jetty-start/src/test/resources/usecases/home/modules/npn/npn-1.7.0_55.mod b/jetty-start/src/test/resources/usecases/home/modules/npn/npn-1.7.0_55.mod new file mode 100644 index 00000000000..06387a2bbf6 --- /dev/null +++ b/jetty-start/src/test/resources/usecases/home/modules/npn/npn-1.7.0_55.mod @@ -0,0 +1,9 @@ +[name] +npn-boot + +[files] +http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.7.v20140316/npn-boot-1.1.7.v20140316.jar:lib/npn/npn-boot-1.1.7.v20140316.jar + +[ini-template] +--exec +-Xbootclasspath/p:lib/npn/npn-boot-1.1.7.v20140316.jar diff --git a/pom.xml b/pom.xml index b8ef433c27b..e0b06dd210c 100644 --- a/pom.xml +++ b/pom.xml @@ -936,6 +936,18 @@ 1.1.6.v20130911 + + 7u55 + + + java.version + 1.7.0_55 + + + + 1.1.7.v20140316 + + From c3d3edd6c0b22bfaaa249c3d4f03186a81e458e3 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 16 Apr 2014 17:33:22 +0200 Subject: [PATCH 048/135] 432777 - Async Write Loses Data with HTTPS Server. Fixed by properly flipping the aggregate buffer when copying bytes to it. --- .../org/eclipse/jetty/server/HttpOutput.java | 3 +- .../jetty/servlet/SSLAsyncIOServletTest.java | 179 ++++++++++++++++++ jetty-servlet/src/test/resources/keystore.jks | Bin 0 -> 2206 bytes .../src/test/resources/truststore.jks | Bin 0 -> 916 bytes 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SSLAsyncIOServletTest.java create mode 100644 jetty-servlet/src/test/resources/keystore.jks create mode 100644 jetty-servlet/src/test/resources/truststore.jks 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 3379981c827..610db58cbf9 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 @@ -24,7 +24,6 @@ import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritePendingException; import java.util.concurrent.atomic.AtomicReference; - import javax.servlet.RequestDispatcher; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; @@ -885,7 +884,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable // Can we just aggregate the remainder? if (!_complete && _len parameters() + { + return Arrays.asList(new SslContextFactory[]{null}, new SslContextFactory[]{new SslContextFactory()}); + } + + private Server server; + private ServerConnector connector; + private SslContextFactory sslContextFactory; + private String contextPath; + private String servletPath; + + public SSLAsyncIOServletTest(SslContextFactory sslContextFactory) + { + this.sslContextFactory = sslContextFactory; + if (sslContextFactory != null) + { + sslContextFactory.setEndpointIdentificationAlgorithm(""); + sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks"); + sslContextFactory.setTrustStorePassword("storepwd"); + } + } + + public void prepare(HttpServlet servlet) throws Exception + { + server = new Server(); + + connector = new ServerConnector(server, sslContextFactory); + server.addConnector(connector); + + contextPath = "/context"; + ServletContextHandler context = new ServletContextHandler(server, contextPath, true, false); + servletPath = "/servlet"; + context.addServlet(new ServletHolder(servlet), servletPath); + + server.start(); + } + + @After + public void dispose() throws Exception + { + server.stop(); + } + + @Test + public void testAsyncIOWritesWithAggregation() throws Exception + { + Random random = new Random(); + String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + final byte[] content = new byte[50000]; + for (int i = 0; i < content.length; ++i) + content[i] = (byte)chars.charAt(random.nextInt(chars.length())); + + prepare(new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + final AsyncContext asyncContext = request.startAsync(); + asyncContext.setTimeout(0); + final int bufferSize = 4096; + response.setBufferSize(bufferSize); + response.getOutputStream().setWriteListener(new WriteListener() + { + private int writes; + private int written; + + @Override + public void onWritePossible() throws IOException + { + ServletOutputStream output = asyncContext.getResponse().getOutputStream(); + do + { + int toWrite = content.length - written; + if (toWrite == 0) + { + asyncContext.complete(); + return; + } + + toWrite = Math.min(toWrite, bufferSize); + + // Perform a write that is smaller than the buffer size to + // trigger the condition where the bytes are aggregated. + if (writes == 1) + toWrite -= 16; + + output.write(content, written, toWrite); + ++writes; + written += toWrite; + } + while (output.isReady()); + } + + @Override + public void onError(Throwable t) + { + asyncContext.complete(); + } + }); + } + }); + + try (Socket client = newClient()) + { + String request = "" + + "GET " + contextPath + servletPath + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + OutputStream output = client.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + BufferedReader input = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); + SimpleHttpParser parser = new SimpleHttpParser(); + SimpleHttpResponse response = parser.readResponse(input); + Assert.assertEquals("200", response.getCode()); + Assert.assertArrayEquals(content, response.getBody().getBytes("UTF-8")); + } + } + + private Socket newClient() throws IOException + { + return sslContextFactory == null ? new Socket("localhost", connector.getLocalPort()) + : sslContextFactory.getSslContext().getSocketFactory().createSocket("localhost", connector.getLocalPort()); + } +} diff --git a/jetty-servlet/src/test/resources/keystore.jks b/jetty-servlet/src/test/resources/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..428ba54776ede2fdcdeedd879edb927c2abd9953 GIT binary patch literal 2206 zcmcgt`9Bkm8{cNkoMUp6gmShKn!AQX*(l6Nj(i=TnQPOKYtv{*Wg>ItE=Q!pRYH8a z$Sp#S#2lYw#aw;$y9u4T}83H*%lp zAKZay0sy=q1Qoo85aAQh;$ zD(c2EIN#D7WwYDLKUg!CotQPD@dp;5FR#bgaace(^x$6g5frD~(_b(MI^J&*A2DRp zf5Q2onfE(zvUb9|9C`66)YFRNM6~xrz4;iVbU=P|*YT2eWHFJJtr+M@zt2qPm)K~rRcqcs=LM12)PX0TT%QO zlf*xkqD3}7l)1J`5W(>=9nR0e6j-<79<11v3ZuXXcQpoCsqY~n`$FN+S}hcVm5Y>G zXnD{@DYs1@{S0z(lW+?86LWKtku$$-(khsh>0qRUXn=84`GRn?77M^_JY`durnN;KE zW#OJ`h<6xcB{I))ekGpc*Ylt}0cx4|OMBDPQvx4`r`}4Ze5_ipdObGMTi3bZHd5PC zcY0;?uBWu$PSvjJeb87nY7ghNv?%M@SoDl6IWt`bQCosfSh$#D6$ea~QhKM^ud2Ut z+9PYJuVpoELmN-A`F$BicO{BSYg@#tS%avVfb}DxL)|NanJ)#zB!2~?#Ot%H7--9N zU$bs0fS5G!m5M4&WK3#a|H|Tgw*?X-;H+Lu@kwA>qSR~7UC7b)7MJXTn6PG>n@8jP zW+}F^X$$c;U~4ryqRF; z>`j!tbLMK4ZGyY643|~?%Mu#fm!l%wAKjBDmd+VYmp3S#$scD$~bxbf|z#)hShN0*AhRaPDcmqrftGlHq4^54MM$Xfy(2> zH8QYVMzmn_oHbvJCB`IN~E&{1*h&0gEM{e zKvWvzp(!BqMX8`t#)~0nq}Wa zr6>FRPyp;AAB&)1$5@;r$23J{K&~>TWjZf7V$wFzmGM95CXhFG1cJNVAXks}C+&2- zbf9Qn*D8N}Afd2kpwDxns3%1uaFhAqDV8ksWiWY|quuLGZ0)SqrJ!Y8yX}@}IyC$C zQ3rCUsn}#>F#D8%D?q~ySy4j&he%Bs{{7V%rl!ui`@KQP?NTi+_iN{cwom&9RaMRR zB~z!hz|0HAgB9_Ijvpe-zr#jLbckJsc>vmo{+im?t8lA;N#fD4?{lb&J0V8Gocq%; f1ihv=QIDh{M_<9V+45Z2{KE4_qW}V3B0uV%GgrOJ literal 0 HcmV?d00001 diff --git a/jetty-servlet/src/test/resources/truststore.jks b/jetty-servlet/src/test/resources/truststore.jks new file mode 100644 index 0000000000000000000000000000000000000000..839cb8c35151c2b7c64afca24b6b72caad070a05 GIT binary patch literal 916 zcmezO_TO6u1_mY|W(3o$xs} zE~X|%Muz1J{3AIFGbaABoD&*5saD@gH|APIn|qhRGl}gsUzm=o9G*UXZaLfkb^*)o zjA*-gTf)`m_MQJYE&gJ}p^PHkrj!4^W|XX5a=N7A{;n#yaON&k_bHloe-^*hm?Z91 zlB>xeD=<(C>yn{9D54u}krkl}HQ(Uscha(++qf!T9y+xaEfnXd1O zi0)T?voO%;QH9LK;*_O3mBblqm)!31vU@hm;^%>mh5U@y3R%l0gzi`2yxH!+?kPOi zt!Tnsz1x9B3U2~8STZp)GB6^C5HPs_Lx_=~O<3xi>MmQ;D_g$D<_pdct`+TyzWTQ= zW5Finm(sGEe;ty^>vg$!cV)t>;H#Mev23$*WWBpyJ}Ir;RW+Htrt6{Pk&qz&-XG2@ z8@{&Lu%DX7m47Uny+-3w`=4V611q#Ub(U`xZCtSK^2LO^3(s|HW&N14dV4@A&(kX% z*S_eUPs-bSWRp>avt;CP@7K+G&3=b&1eO-s3f`;Cf91p#$)FW&xME3L8sEBQQDVCvfG>mdwqnk+GXd2ihXqpv z;usF(WoYYmu8DZZa4%1z=+hI+*gpkUykAy5tj#grb*gH!M6TqIcifYBGVe^&T#-2O K*=+x>r_BKeJV|!| literal 0 HcmV?d00001 From 7c8c45c3979ad790adbc8cd6c70b37b290c6f8d9 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 17 Apr 2014 13:27:34 +0200 Subject: [PATCH 049/135] 432993 - Improve handling of ProxyTo and Prefix parameters in ProxyServlet.Transparent. Fixed case of empty context path and missing Prefix parameter. --- .../org/eclipse/jetty/proxy/ProxyServlet.java | 33 ++++++++------- .../eclipse/jetty/proxy/ProxyServletTest.java | 42 +++++++++++++++++-- 2 files changed, 55 insertions(+), 20 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 065937c1eb7..09217f0dfe5 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 @@ -597,15 +597,14 @@ public class ProxyServlet extends HttpServlet } /** - * Transparent Proxy. - *

- * This convenience extension to ProxyServlet configures the servlet as a transparent proxy. - * The servlet is configured with init parameters: + * This convenience extension to {@link ProxyServlet} configures the servlet as a transparent proxy. + * This servlet is configured with the following init parameters: *

    - *
  • proxyTo - a URI like http://host:80/context to which the request is proxied. - *
  • prefix - a URI prefix that is striped from the start of the forwarded URI. + *
  • proxyTo - a mandatory URI like http://host:80/context to which the request is proxied.
  • + *
  • prefix - an optional URI prefix that is stripped from the start of the forwarded URI.
  • *
- * For example, if a request is received at /foo/bar and the 'proxyTo' parameter is "http://host:80/context" + *

+ * For example, if a request is received at "/foo/bar", the 'proxyTo' parameter is "http://host:80/context" * and the 'prefix' parameter is "/foo", then the request would be proxied to "http://host:80/context/bar". */ public static class Transparent extends ProxyServlet @@ -630,21 +629,23 @@ public class ProxyServlet extends HttpServlet ServletConfig config = getServletConfig(); - String prefix = config.getInitParameter("prefix"); - _prefix = prefix == null ? _prefix : prefix; - - // Adjust prefix value to account for context path - String contextPath = getServletContext().getContextPath(); - _prefix = _prefix == null ? contextPath : (contextPath + _prefix); - String proxyTo = config.getInitParameter("proxyTo"); _proxyTo = proxyTo == null ? _proxyTo : proxyTo; if (_proxyTo == null) throw new UnavailableException("Init parameter 'proxyTo' is required."); - if (!_prefix.startsWith("/")) - throw new UnavailableException("Init parameter 'prefix' parameter must start with a '/'."); + String prefix = config.getInitParameter("prefix"); + if (prefix != null) + { + if (!prefix.startsWith("/")) + throw new UnavailableException("Init parameter 'prefix' must start with a '/'."); + _prefix = prefix; + } + + // Adjust prefix value to account for context path + String contextPath = getServletContext().getContextPath(); + _prefix = _prefix == null ? contextPath : (contextPath + _prefix); _log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo); } diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java index ec32b8b63f0..7c384b05238 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java @@ -18,8 +18,6 @@ package org.eclipse.jetty.proxy; -import static java.nio.file.StandardOpenOption.CREATE; - import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -30,6 +28,7 @@ import java.net.HttpCookie; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -39,7 +38,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.zip.GZIPOutputStream; - import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; @@ -93,6 +91,11 @@ public class ProxyServletTest private ServerConnector serverConnector; private void prepareProxy(ProxyServlet proxyServlet) throws Exception + { + prepareProxy(proxyServlet, new HashMap()); + } + + private void prepareProxy(ProxyServlet proxyServlet, Map initParams) throws Exception { proxy = new Server(); proxyConnector = new ServerConnector(proxy); @@ -101,6 +104,7 @@ public class ProxyServletTest ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false); this.proxyServlet = proxyServlet; ServletHolder proxyServletHolder = new ServletHolder(proxyServlet); + proxyServletHolder.setInitParameters(initParams); proxyCtx.addServlet(proxyServletHolder, "/*"); proxy.start(); @@ -379,7 +383,7 @@ public class ProxyServletTest final Path temp = Files.createTempFile(targetTestsDir, "test_", null); byte[] kb = new byte[1024]; new Random().nextBytes(kb); - try (OutputStream output = Files.newOutputStream(temp, CREATE)) + try (OutputStream output = Files.newOutputStream(temp, StandardOpenOption.CREATE)) { for (int i = 0; i < length; ++i) output.write(kb); @@ -735,6 +739,36 @@ public class ProxyServletTest Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER)); } + @Test + public void testTransparentProxyWithoutPrefix() throws Exception + { + final String target = "/test"; + prepareServer(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + if (req.getHeader("Via") != null) + resp.addHeader(PROXIED_HEADER, "true"); + resp.setStatus(target.equals(req.getRequestURI()) ? 200 : 404); + } + }); + + final String proxyTo = "http://localhost:" + serverConnector.getLocalPort(); + ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(); + Map initParams = new HashMap<>(); + initParams.put("proxyTo", proxyTo); + prepareProxy(proxyServlet, initParams); + + // Make the request to the proxy, it should transparently forward to the server + ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()) + .path(target) + .timeout(5, TimeUnit.SECONDS) + .send(); + Assert.assertEquals(200, response.getStatus()); + Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER)); + } + @Test public void testCachingProxy() throws Exception { From d90e6210bbd438c9707fba4dbc1b46e433fbfd27 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 16 Apr 2014 17:33:22 +0200 Subject: [PATCH 050/135] 432777 - Async Write Loses Data with HTTPS Server. Fixed by properly flipping the aggregate buffer when copying bytes to it. --- .../org/eclipse/jetty/server/HttpOutput.java | 3 +- .../jetty/servlet/SSLAsyncIOServletTest.java | 179 ++++++++++++++++++ jetty-servlet/src/test/resources/keystore.jks | Bin 0 -> 2206 bytes .../src/test/resources/truststore.jks | Bin 0 -> 916 bytes 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 jetty-servlet/src/test/java/org/eclipse/jetty/servlet/SSLAsyncIOServletTest.java create mode 100644 jetty-servlet/src/test/resources/keystore.jks create mode 100644 jetty-servlet/src/test/resources/truststore.jks 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 3379981c827..610db58cbf9 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 @@ -24,7 +24,6 @@ import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritePendingException; import java.util.concurrent.atomic.AtomicReference; - import javax.servlet.RequestDispatcher; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; @@ -885,7 +884,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable // Can we just aggregate the remainder? if (!_complete && _len parameters() + { + return Arrays.asList(new SslContextFactory[]{null}, new SslContextFactory[]{new SslContextFactory()}); + } + + private Server server; + private ServerConnector connector; + private SslContextFactory sslContextFactory; + private String contextPath; + private String servletPath; + + public SSLAsyncIOServletTest(SslContextFactory sslContextFactory) + { + this.sslContextFactory = sslContextFactory; + if (sslContextFactory != null) + { + sslContextFactory.setEndpointIdentificationAlgorithm(""); + sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks"); + sslContextFactory.setTrustStorePassword("storepwd"); + } + } + + public void prepare(HttpServlet servlet) throws Exception + { + server = new Server(); + + connector = new ServerConnector(server, sslContextFactory); + server.addConnector(connector); + + contextPath = "/context"; + ServletContextHandler context = new ServletContextHandler(server, contextPath, true, false); + servletPath = "/servlet"; + context.addServlet(new ServletHolder(servlet), servletPath); + + server.start(); + } + + @After + public void dispose() throws Exception + { + server.stop(); + } + + @Test + public void testAsyncIOWritesWithAggregation() throws Exception + { + Random random = new Random(); + String chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + final byte[] content = new byte[50000]; + for (int i = 0; i < content.length; ++i) + content[i] = (byte)chars.charAt(random.nextInt(chars.length())); + + prepare(new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + final AsyncContext asyncContext = request.startAsync(); + asyncContext.setTimeout(0); + final int bufferSize = 4096; + response.setBufferSize(bufferSize); + response.getOutputStream().setWriteListener(new WriteListener() + { + private int writes; + private int written; + + @Override + public void onWritePossible() throws IOException + { + ServletOutputStream output = asyncContext.getResponse().getOutputStream(); + do + { + int toWrite = content.length - written; + if (toWrite == 0) + { + asyncContext.complete(); + return; + } + + toWrite = Math.min(toWrite, bufferSize); + + // Perform a write that is smaller than the buffer size to + // trigger the condition where the bytes are aggregated. + if (writes == 1) + toWrite -= 16; + + output.write(content, written, toWrite); + ++writes; + written += toWrite; + } + while (output.isReady()); + } + + @Override + public void onError(Throwable t) + { + asyncContext.complete(); + } + }); + } + }); + + try (Socket client = newClient()) + { + String request = "" + + "GET " + contextPath + servletPath + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "\r\n"; + OutputStream output = client.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + BufferedReader input = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8")); + SimpleHttpParser parser = new SimpleHttpParser(); + SimpleHttpResponse response = parser.readResponse(input); + Assert.assertEquals("200", response.getCode()); + Assert.assertArrayEquals(content, response.getBody().getBytes("UTF-8")); + } + } + + private Socket newClient() throws IOException + { + return sslContextFactory == null ? new Socket("localhost", connector.getLocalPort()) + : sslContextFactory.getSslContext().getSocketFactory().createSocket("localhost", connector.getLocalPort()); + } +} diff --git a/jetty-servlet/src/test/resources/keystore.jks b/jetty-servlet/src/test/resources/keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..428ba54776ede2fdcdeedd879edb927c2abd9953 GIT binary patch literal 2206 zcmcgt`9Bkm8{cNkoMUp6gmShKn!AQX*(l6Nj(i=TnQPOKYtv{*Wg>ItE=Q!pRYH8a z$Sp#S#2lYw#aw;$y9u4T}83H*%lp zAKZay0sy=q1Qoo85aAQh;$ zD(c2EIN#D7WwYDLKUg!CotQPD@dp;5FR#bgaace(^x$6g5frD~(_b(MI^J&*A2DRp zf5Q2onfE(zvUb9|9C`66)YFRNM6~xrz4;iVbU=P|*YT2eWHFJJtr+M@zt2qPm)K~rRcqcs=LM12)PX0TT%QO zlf*xkqD3}7l)1J`5W(>=9nR0e6j-<79<11v3ZuXXcQpoCsqY~n`$FN+S}hcVm5Y>G zXnD{@DYs1@{S0z(lW+?86LWKtku$$-(khsh>0qRUXn=84`GRn?77M^_JY`durnN;KE zW#OJ`h<6xcB{I))ekGpc*Ylt}0cx4|OMBDPQvx4`r`}4Ze5_ipdObGMTi3bZHd5PC zcY0;?uBWu$PSvjJeb87nY7ghNv?%M@SoDl6IWt`bQCosfSh$#D6$ea~QhKM^ud2Ut z+9PYJuVpoELmN-A`F$BicO{BSYg@#tS%avVfb}DxL)|NanJ)#zB!2~?#Ot%H7--9N zU$bs0fS5G!m5M4&WK3#a|H|Tgw*?X-;H+Lu@kwA>qSR~7UC7b)7MJXTn6PG>n@8jP zW+}F^X$$c;U~4ryqRF; z>`j!tbLMK4ZGyY643|~?%Mu#fm!l%wAKjBDmd+VYmp3S#$scD$~bxbf|z#)hShN0*AhRaPDcmqrftGlHq4^54MM$Xfy(2> zH8QYVMzmn_oHbvJCB`IN~E&{1*h&0gEM{e zKvWvzp(!BqMX8`t#)~0nq}Wa zr6>FRPyp;AAB&)1$5@;r$23J{K&~>TWjZf7V$wFzmGM95CXhFG1cJNVAXks}C+&2- zbf9Qn*D8N}Afd2kpwDxns3%1uaFhAqDV8ksWiWY|quuLGZ0)SqrJ!Y8yX}@}IyC$C zQ3rCUsn}#>F#D8%D?q~ySy4j&he%Bs{{7V%rl!ui`@KQP?NTi+_iN{cwom&9RaMRR zB~z!hz|0HAgB9_Ijvpe-zr#jLbckJsc>vmo{+im?t8lA;N#fD4?{lb&J0V8Gocq%; f1ihv=QIDh{M_<9V+45Z2{KE4_qW}V3B0uV%GgrOJ literal 0 HcmV?d00001 diff --git a/jetty-servlet/src/test/resources/truststore.jks b/jetty-servlet/src/test/resources/truststore.jks new file mode 100644 index 0000000000000000000000000000000000000000..839cb8c35151c2b7c64afca24b6b72caad070a05 GIT binary patch literal 916 zcmezO_TO6u1_mY|W(3o$xs} zE~X|%Muz1J{3AIFGbaABoD&*5saD@gH|APIn|qhRGl}gsUzm=o9G*UXZaLfkb^*)o zjA*-gTf)`m_MQJYE&gJ}p^PHkrj!4^W|XX5a=N7A{;n#yaON&k_bHloe-^*hm?Z91 zlB>xeD=<(C>yn{9D54u}krkl}HQ(Uscha(++qf!T9y+xaEfnXd1O zi0)T?voO%;QH9LK;*_O3mBblqm)!31vU@hm;^%>mh5U@y3R%l0gzi`2yxH!+?kPOi zt!Tnsz1x9B3U2~8STZp)GB6^C5HPs_Lx_=~O<3xi>MmQ;D_g$D<_pdct`+TyzWTQ= zW5Finm(sGEe;ty^>vg$!cV)t>;H#Mev23$*WWBpyJ}Ir;RW+Htrt6{Pk&qz&-XG2@ z8@{&Lu%DX7m47Uny+-3w`=4V611q#Ub(U`xZCtSK^2LO^3(s|HW&N14dV4@A&(kX% z*S_eUPs-bSWRp>avt;CP@7K+G&3=b&1eO-s3f`;Cf91p#$)FW&xME3L8sEBQQDVCvfG>mdwqnk+GXd2ihXqpv z;usF(WoYYmu8DZZa4%1z=+hI+*gpkUykAy5tj#grb*gH!M6TqIcifYBGVe^&T#-2O K*=+x>r_BKeJV|!| literal 0 HcmV?d00001 From 7bc0363b30cc53d006ddf2c222ddc5da9dd1beb5 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 17 Apr 2014 13:56:10 +0200 Subject: [PATCH 051/135] Updated profiles for JDK 7u55 and JDK 8u05 with the right NPN and ALPN versions. --- pom.xml | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index 6909f7abead..87c22e7a987 100644 --- a/pom.xml +++ b/pom.xml @@ -829,7 +829,7 @@ 1.1.6.v20130911 - 7.0.0 + 7.0.0.v20140317 @@ -842,7 +842,7 @@ 1.1.6.v20130911 - 7.0.0 + 7.0.0.v20140317 @@ -855,19 +855,7 @@ 1.1.6.v20130911 - 7.0.0 - - - - 8u00 - - - java.version - 1.8.0 - - - - 8.0.0 + 7.0.0.v20140317 @@ -880,6 +868,31 @@ 1.1.7.v20140316 + 7.0.0.v20140317 + + + + 8u00 + + + java.version + 1.8.0 + + + + 8.0.0.v20140317 + + + + 8u05 + + + java.version + 1.8.0_05 + + + + 8.0.0.v20140317 From 2a177f2206b437550691687fe7ecd969dcbb8f4e Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 17 Apr 2014 16:23:52 +0200 Subject: [PATCH 052/135] Added URL for new NPN version. --- .../test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java index c39d1aa58d1..8464832b174 100644 --- a/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java +++ b/jetty-spdy/spdy-npn-tests/src/test/java/org/eclipse/jetty/spdy/server/NPNModuleTest.java @@ -57,6 +57,7 @@ public class NPNModuleTest static { /** The main() method in this test case can be run to validate this list independantly */ + KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.7.v20140316/npn-boot-1.1.7.v20140316.jar"); KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.6.v20130911/npn-boot-1.1.6.v20130911.jar"); KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.5.v20130313/npn-boot-1.1.5.v20130313.jar"); KNOWN_GOOD_NPN_URLS.add("http://central.maven.org/maven2/org/mortbay/jetty/npn/npn-boot/1.1.4.v20130313/npn-boot-1.1.4.v20130313.jar"); From 4aa1fbe45292768efb00527614c0474520935ff9 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 17 Apr 2014 16:28:59 +0200 Subject: [PATCH 053/135] Updated module files for JDK 7u55 and JDK 8u05 with the right ALPN versions. --- .../main/config/modules/protonego-impl/alpn-1.7.0_40.mod | 4 ++-- .../main/config/modules/protonego-impl/alpn-1.7.0_45.mod | 4 ++-- .../main/config/modules/protonego-impl/alpn-1.7.0_51.mod | 4 ++-- .../main/config/modules/protonego-impl/alpn-1.7.0_55.mod | 8 ++++++++ .../src/main/config/modules/protonego-impl/alpn-1.8.0.mod | 4 ++-- .../main/config/modules/protonego-impl/alpn-1.8.0_05.mod | 8 ++++++++ 6 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_55.mod create mode 100644 jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_05.mod diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_40.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_40.mod index 09cda68e9e6..bee4693e8ac 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_40.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_40.mod @@ -2,7 +2,7 @@ protonego-boot [files] -http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar [exec] --Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar +-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_45.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_45.mod index 09cda68e9e6..bee4693e8ac 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_45.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_45.mod @@ -2,7 +2,7 @@ protonego-boot [files] -http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar [exec] --Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar +-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_51.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_51.mod index 09cda68e9e6..bee4693e8ac 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_51.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_51.mod @@ -2,7 +2,7 @@ protonego-boot [files] -http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0/alpn-boot-7.0.0.jar:lib/alpn/alpn-boot-7.0.0.jar +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar [exec] --Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.jar +-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_55.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_55.mod new file mode 100644 index 00000000000..bee4693e8ac --- /dev/null +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.7.0_55.mod @@ -0,0 +1,8 @@ +[name] +protonego-boot + +[files] +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/7.0.0.v20140317/alpn-boot-7.0.0.v20140317.jar:lib/alpn/alpn-boot-7.0.0.v20140317.jar + +[exec] +-Xbootclasspath/p:lib/alpn/alpn-boot-7.0.0.v20140317.jar diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0.mod index b738281cb93..4089153ac14 100644 --- a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0.mod +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0.mod @@ -2,7 +2,7 @@ protonego-boot [files] -http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.0.0/alpn-boot-8.0.0.jar:lib/alpn/alpn-boot-8.0.0.jar +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.0.0.v20140317/alpn-boot-8.0.0.v20140317.jar:lib/alpn/alpn-boot-8.0.0.v20140317.jar [exec] --Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.jar +-Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.v20140317.jar diff --git a/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_05.mod b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_05.mod new file mode 100644 index 00000000000..4089153ac14 --- /dev/null +++ b/jetty-alpn/jetty-alpn-server/src/main/config/modules/protonego-impl/alpn-1.8.0_05.mod @@ -0,0 +1,8 @@ +[name] +protonego-boot + +[files] +http://central.maven.org/maven2/org/mortbay/jetty/alpn/alpn-boot/8.0.0.v20140317/alpn-boot-8.0.0.v20140317.jar:lib/alpn/alpn-boot-8.0.0.v20140317.jar + +[exec] +-Xbootclasspath/p:lib/alpn/alpn-boot-8.0.0.v20140317.jar From 9f76856fcff767e0d8a91ef5a311fcc498dd2a26 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 17 Apr 2014 10:38:06 -0700 Subject: [PATCH 054/135] EndPoint onIdleExpired should not close on its own + From discussion with Simone, the dispatched fillInterest.onFail() needs to occur, so that the AbstractConnection.onReadTimeout() can handle the close conditions needed by SPDY and WebSocket. The EndPoint close within onIdleExpired is a race condition with this onReadTimeout desired behavior. --- .../eclipse/jetty/io/AbstractEndPoint.java | 5 +-- .../jetty/io/ByteArrayEndPointTest.java | 39 +++++++++++++++---- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java index 6517dbd3427..bfcb1aa18ca 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java @@ -142,12 +142,9 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint @Override protected void onIdleExpired(TimeoutException timeout) { - boolean output_shutdown=isOutputShutdown(); - boolean input_shutdown=isInputShutdown(); + // Note: Rely on fillInterest to notify onReadTimeout to close connection. _fillInterest.onFail(timeout); _writeFlusher.onFail(timeout); - if (isOpen() && output_shutdown || input_shutdown) - close(); } @Override diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java index 1db6908fe78..d23be980148 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.io; +import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.instanceOf; @@ -36,6 +37,8 @@ import org.eclipse.jetty.toolchain.test.AdvancedRunner; import org.eclipse.jetty.toolchain.test.annotation.Slow; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.FutureCallback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.TimerScheduler; import org.junit.After; @@ -235,6 +238,26 @@ public class ByteArrayEndPointTest assertEquals(null, fcb.get()); assertEquals(" more.", endp.getOutputString()); } + + /** + * Simulate AbstractConnection.ReadCallback.failed() + */ + public static class Closer extends FutureCallback + { + private EndPoint endp; + + public Closer(EndPoint endp) + { + this.endp = endp; + } + + @Override + public void failed(Throwable cause) + { + endp.close(); + super.failed(cause); + } + } @Slow @Test @@ -275,7 +298,7 @@ public class ByteArrayEndPointTest assertThat(t.getCause(), instanceOf(TimeoutException.class)); } assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2)); - assertTrue(endp.isOpen()); + assertThat("Endpoint open", endp.isOpen(), is(true)); // We need to delay the write timeout test below from the read timeout test above. // The reason is that the scheduler thread that fails the endPoint WriteFlusher @@ -298,17 +321,19 @@ public class ByteArrayEndPointTest assertThat(t.getCause(), instanceOf(TimeoutException.class)); } assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2)); - assertTrue(endp.isOpen()); + assertThat("Endpoint open", endp.isOpen(), is(true)); - // Still no idle close - Thread.sleep(idleTimeout * 2); - assertTrue(endp.isOpen()); + endp.fillInterested(new Closer(endp)); + + // Still no idle close (wait half the time) + Thread.sleep(idleTimeout / 2); + assertThat("Endpoint open", endp.isOpen(), is(true)); // shutdown out endp.shutdownOutput(); - // idle close + // idle close (wait double the time) Thread.sleep(idleTimeout * 2); - assertFalse(endp.isOpen()); + assertThat("Endpoint open", endp.isOpen(), is(false)); } } From 1185febb7456a3a07c10d84eb1be6578ae0df82a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 18 Apr 2014 15:19:18 +0200 Subject: [PATCH 055/135] Merged branch 'master' into '431642'. --- .../org/eclipse/jetty/proxy/ProxyServlet.java | 2 + .../eclipse/jetty/proxy/ProxyServletTest.java | 65 +++++++++---------- 2 files changed, 34 insertions(+), 33 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 8799234beaf..3323f1c2c8c 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 @@ -41,6 +41,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; @@ -49,6 +50,7 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.HttpCookieStore; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java index 93ea5dbf757..42257a3446c 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java @@ -28,6 +28,7 @@ import java.net.HttpCookie; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -38,7 +39,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.zip.GZIPOutputStream; - import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; @@ -109,17 +109,16 @@ public class ProxyServletTest private void prepareProxy() throws Exception { - prepareProxy(proxyServlet); + prepareProxy(new HashMap()); } - private void prepareProxy(ProxyServlet proxyServlet) throws Exception + private void prepareProxy(Map initParams) throws Exception { proxy = new Server(); proxyConnector = new ServerConnector(proxy); proxy.addConnector(proxyConnector); ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false); - this.proxyServlet = proxyServlet; ServletHolder proxyServletHolder = new ServletHolder(proxyServlet); proxyServletHolder.setInitParameters(initParams); proxyCtx.addServlet(proxyServletHolder, "/*"); @@ -166,7 +165,7 @@ public class ProxyServletTest @Test public void testProxyDown() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new EmptyHttpServlet()); // Shutdown the proxy @@ -188,7 +187,7 @@ public class ProxyServletTest @Test public void testServerDown() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new EmptyHttpServlet()); // Shutdown the server @@ -208,7 +207,7 @@ public class ProxyServletTest ((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(true); try { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -233,7 +232,7 @@ public class ProxyServletTest @Test public void testProxyWithoutContent() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -255,7 +254,7 @@ public class ProxyServletTest @Test public void testProxyWithResponseContent() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); HttpClient result = new HttpClient(); result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort())); @@ -300,7 +299,7 @@ public class ProxyServletTest @Test public void testProxyWithRequestContentAndResponseContent() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -328,7 +327,7 @@ public class ProxyServletTest @Test public void testProxyWithBigRequestContentIgnored() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -356,7 +355,7 @@ public class ProxyServletTest final byte[] content = new byte[128 * 1024]; new Random().nextBytes(content); - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -391,7 +390,7 @@ public class ProxyServletTest @Test public void testProxyWithBigResponseContentWithSlowReader() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); // Create a 6 MiB file final int length = 6 * 1024; @@ -452,7 +451,7 @@ public class ProxyServletTest @Test public void testProxyWithQueryString() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); String query = "a=1&b=%E2%82%AC"; prepareServer(new HttpServlet() { @@ -474,7 +473,7 @@ public class ProxyServletTest @Test public void testProxyLongPoll() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); final long timeout = 1000; prepareServer(new HttpServlet() { @@ -525,7 +524,7 @@ public class ProxyServletTest @Test public void testProxyRequestExpired() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); final long timeout = 1000; proxyServlet.setTimeout(timeout); prepareServer(new HttpServlet() @@ -557,7 +556,7 @@ public class ProxyServletTest @Test(expected = TimeoutException.class) public void testClientRequestExpired() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); final long timeout = 1000; proxyServlet.setTimeout(3 * timeout); prepareServer(new HttpServlet() @@ -587,7 +586,7 @@ public class ProxyServletTest @Test public void testProxyXForwardedHostHeaderIsPresent() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -608,7 +607,7 @@ public class ProxyServletTest @Test public void testProxyWhiteList() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new EmptyHttpServlet()); int port = serverConnector.getLocalPort(); proxyServlet.getWhiteListHosts().add("127.0.0.1:" + port); @@ -629,7 +628,7 @@ public class ProxyServletTest @Test public void testProxyBlackList() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new EmptyHttpServlet()); int port = serverConnector.getLocalPort(); proxyServlet.getBlackListHosts().add("localhost:" + port); @@ -650,7 +649,7 @@ public class ProxyServletTest @Test public void testClientExcludedHosts() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -705,8 +704,8 @@ public class ProxyServletTest }); String proxyTo = "http://localhost:" + serverConnector.getLocalPort(); - ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix); - prepareProxy(proxyServlet); + proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix); + prepareProxy(); // Make the request to the proxy, it should transparently forward to the server ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()) @@ -744,8 +743,8 @@ public class ProxyServletTest String proxyTo = "http://localhost:" + serverConnector.getLocalPort(); String prefix = "/proxy"; - ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix); - prepareProxy(proxyServlet); + proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix); + prepareProxy(); // Make the request to the proxy, it should transparently forward to the server ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()) @@ -772,10 +771,10 @@ public class ProxyServletTest }); final String proxyTo = "http://localhost:" + serverConnector.getLocalPort(); - ProxyServlet.Transparent proxyServlet = new ProxyServlet.Transparent(); + proxyServlet = new ProxyServlet.Transparent(); Map initParams = new HashMap<>(); initParams.put("proxyTo", proxyTo); - prepareProxy(proxyServlet, initParams); + prepareProxy(initParams); // Make the request to the proxy, it should transparently forward to the server ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()) @@ -805,7 +804,7 @@ public class ProxyServletTest // it is only used for this test and to verify that ProxyServlet can be // subclassed enough to write your own caching servlet final String cacheHeader = "X-Cached"; - ProxyServlet proxyServlet = new ProxyServlet() + proxyServlet = new ProxyServlet() { private Map cache = new HashMap<>(); private Map temp = new HashMap<>(); @@ -850,7 +849,7 @@ public class ProxyServletTest super.onResponseSuccess(request, response, proxyResponse); } }; - prepareProxy(proxyServlet); + prepareProxy(); // First request ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) @@ -872,7 +871,7 @@ public class ProxyServletTest @Test public void testRedirectsAreProxied() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -897,7 +896,7 @@ public class ProxyServletTest public void testGZIPContentIsProxied() throws Exception { final byte[] content = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -924,7 +923,7 @@ public class ProxyServletTest @Test(expected = TimeoutException.class) public void shouldHandleWrongContentLength() throws Exception { - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override @@ -948,7 +947,7 @@ public class ProxyServletTest public void testCookiesFromDifferentClientsAreNotMixed() throws Exception { final String name = "biscuit"; - prepareProxy(new ProxyServlet()); + prepareProxy(); prepareServer(new HttpServlet() { @Override From aeb27cd461d2faa4b77413e029f04f4b13e79777 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sat, 19 Apr 2014 12:36:15 +0200 Subject: [PATCH 056/135] 431642 - Implement ProxyServlet using Servlet 3.1 async I/O. Intermediate commit that implements asynchronous content in HttpClient, for the HTTP protocol, passing the tests. This work needs to be extended to FCGI and SPDY and finally implement the asynchronous proxy servlet. --- .../eclipse/jetty/client/HttpReceiver.java | 15 +- .../jetty/client/ResponseNotifier.java | 83 ++++++++---- .../client/http/HttpReceiverOverHTTP.java | 128 +++++++++++------- .../client/util/DeferredContentProvider.java | 13 +- .../client/HttpClientAsyncContentTest.java | 127 +++++++++++++++++ .../fcgi/client/http/HttpChannelOverFCGI.java | 4 +- .../client/http/HttpReceiverOverFCGI.java | 5 +- .../client/http/HttpReceiverOverSPDY.java | 3 +- 8 files changed, 291 insertions(+), 87 deletions(-) create mode 100644 jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java index d7a7b6eae3c..becab10b57a 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpReceiver.java @@ -33,6 +33,7 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -48,8 +49,8 @@ import org.eclipse.jetty.util.log.Logger; * is available *

  • {@link #responseHeader(HttpExchange, HttpField)}, when a HTTP field is available
  • *
  • {@link #responseHeaders(HttpExchange)}, when all HTTP headers are available
  • - *
  • {@link #responseContent(HttpExchange, ByteBuffer)}, when HTTP content is available; this is the only method - * that may be invoked multiple times with different buffers containing different content
  • + *
  • {@link #responseContent(HttpExchange, ByteBuffer, Callback)}, when HTTP content is available; this is the only + * method that may be invoked multiple times with different buffers containing different content
  • *
  • {@link #responseSuccess(HttpExchange)}, when the response is complete
  • * * At any time, subclasses may invoke {@link #responseFailure(Throwable)} to indicate that the response has failed @@ -237,7 +238,7 @@ public abstract class HttpReceiver HttpResponse response = exchange.getResponse(); if (LOG.isDebugEnabled()) - LOG.debug("Response headers {}{}{}", response, System.getProperty("line.separator"), response.getHeaders().toString().trim()); + LOG.debug("Response headers {}{}{}", response, System.lineSeparator(), response.getHeaders().toString().trim()); ResponseNotifier notifier = getHttpDestination().getResponseNotifier(); notifier.notifyHeaders(exchange.getConversation().getResponseListeners(), response); @@ -269,7 +270,7 @@ public abstract class HttpReceiver * @param buffer the response HTTP content buffer * @return whether the processing should continue */ - protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer) + protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer, Callback callback) { out: while (true) { @@ -292,18 +293,18 @@ public abstract class HttpReceiver HttpResponse response = exchange.getResponse(); if (LOG.isDebugEnabled()) - LOG.debug("Response content {}{}{}", response, System.getProperty("line.separator"), BufferUtil.toDetailString(buffer)); + LOG.debug("Response content {}{}{}", response, System.lineSeparator(), BufferUtil.toDetailString(buffer)); ContentDecoder decoder = this.decoder; if (decoder != null) { buffer = decoder.decode(buffer); if (LOG.isDebugEnabled()) - LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.getProperty("line.separator"), BufferUtil.toDetailString(buffer)); + LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.lineSeparator(), BufferUtil.toDetailString(buffer)); } ResponseNotifier notifier = getHttpDestination().getResponseNotifier(); - notifier.notifyContent(exchange.getConversation().getResponseListeners(), response, buffer); + notifier.notifyContent(exchange.getConversation().getResponseListeners(), response, buffer, callback); return true; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java index 4b824058e89..1b40103cf63 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java @@ -27,6 +27,8 @@ import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.IteratingNestedCallback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -106,35 +108,17 @@ public class ResponseNotifier } } - public void notifyContent(List listeners, Response response, ByteBuffer buffer) + public void notifyContent(List listeners, Response response, ByteBuffer buffer, Callback callback) { - // TODO: we need to create a "cumulative" callback that keeps track of how many listeners - // TODO: are invoked, and how many of these actually invoked the callback, and eventually - // TODO: call the callback passed to this method. - - // Slice the buffer to avoid that listeners peek into data they should not look at. - buffer = buffer.slice(); - if (!buffer.hasRemaining()) - return; - // Optimized to avoid allocations of iterator instances - for (int i = 0; i < listeners.size(); ++i) - { - Response.ResponseListener listener = listeners.get(i); - if (listener instanceof Response.ContentListener) - { - // The buffer was sliced, so we always clear it (position=0, limit=capacity) - // before passing it to the listener that may consume it. - buffer.clear(); - notifyContent((Response.ContentListener)listener, response, buffer); - } - } + ContentCallback contentCallback = new ContentCallback(listeners, response, buffer, callback); + contentCallback.iterate(); } - private void notifyContent(Response.ContentListener listener, Response response, ByteBuffer buffer) + private void notifyContent(Response.AsyncContentListener listener, Response response, ByteBuffer buffer, Callback callback) { try { - listener.onContent(response, buffer); + listener.onContent(response, buffer, callback); } catch (Throwable x) { @@ -222,7 +206,8 @@ public class ResponseNotifier } notifyHeaders(listeners, response); if (response instanceof ContentResponse) - notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent())); + // TODO: handle callback + notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter()); notifySuccess(listeners, response); } @@ -243,7 +228,8 @@ public class ResponseNotifier } notifyHeaders(listeners, response); if (response instanceof ContentResponse) - notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent())); + // TODO: handle callback + notifyContent(listeners, response, ByteBuffer.wrap(((ContentResponse)response).getContent()), new Callback.Adapter()); notifyFailure(listeners, response, failure); } @@ -252,4 +238,51 @@ public class ResponseNotifier forwardFailure(listeners, response, responseFailure); notifyComplete(listeners, new Result(request, requestFailure, response, responseFailure)); } + + private class ContentCallback extends IteratingNestedCallback + { + private final List listeners; + private final Response response; + private final ByteBuffer buffer; + private int index; + + private ContentCallback(List listeners, Response response, ByteBuffer buffer, Callback callback) + { + super(callback); + this.listeners = listeners; + this.response = response; + // Slice the buffer to avoid that listeners peek into data they should not look at. + this.buffer = buffer.slice(); + } + + @Override + protected Action process() throws Exception + { + if (index == listeners.size()) + return Action.SUCCEEDED; + + Response.ResponseListener listener = listeners.get(index); + if (listener instanceof Response.AsyncContentListener) + { + // The buffer was sliced, so we always clear it + // (clear => position=0, limit=capacity) before + // passing it to the listener that may consume it. + buffer.clear(); + ResponseNotifier.this.notifyContent((Response.AsyncContentListener)listener, response, buffer, this); + return Action.SCHEDULED; + } + else + { + succeeded(); + return Action.SCHEDULED; + } + } + + @Override + public void succeeded() + { + ++index; + super.succeeded(); + } + } } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java index 0447164f56c..7d5af9eb566 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.client.http; import java.io.EOFException; import java.nio.ByteBuffer; +import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpExchange; @@ -32,12 +33,13 @@ import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; -import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.Callback; public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler { private final HttpParser parser = new HttpParser(this); + private ByteBuffer buffer; private boolean shutdown; public HttpReceiverOverHTTP(HttpChannelOverHTTP channel) @@ -58,63 +60,75 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res public void receive() { - HttpConnectionOverHTTP connection = getHttpConnection(); - EndPoint endPoint = connection.getEndPoint(); HttpClient client = getHttpDestination().getHttpClient(); ByteBufferPool bufferPool = client.getByteBufferPool(); - ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true); - try + buffer = bufferPool.acquire(client.getResponseBufferSize(), true); + if (process()) { - while (true) + bufferPool.release(buffer); + // Don't linger the buffer around if we are idle. + buffer = null; + } + } + + private boolean process() + { + HttpConnectionOverHTTP connection = getHttpConnection(); + EndPoint endPoint = connection.getEndPoint(); + ByteBuffer buffer = this.buffer; + while (true) + { + try { // Connection may be closed in a parser callback if (connection.isClosed()) { - LOG.debug("{} closed", connection); - break; + if (LOG.isDebugEnabled()) + LOG.debug("{} closed", connection); + return true; + } + + if (!parse(buffer)) + return false; + + int read = endPoint.fill(buffer); + // Avoid boxing of variable 'read' + if (LOG.isDebugEnabled()) + LOG.debug("Read {} bytes from {}", read, endPoint); + + if (read > 0) + { + if (!parse(buffer)) + return false; + } + else if (read == 0) + { + fillInterested(); + return true; } else { - int read = endPoint.fill(buffer); - if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read' - LOG.debug("Read {} bytes from {}", read, endPoint); - if (read > 0) - { - parse(buffer); - } - else if (read == 0) - { - fillInterested(); - break; - } - else - { - shutdown(); - break; - } + shutdown(); + return true; } } - } - catch (EofException x) - { - LOG.ignore(x); - failAndClose(x); - } - catch (Exception x) - { - LOG.debug(x); - failAndClose(x); - } - finally - { - bufferPool.release(buffer); + catch (Throwable x) + { + LOG.debug(x); + failAndClose(x); + return true; + } } } - private void parse(ByteBuffer buffer) + private boolean parse(ByteBuffer buffer) { while (buffer.hasRemaining()) - parser.parseNext(buffer); + { + if (parser.parseNext(buffer)) + return parser.isStart(); + } + return true; } private void fillInterested() @@ -195,13 +209,33 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res if (exchange == null) return false; - // TODO: need to create the callback here, then check whether it has completed - // TODO: after the call to responseContent. If it has, return false. - // TODO: if it has not, return true, and when will be invoked, we need to - // TODO: proceed with parsing. + final AtomicBoolean completed = new AtomicBoolean(); + Callback callback = new Callback() + { + @Override + public void succeeded() + { + if (!completed.compareAndSet(false, true)) + { + LOG.debug("Content consumed asynchronously, resuming processing"); + if (process()) + { + // TODO: release the buffer to the pool ! + } + } + } - responseContent(exchange, buffer); - return false; + @Override + public void failed(Throwable x) + { + failAndClose(x); + } + }; + responseContent(exchange, buffer, callback); + // Return false to have the parser continue parsing. + // TODO: there is a race here: when this thread returns true, the parser is still running + // TODO: some stateful code that may be changed concurrently by the callback thread. + return completed.compareAndSet(false, true); } @Override diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java index 73ad9614646..55a82f79ce1 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/DeferredContentProvider.java @@ -235,10 +235,14 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable synchronized (lock) { chunk = current; - --size; - lock.notify(); + if (chunk != null) + { + --size; + lock.notify(); + } } - chunk.callback.succeeded(); + if (chunk != null) + chunk.callback.succeeded(); } @Override @@ -251,7 +255,8 @@ public class DeferredContentProvider implements AsyncContentProvider, Closeable failure = x; lock.notify(); } - chunk.callback.failed(x); + if (chunk != null) + chunk.callback.failed(x); } } diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java new file mode 100644 index 00000000000..56fcd1a9220 --- /dev/null +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java @@ -0,0 +1,127 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.net.Socket; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.api.Result; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.handler.AbstractHandler; +import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.Assert; +import org.junit.Test; + +public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest +{ + public HttpClientAsyncContentTest(SslContextFactory sslContextFactory) + { + super(sslContextFactory); + } + + @Test + public void testSmallAsyncContent() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + ServletOutputStream output = response.getOutputStream(); + output.write(65); + output.flush(); + output.write(66); + } + }); + + final AtomicInteger contentCount = new AtomicInteger(); + final AtomicReference callbackRef = new AtomicReference<>(); + final AtomicReference contentLatch = new AtomicReference<>(new CountDownLatch(1)); + final CountDownLatch completeLatch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .onResponseContentAsync(new Response.AsyncContentListener() + { + @Override + public void onContent(Response response, ByteBuffer content, Callback callback) + { + contentCount.incrementAndGet(); + callbackRef.set(callback); + contentLatch.get().countDown(); + } + }) + .send(new Response.CompleteListener() + { + @Override + public void onComplete(Result result) + { + completeLatch.countDown(); + } + }); + + Assert.assertTrue(contentLatch.get().await(555, TimeUnit.SECONDS)); + Callback callback = callbackRef.get(); + + // Wait a while to be sure that the parsing does not proceed. + TimeUnit.MILLISECONDS.sleep(1000); + + Assert.assertEquals(1, contentCount.get()); + + // Succeed the content callback to proceed with parsing. + callbackRef.set(null); + contentLatch.set(new CountDownLatch(1)); + callback.succeeded(); + + Assert.assertTrue(contentLatch.get().await(555, TimeUnit.SECONDS)); + callback = callbackRef.get(); + + // Wait a while to be sure that the parsing does not proceed. + TimeUnit.MILLISECONDS.sleep(1000); + + Assert.assertEquals(2, contentCount.get()); + Assert.assertEquals(1, completeLatch.getCount()); + + // Succeed the content callback to proceed with parsing. + callbackRef.set(null); + contentLatch.set(new CountDownLatch(1)); + callback.succeeded(); + + Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); + Assert.assertEquals(2, contentCount.get()); + } + + public void test() throws Exception + { + try (Socket socket = new Socket()) + { + System.out.println("socket = " + socket); + } + } +} diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index 1c33cf0518d..36d91e076ab 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -30,6 +30,7 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.IdleTimeout; +import org.eclipse.jetty.util.Callback; public class HttpChannelOverFCGI extends HttpChannel { @@ -105,7 +106,8 @@ public class HttpChannelOverFCGI extends HttpChannel protected boolean content(ByteBuffer buffer) { HttpExchange exchange = getHttpExchange(); - return exchange != null && receiver.responseContent(exchange, buffer); + // TODO: handle callback properly + return exchange != null && receiver.responseContent(exchange, buffer, new Callback.Adapter()); } protected boolean responseSuccess() diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java index a222c8c4654..2cdd34d00f6 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpReceiverOverFCGI.java @@ -24,6 +24,7 @@ import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpExchange; import org.eclipse.jetty.client.HttpReceiver; import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.util.Callback; public class HttpReceiverOverFCGI extends HttpReceiver { @@ -51,9 +52,9 @@ public class HttpReceiverOverFCGI extends HttpReceiver } @Override - protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer) + protected boolean responseContent(HttpExchange exchange, ByteBuffer buffer, Callback callback) { - return super.responseContent(exchange, buffer); + return super.responseContent(exchange, buffer, callback); } @Override diff --git a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java index 40ba14d27f3..7708fd2c825 100644 --- a/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java +++ b/jetty-spdy/spdy-http-client-transport/src/main/java/org/eclipse/jetty/spdy/client/http/HttpReceiverOverSPDY.java @@ -123,7 +123,8 @@ public class HttpReceiverOverSPDY extends HttpReceiver implements StreamFrameLis { int length = dataInfo.length(); // TODO: avoid data copy here - boolean process = responseContent(exchange, dataInfo.asByteBuffer(false)); + // TODO: handle callback properly + boolean process = responseContent(exchange, dataInfo.asByteBuffer(false), new Callback.Adapter()); dataInfo.consume(length); if (process) From 900dea37195735fd6e43442c3945ac746e2a1776 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 22 Apr 2014 16:07:45 -0700 Subject: [PATCH 057/135] 433262 - WebSocket / Advanced close use cases + ClientCloseTest implementation of various outlined use cases. --- .../endpoints/AbstractJsrEventDriver.java | 3 +- .../endpoints/JsrAnnotatedEventDriver.java | 4 +- .../endpoints/JsrEndpointEventDriver.java | 4 +- .../client/io/ConnectionManager.java | 11 +- .../io/WebSocketClientSelectorManager.java | 7 +- .../websocket/client/ClientCloseTest.java | 626 ++++++++++++++++++ .../websocket/client/ServerWriteThread.java | 18 - .../websocket/client/SlowServerTest.java | 33 +- .../jetty/websocket/client/TimeoutTest.java | 115 ---- .../test/resources/jetty-logging.properties | 9 +- .../jetty/websocket/common/CloseInfo.java | 26 +- .../jetty/websocket/common/Parser.java | 31 +- .../websocket/common/WebSocketSession.java | 66 +- .../common/events/AbstractEventDriver.java | 11 +- .../io/AbstractWebSocketConnection.java | 75 ++- .../websocket/common/io/FrameFlusher.java | 535 ++++++++------- .../jetty/websocket/common/io/IOState.java | 78 ++- .../websocket/common/util/ReflectUtils.java | 2 +- .../jetty/websocket/common/CloseInfoTest.java | 166 +++++ .../websocket/common/ab/TestABCase4.java | 58 +- .../common/test/BlockheadClient.java | 49 +- .../common/test/BlockheadServer.java | 38 +- .../server/WebSocketServerFactory.java | 19 +- .../websocket/server/WebSocketCloseTest.java | 40 +- 24 files changed, 1436 insertions(+), 588 deletions(-) create mode 100644 jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java delete mode 100644 jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java create mode 100644 jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/CloseInfoTest.java diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java index 3b91b387a73..d7b9ced7f14 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/AbstractJsrEventDriver.java @@ -31,11 +31,10 @@ import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.events.AbstractEventDriver; -import org.eclipse.jetty.websocket.common.events.EventDriver; import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata; -public abstract class AbstractJsrEventDriver extends AbstractEventDriver implements EventDriver +public abstract class AbstractJsrEventDriver extends AbstractEventDriver { protected final EndpointMetadata metadata; protected final EndpointConfig config; diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java index a550c20b8b4..0cf9df7ab51 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrAnnotatedEventDriver.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.io.Reader; import java.nio.ByteBuffer; import java.util.Map; + import javax.websocket.CloseReason; import javax.websocket.DecodeException; @@ -31,7 +32,6 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.Frame; -import org.eclipse.jetty.websocket.common.events.EventDriver; import org.eclipse.jetty.websocket.common.message.MessageInputStream; import org.eclipse.jetty.websocket.common.message.MessageReader; import org.eclipse.jetty.websocket.common.message.SimpleBinaryMessage; @@ -44,7 +44,7 @@ import org.eclipse.jetty.websocket.jsr356.messages.TextPartialOnMessage; /** * Base implementation for JSR-356 Annotated event drivers. */ -public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver implements EventDriver +public class JsrAnnotatedEventDriver extends AbstractJsrEventDriver { private static final Logger LOG = Log.getLogger(JsrAnnotatedEventDriver.class); private final JsrEvents events; diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java index b977147fb91..9ac49062ace 100644 --- a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/endpoints/JsrEndpointEventDriver.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.io.Reader; import java.nio.ByteBuffer; import java.util.Map; + import javax.websocket.CloseReason; import javax.websocket.Endpoint; import javax.websocket.MessageHandler; @@ -34,7 +35,6 @@ import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.Frame; -import org.eclipse.jetty.websocket.common.events.EventDriver; import org.eclipse.jetty.websocket.common.message.MessageInputStream; import org.eclipse.jetty.websocket.common.message.MessageReader; import org.eclipse.jetty.websocket.jsr356.JsrPongMessage; @@ -49,7 +49,7 @@ import org.eclipse.jetty.websocket.jsr356.messages.TextWholeMessage; /** * EventDriver for websocket that extend from {@link javax.websocket.Endpoint} */ -public class JsrEndpointEventDriver extends AbstractJsrEventDriver implements EventDriver +public class JsrEndpointEventDriver extends AbstractJsrEventDriver { private static final Logger LOG = Log.getLogger(JsrEndpointEventDriver.class); diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java index da112f9cb1e..ab20d77d9de 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/ConnectionManager.java @@ -32,6 +32,7 @@ import java.util.concurrent.ConcurrentLinkedQueue; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketException; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.WebSocketClient; @@ -158,7 +159,7 @@ public class ConnectionManager extends ContainerLifeCycle sessions.add(session); } - private void closeAllConnections() + private void shutdownAllConnections() { for (WebSocketSession session : sessions) { @@ -166,11 +167,13 @@ public class ConnectionManager extends ContainerLifeCycle { try { - session.getConnection().close(); + session.getConnection().close( + StatusCode.SHUTDOWN, + "Shutdown"); } catch (Throwable t) { - LOG.debug("During Close All Connections",t); + LOG.debug("During Shutdown All Connections",t); } } } @@ -203,7 +206,7 @@ public class ConnectionManager extends ContainerLifeCycle @Override protected void doStop() throws Exception { - closeAllConnections(); + shutdownAllConnections(); sessions.clear(); super.doStop(); removeBean(selector); diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java index 928e911e278..6616f54f996 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/WebSocketClientSelectorManager.java @@ -98,7 +98,7 @@ public class WebSocketClientSelectorManager extends SelectorManager else { // Standard "ws://" - endPoint.setIdleTimeout(connectPromise.getClient().getMaxIdleTimeout()); + endPoint.setIdleTimeout(connectPromise.getDriver().getPolicy().getIdleTimeout()); return newUpgradeConnection(channel,endPoint,connectPromise); } } @@ -139,4 +139,9 @@ public class WebSocketClientSelectorManager extends SelectorManager { this.sslContextFactory = sslContextFactory; } + + public WebSocketPolicy getPolicy() + { + return policy; + } } diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java new file mode 100644 index 00000000000..3a7f33874bd --- /dev/null +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java @@ -0,0 +1,626 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.websocket.client; + +import static org.hamcrest.Matchers.*; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.SelectChannelEndPoint; +import org.eclipse.jetty.io.SelectorManager.ManagedSelector; +import org.eclipse.jetty.toolchain.test.EventQueue; +import org.eclipse.jetty.toolchain.test.TestTracker; +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.thread.Scheduler; +import org.eclipse.jetty.websocket.api.ProtocolException; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.api.WebSocketAdapter; +import org.eclipse.jetty.websocket.client.io.ConnectionManager; +import org.eclipse.jetty.websocket.client.io.WebSocketClientSelectorManager; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.OpCode; +import org.eclipse.jetty.websocket.common.WebSocketFrame; +import org.eclipse.jetty.websocket.common.WebSocketSession; +import org.eclipse.jetty.websocket.common.frames.TextFrame; +import org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection; +import org.eclipse.jetty.websocket.common.test.BlockheadServer; +import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection; +import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; +import org.eclipse.jetty.websocket.common.test.RawFrameBuilder; +import org.hamcrest.Matcher; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class ClientCloseTest +{ + private static final Logger LOG = Log.getLogger(ClientCloseTest.class); + + private static class CloseTrackingSocket extends WebSocketAdapter + { + private static final Logger LOG = Log.getLogger(ClientCloseTest.CloseTrackingSocket.class); + + public int closeCode = -1; + public String closeReason = null; + public CountDownLatch closeLatch = new CountDownLatch(1); + public CountDownLatch openLatch = new CountDownLatch(1); + + public EventQueue messageQueue = new EventQueue<>(); + public EventQueue errorQueue = new EventQueue<>(); + + public void assertNoCloseEvent() + { + Assert.assertThat("Client Close Event",closeLatch.getCount(),is(1L)); + Assert.assertThat("Client Close Event Status Code ",closeCode,is(-1)); + } + + public void assertReceivedCloseEvent(int clientTimeoutMs, Matcher statusCodeMatcher, Matcher reasonMatcher) + throws InterruptedException + { + long maxTimeout = clientTimeoutMs * 2; + + Assert.assertThat("Client Close Event Occurred",closeLatch.await(maxTimeout,TimeUnit.MILLISECONDS),is(true)); + Assert.assertThat("Client Close Event Status Code",closeCode,statusCodeMatcher); + if (reasonMatcher == null) + { + Assert.assertThat("Client Close Event Reason",closeReason,nullValue()); + } + else + { + Assert.assertThat("Client Close Event Reason",closeReason,reasonMatcher); + } + } + + public void assertReceivedError(Class expectedThrownClass, Matcher messageMatcher) throws TimeoutException, + InterruptedException + { + errorQueue.awaitEventCount(1,500,TimeUnit.MILLISECONDS); + Throwable actual = errorQueue.poll(); + Assert.assertThat("Client Error Event",actual,instanceOf(expectedThrownClass)); + if (messageMatcher == null) + { + Assert.assertThat("Client Error Event Message",actual.getMessage(),nullValue()); + } + else + { + Assert.assertThat("Client Error Event Message",actual.getMessage(),messageMatcher); + } + } + + public void clearQueues() + { + messageQueue.clear(); + errorQueue.clear(); + } + + @Override + public void onWebSocketClose(int statusCode, String reason) + { + LOG.debug("onWebSocketClose({},{})",statusCode,reason); + super.onWebSocketClose(statusCode,reason); + closeCode = statusCode; + closeReason = reason; + closeLatch.countDown(); + } + + @Override + public void onWebSocketConnect(Session session) + { + super.onWebSocketConnect(session); + openLatch.countDown(); + } + + @Override + public void onWebSocketError(Throwable cause) + { + LOG.debug("onWebSocketError",cause); + Assert.assertThat("Error capture",errorQueue.offer(cause),is(true)); + } + + @Override + public void onWebSocketText(String message) + { + LOG.debug("onWebSocketText({})",message); + messageQueue.offer(message); + } + + public EndPoint getEndPoint() throws Exception + { + Session session = getSession(); + Assert.assertThat("Session type",session,instanceOf(WebSocketSession.class)); + + WebSocketSession wssession = (WebSocketSession)session; + Field fld = wssession.getClass().getDeclaredField("connection"); + fld.setAccessible(true); + Assert.assertThat("Field: connection",fld,notNullValue()); + + Object val = fld.get(wssession); + Assert.assertThat("Connection type",val,instanceOf(AbstractWebSocketConnection.class)); + @SuppressWarnings("resource") + AbstractWebSocketConnection wsconn = (AbstractWebSocketConnection)val; + return wsconn.getEndPoint(); + } + } + + @Rule + public TestTracker tt = new TestTracker(); + + private BlockheadServer server; + private WebSocketClient client; + + private void confirmConnection(CloseTrackingSocket clientSocket, Future clientFuture, ServerConnection serverConn) throws Exception + { + // Wait for client connect on via future + clientFuture.get(500,TimeUnit.MILLISECONDS); + + // Wait for client connect via client websocket + Assert.assertThat("Client WebSocket is Open",clientSocket.openLatch.await(500,TimeUnit.MILLISECONDS),is(true)); + + try + { + // Send message from client to server + final String echoMsg = "echo-test"; + Future testFut = clientSocket.getRemote().sendStringByFuture(echoMsg); + + // Wait for send future + testFut.get(500,TimeUnit.MILLISECONDS); + + // Read Frame on server side + IncomingFramesCapture serverCapture = serverConn.readFrames(1,500,TimeUnit.MILLISECONDS); + serverCapture.assertNoErrors(); + serverCapture.assertFrameCount(1); + WebSocketFrame frame = serverCapture.getFrames().poll(); + Assert.assertThat("Server received frame",frame.getOpCode(),is(OpCode.TEXT)); + Assert.assertThat("Server received frame payload",frame.getPayloadAsUTF8(),is(echoMsg)); + + // Server send echo reply + serverConn.write(new TextFrame().setPayload(echoMsg)); + + // Wait for received echo + clientSocket.messageQueue.awaitEventCount(1,1,TimeUnit.SECONDS); + + // Verify received message + String recvMsg = clientSocket.messageQueue.poll(); + Assert.assertThat("Received message",recvMsg,is(echoMsg)); + + // Verify that there are no errors + Assert.assertThat("Error events",clientSocket.errorQueue,empty()); + } + finally + { + clientSocket.clearQueues(); + } + } + + private void confirmServerReceivedCloseFrame(ServerConnection serverConn, int expectedCloseCode, Matcher closeReasonMatcher) throws IOException, + TimeoutException + { + IncomingFramesCapture serverCapture = serverConn.readFrames(1,500,TimeUnit.MILLISECONDS); + serverCapture.assertNoErrors(); + serverCapture.assertFrameCount(1); + serverCapture.assertHasFrame(OpCode.CLOSE,1); + WebSocketFrame frame = serverCapture.getFrames().poll(); + Assert.assertThat("Server received close frame",frame.getOpCode(),is(OpCode.CLOSE)); + CloseInfo closeInfo = new CloseInfo(frame); + Assert.assertThat("Server received close code",closeInfo.getStatusCode(),is(expectedCloseCode)); + if (closeReasonMatcher == null) + { + Assert.assertThat("Server received close reason",closeInfo.getReason(),nullValue()); + } + else + { + Assert.assertThat("Server received close reason",closeInfo.getReason(),closeReasonMatcher); + } + } + + public static class TestWebSocketClient extends WebSocketClient + { + @Override + protected ConnectionManager newConnectionManager() + { + return new TestConnectionManager(this); + } + } + + public static class TestConnectionManager extends ConnectionManager + { + public TestConnectionManager(WebSocketClient client) + { + super(client); + } + + @Override + protected WebSocketClientSelectorManager newWebSocketClientSelectorManager(WebSocketClient client) + { + return new TestSelectorManager(client); + } + } + + public static class TestSelectorManager extends WebSocketClientSelectorManager + { + public TestSelectorManager(WebSocketClient client) + { + super(client); + } + + @Override + protected EndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException + { + return new TestEndPoint(channel,selectSet,selectionKey,getScheduler(),getPolicy().getIdleTimeout()); + } + } + + public static class TestEndPoint extends SelectChannelEndPoint + { + public AtomicBoolean congestedFlush = new AtomicBoolean(false); + + public TestEndPoint(SocketChannel channel, ManagedSelector selector, SelectionKey key, Scheduler scheduler, long idleTimeout) + { + super(channel,selector,key,scheduler,idleTimeout); + } + + @Override + public boolean flush(ByteBuffer... buffers) throws IOException + { + boolean flushed = super.flush(buffers); + congestedFlush.set(!flushed); + return flushed; + } + } + + @Before + public void startClient() throws Exception + { + client = new TestWebSocketClient(); + client.start(); + } + + @Before + public void startServer() throws Exception + { + server = new BlockheadServer(); + server.start(); + } + + @After + public void stopClient() throws Exception + { + if (client.isRunning()) + { + client.stop(); + } + } + + @After + public void stopServer() throws Exception + { + server.stop(); + } + + @Test + public void testHalfClose() throws Exception + { + // Set client timeout + final int timeout = 1000; + client.setMaxIdleTimeout(timeout); + + // Client connects + CloseTrackingSocket clientSocket = new CloseTrackingSocket(); + Future clientConnectFuture = client.connect(clientSocket,server.getWsUri()); + + // Server accepts connect + ServerConnection serverConn = server.accept(); + serverConn.upgrade(); + + // client confirms connection via echo + confirmConnection(clientSocket,clientConnectFuture,serverConn); + + // client sends close frame (code 1000, normal) + final String origCloseReason = "Normal Close"; + clientSocket.getSession().close(StatusCode.NORMAL,origCloseReason); + + // server receives close frame + confirmServerReceivedCloseFrame(serverConn,StatusCode.NORMAL,is(origCloseReason)); + + // server sends 2 messages + serverConn.write(new TextFrame().setPayload("Hello")); + serverConn.write(new TextFrame().setPayload("World")); + + // server sends close frame (code 1000, no reason) + CloseInfo sclose = new CloseInfo(StatusCode.NORMAL,"From Server"); + serverConn.write(sclose.asFrame()); + + // client receives 2 messages + clientSocket.messageQueue.awaitEventCount(2,1,TimeUnit.SECONDS); + + // Verify received messages + String recvMsg = clientSocket.messageQueue.poll(); + Assert.assertThat("Received message 1",recvMsg,is("Hello")); + recvMsg = clientSocket.messageQueue.poll(); + Assert.assertThat("Received message 2",recvMsg,is("World")); + + // Verify that there are no errors + Assert.assertThat("Error events",clientSocket.errorQueue,empty()); + + // client close event on ws-endpoint + clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.NORMAL),containsString("From Server")); + } + + @Test + public void testNetworkCongestion() throws Exception + { + // Set client timeout + final int timeout = 1000; + client.setMaxIdleTimeout(timeout); + + // Client connects + CloseTrackingSocket clientSocket = new CloseTrackingSocket(); + Future clientConnectFuture = client.connect(clientSocket,server.getWsUri()); + + // Server accepts connect + ServerConnection serverConn = server.accept(); + serverConn.upgrade(); + + // client confirms connection via echo + confirmConnection(clientSocket,clientConnectFuture,serverConn); + + // client sends BIG frames (until it cannot write anymore) + // server must not read (for test purpose, in order to congest connection) + // when write is congested, client enqueue close frame + // client initiate write, but write never completes + EndPoint endp = clientSocket.getEndPoint(); + Assert.assertThat("EndPoint is testable",endp,instanceOf(TestEndPoint.class)); + TestEndPoint testendp = (TestEndPoint)endp; + + char msg[] = new char[10240]; + int writeCount = 0; + long writeSize = 0; + int i = 0; + while (!testendp.congestedFlush.get()) + { + int z = i - ((i / 26) * 26); + char c = (char)('a' + z); + Arrays.fill(msg,c); + clientSocket.getRemote().sendStringByFuture(String.valueOf(msg)); + writeCount++; + writeSize += msg.length; + } + LOG.debug("Wrote {} frames totalling {} bytes of payload before congestion kicked in",writeCount,writeSize); + + // Verify that there are no errors + Assert.assertThat("Error events",clientSocket.errorQueue,empty()); + + // client idle timeout triggers close event on client ws-endpoint + // client close event on ws-endpoint + clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.ABNORMAL),containsString("Timeout")); + } + + @Test + public void testProtocolException() throws Exception + { + // Set client timeout + final int timeout = 1000; + client.setMaxIdleTimeout(timeout); + + // Client connects + CloseTrackingSocket clientSocket = new CloseTrackingSocket(); + Future clientConnectFuture = client.connect(clientSocket,server.getWsUri()); + + // Server accepts connect + ServerConnection serverConn = server.accept(); + serverConn.upgrade(); + + // client confirms connection via echo + confirmConnection(clientSocket,clientConnectFuture,serverConn); + + // client should not have received close message (yet) + clientSocket.assertNoCloseEvent(); + + // server sends bad close frame (too big of a reason message) + byte msg[] = new byte[400]; + Arrays.fill(msg,(byte)'x'); + ByteBuffer bad = ByteBuffer.allocate(500); + RawFrameBuilder.putOpFin(bad,OpCode.CLOSE,true); + RawFrameBuilder.putLength(bad,msg.length + 2,false); + bad.putShort((short)StatusCode.NORMAL); + bad.put(msg); + BufferUtil.flipToFlush(bad,0); + serverConn.write(bad); + + // client should have noticed the error + clientSocket.assertReceivedError(ProtocolException.class,containsString("Invalid control frame")); + + // client parse invalid frame, notifies server of close (protocol error) + confirmServerReceivedCloseFrame(serverConn,StatusCode.PROTOCOL,allOf(containsString("Invalid control frame"),containsString("length"))); + + // server disconnects + serverConn.disconnect(); + + // client triggers close event on client ws-endpoint + clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.PROTOCOL),allOf(containsString("Invalid control frame"),containsString("length"))); + } + + @Test + public void testReadEOF() throws Exception + { + // Set client timeout + final int timeout = 1000; + client.setMaxIdleTimeout(timeout); + + // Client connects + CloseTrackingSocket clientSocket = new CloseTrackingSocket(); + Future clientConnectFuture = client.connect(clientSocket,server.getWsUri()); + + // Server accepts connect + ServerConnection serverConn = server.accept(); + serverConn.upgrade(); + + // client confirms connection via echo + confirmConnection(clientSocket,clientConnectFuture,serverConn); + + // client sends close frame + final String origCloseReason = "Normal Close"; + clientSocket.getSession().close(StatusCode.NORMAL,origCloseReason); + + // server receives close frame + confirmServerReceivedCloseFrame(serverConn,StatusCode.NORMAL,is(origCloseReason)); + + // client should not have received close message (yet) + clientSocket.assertNoCloseEvent(); + + // server shuts down connection (no frame reply) + serverConn.disconnect(); + + // client reads -1 (EOF) + // client triggers close event on client ws-endpoint + clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.ABNORMAL),containsString("EOF")); + } + + @Test + public void testServerNoCloseHandshake() throws Exception + { + // Set client timeout + final int timeout = 1000; + client.setMaxIdleTimeout(timeout); + + // Client connects + CloseTrackingSocket clientSocket = new CloseTrackingSocket(); + Future clientConnectFuture = client.connect(clientSocket,server.getWsUri()); + + // Server accepts connect + ServerConnection serverConn = server.accept(); + serverConn.upgrade(); + + // client confirms connection via echo + confirmConnection(clientSocket,clientConnectFuture,serverConn); + + // client sends close frame + final String origCloseReason = "Normal Close"; + clientSocket.getSession().close(StatusCode.NORMAL,origCloseReason); + + // server receives close frame + confirmServerReceivedCloseFrame(serverConn,StatusCode.NORMAL,is(origCloseReason)); + + // client should not have received close message (yet) + clientSocket.assertNoCloseEvent(); + + // server never sends close frame handshake + // server sits idle + + // client idle timeout triggers close event on client ws-endpoint + // assert - close code==1006 (abnormal) + // assert - close reason message contains (timeout) + clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.ABNORMAL),containsString("Timeout")); + } + + @Test + public void testStopLifecycle() throws Exception + { + // Set client timeout + final int timeout = 1000; + client.setMaxIdleTimeout(timeout); + + int clientCount = 3; + CloseTrackingSocket clientSockets[] = new CloseTrackingSocket[clientCount]; + ServerConnection serverConns[] = new ServerConnection[clientCount]; + + // Connect Multiple Clients + for (int i = 0; i < clientCount; i++) + { + // Client Request Upgrade + clientSockets[i] = new CloseTrackingSocket(); + Future clientConnectFuture = client.connect(clientSockets[i],server.getWsUri()); + + // Server accepts connection + serverConns[i] = server.accept(); + serverConns[i].upgrade(); + + // client confirms connection via echo + confirmConnection(clientSockets[i],clientConnectFuture,serverConns[i]); + } + + // client lifecycle stop + client.stop(); + + // clients send close frames (code 1001, shutdown) + for (int i = 0; i < clientCount; i++) + { + // server receives close frame + confirmServerReceivedCloseFrame(serverConns[i],StatusCode.SHUTDOWN,containsString("Shutdown")); + } + + // clients disconnect + for (int i = 0; i < clientCount; i++) + { + clientSockets[i].assertReceivedCloseEvent(timeout,is(StatusCode.SHUTDOWN),containsString("Shutdown")); + } + } + + @Test + public void testWriteException() throws Exception + { + // Set client timeout + final int timeout = 1000; + client.setMaxIdleTimeout(timeout); + + // Client connects + CloseTrackingSocket clientSocket = new CloseTrackingSocket(); + Future clientConnectFuture = client.connect(clientSocket,server.getWsUri()); + + // Server accepts connect + ServerConnection serverConn = server.accept(); + serverConn.upgrade(); + + // client confirms connection via echo + confirmConnection(clientSocket,clientConnectFuture,serverConn); + + // setup client endpoint for write failure (test only) + EndPoint endp = clientSocket.getEndPoint(); + endp.shutdownOutput(); + + // client enqueue close frame + // client write failure + final String origCloseReason = "Normal Close"; + clientSocket.getSession().close(StatusCode.NORMAL,origCloseReason); + + clientSocket.assertReceivedError(EofException.class,null); + + // client triggers close event on client ws-endpoint + // assert - close code==1006 (abnormal) + // assert - close reason message contains (write failure) + clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.ABNORMAL),containsString("EOF")); + } +} diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerWriteThread.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerWriteThread.java index e06e883a6e2..2cea2fda81a 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerWriteThread.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ServerWriteThread.java @@ -19,7 +19,6 @@ package org.eclipse.jetty.websocket.client; import java.io.IOException; -import java.util.concurrent.Exchanger; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -32,7 +31,6 @@ public class ServerWriteThread extends Thread { private static final Logger LOG = Log.getLogger(ServerWriteThread.class); private final ServerConnection conn; - private Exchanger exchanger; private int slowness = -1; private int messageCount = 100; private String message = "Hello"; @@ -42,11 +40,6 @@ public class ServerWriteThread extends Thread this.conn = conn; } - public Exchanger getExchanger() - { - return exchanger; - } - public String getMessage() { return message; @@ -73,12 +66,6 @@ public class ServerWriteThread extends Thread { conn.write(new TextFrame().setPayload(message)); - if (exchanger != null) - { - // synchronized on exchange - exchanger.exchange(message); - } - m.incrementAndGet(); if (slowness > 0) @@ -93,11 +80,6 @@ public class ServerWriteThread extends Thread } } - public void setExchanger(Exchanger exchanger) - { - this.exchanger = exchanger; - } - public void setMessage(String message) { this.message = message; diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowServerTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowServerTest.java index 80720f329f3..4f56ec6d5b0 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowServerTest.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/SlowServerTest.java @@ -49,7 +49,7 @@ public class SlowServerTest public void startClient() throws Exception { client = new WebSocketClient(); - client.getPolicy().setIdleTimeout(60000); + client.setMaxIdleTimeout(60000); client.start(); } @@ -78,7 +78,7 @@ public class SlowServerTest { JettyTrackingSocket tsocket = new JettyTrackingSocket(); client.setMasker(new ZeroMasker()); - client.getPolicy().setIdleTimeout(60000); + client.setMaxIdleTimeout(60000); URI wsUri = server.getWsUri(); Future future = client.connect(tsocket,wsUri); @@ -123,41 +123,38 @@ public class SlowServerTest @Slow public void testServerSlowToSend() throws Exception { - // final Exchanger exchanger = new Exchanger(); - JettyTrackingSocket tsocket = new JettyTrackingSocket(); - // tsocket.messageExchanger = exchanger; + JettyTrackingSocket clientSocket = new JettyTrackingSocket(); client.setMasker(new ZeroMasker()); - client.getPolicy().setIdleTimeout(60000); + client.setMaxIdleTimeout(60000); URI wsUri = server.getWsUri(); - Future future = client.connect(tsocket,wsUri); + Future clientConnectFuture = client.connect(clientSocket,wsUri); - ServerConnection sconnection = server.accept(); - sconnection.setSoTimeout(60000); - sconnection.upgrade(); + ServerConnection serverConn = server.accept(); + serverConn.setSoTimeout(60000); + serverConn.upgrade(); // Confirm connected - future.get(500,TimeUnit.MILLISECONDS); - tsocket.waitForConnected(500,TimeUnit.MILLISECONDS); + clientConnectFuture.get(500,TimeUnit.MILLISECONDS); + clientSocket.waitForConnected(500,TimeUnit.MILLISECONDS); // Have server write slowly. int messageCount = 1000; - ServerWriteThread writer = new ServerWriteThread(sconnection); + ServerWriteThread writer = new ServerWriteThread(serverConn); writer.setMessageCount(messageCount); writer.setMessage("Hello"); - // writer.setExchanger(exchanger); writer.setSlowness(10); writer.start(); writer.join(); // Verify receive - Assert.assertThat("Message Receive Count",tsocket.messageQueue.size(),is(messageCount)); + Assert.assertThat("Message Receive Count",clientSocket.messageQueue.size(),is(messageCount)); // Close - sconnection.close(StatusCode.NORMAL); + serverConn.close(StatusCode.NORMAL); - Assert.assertTrue("Client Socket Closed",tsocket.closeLatch.await(10,TimeUnit.SECONDS)); - tsocket.assertCloseCode(StatusCode.NORMAL); + Assert.assertTrue("Client Socket Closed",clientSocket.closeLatch.await(10,TimeUnit.SECONDS)); + clientSocket.assertCloseCode(StatusCode.NORMAL); } } diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java deleted file mode 100644 index 72c1fc19545..00000000000 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/TimeoutTest.java +++ /dev/null @@ -1,115 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2014 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.websocket.client; - -import static org.hamcrest.Matchers.lessThanOrEqualTo; - -import java.net.URI; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -import org.eclipse.jetty.toolchain.test.TestTracker; -import org.eclipse.jetty.websocket.api.Session; -import org.eclipse.jetty.websocket.api.StatusCode; -import org.eclipse.jetty.websocket.common.test.BlockheadServer; -import org.eclipse.jetty.websocket.common.test.BlockheadServer.ServerConnection; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -/** - * Various tests for Timeout handling - */ -public class TimeoutTest -{ - @Rule - public TestTracker tt = new TestTracker(); - - private BlockheadServer server; - private WebSocketClient client; - - @Before - public void startClient() throws Exception - { - client = new WebSocketClient(); - client.getPolicy().setIdleTimeout(250); // idle timeout (for all tests here) - client.start(); - } - - @Before - public void startServer() throws Exception - { - server = new BlockheadServer(); - server.start(); - } - - @After - public void stopClient() throws Exception - { - client.stop(); - } - - @After - public void stopServer() throws Exception - { - server.stop(); - } - - /** - * In a situation where the upgrade/connection is successful, and there is no activity for a while, the idle timeout triggers on the client side and - * automatically initiates a close handshake. - */ - @Test - public void testIdleDetectedByClient() throws Exception - { - JettyTrackingSocket wsocket = new JettyTrackingSocket(); - - URI wsUri = server.getWsUri(); - client.setMaxIdleTimeout(1000); - Future future = client.connect(wsocket,wsUri); - - ServerConnection ssocket = server.accept(); - ssocket.upgrade(); - - try - { - ssocket.startEcho(); - // Validate that connect occurred - future.get(500,TimeUnit.MILLISECONDS); - wsocket.waitForConnected(500,TimeUnit.MILLISECONDS); - - // Wait for inactivity idle timeout. - long start = System.currentTimeMillis(); - wsocket.waitForClose(2,TimeUnit.SECONDS); - long end = System.currentTimeMillis(); - long dur = (end - start); - // Make sure idle timeout takes less than 5 total seconds - Assert.assertThat("Idle Timeout",dur,lessThanOrEqualTo(3000L)); - - // Client should see a close event, with abnormal status NO_CLOSE - wsocket.assertCloseCode(StatusCode.ABNORMAL); - } - finally - { - ssocket.stopEcho(); - } - } -} diff --git a/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties index 7c9bd368345..9668b131052 100644 --- a/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-client/src/test/resources/jetty-logging.properties @@ -1,12 +1,17 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.LEVEL=DEBUG -# org.eclipse.jetty.io.ChannelEndPoint.LEVEL=INFO +# org.eclipse.jetty.io.ChannelEndPoint.LEVEL=DEBUG +# org.eclipse.jetty.io.SelectChannelEndPoint.LEVEL=DEBUG +# org.eclipse.jetty.io.IdleTimeout.LEVEL=DEBUG +# org.eclipse.jetty.io.FillInterest.LEVEL=DEBUG +# org.eclipse.jetty.io.AbstractConnection.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=WARN # org.eclipse.jetty.websocket.LEVEL=DEBUG # org.eclipse.jetty.websocket.client.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.LEVEL=DEBUG -# org.eclipse.jetty.websocket.common.io.WriteBytesProvider.LEVEL=DEBUG +# org.eclipse.jetty.websocket.common.io.IOState.LEVEL=DEBUG + # org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG # org.eclipse.jetty.websocket.client.TrackingSocket.LEVEL=DEBUG diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/CloseInfo.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/CloseInfo.java index 03a737c1435..f14c25241ae 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/CloseInfo.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/CloseInfo.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.common; import java.nio.ByteBuffer; +import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; import org.eclipse.jetty.util.Utf8StringBuilder; @@ -65,7 +66,8 @@ public class CloseInfo statusCode |= (data.get() & 0xFF) << 8; statusCode |= (data.get() & 0xFF); - if(validate) { + if (validate) + { if ((statusCode < StatusCode.NORMAL) || (statusCode == StatusCode.UNDEFINED) || (statusCode == StatusCode.NO_CLOSE) || (statusCode == StatusCode.NO_CODE) || ((statusCode > 1011) && (statusCode <= 2999)) || (statusCode >= 5000)) { @@ -120,7 +122,7 @@ public class CloseInfo public CloseInfo(int statusCode) { - this(statusCode, null); + this(statusCode,null); } public CloseInfo(int statusCode, String reason) @@ -144,8 +146,9 @@ public class CloseInfo utf = StringUtil.getUtf8Bytes(reason); len += utf.length; } - - ByteBuffer buf = ByteBuffer.allocate(len); + + ByteBuffer buf = BufferUtil.allocate(len); + BufferUtil.flipToFill(buf); buf.put((byte)((statusCode >>> 8) & 0xFF)); buf.put((byte)((statusCode >>> 0) & 0xFF)); @@ -153,7 +156,7 @@ public class CloseInfo { buf.put(utf,0,utf.length); } - buf.flip(); + BufferUtil.flipToFlush(buf,0); return buf; } @@ -162,7 +165,14 @@ public class CloseInfo { CloseFrame frame = new CloseFrame(); frame.setFin(true); - frame.setPayload(asByteBuffer()); + if ((statusCode >= 1000) && (statusCode != StatusCode.NO_CLOSE) && (statusCode != StatusCode.NO_CODE)) + { + if (statusCode == StatusCode.FAILED_TLS_HANDSHAKE) + { + throw new ProtocolException("Close Frame with status code " + statusCode + " not allowed (per RFC6455)"); + } + frame.setPayload(asByteBuffer()); + } return frame; } @@ -180,10 +190,10 @@ public class CloseInfo { return !((statusCode == StatusCode.NORMAL) || (statusCode == StatusCode.NO_CODE)); } - + public boolean isAbnormal() { - return (statusCode == StatusCode.ABNORMAL); + return (statusCode != StatusCode.NORMAL); } @Override diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Parser.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Parser.java index 0cf7e5f4b2c..86195a0289d 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Parser.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/Parser.java @@ -96,8 +96,9 @@ public class Parser private void assertSanePayloadLength(long len) { - if (LOG.isDebugEnabled()) - LOG.debug("Payload Length: {} - {}",len,this); + if (LOG.isDebugEnabled()) { + LOG.debug("{} Payload Length: {} - {}",policy.getBehavior(),len,this); + } // Since we use ByteBuffer so often, having lengths over Integer.MAX_VALUE is really impossible. if (len > Integer.MAX_VALUE) @@ -239,7 +240,7 @@ public class Parser incomingFramesHandler.incomingError(e); } - public void parse(ByteBuffer buffer) + public void parse(ByteBuffer buffer) throws WebSocketException { if (buffer.remaining() <= 0) { @@ -266,13 +267,20 @@ public class Parser { buffer.position(buffer.limit()); // consume remaining reset(); + // let session know notifyWebSocketException(e); + // need to throw for proper close behavior in connection + throw e; } catch (Throwable t) { buffer.position(buffer.limit()); // consume remaining reset(); - notifyWebSocketException(new WebSocketException(t)); + // let session know + WebSocketException e = new WebSocketException(t); + notifyWebSocketException(e); + // need to throw for proper close behavior in connection + throw e; } } @@ -299,7 +307,9 @@ public class Parser private boolean parseFrame(ByteBuffer buffer) { if (LOG.isDebugEnabled()) + { LOG.debug("{} Parsing {} bytes",policy.getBehavior(),buffer.remaining()); + } while (buffer.hasRemaining()) { switch (state) @@ -318,7 +328,8 @@ public class Parser } if (LOG.isDebugEnabled()) - LOG.debug("OpCode {}, fin={} rsv={}{}{}", + LOG.debug("{} OpCode {}, fin={} rsv={}{}{}", + policy.getBehavior(), OpCode.name(opcode), fin, (isRsv1InUse()?'1':'.'), @@ -412,11 +423,6 @@ public class Parser throw new ProtocolException("RSV3 not allowed to be set"); } } - else - { - if (LOG.isDebugEnabled()) - LOG.debug("OpCode {}, fin={} rsv=000",OpCode.name(opcode),fin); - } state = State.PAYLOAD_LEN; break; @@ -591,8 +597,9 @@ public class Parser buffer.limit(limit); buffer.position(buffer.position() + window.remaining()); - if (LOG.isDebugEnabled()) - LOG.debug("Window: {}",BufferUtil.toDetailString(window)); + if (LOG.isDebugEnabled()) { + LOG.debug("{} Window: {}",policy.getBehavior(),BufferUtil.toDetailString(window)); + } maskProcessor.process(window); diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java index 460d1d73b54..04637e392da 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketSession.java @@ -41,6 +41,7 @@ import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.UpgradeRequest; import org.eclipse.jetty.websocket.api.UpgradeResponse; +import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketException; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory; @@ -90,20 +91,19 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc @Override public void close() { - this.close(StatusCode.NORMAL, null); + this.close(StatusCode.NORMAL,null); } @Override public void close(CloseStatus closeStatus) { - this.close(closeStatus.getCode(), closeStatus.getPhrase()); + this.close(closeStatus.getCode(),closeStatus.getPhrase()); } @Override public void close(int statusCode, String reason) { - connection.close(statusCode, reason); - notifyClose(statusCode, reason); + connection.close(statusCode,reason); } /** @@ -115,7 +115,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc connection.disconnect(); // notify of harsh disconnect - notifyClose(StatusCode.NO_CLOSE, "Harsh disconnect"); + notifyClose(StatusCode.NO_CLOSE,"Harsh disconnect"); } public void dispatch(Runnable runnable) @@ -130,7 +130,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc out.append(indent).append(" +- incomingHandler : "); if (incomingHandler instanceof Dumpable) { - ((Dumpable)incomingHandler).dump(out, indent + " "); + ((Dumpable)incomingHandler).dump(out,indent + " "); } else { @@ -140,7 +140,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc out.append(indent).append(" +- outgoingHandler : "); if (outgoingHandler instanceof Dumpable) { - ((Dumpable)outgoingHandler).dump(out, indent + " "); + ((Dumpable)outgoingHandler).dump(out,indent + " "); } else { @@ -273,7 +273,7 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc { final int prime = 31; int result = 1; - result = (prime * result) + ((connection == null) ? 0 : connection.hashCode()); + result = (prime * result) + ((connection == null)?0:connection.hashCode()); return result; } @@ -328,7 +328,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc public void notifyClose(int statusCode, String reason) { - websocket.onClose(new CloseInfo(statusCode, reason)); + if (LOG.isDebugEnabled()) + { + LOG.debug("notifyClose({},{})",statusCode,reason); + } + websocket.onClose(new CloseInfo(statusCode,reason)); } public void notifyError(Throwable cause) @@ -342,12 +346,13 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc { switch (state) { - case CLOSING: + case CLOSED: // notify session listeners for (SessionListener listener : sessionListeners) { try { + LOG.debug("{}.onSessionClosed()",listener.getClass().getSimpleName()); listener.onSessionClosed(this); } catch (Throwable t) @@ -355,12 +360,10 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc LOG.ignore(t); } } - break; - case CLOSED: IOState ioState = this.connection.getIOState(); CloseInfo close = ioState.getCloseInfo(); // confirmed close of local endpoint - notifyClose(close.getStatusCode(), close.getReason()); + notifyClose(close.getStatusCode(),close.getReason()); break; case OPEN: // notify session listeners @@ -394,17 +397,32 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc connection.getIOState().onConnected(); // Connect remote - remote = new WebSocketRemoteEndpoint(connection, outgoingHandler, getBatchMode()); + remote = new WebSocketRemoteEndpoint(connection,outgoingHandler,getBatchMode()); - // Open WebSocket - websocket.openSession(this); - - // Open connection - connection.getIOState().onOpened(); - - if (LOG.isDebugEnabled()) + try { - LOG.debug("open -> {}", dump()); + // Open WebSocket + websocket.openSession(this); + + // Open connection + connection.getIOState().onOpened(); + + if (LOG.isDebugEnabled()) + { + LOG.debug("open -> {}",dump()); + } + } + catch (Throwable t) + { + // Exception on end-user WS-Endpoint. + // Fast-fail & close connection with reason. + int statusCode = StatusCode.SERVER_ERROR; + if(policy.getBehavior() == WebSocketBehavior.CLIENT) + { + statusCode = StatusCode.POLICY_VIOLATION; + } + + close(statusCode,t.getMessage()); } } @@ -444,11 +462,11 @@ public class WebSocketSession extends ContainerLifeCycle implements Session, Inc List values = entry.getValue(); if (values != null) { - this.parameterMap.put(entry.getKey(), values.toArray(new String[values.size()])); + this.parameterMap.put(entry.getKey(),values.toArray(new String[values.size()])); } else { - this.parameterMap.put(entry.getKey(), new String[0]); + this.parameterMap.put(entry.getKey(),new String[0]); } } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java index 8cc60f06c43..7a55b2db4f9 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/events/AbstractEventDriver.java @@ -88,13 +88,7 @@ public abstract class AbstractEventDriver implements IncomingFrames, EventDriver { if (LOG.isDebugEnabled()) { - LOG.debug("incoming(WebSocketException)",e); - } - - if (e instanceof CloseException) - { - CloseException close = (CloseException)e; - terminateConnection(close.getStatusCode(),close.getMessage()); + LOG.debug("incomingError(" + e.getClass().getName() + ")",e); } onError(e); @@ -105,7 +99,7 @@ public abstract class AbstractEventDriver implements IncomingFrames, EventDriver { if (LOG.isDebugEnabled()) { - LOG.debug("{}.onFrame({})",websocket.getClass().getSimpleName(),frame); + LOG.debug("incomingFrame({})",frame); } try @@ -226,6 +220,7 @@ public abstract class AbstractEventDriver implements IncomingFrames, EventDriver catch (Throwable t) { unhandled(t); + throw t; } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java index c9f98bb9d57..cea5909865c 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java @@ -32,7 +32,6 @@ import java.util.concurrent.atomic.AtomicLong; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; -import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; @@ -54,13 +53,13 @@ import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.ConnectionState; import org.eclipse.jetty.websocket.common.Generator; import org.eclipse.jetty.websocket.common.LogicalConnection; +import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.Parser; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener; /** - * Provides the implementation of {@link LogicalConnection} within the - * framework of the new {@link Connection} framework of {@code jetty-io}. + * Provides the implementation of {@link LogicalConnection} within the framework of the new {@link Connection} framework of {@code jetty-io}. */ public abstract class AbstractWebSocketConnection extends AbstractConnection implements LogicalConnection, ConnectionStateListener, Dumpable { @@ -68,7 +67,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp { private Flusher(ByteBufferPool bufferPool, Generator generator, EndPoint endpoint) { - super(bufferPool, generator, endpoint, getPolicy().getMaxBinaryMessageBufferSize(), 8); + super(bufferPool,generator,endpoint,getPolicy().getMaxBinaryMessageBufferSize(),8); } @Override @@ -106,7 +105,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp // Abnormal Close reason = CloseStatus.trimMaxReasonLength(reason); session.notifyError(x); - session.notifyClose(StatusCode.NO_CLOSE,reason); + session.notifyClose(StatusCode.ABNORMAL,reason); disconnect(); // disconnect endpoint & connection } @@ -116,10 +115,11 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp { private final boolean outputOnly; - public OnDisconnectCallback(boolean outputOnly) { + public OnDisconnectCallback(boolean outputOnly) + { this.outputOnly = outputOnly; } - + @Override public void writeFailed(Throwable x) { @@ -218,10 +218,10 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp @Override public void close(int statusCode, String reason) { + LOG.debug("close({},{})",statusCode,reason); CloseInfo close = new CloseInfo(statusCode,reason); if (statusCode == StatusCode.ABNORMAL) { - flusher.close(); // TODO this makes the IdleTimeoutTest pass, but I'm dubious it is the correct way ioState.onAbnormalClose(close); } else @@ -230,7 +230,6 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp } } - @Override public void disconnect() { @@ -366,7 +365,9 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp @Override public void onClose() { + LOG.debug("{} onClose()",policy.getBehavior()); super.onClose(); + // ioState.onDisconnected(); flusher.close(); } @@ -385,18 +386,15 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp { // Fire out a close frame, indicating abnormal shutdown, then disconnect CloseInfo abnormal = new CloseInfo(StatusCode.SHUTDOWN,"Abnormal Close - " + ioState.getCloseInfo().getReason()); - outgoingFrame(abnormal.asFrame(),new OnDisconnectCallback(false), BatchMode.OFF); - } - else - { - // Just disconnect - this.disconnect(false); + outgoingFrame(abnormal.asFrame(),new OnDisconnectCallback(false),BatchMode.OFF); } + // Just disconnect + this.disconnect(false); break; case CLOSING: CloseInfo close = ioState.getCloseInfo(); // reply to close handshake from remote - outgoingFrame(close.asFrame(),new OnDisconnectCallback(true), BatchMode.OFF); + outgoingFrame(close.asFrame(),new OnDisconnectCallback(true),BatchMode.OFF); default: break; } @@ -447,20 +445,26 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp @Override protected boolean onReadTimeout() { - LOG.debug("{} Read Timeout",policy.getBehavior()); - IOState state = getIOState(); - if ((state.getConnectionState() == ConnectionState.CLOSING) || (state.getConnectionState() == ConnectionState.CLOSED)) + ConnectionState cstate = state.getConnectionState(); + LOG.debug("{} Read Timeout - {}",policy.getBehavior(),cstate); + + if (cstate == ConnectionState.CLOSED) { - // close already initiated, extra timeouts not relevant + // close already completed, extra timeouts not relevant // allow underlying connection and endpoint to disconnect on its own return true; } - // Initiate close - politely send close frame. - session.notifyError(new SocketTimeoutException("Timeout on Read")); - // This is an Abnormal Close condition - close(StatusCode.ABNORMAL,"Idle Timeout"); + try + { + session.notifyError(new SocketTimeoutException("Timeout on Read")); + } + finally + { + // This is an Abnormal Close condition + close(StatusCode.ABNORMAL,"Idle Timeout"); + } return false; } @@ -476,7 +480,21 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp LOG.debug("outgoingFrame({}, {})",frame,callback); } - flusher.enqueue(frame,callback, batchMode); + CloseInfo close = null; + // grab a copy of the frame details before masking and whatnot + if (frame.getOpCode() == OpCode.CLOSE) + { + close = new CloseInfo(frame); + } + + flusher.enqueue(frame,callback,batchMode); + + // now trigger local close + if (close != null) + { + LOG.debug("outgoing CLOSE frame - {}: {}",frame,close); + ioState.onCloseLocal(close); + } } private int read(ByteBuffer buffer) @@ -504,7 +522,6 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp LOG.debug("Filled {} bytes - {}",filled,BufferUtil.toDetailString(buffer)); } parser.parse(buffer); - // TODO: has the end user application already consumed what it was given? } } } @@ -520,6 +537,12 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp close(e.getStatusCode(),e.getMessage()); return -1; } + catch (Throwable t) + { + LOG.warn(t); + close(StatusCode.ABNORMAL,t.getMessage()); + return -1; + } } @Override diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FrameFlusher.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FrameFlusher.java index a7bb3cb8c7d..6e995e579fc 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FrameFlusher.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/FrameFlusher.java @@ -29,7 +29,6 @@ import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.ArrayQueue; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IteratingCallback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -45,246 +44,22 @@ import org.eclipse.jetty.websocket.common.frames.BinaryFrame; */ public class FrameFlusher { - public static final BinaryFrame FLUSH_FRAME = new BinaryFrame(); - private static final Logger LOG = Log.getLogger(FrameFlusher.class); - - private final ByteBufferPool bufferPool; - private final EndPoint endpoint; - private final int bufferSize; - private final Generator generator; - private final int maxGather; - private final Object lock = new Object(); - private final ArrayQueue queue = new ArrayQueue<>(16, 16, lock); - private final Flusher flusher = new Flusher(); - private final AtomicBoolean closed = new AtomicBoolean(); - private volatile Throwable failure; - - public FrameFlusher(ByteBufferPool bufferPool, Generator generator, EndPoint endpoint, int bufferSize, int maxGather) - { - this.bufferPool = bufferPool; - this.endpoint = endpoint; - this.bufferSize = bufferSize; - this.generator = Objects.requireNonNull(generator); - this.maxGather = maxGather; - } - - public void enqueue(Frame frame, WriteCallback callback, BatchMode batchMode) - { - if (closed.get()) - { - notifyCallbackFailure(callback, new EOFException("Connection has been closed locally")); - return; - } - if (flusher.isFailed()) - { - notifyCallbackFailure(callback, failure); - return; - } - - FrameEntry entry = new FrameEntry(frame, callback, batchMode); - - synchronized (lock) - { - switch (frame.getOpCode()) - { - case OpCode.PING: - { - // Prepend PINGs so they are processed first. - queue.add(0, entry); - break; - } - case OpCode.CLOSE: - { - // There may be a chance that other frames are - // added after this close frame, but we will - // fail them later to keep it simple here. - closed.set(true); - queue.add(entry); - break; - } - default: - { - queue.add(entry); - break; - } - } - } - - if (LOG.isDebugEnabled()) - LOG.debug("{} queued {}", this, entry); - - flusher.iterate(); - } - - public void close() - { - if (closed.compareAndSet(false, true)) - { - LOG.debug("{} closing {}", this); - EOFException eof = new EOFException("Connection has been closed locally"); - flusher.failed(eof); - - // Fail also queued entries. - List entries = new ArrayList<>(); - synchronized (lock) - { - entries.addAll(queue); - queue.clear(); - } - // Notify outside sync block. - for (FrameEntry entry : entries) - notifyCallbackFailure(entry.callback, eof); - } - } - - protected void onFailure(Throwable x) - { - LOG.warn(x); - } - - protected void notifyCallbackSuccess(WriteCallback callback) - { - try - { - if (callback != null) - callback.writeSuccess(); - } - catch (Throwable x) - { - LOG.debug("Exception while notifying success of callback " + callback, x); - } - } - - protected void notifyCallbackFailure(WriteCallback callback, Throwable failure) - { - try - { - if (callback != null) - callback.writeFailed(failure); - } - catch (Throwable x) - { - LOG.debug("Exception while notifying failure of callback " + callback, x); - } - } - - @Override - public String toString() - { - ByteBuffer aggregate = flusher.aggregate; - return String.format("%s[queueSize=%d,aggregateSize=%d,failure=%s]", - getClass().getSimpleName(), - queue.size(), - aggregate == null ? 0 : aggregate.position(), - failure); - } - private class Flusher extends IteratingCallback { private final List entries = new ArrayList<>(maxGather); - private final List buffers = new ArrayList<>(maxGather * 2 + 1); + private final List buffers = new ArrayList<>((maxGather * 2) + 1); private ByteBuffer aggregate; private BatchMode batchMode; - @Override - protected Action process() throws Exception - { - int space = aggregate == null ? bufferSize : BufferUtil.space(aggregate); - BatchMode currentBatchMode = BatchMode.AUTO; - synchronized (lock) - { - while (entries.size() <= maxGather && !queue.isEmpty()) - { - FrameEntry entry = queue.remove(0); - currentBatchMode = BatchMode.max(currentBatchMode, entry.batchMode); - - // Force flush if we need to. - if (entry.frame == FLUSH_FRAME) - currentBatchMode = BatchMode.OFF; - - int payloadLength = BufferUtil.length(entry.frame.getPayload()); - int approxFrameLength = Generator.MAX_HEADER_LENGTH + payloadLength; - - // If it is a "big" frame, avoid copying into the aggregate buffer. - if (approxFrameLength > (bufferSize >> 2)) - currentBatchMode = BatchMode.OFF; - - // If the aggregate buffer overflows, do not batch. - space -= approxFrameLength; - if (space <= 0) - currentBatchMode = BatchMode.OFF; - - entries.add(entry); - } - } - - if (LOG.isDebugEnabled()) - LOG.debug("{} processing {} entries: {}", FrameFlusher.this, entries.size(), entries); - - if (entries.isEmpty()) - { - if (batchMode != BatchMode.AUTO) - { - // Nothing more to do, release the aggregate buffer if we need to. - // Releasing it here rather than in succeeded() allows for its reuse. - releaseAggregate(); - return Action.IDLE; - } - - LOG.debug("{} auto flushing", FrameFlusher.this); - return flush(); - } - - batchMode = currentBatchMode; - - return currentBatchMode == BatchMode.OFF ? flush() : batch(); - } - - private Action flush() - { - if (!BufferUtil.isEmpty(aggregate)) - { - buffers.add(aggregate); - if (LOG.isDebugEnabled()) - LOG.debug("{} flushing aggregate {}", FrameFlusher.this, aggregate); - } - - // Do not allocate the iterator here. - for (int i = 0; i < entries.size(); ++i) - { - FrameEntry entry = entries.get(i); - // Skip the "synthetic" frame used for flushing. - if (entry.frame == FLUSH_FRAME) - continue; - buffers.add(entry.generateHeaderBytes()); - ByteBuffer payload = entry.frame.getPayload(); - if (BufferUtil.hasContent(payload)) - buffers.add(payload); - } - - if (LOG.isDebugEnabled()) - LOG.debug("{} flushing {} frames: {}", FrameFlusher.this, entries.size(), entries); - - if (buffers.isEmpty()) - { - releaseAggregate(); - // We may have the FLUSH_FRAME to notify. - succeedEntries(); - return Action.IDLE; - } - - endpoint.write(this, buffers.toArray(new ByteBuffer[buffers.size()])); - buffers.clear(); - return Action.SCHEDULED; - } - private Action batch() { if (aggregate == null) { - aggregate = bufferPool.acquire(bufferSize, true); + aggregate = bufferPool.acquire(bufferSize,true); if (LOG.isDebugEnabled()) - LOG.debug("{} acquired aggregate buffer {}", FrameFlusher.this, aggregate); + { + LOG.debug("{} acquired aggregate buffer {}",FrameFlusher.this,aggregate); + } } // Do not allocate the iterator here. @@ -296,17 +71,149 @@ public class FrameFlusher ByteBuffer payload = entry.frame.getPayload(); if (BufferUtil.hasContent(payload)) - BufferUtil.append(aggregate, payload); + { + BufferUtil.append(aggregate,payload); + } } if (LOG.isDebugEnabled()) - LOG.debug("{} aggregated {} frames: {}", FrameFlusher.this, entries.size(), entries); + { + LOG.debug("{} aggregated {} frames: {}",FrameFlusher.this,entries.size(),entries); + } succeeded(); return Action.SCHEDULED; } + @Override + protected void completed() + { + // This IteratingCallback never completes. + } + + @Override + public void failed(Throwable x) + { + for (FrameEntry entry : entries) + { + notifyCallbackFailure(entry.callback,x); + entry.release(); + } + entries.clear(); + super.failed(x); + failure = x; + onFailure(x); + } + + private Action flush() + { + if (!BufferUtil.isEmpty(aggregate)) + { + buffers.add(aggregate); + if (LOG.isDebugEnabled()) + { + LOG.debug("{} flushing aggregate {}",FrameFlusher.this,aggregate); + } + } + + // Do not allocate the iterator here. + for (int i = 0; i < entries.size(); ++i) + { + FrameEntry entry = entries.get(i); + // Skip the "synthetic" frame used for flushing. + if (entry.frame == FLUSH_FRAME) + { + continue; + } + buffers.add(entry.generateHeaderBytes()); + ByteBuffer payload = entry.frame.getPayload(); + if (BufferUtil.hasContent(payload)) + { + buffers.add(payload); + } + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("{} flushing {} frames: {}",FrameFlusher.this,entries.size(),entries); + } + + if (buffers.isEmpty()) + { + releaseAggregate(); + // We may have the FLUSH_FRAME to notify. + succeedEntries(); + return Action.IDLE; + } + + endpoint.write(this,buffers.toArray(new ByteBuffer[buffers.size()])); + buffers.clear(); + return Action.SCHEDULED; + } + + @Override + protected Action process() throws Exception + { + int space = aggregate == null?bufferSize:BufferUtil.space(aggregate); + BatchMode currentBatchMode = BatchMode.AUTO; + synchronized (lock) + { + while ((entries.size() <= maxGather) && !queue.isEmpty()) + { + FrameEntry entry = queue.remove(0); + currentBatchMode = BatchMode.max(currentBatchMode,entry.batchMode); + + // Force flush if we need to. + if (entry.frame == FLUSH_FRAME) + { + currentBatchMode = BatchMode.OFF; + } + + int payloadLength = BufferUtil.length(entry.frame.getPayload()); + int approxFrameLength = Generator.MAX_HEADER_LENGTH + payloadLength; + + // If it is a "big" frame, avoid copying into the aggregate buffer. + if (approxFrameLength > (bufferSize >> 2)) + { + currentBatchMode = BatchMode.OFF; + } + + // If the aggregate buffer overflows, do not batch. + space -= approxFrameLength; + if (space <= 0) + { + currentBatchMode = BatchMode.OFF; + } + + entries.add(entry); + } + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("{} processing {} entries: {}",FrameFlusher.this,entries.size(),entries); + } + + if (entries.isEmpty()) + { + if (batchMode != BatchMode.AUTO) + { + // Nothing more to do, release the aggregate buffer if we need to. + // Releasing it here rather than in succeeded() allows for its reuse. + releaseAggregate(); + return Action.IDLE; + } + + LOG.debug("{} auto flushing",FrameFlusher.this); + return flush(); + } + + batchMode = currentBatchMode; + + return currentBatchMode == BatchMode.OFF?flush():batch(); + } + private void releaseAggregate() { - if (aggregate != null && BufferUtil.isEmpty(aggregate)) + if ((aggregate != null) && BufferUtil.isEmpty(aggregate)) { bufferPool.release(aggregate); aggregate = null; @@ -331,26 +238,6 @@ public class FrameFlusher } entries.clear(); } - - @Override - protected void completed() - { - // This IteratingCallback never completes. - } - - @Override - public void failed(Throwable x) - { - for (FrameEntry entry : entries) - { - notifyCallbackFailure(entry.callback, x); - entry.release(); - } - entries.clear(); - super.failed(x); - failure = x; - onFailure(x); - } } private class FrameEntry @@ -374,7 +261,7 @@ public class FrameFlusher private void generateHeaderBytes(ByteBuffer buffer) { - generator.generateHeaderBytes(frame, buffer); + generator.generateHeaderBytes(frame,buffer); } private void release() @@ -389,7 +276,145 @@ public class FrameFlusher @Override public String toString() { - return String.format("%s[%s,%s,%s,%s]", getClass().getSimpleName(), frame, callback, batchMode, failure); + return String.format("%s[%s,%s,%s,%s]",getClass().getSimpleName(),frame,callback,batchMode,failure); } } + + public static final BinaryFrame FLUSH_FRAME = new BinaryFrame(); + private static final Logger LOG = Log.getLogger(FrameFlusher.class); + private final ByteBufferPool bufferPool; + private final EndPoint endpoint; + private final int bufferSize; + private final Generator generator; + private final int maxGather; + private final Object lock = new Object(); + private final ArrayQueue queue = new ArrayQueue<>(16,16,lock); + private final Flusher flusher = new Flusher(); + private final AtomicBoolean closed = new AtomicBoolean(); + private volatile Throwable failure; + + public FrameFlusher(ByteBufferPool bufferPool, Generator generator, EndPoint endpoint, int bufferSize, int maxGather) + { + this.bufferPool = bufferPool; + this.endpoint = endpoint; + this.bufferSize = bufferSize; + this.generator = Objects.requireNonNull(generator); + this.maxGather = maxGather; + } + + public void close() + { + if (closed.compareAndSet(false,true)) + { + LOG.debug("{} closing {}",this); + EOFException eof = new EOFException("Connection has been closed locally"); + flusher.failed(eof); + + // Fail also queued entries. + List entries = new ArrayList<>(); + synchronized (lock) + { + entries.addAll(queue); + queue.clear(); + } + // Notify outside sync block. + for (FrameEntry entry : entries) + { + notifyCallbackFailure(entry.callback,eof); + } + } + } + + public void enqueue(Frame frame, WriteCallback callback, BatchMode batchMode) + { + if (closed.get()) + { + notifyCallbackFailure(callback,new EOFException("Connection has been closed locally")); + return; + } + if (flusher.isFailed()) + { + notifyCallbackFailure(callback,failure); + return; + } + + FrameEntry entry = new FrameEntry(frame,callback,batchMode); + + synchronized (lock) + { + switch (frame.getOpCode()) + { + case OpCode.PING: + { + // Prepend PINGs so they are processed first. + queue.add(0,entry); + break; + } + case OpCode.CLOSE: + { + // There may be a chance that other frames are + // added after this close frame, but we will + // fail them later to keep it simple here. + closed.set(true); + queue.add(entry); + break; + } + default: + { + queue.add(entry); + break; + } + } + } + + if (LOG.isDebugEnabled()) + { + LOG.debug("{} queued {}",this,entry); + } + + flusher.iterate(); + } + + protected void notifyCallbackFailure(WriteCallback callback, Throwable failure) + { + try + { + if (callback != null) + { + callback.writeFailed(failure); + } + } + catch (Throwable x) + { + LOG.debug("Exception while notifying failure of callback " + callback,x); + } + } + + protected void notifyCallbackSuccess(WriteCallback callback) + { + try + { + if (callback != null) + { + callback.writeSuccess(); + } + } + catch (Throwable x) + { + LOG.debug("Exception while notifying success of callback " + callback,x); + } + } + + protected void onFailure(Throwable x) + { + LOG.warn(x); + } + + @Override + public String toString() + { + ByteBuffer aggregate = flusher.aggregate; + return String.format("%s[queueSize=%d,aggregateSize=%d,failure=%s]",getClass().getSimpleName(),queue.size(),aggregate == null?0:aggregate.position(), + failure); + } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java index 6d4dbc42729..815d2d30ad0 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java @@ -139,6 +139,10 @@ public class IOState { for (ConnectionStateListener listener : listeners) { + if (LOG.isDebugEnabled()) + { + LOG.debug("{}.onConnectionStateChange({})",listener.getClass().getSimpleName(),state.name()); + } listener.onConnectionStateChange(state); } } @@ -166,8 +170,7 @@ public class IOState } this.state = ConnectionState.CLOSED; - if (closeInfo == null) - this.closeInfo = close; + this.closeInfo = close; this.inputAvailable = false; this.outputAvailable = false; this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL; @@ -193,16 +196,16 @@ public class IOState if (initialState == ConnectionState.CONNECTED) { - // fast close. a local close request from end-user onConnected() method + // fast close. a local close request from end-user onConnect/onOpen method LOG.debug("FastClose in CONNECTED detected"); // Force the state open (to allow read/write to endpoint) onOpened(); + LOG.debug("FastClose continuing with Closure"); } synchronized (this) { - if (closeInfo == null) - closeInfo = close; + closeInfo = close; boolean in = inputAvailable; boolean out = outputAvailable; @@ -236,7 +239,6 @@ public class IOState LOG.debug("notifying state listeners: {}",event); notifyStateListeners(event); - /* // if abnormal, we don't expect an answer. if (close.isAbnormal()) { @@ -253,7 +255,6 @@ public class IOState notifyStateListeners(event); return; } - */ } } @@ -272,8 +273,7 @@ public class IOState return; } - if (closeInfo == null) - closeInfo = close; + closeInfo = close; boolean in = inputAvailable; boolean out = outputAvailable; @@ -360,7 +360,7 @@ public class IOState // already opened return; } - + if (this.state != ConnectionState.CONNECTED) { LOG.debug("Unable to open, not in CONNECTED state: {}",this.state); @@ -394,12 +394,11 @@ public class IOState return; } - CloseInfo close = new CloseInfo(StatusCode.NO_CLOSE,"Read EOF"); + CloseInfo close = new CloseInfo(StatusCode.ABNORMAL,"Read EOF"); this.cleanClose = false; this.state = ConnectionState.CLOSED; - if (closeInfo == null) - this.closeInfo = close; + this.closeInfo = close; this.inputAvailable = false; this.outputAvailable = false; this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL; @@ -408,6 +407,58 @@ public class IOState notifyStateListeners(event); } + public void onDisconnected() + { + ConnectionState event = null; + synchronized (this) + { + if (this.state == ConnectionState.CLOSED) + { + // already closed + return; + } + + CloseInfo close = new CloseInfo(StatusCode.ABNORMAL,"Disconnected"); + + this.cleanClose = false; + this.state = ConnectionState.CLOSED; + this.closeInfo = close; + this.inputAvailable = false; + this.outputAvailable = false; + this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL; + event = this.state; + } + notifyStateListeners(event); + } + + @Override + public String toString() + { + StringBuilder str = new StringBuilder(); + str.append(this.getClass().getSimpleName()); + str.append("@").append(Integer.toHexString(hashCode())); + str.append("[").append(state); + str.append(','); + if (!inputAvailable) + { + str.append('!'); + } + str.append("in,"); + if (!outputAvailable) + { + str.append('!'); + } + str.append("out"); + if ((state == ConnectionState.CLOSED) || (state == ConnectionState.CLOSING)) + { + str.append(",close=").append(closeInfo); + str.append(",clean=").append(cleanClose); + str.append(",closeSource=").append(closeHandshakeSource); + } + str.append(']'); + return str.toString(); + } + public boolean wasAbnormalClose() { return closeHandshakeSource == CloseHandshakeSource.ABNORMAL; @@ -427,4 +478,5 @@ public class IOState { return closeHandshakeSource == CloseHandshakeSource.REMOTE; } + } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java index 32b9c1b72da..2194ec516c4 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/util/ReflectUtils.java @@ -392,4 +392,4 @@ public class ReflectUtils } return name; } -} +} \ No newline at end of file diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/CloseInfoTest.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/CloseInfoTest.java new file mode 100644 index 00000000000..a40e3063369 --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/CloseInfoTest.java @@ -0,0 +1,166 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.websocket.common; + +import static org.eclipse.jetty.websocket.api.StatusCode.*; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.nio.ByteBuffer; + +import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.websocket.api.ProtocolException; +import org.eclipse.jetty.websocket.common.frames.CloseFrame; +import org.junit.Test; + +public class CloseInfoTest +{ + /** + * A test where no close is provided + */ + @Test + public void testAnonymousClose() + { + CloseInfo close = new CloseInfo(); + assertThat("close.code",close.getStatusCode(),is(NO_CODE)); + assertThat("close.reason",close.getReason(),nullValue()); + + CloseFrame frame = close.asFrame(); + assertThat("close frame op code",frame.getOpCode(),is(OpCode.CLOSE)); + // should result in no payload + assertThat("close frame has payload",frame.hasPayload(),is(false)); + assertThat("close frame payload length",frame.getPayloadLength(),is(0)); + } + + /** + * A test where NO_CODE (1005) is provided + */ + @Test + public void testNoCode() + { + CloseInfo close = new CloseInfo(NO_CODE); + assertThat("close.code",close.getStatusCode(),is(NO_CODE)); + assertThat("close.reason",close.getReason(),nullValue()); + + CloseFrame frame = close.asFrame(); + assertThat("close frame op code",frame.getOpCode(),is(OpCode.CLOSE)); + // should result in no payload + assertThat("close frame has payload",frame.hasPayload(),is(false)); + assertThat("close frame payload length",frame.getPayloadLength(),is(0)); + } + + /** + * A test where NO_CLOSE (1006) is provided + */ + @Test + public void testNoClose() + { + CloseInfo close = new CloseInfo(NO_CLOSE); + assertThat("close.code",close.getStatusCode(),is(NO_CLOSE)); + assertThat("close.reason",close.getReason(),nullValue()); + + CloseFrame frame = close.asFrame(); + assertThat("close frame op code",frame.getOpCode(),is(OpCode.CLOSE)); + // should result in no payload + assertThat("close frame has payload",frame.hasPayload(),is(false)); + assertThat("close frame payload length",frame.getPayloadLength(),is(0)); + } + + /** + * A test of FAILED_TLS_HANDSHAKE (1007) + */ + @Test + public void testFailedTlsHandshake() + { + CloseInfo close = new CloseInfo(FAILED_TLS_HANDSHAKE); + assertThat("close.code",close.getStatusCode(),is(FAILED_TLS_HANDSHAKE)); + assertThat("close.reason",close.getReason(),nullValue()); + + try + { + @SuppressWarnings("unused") + CloseFrame frame = close.asFrame(); + fail("Expected " + ProtocolException.class.getName()); + } + catch (ProtocolException e) + { + // expected path + assertThat("ProtocolException message",e.getMessage(),containsString("not allowed (per RFC6455)")); + } + } + + /** + * A test of NORMAL (1000) + */ + @Test + public void testNormal() + { + CloseInfo close = new CloseInfo(NORMAL); + assertThat("close.code",close.getStatusCode(),is(NORMAL)); + assertThat("close.reason",close.getReason(),nullValue()); + + CloseFrame frame = close.asFrame(); + assertThat("close frame op code",frame.getOpCode(),is(OpCode.CLOSE)); + assertThat("close frame payload length",frame.getPayloadLength(),is(2)); + } + + private ByteBuffer asByteBuffer(int statusCode, String reason) + { + int len = 2; // status code length + byte utf[] = null; + if (StringUtil.isNotBlank(reason)) + { + utf = StringUtil.getUtf8Bytes(reason); + len += utf.length; + } + + ByteBuffer buf = BufferUtil.allocate(len); + BufferUtil.flipToFill(buf); + buf.put((byte)((statusCode >>> 8) & 0xFF)); + buf.put((byte)((statusCode >>> 0) & 0xFF)); + + if (utf != null) + { + buf.put(utf,0,utf.length); + } + BufferUtil.flipToFlush(buf,0); + + return buf; + } + + @Test + public void testFromFrame() + { + ByteBuffer payload = asByteBuffer(NORMAL,null); + assertThat("payload length", payload.remaining(), is(2)); + CloseFrame frame = new CloseFrame(); + frame.setPayload(payload); + + // create from frame + CloseInfo close = new CloseInfo(frame); + assertThat("close.code",close.getStatusCode(),is(NORMAL)); + assertThat("close.reason",close.getReason(),nullValue()); + + // and back again + frame = close.asFrame(); + assertThat("close frame op code",frame.getOpCode(),is(OpCode.CLOSE)); + assertThat("close frame payload length",frame.getPayloadLength(),is(2)); + } +} diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase4.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase4.java index fe6011db002..6b0f01836c3 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase4.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/ab/TestABCase4.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.websocket.common.ab; import java.nio.ByteBuffer; import org.eclipse.jetty.util.log.StacklessLogging; +import org.eclipse.jetty.websocket.api.ProtocolException; import org.eclipse.jetty.websocket.api.WebSocketBehavior; import org.eclipse.jetty.websocket.api.WebSocketException; import org.eclipse.jetty.websocket.api.WebSocketPolicy; @@ -39,8 +40,7 @@ public class TestABCase4 { ByteBuffer expected = ByteBuffer.allocate(32); - expected.put(new byte[] - { (byte)0x8b, 0x00 }); + expected.put(new byte[] { (byte)0x8b, 0x00 }); expected.flip(); @@ -50,10 +50,17 @@ public class TestABCase4 { Parser parser = new UnitParser(policy); parser.setIncomingFramesHandler(capture); - parser.parse(expected); + try + { + parser.parse(expected); + } + catch (ProtocolException ignore) + { + // ignore + } } - Assert.assertEquals( "error on undefined opcode", 1, capture.getErrorCount(WebSocketException.class)) ; + Assert.assertEquals("error on undefined opcode",1,capture.getErrorCount(WebSocketException.class)); Throwable known = capture.getErrors().poll(); @@ -65,8 +72,7 @@ public class TestABCase4 { ByteBuffer expected = ByteBuffer.allocate(32); - expected.put(new byte[] - { (byte)0x8c, 0x01, 0x00 }); + expected.put(new byte[] { (byte)0x8c, 0x01, 0x00 }); expected.flip(); @@ -76,24 +82,29 @@ public class TestABCase4 { Parser parser = new UnitParser(policy); parser.setIncomingFramesHandler(capture); - parser.parse(expected); + try + { + parser.parse(expected); + } + catch (ProtocolException ignore) + { + // ignore + } } - Assert.assertEquals( "error on undefined opcode", 1, capture.getErrorCount(WebSocketException.class)) ; + Assert.assertEquals("error on undefined opcode",1,capture.getErrorCount(WebSocketException.class)); Throwable known = capture.getErrors().poll(); Assert.assertTrue("undefined option should be in message",known.getMessage().contains("Unknown opcode: 12")); } - @Test public void testParserNonControlOpCode3Case4_1_1() throws Exception { ByteBuffer expected = ByteBuffer.allocate(32); - expected.put(new byte[] - { (byte)0x83, 0x00 }); + expected.put(new byte[] { (byte)0x83, 0x00 }); expected.flip(); @@ -103,10 +114,17 @@ public class TestABCase4 { Parser parser = new UnitParser(policy); parser.setIncomingFramesHandler(capture); - parser.parse(expected); + try + { + parser.parse(expected); + } + catch (ProtocolException ignore) + { + // ignore + } } - Assert.assertEquals( "error on undefined opcode", 1, capture.getErrorCount(WebSocketException.class)) ; + Assert.assertEquals("error on undefined opcode",1,capture.getErrorCount(WebSocketException.class)); Throwable known = capture.getErrors().poll(); @@ -118,8 +136,7 @@ public class TestABCase4 { ByteBuffer expected = ByteBuffer.allocate(32); - expected.put(new byte[] - { (byte)0x84, 0x01, 0x00 }); + expected.put(new byte[] { (byte)0x84, 0x01, 0x00 }); expected.flip(); @@ -129,10 +146,17 @@ public class TestABCase4 { Parser parser = new UnitParser(policy); parser.setIncomingFramesHandler(capture); - parser.parse(expected); + try + { + parser.parse(expected); + } + catch (ProtocolException ignore) + { + // ignore + } } - Assert.assertEquals( "error on undefined opcode", 1, capture.getErrorCount(WebSocketException.class)) ; + Assert.assertEquals("error on undefined opcode",1,capture.getErrorCount(WebSocketException.class)); Throwable known = capture.getErrors().poll(); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java index bd02460c663..dedba3e351c 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java @@ -18,7 +18,8 @@ package org.eclipse.jetty.websocket.common.test; -import java.io.Closeable; +import static org.hamcrest.Matchers.*; + import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -68,10 +69,6 @@ import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener; import org.eclipse.jetty.websocket.common.io.http.HttpResponseHeaderParser; import org.junit.Assert; -import static org.hamcrest.Matchers.anyOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; - /** * A simple websocket client for performing unit tests with. *

    @@ -84,7 +81,7 @@ import static org.hamcrest.Matchers.notNullValue; * with regards to basic IO behavior, a write should work as expected, a read should work as expected, but what byte it sends or reads is not within its * scope. */ -public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener, Closeable +public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener, AutoCloseable { private static final String REQUEST_HASH_KEY = "dGhlIHNhbXBsZSBub25jZQ=="; private static final int BUFFER_SIZE = 8192; @@ -182,22 +179,14 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti public void close(int statusCode, String message) { + LOG.debug("close({},{})",statusCode,message); CloseInfo close = new CloseInfo(statusCode,message); - ioState.onCloseLocal(close); - if (!ioState.isClosed()) { - WebSocketFrame frame = close.asFrame(); - LOG.debug("Issuing: {}",frame); - try - { - write(frame); - } - catch (IOException e) - { - LOG.debug(e); - } + ioState.onCloseLocal(close); + } else { + LOG.debug("Not issuing close. ioState = {}",ioState); } } @@ -429,13 +418,8 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti { LOG.info("Client parsed {} frames",count); } - - if (frame.getOpCode() == OpCode.CLOSE) - { - CloseInfo close = new CloseInfo(frame); - ioState.onCloseRemote(close); - } - + + // Capture Frame Copy WebSocketFrame copy = WebSocketFrame.copy(frame); incomingFrames.incomingFrame(copy); } @@ -448,6 +432,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti @Override public void onConnectionStateChange(ConnectionState state) { + LOG.debug("CLIENT onConnectionStateChange() - {}", state); switch (state) { case CLOSED: @@ -455,10 +440,17 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti // this.disconnect(); break; case CLOSING: - if (ioState.wasRemoteCloseInitiated()) + CloseInfo close = ioState.getCloseInfo(); + + WebSocketFrame frame = close.asFrame(); + LOG.debug("Issuing: {}",frame); + try { - CloseInfo close = ioState.getCloseInfo(); - close(close.getStatusCode(),close.getReason()); + write(frame); + } + catch (IOException e) + { + LOG.debug(e); } break; default: @@ -701,6 +693,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti { if (!ioState.isOpen()) { + LOG.debug("IO Not Open / Not Writing: {}",frame); return; } LOG.debug("write(Frame->{}) to {}",frame,outgoing); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadServer.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadServer.java index c3cd1ad8554..1b665bded92 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadServer.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadServer.java @@ -54,6 +54,7 @@ import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; import org.eclipse.jetty.websocket.api.extensions.Frame; import org.eclipse.jetty.websocket.api.extensions.IncomingFrames; import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames; +import org.eclipse.jetty.websocket.api.extensions.Frame.Type; import org.eclipse.jetty.websocket.common.AcceptHash; import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.Generator; @@ -124,7 +125,6 @@ public class BlockheadServer { write(new CloseFrame()); flush(); - disconnect(); } public void close(int statusCode) throws IOException @@ -132,7 +132,6 @@ public class BlockheadServer CloseInfo close = new CloseInfo(statusCode); write(close.asFrame()); flush(); - disconnect(); } public void disconnect() @@ -229,6 +228,19 @@ public class BlockheadServer CloseInfo close = new CloseInfo(frame); LOG.debug("Close frame: {}",close); } + + Type type = frame.getType(); + if (echoing.get() && (type.isData() || type.isContinuation())) + { + try + { + write(WebSocketFrame.copy(frame)); + } + catch (IOException e) + { + LOG.warn(e); + } + } } @Override @@ -317,9 +329,18 @@ public class BlockheadServer return len; } + /** + * @deprecated use {@link #readFrames(int, int, TimeUnit)} for correct parameter order + */ + @Deprecated public IncomingFramesCapture readFrames(int expectedCount, TimeUnit timeoutUnit, int timeoutDuration) throws IOException, TimeoutException { - LOG.debug("Read: waiting for {} frame(s) from server",expectedCount); + return readFrames(expectedCount,timeoutDuration,timeoutUnit); + } + + public IncomingFramesCapture readFrames(int expectedCount, int timeoutDuration, TimeUnit timeoutUnit) throws IOException, TimeoutException + { + LOG.debug("Read: waiting for {} frame(s) from client",expectedCount); int startCount = incomingFrames.size(); ByteBuffer buf = bufferPool.acquire(BUFFER_SIZE,false); @@ -562,13 +583,22 @@ public class BlockheadServer public void write(Frame frame) throws IOException { LOG.debug("write(Frame->{}) to {}",frame,outgoing); - outgoing.outgoingFrame(frame,null, BatchMode.OFF); + outgoing.outgoingFrame(frame,null,BatchMode.OFF); } public void write(int b) throws IOException { getOutputStream().write(b); } + + public void write(ByteBuffer buf) throws IOException + { + byte arr[] = BufferUtil.toArray(buf); + if ((arr != null) && (arr.length > 0)) + { + getOutputStream().write(arr); + } + } } private static final Logger LOG = Log.getLogger(BlockheadServer.class); diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java index bebe7c0c379..f6a2f2c7bd8 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java @@ -44,6 +44,7 @@ import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.websocket.api.InvalidWebSocketException; +import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.WebSocketException; import org.eclipse.jetty.websocket.api.WebSocketPolicy; import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory; @@ -199,11 +200,23 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc } } - protected void closeAllConnections() + protected void shutdownAllConnections() { for (WebSocketSession session : openSessions) { - session.close(); + if (session.getConnection() != null) + { + try + { + session.getConnection().close( + StatusCode.SHUTDOWN, + "Shutdown"); + } + catch (Throwable t) + { + LOG.debug("During Shutdown All Connections",t); + } + } } openSessions.clear(); } @@ -269,7 +282,7 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc @Override protected void doStop() throws Exception { - closeAllConnections(); + shutdownAllConnections(); super.doStop(); } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java index 0c2ddb32eae..5a4707c1af1 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java @@ -114,7 +114,7 @@ public class WebSocketCloseTest public void onWebSocketConnect(Session sess) { LOG.debug("onWebSocketConnect({})",sess); - sess.close(); + sess.close(StatusCode.NORMAL,"FastCloseServer"); } } @@ -129,14 +129,10 @@ public class WebSocketCloseTest public void onWebSocketConnect(Session sess) { LOG.debug("onWebSocketConnect({})",sess); + // Test failure due to unhandled exception + // this should trigger a fast-fail closure during open/connect throw new RuntimeException("Intentional FastFail"); } - - @Override - public void onWebSocketError(Throwable cause) - { - errors.add(cause); - } } private static final Logger LOG = Log.getLogger(WebSocketCloseTest.class); @@ -163,30 +159,28 @@ public class WebSocketCloseTest @Test public void testFastClose() throws Exception { - BlockheadClient client = new BlockheadClient(server.getServerUri()); - client.setProtocols("fastclose"); - client.setTimeout(TimeUnit.SECONDS,1); - try + try (BlockheadClient client = new BlockheadClient(server.getServerUri())) { + client.setProtocols("fastclose"); + client.setTimeout(TimeUnit.SECONDS,1); client.connect(); client.sendStandardRequest(); client.expectUpgradeResponse(); + // Verify that client got close frame IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); WebSocketFrame frame = capture.getFrames().poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL)); - + + // Notify server of close handshake client.write(close.asFrame()); // respond with close - + + // ensure server socket got close event Assert.assertThat("Fast Close Latch",closeSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true)); Assert.assertThat("Fast Close.statusCode",closeSocket.closeStatusCode,is(StatusCode.NORMAL)); } - finally - { - client.close(); - } } /** @@ -195,11 +189,10 @@ public class WebSocketCloseTest @Test public void testFastFail() throws Exception { - BlockheadClient client = new BlockheadClient(server.getServerUri()); - client.setProtocols("fastfail"); - client.setTimeout(TimeUnit.SECONDS,1); - try + try (BlockheadClient client = new BlockheadClient(server.getServerUri())) { + client.setProtocols("fastfail"); + client.setTimeout(TimeUnit.SECONDS,1); try (StacklessLogging scope = new StacklessLogging(AbstractEventDriver.class)) { client.connect(); @@ -214,14 +207,11 @@ public class WebSocketCloseTest client.write(close.asFrame()); // respond with close + // ensure server socket got close event Assert.assertThat("Fast Fail Latch",closeSocket.closeLatch.await(1,TimeUnit.SECONDS),is(true)); Assert.assertThat("Fast Fail.statusCode",closeSocket.closeStatusCode,is(StatusCode.SERVER_ERROR)); Assert.assertThat("Fast Fail.errors",closeSocket.errors.size(),is(1)); } } - finally - { - client.close(); - } } } From 2bc9accd98e8a61eb11e69b79bb924ce75cc5594 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 23 Apr 2014 12:56:00 -0700 Subject: [PATCH 058/135] 433262 - WebSocket / Advanced close use cases + Fixing bad assumptions in unit tests where the client would send a few frames to test the protocol behavior followed by the close frame. But the test expected the server to initiate the close, but this setup of the tests would mean that the client initiated the close. --- .../common/test/BlockheadClient.java | 6 + .../jetty/websocket/common/test/Fuzzer.java | 27 +--- .../websocket/server/IdleTimeoutTest.java | 17 +- .../websocket/server/ab/TestABCase1.java | 128 ++------------- .../websocket/server/ab/TestABCase2.java | 89 ++--------- .../websocket/server/ab/TestABCase3.java | 49 +----- .../websocket/server/ab/TestABCase4.java | 80 ++-------- .../websocket/server/ab/TestABCase5.java | 149 +++--------------- .../websocket/server/ab/TestABCase6.java | 84 ++-------- .../server/ab/TestABCase6_BadUTF.java | 19 +-- .../server/ab/TestABCase6_GoodUTF.java | 7 +- .../websocket/server/ab/TestABCase7.java | 87 ++-------- .../server/ab/TestABCase7_BadStatusCodes.java | 14 +- .../ab/TestABCase7_GoodStatusCodes.java | 14 +- .../websocket/server/ab/TestABCase9.java | 98 ++---------- 15 files changed, 158 insertions(+), 710 deletions(-) diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java index dedba3e351c..b4634c5b5ca 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java @@ -524,7 +524,13 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti return len; } + @Deprecated public IncomingFramesCapture readFrames(int expectedCount, TimeUnit timeoutUnit, int timeoutDuration) throws IOException, TimeoutException + { + return readFrames(expectedCount,timeoutDuration,timeoutUnit); + } + + public IncomingFramesCapture readFrames(int expectedCount, int timeoutDuration, TimeUnit timeoutUnit) throws IOException, TimeoutException { LOG.debug("Read: waiting for {} frame(s) from server",expectedCount); diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzer.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzer.java index 3d6c6414838..8ebfc946463 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzer.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzer.java @@ -45,7 +45,7 @@ import static org.hamcrest.Matchers.is; /** * Fuzzing utility for the AB tests. */ -public class Fuzzer +public class Fuzzer implements AutoCloseable { public static enum CloseState { @@ -117,8 +117,14 @@ public class Fuzzer buf.flip(); return buf; } + + @Override + public void close() throws Exception + { + this.client.disconnect(); + } - public void close() + public void disconnect() { this.client.disconnect(); } @@ -187,23 +193,6 @@ public class Fuzzer // TODO Should test for no more frames. success if connection closed. } - public void expectServerDisconnect(DisconnectMode mode) - { - client.expectServerDisconnect(); - IOState ios = client.getIOState(); - - switch (mode) - { - case CLEAN: - Assert.assertTrue(ios.wasRemoteCloseInitiated()); - Assert.assertTrue(ios.wasCleanClose()); - break; - case UNCLEAN: - Assert.assertTrue(ios.wasRemoteCloseInitiated()); - break; - } - } - public CloseState getCloseState() { IOState ios = client.getIOState(); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java index 7a6998bb886..7fdada494e0 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java @@ -18,14 +18,22 @@ package org.eclipse.jetty.websocket.server; +import static org.hamcrest.Matchers.*; + import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.common.CloseInfo; +import org.eclipse.jetty.websocket.common.OpCode; +import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; +import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.server.helper.RFCSocket; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.junit.AfterClass; +import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -83,8 +91,13 @@ public class IdleTimeoutTest // The server could not read this frame, if it is in this half closed state client.write(new TextFrame().setPayload("Hello")); - // Expect server to be disconnected at this point - client.expectServerDisconnect(); + // Expect server to have closed due to its own timeout + IncomingFramesCapture capture = client.readFrames(1,500,TimeUnit.MILLISECONDS); + WebSocketFrame frame = capture.getFrames().poll(); + Assert.assertThat("frame opcode",frame.getOpCode(),is(OpCode.CLOSE)); + CloseInfo close = new CloseInfo(frame); + Assert.assertThat("close code",close.getStatusCode(),is(StatusCode.SHUTDOWN)); + Assert.assertThat("close reason",close.getReason(),containsString("Timeout")); } finally { diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java index 20ff6422e13..502fb808dea 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase1.java @@ -48,18 +48,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new TextFrame()); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -81,18 +75,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -114,18 +102,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -147,18 +129,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -180,18 +156,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -213,18 +183,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -246,18 +210,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -284,19 +242,13 @@ public class TestABCase1 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.SLOW); fuzzer.setSlowSendSegmentSize(segmentSize); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -314,18 +266,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new BinaryFrame()); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -347,18 +293,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -380,18 +320,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -413,18 +347,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -446,18 +374,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -479,18 +401,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -512,18 +428,12 @@ public class TestABCase1 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } @@ -550,19 +460,13 @@ public class TestABCase1 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(SendMode.SLOW); fuzzer.setSlowSendSegmentSize(segmentSize); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase2.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase2.java index 0e43806a8ba..4a1a1c1cb9c 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase2.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase2.java @@ -50,18 +50,13 @@ public class TestABCase2 extends AbstractABCase WebSocketFrame expect = new PongFrame(); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -89,18 +84,13 @@ public class TestABCase2 extends AbstractABCase send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -128,8 +118,7 @@ public class TestABCase2 extends AbstractABCase send.add(new CloseInfo(StatusCode.NORMAL).asFrame()); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.SLOW); @@ -137,10 +126,6 @@ public class TestABCase2 extends AbstractABCase fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -159,18 +144,13 @@ public class TestABCase2 extends AbstractABCase expect.add(new PongFrame().setPayload(copyOf(payload))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -179,8 +159,7 @@ public class TestABCase2 extends AbstractABCase @Test public void testCase2_3() throws Exception { - byte payload[] = new byte[] - { 0x00, (byte)0xFF, (byte)0xFE, (byte)0xFD, (byte)0xFC, (byte)0xFB, 0x00, (byte)0xFF }; + byte payload[] = new byte[] { 0x00, (byte)0xFF, (byte)0xFE, (byte)0xFD, (byte)0xFC, (byte)0xFB, 0x00, (byte)0xFF }; List send = new ArrayList<>(); send.add(new PingFrame().setPayload(payload)); @@ -190,18 +169,13 @@ public class TestABCase2 extends AbstractABCase expect.add(new PongFrame().setPayload(copyOf(payload))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -221,18 +195,13 @@ public class TestABCase2 extends AbstractABCase expect.add(new PongFrame().setPayload(copyOf(payload))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -241,32 +210,26 @@ public class TestABCase2 extends AbstractABCase @Test public void testCase2_5() throws Exception { - try(StacklessLogging scope = new StacklessLogging(Parser.class)) + try (StacklessLogging scope = new StacklessLogging(Parser.class)) { byte payload[] = new byte[126]; // intentionally too big Arrays.fill(payload,(byte)'5'); ByteBuffer buf = ByteBuffer.wrap(payload); - + List send = new ArrayList<>(); // trick websocket frame into making extra large payload for ping send.add(new BadFrame(OpCode.PING).setPayload(buf)); send.add(new CloseInfo(StatusCode.NORMAL,"Test 2.5").asFrame()); - + List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - - Fuzzer fuzzer = new Fuzzer(this); - try + + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.CLEAN); - } - finally - { - fuzzer.close(); } } } @@ -288,8 +251,7 @@ public class TestABCase2 extends AbstractABCase expect.add(new PongFrame().setPayload(copyOf(payload))); expect.add(new CloseInfo(StatusCode.NORMAL,"Test 2.6").asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.SLOW); @@ -297,10 +259,6 @@ public class TestABCase2 extends AbstractABCase fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -316,18 +274,13 @@ public class TestABCase2 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -343,18 +296,13 @@ public class TestABCase2 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -372,17 +320,12 @@ public class TestABCase2 extends AbstractABCase expect.add(new PongFrame().setPayload("our ping")); // our pong expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase3.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase3.java index 4b5373b9cb2..de9ec69ea56 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase3.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase3.java @@ -51,18 +51,13 @@ public class TestABCase3 extends AbstractABCase WebSocketFrame expect = new CloseInfo(StatusCode.PROTOCOL).asFrame(); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -80,18 +75,13 @@ public class TestABCase3 extends AbstractABCase expect.add(new TextFrame().setPayload("small")); // echo on good frame expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -109,18 +99,13 @@ public class TestABCase3 extends AbstractABCase expect.add(new TextFrame().setPayload("small")); // echo on good frame expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -138,8 +123,7 @@ public class TestABCase3 extends AbstractABCase expect.add(new TextFrame().setPayload("small")); // echo on good frame expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.SLOW); @@ -147,10 +131,6 @@ public class TestABCase3 extends AbstractABCase fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -168,18 +148,13 @@ public class TestABCase3 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -197,18 +172,13 @@ public class TestABCase3 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -230,17 +200,12 @@ public class TestABCase3 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase4.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase4.java index ffb69614164..61f59adbd62 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase4.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase4.java @@ -53,18 +53,13 @@ public class TestABCase4 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -82,18 +77,13 @@ public class TestABCase4 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -111,18 +101,13 @@ public class TestABCase4 extends AbstractABCase expect.add(new TextFrame().setPayload("hello")); // echo expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -132,7 +117,7 @@ public class TestABCase4 extends AbstractABCase public void testCase4_1_4() throws Exception { ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad")); - + List send = new ArrayList<>(); send.add(new TextFrame().setPayload("hello")); send.add(new BadFrame((byte)6).setPayload(buf)); // intentionally bad @@ -142,18 +127,13 @@ public class TestABCase4 extends AbstractABCase expect.add(new TextFrame().setPayload("hello")); // echo expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -163,7 +143,7 @@ public class TestABCase4 extends AbstractABCase public void testCase4_1_5() throws Exception { ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad")); - + List send = new ArrayList<>(); send.add(new TextFrame().setPayload("hello")); send.add(new BadFrame((byte)7).setPayload(buf)); // intentionally bad @@ -173,18 +153,13 @@ public class TestABCase4 extends AbstractABCase expect.add(new TextFrame().setPayload("hello")); // echo expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -199,18 +174,13 @@ public class TestABCase4 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -220,25 +190,20 @@ public class TestABCase4 extends AbstractABCase public void testCase4_2_2() throws Exception { ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad")); - + List send = new ArrayList<>(); send.add(new BadFrame((byte)12).setPayload(buf)); // intentionally bad List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -256,18 +221,13 @@ public class TestABCase4 extends AbstractABCase expect.add(new TextFrame().setPayload("hello")); // echo expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -277,7 +237,7 @@ public class TestABCase4 extends AbstractABCase public void testCase4_2_4() throws Exception { ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad")); - + List send = new ArrayList<>(); send.add(new TextFrame().setPayload("hello")); send.add(new BadFrame((byte)14).setPayload(buf)); // intentionally bad @@ -287,18 +247,13 @@ public class TestABCase4 extends AbstractABCase expect.add(new TextFrame().setPayload("hello")); // echo expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -308,7 +263,7 @@ public class TestABCase4 extends AbstractABCase public void testCase4_2_5() throws Exception { ByteBuffer buf = ByteBuffer.wrap(StringUtil.getUtf8Bytes("bad")); - + List send = new ArrayList<>(); send.add(new TextFrame().setPayload("hello")); send.add(new BadFrame((byte)15).setPayload(buf)); // intentionally bad @@ -318,17 +273,12 @@ public class TestABCase4 extends AbstractABCase expect.add(new TextFrame().setPayload("hello")); // echo expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java index c957fc9b3e2..c6349a317ed 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase5.java @@ -57,19 +57,14 @@ public class TestABCase5 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } - } + } /** * Send continuation+fin, then text+fin (framewise) @@ -85,18 +80,13 @@ public class TestABCase5 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME); fuzzer.sendAndIgnoreBrokenPipe(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -113,8 +103,7 @@ public class TestABCase5 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.SLOW); @@ -122,10 +111,6 @@ public class TestABCase5 extends AbstractABCase fuzzer.sendAndIgnoreBrokenPipe(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -142,18 +127,13 @@ public class TestABCase5 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.sendAndIgnoreBrokenPipe(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -170,18 +150,13 @@ public class TestABCase5 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME); fuzzer.sendAndIgnoreBrokenPipe(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -198,8 +173,7 @@ public class TestABCase5 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.SLOW); @@ -207,10 +181,6 @@ public class TestABCase5 extends AbstractABCase fuzzer.sendAndIgnoreBrokenPipe(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -230,18 +200,13 @@ public class TestABCase5 extends AbstractABCase expect.add(new TextFrame().setPayload("fragment1fragment2")); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -262,18 +227,13 @@ public class TestABCase5 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.sendAndIgnoreBrokenPipe(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -294,18 +254,13 @@ public class TestABCase5 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -322,18 +277,13 @@ public class TestABCase5 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -365,8 +315,7 @@ public class TestABCase5 extends AbstractABCase expect2.add(new TextFrame().setPayload("f1,f2,f3,f4,f5")); expect2.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -382,10 +331,6 @@ public class TestABCase5 extends AbstractABCase fuzzer.send(send2); fuzzer.expect(expect2); } - finally - { - fuzzer.close(); - } } /** @@ -402,18 +347,13 @@ public class TestABCase5 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -442,8 +382,7 @@ public class TestABCase5 extends AbstractABCase expect2.add(new TextFrame().setPayload("f1,f2,f3,f4,f5")); expect2.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME); @@ -456,10 +395,6 @@ public class TestABCase5 extends AbstractABCase fuzzer.send(send2); fuzzer.expect(expect2); } - finally - { - fuzzer.close(); - } } /** @@ -488,8 +423,7 @@ public class TestABCase5 extends AbstractABCase expect2.add(new TextFrame().setPayload("f1,f2,f3,f4,f5")); expect2.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.SLOW); @@ -503,10 +437,6 @@ public class TestABCase5 extends AbstractABCase fuzzer.send(send2); fuzzer.expect(expect2); } - finally - { - fuzzer.close(); - } } /** @@ -524,18 +454,13 @@ public class TestABCase5 extends AbstractABCase expect.add(new TextFrame().setPayload("hello, world")); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -553,18 +478,13 @@ public class TestABCase5 extends AbstractABCase expect.add(new TextFrame().setPayload("hello, world")); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -582,8 +502,7 @@ public class TestABCase5 extends AbstractABCase expect.add(new TextFrame().setPayload("hello, world")); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.SLOW); @@ -591,10 +510,6 @@ public class TestABCase5 extends AbstractABCase fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -614,18 +529,13 @@ public class TestABCase5 extends AbstractABCase expect.add(new TextFrame().setPayload("hello, world")); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -645,18 +555,13 @@ public class TestABCase5 extends AbstractABCase expect.add(new TextFrame().setPayload("hello, world")); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.PER_FRAME); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -676,8 +581,7 @@ public class TestABCase5 extends AbstractABCase expect.add(new TextFrame().setPayload("hello, world")); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.SLOW); @@ -685,10 +589,6 @@ public class TestABCase5 extends AbstractABCase fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -706,17 +606,12 @@ public class TestABCase5 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try(Fuzzer fuzzer = new Fuzzer(this);StacklessLogging supress = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java index 6ead418034f..c11aebf4840 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6.java @@ -90,18 +90,13 @@ public class TestABCase6 extends AbstractABCase expect.add(new TextFrame()); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -120,18 +115,13 @@ public class TestABCase6 extends AbstractABCase expect.add(new TextFrame()); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -150,18 +140,13 @@ public class TestABCase6 extends AbstractABCase expect.add(new TextFrame().setPayload("middle")); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -172,7 +157,7 @@ public class TestABCase6 extends AbstractABCase { String utf1 = "Hello-\uC2B5@\uC39F\uC3A4"; String utf2 = "\uC3BC\uC3A0\uC3A1-UTF-8!!"; - + ByteBuffer b1 = ByteBuffer.wrap(StringUtil.getUtf8Bytes(utf1)); ByteBuffer b2 = ByteBuffer.wrap(StringUtil.getUtf8Bytes(utf2)); @@ -189,18 +174,13 @@ public class TestABCase6 extends AbstractABCase expect.add(new TextFrame().setPayload(e1)); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -220,18 +200,13 @@ public class TestABCase6 extends AbstractABCase expect.add(new TextFrame().setPayload(ByteBuffer.wrap(msg))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -250,18 +225,13 @@ public class TestABCase6 extends AbstractABCase expect.add(new TextFrame().setPayload(ByteBuffer.wrap(msg))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -279,18 +249,13 @@ public class TestABCase6 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -311,8 +276,7 @@ public class TestABCase6 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -325,10 +289,6 @@ public class TestABCase6 extends AbstractABCase fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -351,8 +311,7 @@ public class TestABCase6 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -363,10 +322,6 @@ public class TestABCase6 extends AbstractABCase fuzzer.send(new ContinuationFrame().setPayload(ByteBuffer.wrap(part3)).setFin(true)); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -393,15 +348,13 @@ public class TestABCase6 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try (Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); ByteBuffer net = fuzzer.asNetworkBuffer(send); - int splits[] = - { 17, 21, net.limit() }; + int splits[] = { 17, 21, net.limit() }; ByteBuffer part1 = net.slice(); // Header + good UTF part1.limit(splits[0]); @@ -419,12 +372,6 @@ public class TestABCase6 extends AbstractABCase fuzzer.send(part3); // the rest (shouldn't work) fuzzer.expect(expect); - - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.UNCLEAN); - } - finally - { - fuzzer.close(); } } } @@ -445,8 +392,7 @@ public class TestABCase6 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging scope = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging scope = new StacklessLogging(Parser.class)) { fuzzer.connect(); @@ -460,9 +406,5 @@ public class TestABCase6 extends AbstractABCase fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_BadUTF.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_BadUTF.java index 7c61776a96e..3ff16439407 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_BadUTF.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_BadUTF.java @@ -163,18 +163,15 @@ public class TestABCase6_BadUTF extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging supress = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this)) { - fuzzer.connect(); - fuzzer.setSendMode(Fuzzer.SendMode.BULK); - fuzzer.send(send); - fuzzer.expect(expect); - fuzzer.expectServerDisconnect(Fuzzer.DisconnectMode.UNCLEAN); - } - finally - { - fuzzer.close(); + try (StacklessLogging supress = new StacklessLogging(Parser.class)) + { + fuzzer.connect(); + fuzzer.setSendMode(Fuzzer.SendMode.BULK); + fuzzer.send(send); + fuzzer.expect(expect); + } } } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_GoodUTF.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_GoodUTF.java index 8eebe6e5cef..f5e9cbeb760 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_GoodUTF.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase6_GoodUTF.java @@ -139,17 +139,12 @@ public class TestABCase6_GoodUTF extends AbstractABCase expect.add(new TextFrame().setPayload(clone(msg))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7.java index 8c605515a5d..b8ad71b0dcc 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7.java @@ -64,18 +64,13 @@ public class TestABCase7 extends AbstractABCase expect.add(new TextFrame().setPayload("Hello World")); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -91,8 +86,7 @@ public class TestABCase7 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -100,10 +94,6 @@ public class TestABCase7 extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -119,8 +109,7 @@ public class TestABCase7 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -128,10 +117,6 @@ public class TestABCase7 extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -147,8 +132,7 @@ public class TestABCase7 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -156,10 +140,6 @@ public class TestABCase7 extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -176,8 +156,7 @@ public class TestABCase7 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -185,10 +164,6 @@ public class TestABCase7 extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -210,8 +185,7 @@ public class TestABCase7 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -219,10 +193,6 @@ public class TestABCase7 extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -237,8 +207,7 @@ public class TestABCase7 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -246,10 +215,6 @@ public class TestABCase7 extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -258,8 +223,7 @@ public class TestABCase7 extends AbstractABCase @Test public void testCase7_3_2() throws Exception { - byte payload[] = new byte[] - { 0x00 }; + byte payload[] = new byte[] { 0x00 }; ByteBuffer buf = ByteBuffer.wrap(payload); List send = new ArrayList<>(); @@ -268,8 +232,7 @@ public class TestABCase7 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging scope = new StacklessLogging(Parser.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging scope = new StacklessLogging(Parser.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -277,10 +240,6 @@ public class TestABCase7 extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -295,8 +254,7 @@ public class TestABCase7 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -304,10 +262,6 @@ public class TestABCase7 extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -322,8 +276,7 @@ public class TestABCase7 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.NORMAL,"Hic").asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -331,10 +284,6 @@ public class TestABCase7 extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -353,8 +302,7 @@ public class TestABCase7 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.NORMAL,reason).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging logging = new StacklessLogging(AbstractWebSocketConnection.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging logging = new StacklessLogging(AbstractWebSocketConnection.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -362,10 +310,6 @@ public class TestABCase7 extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -390,8 +334,7 @@ public class TestABCase7 extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.BAD_PAYLOAD).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try(StacklessLogging scope = new StacklessLogging(Parser.class,CloseInfo.class)) + try (Fuzzer fuzzer = new Fuzzer(this); StacklessLogging scope = new StacklessLogging(Parser.class,CloseInfo.class)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -399,9 +342,5 @@ public class TestABCase7 extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_BadStatusCodes.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_BadStatusCodes.java index b3626f13f67..c76e174b919 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_BadStatusCodes.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_BadStatusCodes.java @@ -98,8 +98,7 @@ public class TestABCase7_BadStatusCodes extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -107,10 +106,6 @@ public class TestABCase7_BadStatusCodes extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -131,8 +126,7 @@ public class TestABCase7_BadStatusCodes extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseInfo(StatusCode.PROTOCOL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -140,9 +134,5 @@ public class TestABCase7_BadStatusCodes extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_GoodStatusCodes.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_GoodStatusCodes.java index 425f65709c3..5e8da150f12 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_GoodStatusCodes.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase7_GoodStatusCodes.java @@ -93,8 +93,7 @@ public class TestABCase7_GoodStatusCodes extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseFrame().setPayload(clone(payload))); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -102,10 +101,6 @@ public class TestABCase7_GoodStatusCodes extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } /** @@ -125,8 +120,7 @@ public class TestABCase7_GoodStatusCodes extends AbstractABCase List expect = new ArrayList<>(); expect.add(new CloseFrame().setPayload(clone(payload))); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); @@ -134,9 +128,5 @@ public class TestABCase7_GoodStatusCodes extends AbstractABCase fuzzer.expect(expect); fuzzer.expectNoMoreFrames(); } - finally - { - fuzzer.close(); - } } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java index 39503948e27..1b117f4bd05 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java @@ -96,18 +96,13 @@ public class TestABCase9 extends AbstractABCase expect.add(toDataFrame(opcode).setPayload(copyOf(msg))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect,TimeUnit.SECONDS,8); } - finally - { - fuzzer.close(); - } } private void assertSlowFrameEcho(byte opcode, int overallMsgSize, int segmentSize) throws Exception @@ -124,8 +119,7 @@ public class TestABCase9 extends AbstractABCase expect.add(toDataFrame(opcode).setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.SLOW); @@ -133,10 +127,6 @@ public class TestABCase9 extends AbstractABCase fuzzer.send(send); fuzzer.expect(expect,TimeUnit.SECONDS,8); } - finally - { - fuzzer.close(); - } } /** @@ -157,18 +147,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new TextFrame().setPayload(msg)); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -189,18 +174,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -222,18 +202,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect,TimeUnit.SECONDS,4); } - finally - { - fuzzer.close(); - } } /** @@ -255,18 +230,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect,TimeUnit.SECONDS,8); } - finally - { - fuzzer.close(); - } } /** @@ -288,18 +258,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect,TimeUnit.SECONDS,16); } - finally - { - fuzzer.close(); - } } /** @@ -321,18 +286,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new TextFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect,TimeUnit.SECONDS,32); } - finally - { - fuzzer.close(); - } } /** @@ -352,18 +312,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new BinaryFrame().setPayload(copyOf(data))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -384,18 +339,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect); } - finally - { - fuzzer.close(); - } } /** @@ -417,18 +367,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect,TimeUnit.SECONDS,4); } - finally - { - fuzzer.close(); - } } /** @@ -450,18 +395,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect,TimeUnit.SECONDS,8); } - finally - { - fuzzer.close(); - } } /** @@ -483,18 +423,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect,TimeUnit.SECONDS,16); } - finally - { - fuzzer.close(); - } } /** @@ -516,18 +451,13 @@ public class TestABCase9 extends AbstractABCase expect.add(new BinaryFrame().setPayload(clone(buf))); expect.add(new CloseInfo(StatusCode.NORMAL).asFrame()); - Fuzzer fuzzer = new Fuzzer(this); - try + try(Fuzzer fuzzer = new Fuzzer(this)) { fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); fuzzer.expect(expect,TimeUnit.SECONDS,32); } - finally - { - fuzzer.close(); - } } /** From 76a1e032ebc6353e49900d682ce7c2abdc87af75 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 23 Apr 2014 22:26:22 +0200 Subject: [PATCH 059/135] Allow subclasses to override the proxied request content. --- .../org/eclipse/jetty/proxy/ProxyServlet.java | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 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 09217f0dfe5..32130186d31 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 @@ -41,6 +41,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; @@ -427,30 +428,14 @@ public class ProxyServlet extends HttpServlet addViaHeader(proxyRequest); addXForwardedHeaders(proxyRequest, request); - if (hasContent) - { - proxyRequest.content(new InputStreamContentProvider(request.getInputStream()) - { - @Override - public long getLength() - { - return request.getContentLength(); - } - - @Override - protected ByteBuffer onRead(byte[] buffer, int offset, int length) - { - _log.debug("{} proxying content to upstream: {} bytes", requestId, length); - return super.onRead(buffer, offset, length); - } - }); - } - final AsyncContext asyncContext = request.startAsync(); // We do not timeout the continuation, but the proxy request asyncContext.setTimeout(0); request.setAttribute(ASYNC_CONTEXT, asyncContext); + if (hasContent) + proxyRequest.content(proxyRequestContent(proxyRequest, request)); + customizeProxyRequest(proxyRequest, request); if (_log.isDebugEnabled()) @@ -490,6 +475,25 @@ public class ProxyServlet extends HttpServlet proxyRequest.send(new ProxyResponseListener(request, response)); } + protected ContentProvider proxyRequestContent(Request proxyRequest, final HttpServletRequest request) throws IOException + { + return new InputStreamContentProvider(request.getInputStream()) + { + @Override + public long getLength() + { + return request.getContentLength(); + } + + @Override + protected ByteBuffer onRead(byte[] buffer, int offset, int length) + { + _log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length); + return super.onRead(buffer, offset, length); + } + }; + } + protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException { response.sendError(HttpServletResponse.SC_FORBIDDEN); From 61b2e7f75ea7398e33452b78af4282091fe7b26b Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 24 Apr 2014 10:01:46 +0200 Subject: [PATCH 060/135] Improved failsafe close handling for half closed endpoints --- .../eclipse/jetty/io/AbstractEndPoint.java | 20 ++++++++++++++--- .../org/eclipse/jetty/io/FillInterest.java | 7 +++++- .../org/eclipse/jetty/io/WriteFlusher.java | 22 ++++++++++++------- .../jetty/io/ByteArrayEndPointTest.java | 7 +++--- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java index bfcb1aa18ca..8fa2cc86ef9 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java @@ -44,6 +44,7 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint return AbstractEndPoint.this.needsFill(); } }; + private final WriteFlusher _writeFlusher = new WriteFlusher(this) { @Override @@ -142,9 +143,22 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint @Override protected void onIdleExpired(TimeoutException timeout) { - // Note: Rely on fillInterest to notify onReadTimeout to close connection. - _fillInterest.onFail(timeout); - _writeFlusher.onFail(timeout); + boolean output_shutdown=isOutputShutdown(); + boolean input_shutdown=isInputShutdown(); + boolean fillFailed = _fillInterest.onFail(timeout); + boolean writeFailed = _writeFlusher.onFail(timeout); + + // If the endpoint is half closed and there was no onFail handling, the close here + // This handles the situation where the connection has completed its close handling + // and the endpoint is half closed, but the other party does not complete the close. + // This perhaps should not check for half closed, however the servlet spec case allows + // for a dispatched servlet or suspended request to extend beyond the connections idle + // time. So if this test would always close an idle endpoint that is not handled, then + // we would need a mode to ignore timeouts for some HTTP states + if (isOpen() && (output_shutdown || input_shutdown) && !(fillFailed || writeFailed)) + close(); + else + LOG.debug("Ignored idle endpoint {}",this); } @Override diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java index 0f3c2e55eaf..b2c3f685559 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java @@ -93,12 +93,17 @@ public abstract class FillInterest /* ------------------------------------------------------------ */ /** Call to signal a failure to a registered interest + * @return true if the cause was passed to a {@link Callback} instance */ - public void onFail(Throwable cause) + public boolean onFail(Throwable cause) { Callback callback=_interested.get(); if (callback!=null && _interested.compareAndSet(callback,null)) + { callback.failed(cause); + return true; + } + return false; } /* ------------------------------------------------------------ */ diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java index 326ef3faff6..dd44e531e32 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java @@ -253,10 +253,14 @@ abstract public class WriteFlusher return _buffers; } - protected void fail(Throwable cause) + protected boolean fail(Throwable cause) { if (_callback!=null) + { _callback.failed(cause); + return true; + } + return false; } protected void complete() @@ -430,7 +434,12 @@ abstract public class WriteFlusher } } - public void onFail(Throwable cause) + /* ------------------------------------------------------------ */ + /** Notify the flusher of a failure + * @param cause The cause of the failure + * @return true if the flusher passed the failure to a {@link Callback} instance + */ + public boolean onFail(Throwable cause) { // Keep trying to handle the failure until we get to IDLE or FAILED state while(true) @@ -442,7 +451,7 @@ abstract public class WriteFlusher case FAILED: if (DEBUG) LOG.debug("ignored: {} {}", this, cause); - return; + return false; case PENDING: if (DEBUG) @@ -450,10 +459,7 @@ abstract public class WriteFlusher PendingState pending = (PendingState)current; if (updateState(pending,__IDLE)) - { - pending.fail(cause); - return; - } + return pending.fail(cause); break; default: @@ -461,7 +467,7 @@ abstract public class WriteFlusher LOG.debug("failed: {} {}", this, cause); if (updateState(current,new FailedState(cause))) - return; + return false; break; } } diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java index d23be980148..f6cefa12e9b 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java @@ -19,8 +19,6 @@ package org.eclipse.jetty.io; import static org.hamcrest.Matchers.*; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -37,8 +35,6 @@ import org.eclipse.jetty.toolchain.test.AdvancedRunner; import org.eclipse.jetty.toolchain.test.annotation.Slow; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.FutureCallback; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.TimerScheduler; import org.junit.After; @@ -132,6 +128,7 @@ public class ByteArrayEndPointTest assertEquals(true,endp.flush(BufferUtil.EMPTY_BUFFER,BufferUtil.toBuffer(" and"),BufferUtil.toBuffer(" more"))); assertEquals("some output some more and more",endp.getOutputString()); + endp.close(); } @Test @@ -150,6 +147,7 @@ public class ByteArrayEndPointTest assertEquals(true,endp.flush(data)); assertEquals("data.",BufferUtil.toString(endp.takeOutput())); + endp.close(); } @@ -237,6 +235,7 @@ public class ByteArrayEndPointTest assertTrue(fcb.isDone()); assertEquals(null, fcb.get()); assertEquals(" more.", endp.getOutputString()); + endp.close(); } /** From 6861e2db996a03d0663646832751621c5734b936 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 17 Apr 2014 10:38:06 -0700 Subject: [PATCH 061/135] EndPoint onIdleExpired should not close on its own + From discussion with Simone, the dispatched fillInterest.onFail() needs to occur, so that the AbstractConnection.onReadTimeout() can handle the close conditions needed by SPDY and WebSocket. The EndPoint close within onIdleExpired is a race condition with this onReadTimeout desired behavior. (cherry picked from commit 9f76856fcff767e0d8a91ef5a311fcc498dd2a26) --- .../eclipse/jetty/io/AbstractEndPoint.java | 5 +-- .../jetty/io/ByteArrayEndPointTest.java | 39 +++++++++++++++---- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java index 6517dbd3427..bfcb1aa18ca 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java @@ -142,12 +142,9 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint @Override protected void onIdleExpired(TimeoutException timeout) { - boolean output_shutdown=isOutputShutdown(); - boolean input_shutdown=isInputShutdown(); + // Note: Rely on fillInterest to notify onReadTimeout to close connection. _fillInterest.onFail(timeout); _writeFlusher.onFail(timeout); - if (isOpen() && output_shutdown || input_shutdown) - close(); } @Override diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java index 1db6908fe78..d23be980148 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.io; +import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.instanceOf; @@ -36,6 +37,8 @@ import org.eclipse.jetty.toolchain.test.AdvancedRunner; import org.eclipse.jetty.toolchain.test.annotation.Slow; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.FutureCallback; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.TimerScheduler; import org.junit.After; @@ -235,6 +238,26 @@ public class ByteArrayEndPointTest assertEquals(null, fcb.get()); assertEquals(" more.", endp.getOutputString()); } + + /** + * Simulate AbstractConnection.ReadCallback.failed() + */ + public static class Closer extends FutureCallback + { + private EndPoint endp; + + public Closer(EndPoint endp) + { + this.endp = endp; + } + + @Override + public void failed(Throwable cause) + { + endp.close(); + super.failed(cause); + } + } @Slow @Test @@ -275,7 +298,7 @@ public class ByteArrayEndPointTest assertThat(t.getCause(), instanceOf(TimeoutException.class)); } assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2)); - assertTrue(endp.isOpen()); + assertThat("Endpoint open", endp.isOpen(), is(true)); // We need to delay the write timeout test below from the read timeout test above. // The reason is that the scheduler thread that fails the endPoint WriteFlusher @@ -298,17 +321,19 @@ public class ByteArrayEndPointTest assertThat(t.getCause(), instanceOf(TimeoutException.class)); } assertThat(System.currentTimeMillis() - start, greaterThan(idleTimeout / 2)); - assertTrue(endp.isOpen()); + assertThat("Endpoint open", endp.isOpen(), is(true)); - // Still no idle close - Thread.sleep(idleTimeout * 2); - assertTrue(endp.isOpen()); + endp.fillInterested(new Closer(endp)); + + // Still no idle close (wait half the time) + Thread.sleep(idleTimeout / 2); + assertThat("Endpoint open", endp.isOpen(), is(true)); // shutdown out endp.shutdownOutput(); - // idle close + // idle close (wait double the time) Thread.sleep(idleTimeout * 2); - assertFalse(endp.isOpen()); + assertThat("Endpoint open", endp.isOpen(), is(false)); } } From 9a428def2df1b8c6b0d0428b200a03eda1d6f31c Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 24 Apr 2014 10:01:46 +0200 Subject: [PATCH 062/135] Improved failsafe close handling for half closed endpoints (cherry picked from commit 61b2e7f75ea7398e33452b78af4282091fe7b26b) --- .../eclipse/jetty/io/AbstractEndPoint.java | 20 ++++++++++++++--- .../org/eclipse/jetty/io/FillInterest.java | 7 +++++- .../org/eclipse/jetty/io/WriteFlusher.java | 22 ++++++++++++------- .../jetty/io/ByteArrayEndPointTest.java | 7 +++--- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java index bfcb1aa18ca..8fa2cc86ef9 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java @@ -44,6 +44,7 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint return AbstractEndPoint.this.needsFill(); } }; + private final WriteFlusher _writeFlusher = new WriteFlusher(this) { @Override @@ -142,9 +143,22 @@ public abstract class AbstractEndPoint extends IdleTimeout implements EndPoint @Override protected void onIdleExpired(TimeoutException timeout) { - // Note: Rely on fillInterest to notify onReadTimeout to close connection. - _fillInterest.onFail(timeout); - _writeFlusher.onFail(timeout); + boolean output_shutdown=isOutputShutdown(); + boolean input_shutdown=isInputShutdown(); + boolean fillFailed = _fillInterest.onFail(timeout); + boolean writeFailed = _writeFlusher.onFail(timeout); + + // If the endpoint is half closed and there was no onFail handling, the close here + // This handles the situation where the connection has completed its close handling + // and the endpoint is half closed, but the other party does not complete the close. + // This perhaps should not check for half closed, however the servlet spec case allows + // for a dispatched servlet or suspended request to extend beyond the connections idle + // time. So if this test would always close an idle endpoint that is not handled, then + // we would need a mode to ignore timeouts for some HTTP states + if (isOpen() && (output_shutdown || input_shutdown) && !(fillFailed || writeFailed)) + close(); + else + LOG.debug("Ignored idle endpoint {}",this); } @Override diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java index 0f3c2e55eaf..b2c3f685559 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java @@ -93,12 +93,17 @@ public abstract class FillInterest /* ------------------------------------------------------------ */ /** Call to signal a failure to a registered interest + * @return true if the cause was passed to a {@link Callback} instance */ - public void onFail(Throwable cause) + public boolean onFail(Throwable cause) { Callback callback=_interested.get(); if (callback!=null && _interested.compareAndSet(callback,null)) + { callback.failed(cause); + return true; + } + return false; } /* ------------------------------------------------------------ */ diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java index 326ef3faff6..dd44e531e32 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java @@ -253,10 +253,14 @@ abstract public class WriteFlusher return _buffers; } - protected void fail(Throwable cause) + protected boolean fail(Throwable cause) { if (_callback!=null) + { _callback.failed(cause); + return true; + } + return false; } protected void complete() @@ -430,7 +434,12 @@ abstract public class WriteFlusher } } - public void onFail(Throwable cause) + /* ------------------------------------------------------------ */ + /** Notify the flusher of a failure + * @param cause The cause of the failure + * @return true if the flusher passed the failure to a {@link Callback} instance + */ + public boolean onFail(Throwable cause) { // Keep trying to handle the failure until we get to IDLE or FAILED state while(true) @@ -442,7 +451,7 @@ abstract public class WriteFlusher case FAILED: if (DEBUG) LOG.debug("ignored: {} {}", this, cause); - return; + return false; case PENDING: if (DEBUG) @@ -450,10 +459,7 @@ abstract public class WriteFlusher PendingState pending = (PendingState)current; if (updateState(pending,__IDLE)) - { - pending.fail(cause); - return; - } + return pending.fail(cause); break; default: @@ -461,7 +467,7 @@ abstract public class WriteFlusher LOG.debug("failed: {} {}", this, cause); if (updateState(current,new FailedState(cause))) - return; + return false; break; } } diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java index d23be980148..f6cefa12e9b 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/ByteArrayEndPointTest.java @@ -19,8 +19,6 @@ package org.eclipse.jetty.io; import static org.hamcrest.Matchers.*; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.instanceOf; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -37,8 +35,6 @@ import org.eclipse.jetty.toolchain.test.AdvancedRunner; import org.eclipse.jetty.toolchain.test.annotation.Slow; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.FutureCallback; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.util.thread.TimerScheduler; import org.junit.After; @@ -132,6 +128,7 @@ public class ByteArrayEndPointTest assertEquals(true,endp.flush(BufferUtil.EMPTY_BUFFER,BufferUtil.toBuffer(" and"),BufferUtil.toBuffer(" more"))); assertEquals("some output some more and more",endp.getOutputString()); + endp.close(); } @Test @@ -150,6 +147,7 @@ public class ByteArrayEndPointTest assertEquals(true,endp.flush(data)); assertEquals("data.",BufferUtil.toString(endp.takeOutput())); + endp.close(); } @@ -237,6 +235,7 @@ public class ByteArrayEndPointTest assertTrue(fcb.isDone()); assertEquals(null, fcb.get()); assertEquals(" more.", endp.getOutputString()); + endp.close(); } /** From 63d59feb0f06074564a6375bad2e3b36e784c889 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Thu, 24 Apr 2014 11:20:20 +0200 Subject: [PATCH 063/135] 433370 - PATCH method does not work with ProxyServlet. Fixed by using Request.method(String) rather than Request.method(HttpMethod). --- .../src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java | 3 +-- 1 file changed, 1 insertion(+), 2 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 32130186d31..c69061a6697 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 @@ -48,7 +48,6 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.InputStreamContentProvider; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.util.HttpCookieStore; import org.eclipse.jetty.util.log.Log; @@ -391,7 +390,7 @@ public class ProxyServlet extends HttpServlet } final Request proxyRequest = _client.newRequest(rewrittenURI) - .method(HttpMethod.fromString(request.getMethod())) + .method(request.getMethod()) .version(HttpVersion.fromString(request.getProtocol())); // Copy headers From e2ed934978b958d6fccb28a8a5d04768f7c0432d Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Thu, 24 Apr 2014 11:46:14 +0200 Subject: [PATCH 064/135] 433365 No such servlet: __org.eclipse.jetty.servlet.JspPropertyGroupServlet__ --- .../org/eclipse/jetty/webapp/StandardDescriptorProcessor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java index 06cd46f9ad5..ad7bbd77723 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/StandardDescriptorProcessor.java @@ -1417,12 +1417,13 @@ public class StandardDescriptorProcessor extends IterativeDescriptorProcessor { jsp_pg_servlet=new ServletHolder(JspPropertyGroupServlet.NAME,new JspPropertyGroupServlet(context,handler)); _servletHolderMap.put(JspPropertyGroupServlet.NAME,jsp_pg_servlet); + _servletHolders.add(jsp_pg_servlet); } ServletMapping mapping = new ServletMapping(); mapping.setServletName(JspPropertyGroupServlet.NAME); mapping.setPathSpecs(paths.toArray(new String[paths.size()])); - context.getServletHandler().addServletMapping(mapping); + _servletMappings.add(mapping); } } From 4b26faf8970906b5f22714735f4f9f55d247017e Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Thu, 24 Apr 2014 13:30:49 +0200 Subject: [PATCH 065/135] 433244 Session manager lifecycle cleanup --- .../session/AbstractSessionManager.java | 136 ++++++------------ .../server/session/HashSessionManager.java | 25 ++-- .../session/HashSessionManagerTest.java | 12 +- .../server/session/SessionRenewTest.java | 25 +++- 4 files changed, 86 insertions(+), 112 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java index f76df6864bf..1f966c5957d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionManager.java @@ -26,7 +26,6 @@ import java.util.Enumeration; import java.util.EventListener; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; @@ -49,7 +48,7 @@ import org.eclipse.jetty.server.handler.ContextHandler; 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.AbstractLifeCycle; +import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.statistic.CounterStatistic; import org.eclipse.jetty.util.statistic.SampleStatistic; @@ -66,7 +65,7 @@ import org.eclipse.jetty.util.statistic.SampleStatistic; */ @SuppressWarnings("deprecation") @ManagedObject("Abstract Session Manager") -public abstract class AbstractSessionManager extends AbstractLifeCycle implements SessionManager +public abstract class AbstractSessionManager extends ContainerLifeCycle implements SessionManager { final static Logger __log = SessionHandler.LOG; @@ -82,11 +81,13 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement static final HttpSessionContext __nullSessionContext=new HttpSessionContext() { + @Override public HttpSession getSession(String sessionId) { return null; } + @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public Enumeration getIds() { @@ -162,6 +163,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement } /* ------------------------------------------------------------ */ + @Override public HttpCookie access(HttpSession session,boolean secure) { long now=System.currentTimeMillis(); @@ -187,6 +189,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement } /* ------------------------------------------------------------ */ + @Override public void addEventListener(EventListener listener) { if (listener instanceof HttpSessionAttributeListener) @@ -195,17 +198,22 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement _sessionListeners.add((HttpSessionListener)listener); if (listener instanceof HttpSessionIdListener) _sessionIdListeners.add((HttpSessionIdListener)listener); + addBean(listener,false); } /* ------------------------------------------------------------ */ + @Override public void clearEventListeners() { + for (EventListener e :getBeans(EventListener.class)) + removeBean(e); _sessionAttributeListeners.clear(); _sessionListeners.clear(); _sessionIdListeners.clear(); } /* ------------------------------------------------------------ */ + @Override public void complete(HttpSession session) { AbstractSession s = ((SessionIf)session).getSession(); @@ -237,17 +245,20 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement Thread.currentThread().setContextClassLoader(serverLoader); _sessionIdManager=new HashSessionIdManager(); server.setSessionIdManager(_sessionIdManager); + server.manage(_sessionIdManager); + _sessionIdManager.start(); } finally { Thread.currentThread().setContextClassLoader(_loader); } } + + // server session id is never managed by this manager + addBean(_sessionIdManager,false); } } - if (!_sessionIdManager.isStarted()) - _sessionIdManager.start(); // Look for a session cookie name if (_context!=null) @@ -299,6 +310,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement /** * @return Returns the httpOnly. */ + @Override @ManagedAttribute("true if cookies use the http only flag") public boolean getHttpOnly() { @@ -306,6 +318,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement } /* ------------------------------------------------------------ */ + @Override public HttpSession getHttpSession(String nodeId) { String cluster_id = getSessionIdManager().getClusterId(nodeId); @@ -316,20 +329,11 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement return session; } - /* ------------------------------------------------------------ */ - /** - * @return Returns the metaManager used for cross context session management - * @deprecated Use {@link #getSessionIdManager()} - */ - public SessionIdManager getIdManager() - { - return getSessionIdManager(); - } - /* ------------------------------------------------------------ */ /** * @return Returns the SessionIdManager used for cross context session management */ + @Override @ManagedAttribute("Session ID Manager") public SessionIdManager getSessionIdManager() { @@ -348,16 +352,6 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement return _dftMaxIdleSecs; } - /* ------------------------------------------------------------ */ - /** - * @see #getSessionsMax() - */ - @Deprecated - public int getMaxSessions() - { - return getSessionsMax(); - } - /* ------------------------------------------------------------ */ /** * @return maximum number of sessions @@ -378,26 +372,6 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement return (int)_sessionsStats.getTotal(); } - /* ------------------------------------------------------------ */ - /** - * @deprecated use {@link #getSessionIdManager()} - */ - @Deprecated - public SessionIdManager getMetaManager() - { - return getSessionIdManager(); - } - - /* ------------------------------------------------------------ */ - /** - * @deprecated always returns 0. no replacement available. - */ - @Deprecated - public int getMinSessions() - { - return 0; - } - /* ------------------------------------------------------------ */ @ManagedAttribute("time before a session cookie is re-set (in s)") public int getRefreshCookieAge() @@ -405,7 +379,6 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement return _refreshCookieAge; } - /* ------------------------------------------------------------ */ /** * @return same as SessionCookieConfig.getSecure(). If true, session @@ -438,8 +411,6 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement _secureRequestOnly = secureRequestOnly; } - - /* ------------------------------------------------------------ */ @ManagedAttribute("the set session cookie") public String getSessionCookie() @@ -473,6 +444,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement * * @see org.eclipse.jetty.server.SessionManager#getSessionCookie(javax.servlet.http.HttpSession, java.lang.String, boolean) */ + @Override public HttpCookie getSessionCookie(HttpSession session, String contextPath, boolean requestIsSecure) { if (isUsingCookies()) @@ -526,18 +498,6 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement return _sessionHandler; } - /* ------------------------------------------------------------ */ - /** - * @deprecated Need to review if it is needed. - */ - @SuppressWarnings("rawtypes") - public Map getSessionMap() - { - throw new UnsupportedOperationException(); - } - - - /* ------------------------------------------------------------ */ @ManagedAttribute("number of currently active sessions") public int getSessions() @@ -546,6 +506,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement } /* ------------------------------------------------------------ */ + @Override @ManagedAttribute("name of use for URL session tracking") public String getSessionIdPathParameterName() { @@ -553,6 +514,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement } /* ------------------------------------------------------------ */ + @Override public String getSessionIdPathParameterNamePrefix() { return _sessionIdPathParameterNamePrefix; @@ -562,12 +524,14 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement /** * @return Returns the usingCookies. */ + @Override public boolean isUsingCookies() { return _usingCookies; } /* ------------------------------------------------------------ */ + @Override public boolean isValid(HttpSession session) { AbstractSession s = ((SessionIf)session).getSession(); @@ -575,6 +539,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement } /* ------------------------------------------------------------ */ + @Override public String getClusterId(HttpSession session) { AbstractSession s = ((SessionIf)session).getSession(); @@ -582,6 +547,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement } /* ------------------------------------------------------------ */ + @Override public String getNodeId(HttpSession session) { AbstractSession s = ((SessionIf)session).getSession(); @@ -592,6 +558,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement /** * Create a new HttpSession for a request */ + @Override public HttpSession newHttpSession(HttpServletRequest request) { AbstractSession session=newSession(request); @@ -601,6 +568,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement } /* ------------------------------------------------------------ */ + @Override public void removeEventListener(EventListener listener) { if (listener instanceof HttpSessionAttributeListener) @@ -608,17 +576,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement if (listener instanceof HttpSessionListener) _sessionListeners.remove(listener); } - - /* ------------------------------------------------------------ */ - /** - * @see #statsReset() - */ - @Deprecated - public void resetStats() - { - statsReset(); - } - + /* ------------------------------------------------------------ */ /** * Reset statistics values @@ -640,57 +598,45 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement _httpOnly=httpOnly; } - /* ------------------------------------------------------------ */ - /** - * @param metaManager The metaManager used for cross context session management. - * @deprecated use {@link #setSessionIdManager(SessionIdManager)} - */ - public void setIdManager(SessionIdManager metaManager) - { - setSessionIdManager(metaManager); - } - /* ------------------------------------------------------------ */ /** * @param metaManager The metaManager used for cross context session management. */ + @Override public void setSessionIdManager(SessionIdManager metaManager) { + updateBean(_sessionIdManager, metaManager); _sessionIdManager=metaManager; } - - /* ------------------------------------------------------------ */ /** * @param seconds */ + @Override public void setMaxInactiveInterval(int seconds) { _dftMaxIdleSecs=seconds; } - /* ------------------------------------------------------------ */ public void setRefreshCookieAge(int ageInSeconds) { _refreshCookieAge=ageInSeconds; } - - + /* ------------------------------------------------------------ */ public void setSessionCookie(String cookieName) { _sessionCookie=cookieName; } - - /* ------------------------------------------------------------ */ /** * @param sessionHandler * The sessionHandler to set. */ + @Override public void setSessionHandler(SessionHandler sessionHandler) { _sessionHandler=sessionHandler; @@ -698,6 +644,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement /* ------------------------------------------------------------ */ + @Override public void setSessionIdPathParameterName(String param) { _sessionIdPathParameterName =(param==null||"none".equals(param))?null:param; @@ -844,12 +791,14 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement } /* ------------------------------------------------------------ */ + @Override public Set getDefaultSessionTrackingModes() { return __defaultSessionTrackingModes; } /* ------------------------------------------------------------ */ + @Override public Set getEffectiveSessionTrackingModes() { return Collections.unmodifiableSet(_sessionTrackingModes); @@ -871,8 +820,8 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement return _usingURLs; } - /* ------------------------------------------------------------ */ + @Override public SessionCookieConfig getSessionCookieConfig() { return _cookieConfig; @@ -917,6 +866,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement /** * @see org.eclipse.jetty.server.SessionManager#isCheckingRemoteSessionIdEncoding() */ + @Override @ManagedAttribute("check remote session id encoding") public boolean isCheckingRemoteSessionIdEncoding() { @@ -927,6 +877,7 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement /** * @see org.eclipse.jetty.server.SessionManager#setCheckingRemoteSessionIdEncoding(boolean) */ + @Override public void setCheckingRemoteSessionIdEncoding(boolean remote) { _checkingRemoteSessionIdEncoding=remote; @@ -1091,4 +1042,11 @@ public abstract class AbstractSessionManager extends AbstractLifeCycle implement } } } + + @Override + @Deprecated + public SessionIdManager getMetaManager() + { + throw new UnsupportedOperationException(); + } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java index beda6ce60f1..1effd32f94b 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/HashSessionManager.java @@ -34,6 +34,7 @@ import java.util.concurrent.TimeUnit; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; +import org.eclipse.jetty.server.SessionIdManager; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.ClassLoadingObjectInputStream; import org.eclipse.jetty.util.IO; @@ -60,9 +61,7 @@ public class HashSessionManager extends AbstractSessionManager final static Logger LOG = SessionHandler.LOG; protected final ConcurrentMap _sessions=new ConcurrentHashMap(); - private static int __id; private Scheduler _timer; - private boolean _timerStop=false; private Scheduler.Task _task; long _scavengePeriodMs=30000; long _savePeriodMs=0; //don't do period saves by default @@ -134,14 +133,11 @@ public class HashSessionManager extends AbstractSessionManager @Override public void doStart() throws Exception { - super.doStart(); - - _timerStop=false; //try shared scheduler from Server first _timer = getSessionHandler().getServer().getBean(Scheduler.class); if (_timer == null) { - //try one passwed into the context + //try one passed into the context ServletContext context = ContextHandler.getCurrentContext(); if (context!=null) _timer = (Scheduler)context.getAttribute("org.eclipse.jetty.server.session.timer"); @@ -149,12 +145,14 @@ public class HashSessionManager extends AbstractSessionManager if (_timer == null) { - //make a scheduler if none useable - _timerStop=true; - _timer=new ScheduledExecutorScheduler(); - _timer.start(); + //make a scheduler if none useable + _timer=new ScheduledExecutorScheduler(toString()+"Timer",true); + addBean(_timer,true); } - + else + addBean(_timer,false); + + super.doStart(); setScavengePeriod(getScavengePeriod()); @@ -186,8 +184,6 @@ public class HashSessionManager extends AbstractSessionManager if (_task!=null) _task.cancel(); _task=null; - if (_timer!=null && _timerStop) - _timer.stop(); _timer=null; } @@ -427,7 +423,7 @@ public class HashSessionManager extends AbstractSessionManager /* ------------------------------------------------------------ */ @Override protected void shutdownSessions() throws Exception - { + { // Invalidate all sessions to cause unbind events ArrayList sessions=new ArrayList(_sessions.values()); int loop=100; @@ -672,7 +668,6 @@ public class HashSessionManager extends AbstractSessionManager if (size>0) { ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream(is); - for (int i=0; i Date: Thu, 24 Apr 2014 15:46:08 +0200 Subject: [PATCH 066/135] 433244 Security manager lifecycle cleanup --- .../jetty/security/HashLoginService.java | 4 ++ .../jetty/security/SecurityHandler.java | 61 +++++++++++-------- .../org/eclipse/jetty/server/HttpInput.java | 11 ++++ .../org/eclipse/jetty/server/Request.java | 2 + .../session/HashSessionManagerTest.java | 2 +- 5 files changed, 55 insertions(+), 25 deletions(-) diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java index 55f7ed22a63..335aabd72b8 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/HashLoginService.java @@ -133,6 +133,7 @@ public class HashLoginService extends MappedLoginService implements UserListener /** * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart() */ + @Override protected void doStart() throws Exception { super.doStart(); @@ -154,6 +155,7 @@ public class HashLoginService extends MappedLoginService implements UserListener /** * @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStop() */ + @Override protected void doStop() throws Exception { super.doStop(); @@ -163,6 +165,7 @@ public class HashLoginService extends MappedLoginService implements UserListener } /* ------------------------------------------------------------ */ + @Override public void update(String userName, Credential credential, String[] roleArray) { if (LOG.isDebugEnabled()) @@ -171,6 +174,7 @@ public class HashLoginService extends MappedLoginService implements UserListener } /* ------------------------------------------------------------ */ + @Override public void remove(String userName) { if (LOG.isDebugEnabled()) diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java index 90c0d1b5cee..a6e108e9a70 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java @@ -43,6 +43,7 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler.Context; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.session.AbstractSession; +import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -74,8 +75,6 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti private LoginService _loginService; private IdentityService _identityService; private boolean _renewSession=true; - private boolean _discoveredIdentityService = false; - private boolean _discoveredLoginService = false; /* ------------------------------------------------------------ */ protected SecurityHandler() @@ -266,20 +265,24 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti } /* ------------------------------------------------------------ */ - protected LoginService findLoginService() + protected LoginService findLoginService() throws Exception { Collection list = getServer().getBeans(LoginService.class); - + LoginService service = null; String realm=getRealmName(); if (realm!=null) { - for (LoginService service : list) - if (service.getName()!=null && service.getName().equals(realm)) - return service; + for (LoginService s : list) + if (s.getName()!=null && s.getName().equals(realm)) + { + service=s; + break; + } } else if (list.size()==1) - return list.iterator().next(); - return null; + service = list.iterator().next(); + + return service; } /* ------------------------------------------------------------ */ @@ -342,9 +345,10 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti if (_loginService==null) { setLoginService(findLoginService()); - _discoveredLoginService = true; + if (_loginService!=null) + unmanage(_loginService); } - + if (_identityService==null) { if (_loginService!=null) @@ -353,10 +357,16 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti if (_identityService==null) setIdentityService(findIdentityService()); - if (_identityService==null && _realmName!=null) - setIdentityService(new DefaultIdentityService()); - - _discoveredIdentityService = true; + if (_identityService==null) + { + if (_realmName!=null) + { + setIdentityService(new DefaultIdentityService()); + manage(_identityService); + } + } + else + unmanage(_identityService); } if (_loginService!=null) @@ -387,17 +397,16 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti protected void doStop() throws Exception { //if we discovered the services (rather than had them explicitly configured), remove them. - if (_discoveredIdentityService) + if (!isManaged(_identityService)) { removeBean(_identityService); - _identityService = null; - + _identityService = null; } - if (_discoveredLoginService) + if (!isManaged(_loginService)) { removeBean(_loginService); - _loginService = null; + _loginService=null; } super.doStop(); @@ -427,6 +436,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti /** * @see org.eclipse.jetty.security.Authenticator.AuthConfiguration#isSessionRenewedOnAuthentication() */ + @Override public boolean isSessionRenewedOnAuthentication() { return _renewSession; @@ -473,7 +483,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti { if (!baseRequest.isHandled()) { - response.sendError(Response.SC_FORBIDDEN); + response.sendError(HttpServletResponse.SC_FORBIDDEN); baseRequest.setHandled(true); } return; @@ -488,7 +498,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti LOG.warn("No authenticator for: "+roleInfo); if (!baseRequest.isHandled()) { - response.sendError(Response.SC_FORBIDDEN); + response.sendError(HttpServletResponse.SC_FORBIDDEN); baseRequest.setHandled(true); } return; @@ -524,7 +534,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti boolean authorized=checkWebResourcePermissions(pathInContext, baseRequest, base_response, roleInfo, userAuth.getUserIdentity()); if (!authorized) { - response.sendError(Response.SC_FORBIDDEN, "!role"); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "!role"); baseRequest.setHandled(true); return; } @@ -574,7 +584,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti { // jaspi 3.8.3 send HTTP 500 internal server error, with message // from AuthException - response.sendError(Response.SC_INTERNAL_SERVER_ERROR, e.getMessage()); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } finally { @@ -634,6 +644,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti /* ------------------------------------------------------------ */ public class NotChecked implements Principal { + @Override public String getName() { return null; @@ -656,6 +667,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti /* ------------------------------------------------------------ */ public static final Principal __NO_USER = new Principal() { + @Override public String getName() { return null; @@ -680,6 +692,7 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti */ public static final Principal __NOBODY = new Principal() { + @Override public String getName() { return "Nobody"; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index a1f9a8bed00..e5d31f082a7 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -288,6 +288,14 @@ public abstract class HttpInput extends ServletInputStream implements Runnabl } } + public boolean isAsync() + { + synchronized (lock()) + { + return _contentState==ASYNC; + } + } + /** * @return whether an EOF has been detected, even though there may be content to consume. */ @@ -436,6 +444,7 @@ public abstract class HttpInput extends ServletInputStream implements Runnabl input.blockForContent(); } + @Override public String toString() { return "STREAM"; @@ -471,6 +480,7 @@ public abstract class HttpInput extends ServletInputStream implements Runnabl return true; } + @Override public String toString() { return "EARLY_EOF"; @@ -485,6 +495,7 @@ public abstract class HttpInput extends ServletInputStream implements Runnabl return true; } + @Override public String toString() { return "EOF"; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index df679b4ffab..adf325ef1e6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -339,6 +339,8 @@ public class Request implements HttpServletRequest throw new IllegalStateException("Form too large " + content_length + ">" + maxFormContentSize); } InputStream in = getInputStream(); + if (_input.isAsync()) + throw new IllegalStateException("Cannot extract parameters with async IO"); // Add form params to query params UrlEncoded.decodeTo(in,_baseParameters,encoding,content_length < 0?maxFormContentSize:-1,maxFormKeys); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java index 1cd77af8344..ebd686d53d4 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/session/HashSessionManagerTest.java @@ -42,7 +42,7 @@ public class HashSessionManagerTest @Before public void quietStacks() { - enableStacks(true); + enableStacks(false); } protected void enableStacks(boolean enabled) From 8855b79be5f38cfdab02c6288b19c5afa539c008 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 25 Apr 2014 12:21:55 +0200 Subject: [PATCH 067/135] 433431 Support ServletHandler fall through --- .../eclipse/jetty/servlet/ServletHandler.java | 79 ++++++++++--------- .../servlet/ServletContextHandlerTest.java | 37 ++++++++- 2 files changed, 77 insertions(+), 39 deletions(-) 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 09e57cbecd7..6f0e5b170fb 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 @@ -89,6 +89,10 @@ import org.eclipse.jetty.util.log.Logger; * Unless run as part of a {@link ServletContextHandler} or derivative, the {@link #initialize()} * method must be called manually after start(). */ + +/* ------------------------------------------------------------ */ +/** + */ @ManagedObject("Servlet Handler") public class ServletHandler extends ScopedHandler { @@ -107,6 +111,7 @@ public class ServletHandler extends ScopedHandler private boolean _filterChainsCached=true; private int _maxFilterChainsCacheSize=512; private boolean _startWithUnavailable=false; + private boolean _ensureDefaultServlet=true; private IdentityService _identityService; private ServletHolder[] _servlets=new ServletHolder[0]; @@ -153,7 +158,7 @@ public class ServletHandler extends ScopedHandler updateNameMappings(); updateMappings(); - if (getServletMapping("/")==null) + if (getServletMapping("/")==null && _ensureDefaultServlet) { LOG.debug("Adding Default404Servlet to {}",this); addServletWithMapping(Default404Servlet.class,"/"); @@ -183,6 +188,26 @@ public class ServletHandler extends ScopedHandler } + /* ------------------------------------------------------------ */ + /** + * @return true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other + * default servlet is configured. + */ + public boolean isEnsureDefaultServlet() + { + return _ensureDefaultServlet; + } + + /* ------------------------------------------------------------ */ + /** + * @param ensureDefaultServlet true if ServletHandler always has a default servlet, using {@link Default404Servlet} if no other + * default servlet is configured. + */ + public void setEnsureDefaultServlet(boolean ensureDefaultServlet) + { + _ensureDefaultServlet=ensureDefaultServlet; + } + /* ----------------------------------------------------------------- */ @Override protected void start(LifeCycle l) throws Exception @@ -379,8 +404,6 @@ public class ServletHandler extends ScopedHandler { return _servletMappings; } - - /* ------------------------------------------------------------ */ /** @@ -397,9 +420,6 @@ public class ServletHandler extends ScopedHandler return _servletPathMappings.get(pathSpec); } - - - /* ------------------------------------------------------------ */ /** Get Servlets. * @return Array of defined servlets @@ -528,12 +548,7 @@ public class ServletHandler extends ScopedHandler try { if (servlet_holder==null) - { - if (getHandler()==null) - notFound(request, response); - else - nextHandle(target,baseRequest,request,response); - } + notFound(baseRequest,request, response); else { // unwrap any tunnelling of base Servlet request/responses @@ -1511,11 +1526,11 @@ public class ServletHandler extends ScopedHandler } /* ------------------------------------------------------------ */ - protected void notFound(HttpServletRequest request, HttpServletResponse response) throws IOException + protected void notFound(Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - if(LOG.isDebugEnabled()) - LOG.debug("Not Found "+request.getRequestURI()); - //Override to send an error back, eg with: response.sendError(HttpServletResponse.SC_NOT_FOUND); + LOG.debug("Not Found {}",request.getRequestURI()); + if (getHandler()!=null) + nextHandle(URIUtil.addPaths(request.getServletPath(),request.getPathInfo()),baseRequest,request,response); } /* ------------------------------------------------------------ */ @@ -1615,8 +1630,7 @@ public class ServletHandler extends ScopedHandler // pass to next filter if (_filterHolder!=null) { - if (LOG.isDebugEnabled()) - LOG.debug("call filter " + _filterHolder); + LOG.debug("call filter {}", _filterHolder); Filter filter= _filterHolder.getFilter(); if (_filterHolder.isAsyncSupported()) filter.doFilter(request, response, _next); @@ -1642,20 +1656,15 @@ public class ServletHandler extends ScopedHandler } // Call servlet - HttpServletRequest srequest = (HttpServletRequest)request; - if (_servletHolder != null) + if (_servletHolder == null) + notFound(baseRequest, srequest, (HttpServletResponse)response); + else { if (LOG.isDebugEnabled()) LOG.debug("call servlet " + _servletHolder); _servletHolder.handle(baseRequest,request, response); } - else if (getHandler()==null) - notFound(srequest, (HttpServletResponse)response); - else - nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()), - baseRequest,srequest,(HttpServletResponse)response); - } @Override @@ -1724,20 +1733,13 @@ public class ServletHandler extends ScopedHandler // Call servlet HttpServletRequest srequest = (HttpServletRequest)request; - if (_servletHolder != null) - { - if (LOG.isDebugEnabled()) - LOG.debug("call servlet " + _servletHolder); - _servletHolder.handle(_baseRequest,request, response); - } - else if (getHandler()==null) - notFound(srequest, (HttpServletResponse)response); + if (_servletHolder == null) + notFound((request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest(), srequest, (HttpServletResponse)response); else { - Request baseRequest=(request instanceof Request)?((Request)request):HttpChannel.getCurrentHttpChannel().getRequest(); - nextHandle(URIUtil.addPaths(srequest.getServletPath(),srequest.getPathInfo()), - baseRequest,srequest,(HttpServletResponse)response); - } + LOG.debug("call servlet {}", _servletHolder); + _servletHolder.handle(_baseRequest,request, response); + } } /* ------------------------------------------------------------ */ @@ -1795,6 +1797,7 @@ public class ServletHandler extends ScopedHandler /* ------------------------------------------------------------ */ public static class Default404Servlet extends HttpServlet { + @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java index c4b46990cf0..7ebbe2bec34 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ServletContextHandlerTest.java @@ -40,13 +40,18 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.security.ConstraintSecurityHandler; import org.eclipse.jetty.security.SecurityHandler; +import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.LocalConnector; +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.AbstractHandlerContainer; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.server.session.SessionHandler; +import org.eclipse.jetty.util.component.LifeCycle.Listener; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; @@ -276,7 +281,37 @@ public class ServletContextHandlerTest assertTrue(contextDestroy.get()); } - + + @Test + public void testFallThrough() throws Exception + { + HandlerList list = new HandlerList(); + _server.setHandler(list); + + ServletContextHandler root = new ServletContextHandler(list,"/",ServletContextHandler.SESSIONS); + + ServletHandler servlet = root.getServletHandler(); + servlet.setEnsureDefaultServlet(false); + servlet.addServletWithMapping(HelloServlet.class, "/hello/*"); + + list.addHandler(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + response.sendError(404, "Fell Through"); + } + }); + + _server.start(); + + String response= _connector.getResponses("GET /hello HTTP/1.0\r\n\r\n"); + Assert.assertThat(response, Matchers.containsString("200 OK")); + + response= _connector.getResponses("GET /other HTTP/1.0\r\n\r\n"); + Assert.assertThat(response, Matchers.containsString("404 Fell Through")); + + } private int assertResponseContains(String expected, String response) { From ce5af1d521e2f2027c831ac7dbbfc36e5afe3792 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 25 Apr 2014 12:56:41 +0200 Subject: [PATCH 068/135] 424982 improved PID check in jetty.sh --- .../src/main/resources/bin/jetty.sh | 36 ++++++++----------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/jetty-distribution/src/main/resources/bin/jetty.sh b/jetty-distribution/src/main/resources/bin/jetty.sh index ce489dbe1d4..3190332432e 100644 --- a/jetty-distribution/src/main/resources/bin/jetty.sh +++ b/jetty-distribution/src/main/resources/bin/jetty.sh @@ -105,8 +105,14 @@ findDirectory() running() { - local PID=$(cat "$1" 2>/dev/null) || return 1 - kill -0 "$PID" 2>/dev/null + if [ -f "$1" ] + then + local PID=$(cat "$1" 2>/dev/null) || return 1 + kill -0 "$PID" 2>/dev/null + return + fi + rm -f "$1" + return 1 } started() @@ -408,16 +414,10 @@ case "$ACTION" in else - if [ -f "$JETTY_PID" ] + if running $JETTY_PID then - if running $JETTY_PID - then - echo "Already Running!" - exit 1 - else - # dead pid file - remove - rm -f "$JETTY_PID" - fi + echo "Already Running $(cat $JETTY_PID)!" + exit 1 fi if [ "$JETTY_USER" ] @@ -519,16 +519,10 @@ case "$ACTION" in run|demo) echo "Running Jetty: " - if [ -f "$JETTY_PID" ] + if running "$JETTY_PID" then - if running "$JETTY_PID" - then - echo "Already Running!" - exit 1 - else - # dead pid file - remove - rm -f "$JETTY_PID" - fi + echo Already Running $(cat "$JETTY_PID")! + exit 1 fi exec "${RUN_CMD[@]}" @@ -550,7 +544,7 @@ case "$ACTION" in echo "RUN_CMD = ${RUN_CMD[*]}" echo - if [ -f "$JETTY_PID" ] + if running "$JETTY_PID" then echo "Jetty running pid=$(< "$JETTY_PID")" exit 0 From 15661722b38be21f228a0021c73823d913c7acf7 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 25 Apr 2014 14:22:57 +0200 Subject: [PATCH 069/135] 433483 sync log initialize --- .../java/org/eclipse/jetty/util/log/Log.java | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java index 7d998b39de5..57838f2e68e 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/log/Log.java @@ -151,33 +151,32 @@ public class Log public static void initialized() { - synchronized (Log.class) { if (__initialized) return; __initialized = true; - } - - final long uptime=ManagementFactory.getRuntimeMXBean().getUptime(); - try - { - Class log_class = Loader.loadClass(Log.class, __logClass); - if (LOG == null || !LOG.getClass().equals(log_class)) + final long uptime=ManagementFactory.getRuntimeMXBean().getUptime(); + + try { - LOG = (Logger)log_class.newInstance(); - LOG.debug("Logging to {} via {}", LOG, log_class.getName()); + Class log_class = Loader.loadClass(Log.class, __logClass); + if (LOG == null || !LOG.getClass().equals(log_class)) + { + LOG = (Logger)log_class.newInstance(); + LOG.debug("Logging to {} via {}", LOG, log_class.getName()); + } } + catch(Throwable e) + { + // Unable to load specified Logger implementation, default to standard logging. + initStandardLogging(e); + } + + if (LOG!=null) + LOG.info(String.format("Logging initialized @%dms",uptime)); } - catch(Throwable e) - { - // Unable to load specified Logger implementation, default to standard logging. - initStandardLogging(e); - } - - if (LOG!=null) - LOG.info(String.format("Logging initialized @%dms",uptime)); } private static void initStandardLogging(Throwable e) From bdecc7bd89fac45e8e85d40c9e58805b4344a244 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 25 Apr 2014 14:36:59 +0200 Subject: [PATCH 070/135] 433479 Improved resource javadoc --- .../jetty/server/handler/ContextHandler.java | 13 ++++++++----- .../org/eclipse/jetty/util/resource/Resource.java | 4 ++++ 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index b6b33314bd3..333aecdcc97 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -1384,8 +1384,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu /* ------------------------------------------------------------ */ /** - * @param base - * The resourceBase to set. + * Set the base resource for this context. + * @param base The resource used as the base for all static content of this context. + * @see #setResourceBase(String) */ public void setBaseResource(Resource base) { @@ -1393,9 +1394,11 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu } /* ------------------------------------------------------------ */ - /** - * @param resourceBase - * The base resource as a string. + /** + * Set the base resource for this context. + * @param resourceBase A string representing the base resource for the context. Any string accepted + * by {@link Resource#newResource(String)} may be passed and the call is equivalent to + * setBaseResource(newResource(resourceBase)); */ public void setResourceBase(String resourceBase) { diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java index 401e75a777e..623731a33e6 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java @@ -46,6 +46,10 @@ import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ /** * Abstract resource class. + *

    + * This class provides a resource abstraction, where a resource may be + * a file, a URL or an entry in a jar file. + *

    */ public abstract class Resource implements ResourceFactory, Closeable { From 1fb578165f75f47b9b8ae15756337187f7d453af Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 24 Apr 2014 17:33:02 -0700 Subject: [PATCH 071/135] 433262 - WebSocket / Advanced close use cases + Improved test case handling of frame reading (to prevent future false failures) + Improved AbstractWebSocketConnection handling of close -> disconnect to only trigger on successful write (or failure) of close frame flush attempt. + Flusher close on all forms of disconnect, even half-closed. --- .../jsr356/server/ConfiguratorTest.java | 6 +- .../websocket/client/ClientCloseTest.java | 6 +- .../common/extensions/ExtensionStack.java | 7 +- .../io/AbstractWebSocketConnection.java | 110 ++++++-- .../common/test/BlockheadClient.java | 267 ++++++++++-------- .../jetty/websocket/common/test/Fuzzer.java | 26 +- .../server/AnnotatedMaxMessageSizeTest.java | 10 +- .../jetty/websocket/server/ChromeTest.java | 8 +- .../jetty/websocket/server/FirefoxTest.java | 10 +- .../server/FragmentExtensionTest.java | 8 +- .../server/FrameCompressionExtensionTest.java | 12 +- .../server/IdentityExtensionTest.java | 8 +- .../websocket/server/IdleTimeoutTest.java | 8 +- .../PerMessageDeflateExtensionTest.java | 17 +- .../websocket/server/RequestHeadersTest.java | 2 +- .../websocket/server/WebSocketCloseTest.java | 14 +- .../server/WebSocketServerSessionTest.java | 10 +- .../server/WebSocketServletRFCTest.java | 26 +- .../websocket/server/ab/TestABCase9.java | 22 +- 19 files changed, 328 insertions(+), 249 deletions(-) diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java index 045118a2648..f6cdb3b19da 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java @@ -37,6 +37,7 @@ import javax.websocket.server.ServerEndpointConfig; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.websocket.api.util.QuoteUtil; @@ -44,7 +45,6 @@ import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; import org.eclipse.jetty.websocket.common.test.HttpResponse; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; import org.junit.AfterClass; import org.junit.Assert; @@ -210,8 +210,8 @@ public class ConfiguratorTest client.expectUpgradeResponse(); client.write(new TextFrame().setPayload("X-Dummy")); - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); + WebSocketFrame frame = frames.poll(); Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Request Header [X-Dummy]: \"Bogus\"")); } } diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java index 3a7f33874bd..2b1f824883b 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java @@ -425,7 +425,7 @@ public class ClientCloseTest // client idle timeout triggers close event on client ws-endpoint // client close event on ws-endpoint - clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.ABNORMAL),containsString("Timeout")); + clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.SHUTDOWN),containsString("Timeout")); } @Test @@ -541,9 +541,7 @@ public class ClientCloseTest // server sits idle // client idle timeout triggers close event on client ws-endpoint - // assert - close code==1006 (abnormal) - // assert - close reason message contains (timeout) - clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.ABNORMAL),containsString("Timeout")); + clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.SHUTDOWN),containsString("Timeout")); } @Test diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java index 18e4a9da5f6..a249c0e0574 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/ExtensionStack.java @@ -370,10 +370,13 @@ public class ExtensionStack extends ContainerLifeCycle implements IncomingFrames protected Action process() throws Exception { current = entries.poll(); - LOG.debug("Processing {}", current); if (current == null) + { + LOG.debug("Entering IDLE"); return Action.IDLE; - nextOutgoing.outgoingFrame(current.frame, this, current.batchMode); + } + LOG.debug("Processing {}",current); + nextOutgoing.outgoingFrame(current.frame,this,current.batchMode); return Action.SCHEDULED; } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java index cea5909865c..7494abac802 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java @@ -53,7 +53,6 @@ import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.ConnectionState; import org.eclipse.jetty.websocket.common.Generator; import org.eclipse.jetty.websocket.common.LogicalConnection; -import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.Parser; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.io.IOState.ConnectionStateListener; @@ -133,6 +132,68 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp } } + public class OnCloseLocalCallback implements WriteCallback + { + private final WriteCallback callback; + private final CloseInfo close; + + public OnCloseLocalCallback(WriteCallback callback, CloseInfo close) + { + this.callback = callback; + this.close = close; + } + + public OnCloseLocalCallback(CloseInfo close) + { + this(null,close); + } + + @Override + public void writeFailed(Throwable x) + { + try + { + if (callback != null) + { + callback.writeFailed(x); + } + } + finally + { + onLocalClose(); + } + } + + @Override + public void writeSuccess() + { + try + { + if (callback != null) + { + callback.writeSuccess(); + } + } + finally + { + onLocalClose(); + } + } + + private void onLocalClose() + { + LOG.debug("Local Close Confirmed {}",close); + if (close.isAbnormal()) + { + ioState.onAbnormalClose(close); + } + else + { + ioState.onCloseLocal(close); + } + } + } + public static class Stats { private AtomicLong countFillInterestedEvents = new AtomicLong(0); @@ -220,26 +281,20 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp { LOG.debug("close({},{})",statusCode,reason); CloseInfo close = new CloseInfo(statusCode,reason); - if (statusCode == StatusCode.ABNORMAL) - { - ioState.onAbnormalClose(close); - } - else - { - ioState.onCloseLocal(close); - } + this.outgoingFrame(close.asFrame(),new OnCloseLocalCallback(close),BatchMode.OFF); } @Override public void disconnect() { - LOG.debug("{} disconnect()",policy.getBehavior()); - flusher.close(); disconnect(false); } private void disconnect(boolean onlyOutput) { + LOG.debug("{} disconnect({})",policy.getBehavior(),onlyOutput?"outputOnly":"both"); + // close FrameFlusher, we cannot write anymore at this point. + flusher.close(); EndPoint endPoint = getEndPoint(); // We need to gently close first, to allow // SSL close alerts to be sent by Jetty @@ -388,13 +443,20 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp CloseInfo abnormal = new CloseInfo(StatusCode.SHUTDOWN,"Abnormal Close - " + ioState.getCloseInfo().getReason()); outgoingFrame(abnormal.asFrame(),new OnDisconnectCallback(false),BatchMode.OFF); } - // Just disconnect - this.disconnect(false); + else + { + // Just disconnect + this.disconnect(false); + } break; case CLOSING: - CloseInfo close = ioState.getCloseInfo(); - // reply to close handshake from remote - outgoingFrame(close.asFrame(),new OnDisconnectCallback(true),BatchMode.OFF); + // First occurrence of .onCloseLocal or .onCloseRemote use + if (ioState.wasRemoteCloseInitiated()) + { + CloseInfo close = ioState.getCloseInfo(); + // reply to close handshake from remote + outgoingFrame(close.asFrame(),new OnCloseLocalCallback(new OnDisconnectCallback(true),close),BatchMode.OFF); + } default: break; } @@ -463,7 +525,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp finally { // This is an Abnormal Close condition - close(StatusCode.ABNORMAL,"Idle Timeout"); + close(StatusCode.SHUTDOWN,"Idle Timeout"); } return false; @@ -480,21 +542,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp LOG.debug("outgoingFrame({}, {})",frame,callback); } - CloseInfo close = null; - // grab a copy of the frame details before masking and whatnot - if (frame.getOpCode() == OpCode.CLOSE) - { - close = new CloseInfo(frame); - } - flusher.enqueue(frame,callback,batchMode); - - // now trigger local close - if (close != null) - { - LOG.debug("outgoing CLOSE frame - {}: {}",frame,close); - ioState.onCloseLocal(close); - } } private int read(ByteBuffer buffer) diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java index b4634c5b5ca..d8973d9ec9a 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java @@ -36,12 +36,13 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.MappedByteBufferPool; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.StringUtil; @@ -81,21 +82,102 @@ import org.junit.Assert; * with regards to basic IO behavior, a write should work as expected, a read should work as expected, but what byte it sends or reads is not within its * scope. */ -public class BlockheadClient implements IncomingFrames, OutgoingFrames, ConnectionStateListener, AutoCloseable +public class BlockheadClient implements OutgoingFrames, ConnectionStateListener, AutoCloseable { + private class FrameReadingThread extends Thread implements Runnable, IncomingFrames + { + public long totalBytes = 0; + public long totalReadOps = 0; + public long totalParseOps = 0; + + public EventQueue frames = new EventQueue<>(); + public EventQueue errors = new EventQueue<>(); + + @Override + public void run() + { + LOG.debug("Reading frames from server"); + + byte buf[] = new byte[BUFFER_SIZE]; + try + { + int len = 0; + int available = 0; + while (!eof) + { + available = in.available(); + len = in.read(buf,0,Math.min(available,buf.length)); + totalReadOps++; + if (len < 0) + { + eof = true; + break; + } + else if (len > 0) + { + totalBytes += len; + ByteBuffer bbuf = ByteBuffer.wrap(buf,0,len); + if (LOG.isDebugEnabled()) + { + LOG.debug("Read {} bytes: {}",len,BufferUtil.toDetailString(bbuf)); + } + totalParseOps++; + parser.parse(bbuf); + } + } + } + catch (IOException e) + { + LOG.debug(e); + } + } + + @Override + public String toString() + { + StringBuilder str = new StringBuilder(); + str.append("FrameReadingThread["); + str.append(",frames=" + frames.size()); + str.append(",errors=" + errors.size()); + str.append(String.format(",totalBytes=%,d",totalBytes)); + str.append(String.format(",totalReadOps=%,d",totalReadOps)); + str.append(String.format(",totalParseOps=%,d",totalParseOps)); + str.append("]"); + return str.toString(); + } + + @Override + public synchronized void incomingError(Throwable t) + { + this.errors.add(t); + } + + @Override + public synchronized void incomingFrame(Frame frame) + { + this.frames.add(WebSocketFrame.copy(frame)); + } + + public synchronized void clear() + { + this.frames.clear(); + this.errors.clear(); + } + } + private static final String REQUEST_HASH_KEY = "dGhlIHNhbXBsZSBub25jZQ=="; - private static final int BUFFER_SIZE = 8192; + private static final int BUFFER_SIZE = 64 * 1024; private static final Logger LOG = Log.getLogger(BlockheadClient.class); - /** Set to true to disable timeouts (for debugging reasons) */ - private boolean debug = false; private final URI destHttpURI; private final URI destWebsocketURI; private final ByteBufferPool bufferPool; private final Generator generator; private final Parser parser; - private final IncomingFramesCapture incomingFrames; - private final WebSocketExtensionFactory extensionFactory; + private final WebSocketExtensionFactory extensionFactory; + private FrameReadingThread frameReader; + + private ExecutorService executor; private Socket socket; private OutputStream out; private InputStream in; @@ -103,16 +185,15 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti private String protocols; private List extensions = new ArrayList<>(); private List headers = new ArrayList<>(); - private byte[] clientmask = new byte[] - { (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF }; + private byte[] clientmask = new byte[] { (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF }; private int timeout = 1000; - private AtomicInteger parseCount; private OutgoingFrames outgoing = this; private boolean eof = false; private ExtensionStack extensionStack; private IOState ioState; private CountDownLatch disconnectedLatch = new CountDownLatch(1); private ByteBuffer remainingBuffer; + private String connectionValue = "Upgrade"; public BlockheadClient(URI destWebsocketURI) throws URISyntaxException @@ -137,9 +218,6 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti this.bufferPool = new MappedByteBufferPool(8192); this.generator = new Generator(policy,bufferPool); this.parser = new Parser(policy,bufferPool); - this.parseCount = new AtomicInteger(0); - - this.incomingFrames = new IncomingFramesCapture(); this.extensionFactory = new WebSocketExtensionFactory(policy,bufferPool); this.ioState = new IOState(); @@ -161,9 +239,38 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti return disconnectedLatch.await(timeout,unit); } + protected int blockingRead(ByteBuffer buf) throws IOException + { + if (eof) + { + return -1; + } + + if ((remainingBuffer != null) && (remainingBuffer.remaining() > 0)) + { + return BufferUtil.put(remainingBuffer,buf); + } + + int len = -1; + int b; + while (buf.remaining() > 0) + { + b = in.read(); + if (b == (-1)) + { + eof = true; + break; + } + buf.put((byte)b); + len++; + } + + return len; + } + public void clearCaptured() { - this.incomingFrames.clear(); + frameReader.clear(); } public void clearExtensions() @@ -171,6 +278,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti extensions.clear(); } + @Override public void close() { LOG.debug("close()"); @@ -185,7 +293,9 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti if (!ioState.isClosed()) { ioState.onCloseLocal(close); - } else { + } + else + { LOG.debug("Not issuing close. ioState = {}",ioState); } } @@ -211,6 +321,10 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti IO.close(in); IO.close(out); disconnectedLatch.countDown(); + if (frameReader != null) + { + frameReader.interrupt(); + } if (socket != null) { try @@ -282,8 +396,12 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti extensionStack = new ExtensionStack(this.extensionFactory); extensionStack.negotiate(configs); + // Setup Frame Reader + this.frameReader = new FrameReadingThread(); + this.frameReader.start(); + // Start with default routing - extensionStack.setNextIncoming(this); // the websocket layer + extensionStack.setNextIncoming(frameReader); // the websocket layer extensionStack.setNextOutgoing(outgoing); // the network layer // Configure Parser / Generator @@ -320,6 +438,15 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti return connectionValue; } + public ExecutorService getExecutor() + { + if (executor == null) + { + executor = Executors.newCachedThreadPool(); + } + return executor; + } + private List getExtensionConfigs(HttpResponse response) { List configs = new ArrayList<>(); @@ -397,33 +524,6 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti return destWebsocketURI; } - /** - * Errors received (after extensions) - */ - @Override - public void incomingError(Throwable e) - { - incomingFrames.incomingError(e); - } - - /** - * Frames received (after extensions) - */ - @Override - public void incomingFrame(Frame frame) - { - LOG.debug("incoming({})",frame); - int count = parseCount.incrementAndGet(); - if ((count % 10) == 0) - { - LOG.info("Client parsed {} frames",count); - } - - // Capture Frame Copy - WebSocketFrame copy = WebSocketFrame.copy(frame); - incomingFrames.incomingFrame(copy); - } - public boolean isConnected() { return (socket != null) && (socket.isConnected()); @@ -432,7 +532,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti @Override public void onConnectionStateChange(ConnectionState state) { - LOG.debug("CLIENT onConnectionStateChange() - {}", state); + LOG.debug("CLIENT onConnectionStateChange() - {}",state); switch (state) { case CLOSED: @@ -441,7 +541,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti break; case CLOSING: CloseInfo close = ioState.getCloseInfo(); - + WebSocketFrame frame = close.asFrame(); LOG.debug("Issuing: {}",frame); try @@ -524,68 +624,10 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti return len; } - @Deprecated - public IncomingFramesCapture readFrames(int expectedCount, TimeUnit timeoutUnit, int timeoutDuration) throws IOException, TimeoutException + public EventQueue readFrames(int expectedFrameCount, int timeoutDuration, TimeUnit timeoutUnit) throws Exception { - return readFrames(expectedCount,timeoutDuration,timeoutUnit); - } - - public IncomingFramesCapture readFrames(int expectedCount, int timeoutDuration, TimeUnit timeoutUnit) throws IOException, TimeoutException - { - LOG.debug("Read: waiting for {} frame(s) from server",expectedCount); - - ByteBuffer buf = bufferPool.acquire(BUFFER_SIZE,false); - BufferUtil.clearToFill(buf); - try - { - long msDur = TimeUnit.MILLISECONDS.convert(timeoutDuration,timeoutUnit); - long now = System.currentTimeMillis(); - long expireOn = now + msDur; - LOG.debug("Now: {} - expireOn: {} ({} ms)",now,expireOn,msDur); - - long iter = 0; - - int len = 0; - while (incomingFrames.size() < expectedCount) - { - BufferUtil.clearToFill(buf); - len = read(buf); - if (len > 0) - { - BufferUtil.flipToFlush(buf,0); - if (LOG.isDebugEnabled()) - { - LOG.debug("Read {} bytes: {}",len,BufferUtil.toDetailString(buf)); - } - parser.parse(buf); - } - else - { - if (LOG.isDebugEnabled()) - { - iter++; - if ((iter % 10000000) == 0) - { - LOG.debug("10,000,000 reads of zero length"); - iter = 0; - } - } - } - - if (!debug && (System.currentTimeMillis() > expireOn)) - { - incomingFrames.dump(); - throw new TimeoutException(String.format("Timeout reading all %d expected frames. (managed to only read %d frame(s))",expectedCount, - incomingFrames.size())); - } - } - } - finally - { - bufferPool.release(buf); - } - - return incomingFrames; + frameReader.frames.awaitEventCount(expectedFrameCount,timeoutDuration,timeoutUnit); + return frameReader.frames; } public HttpResponse readResponseHeader() throws IOException @@ -641,9 +683,9 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti this.connectionValue = connectionValue; } - public void setDebug(boolean flag) + public void setExecutor(ExecutorService executor) { - this.debug = flag; + this.executor = executor; } public void setProtocols(String protocols) @@ -651,7 +693,7 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti this.protocols = protocols; } - public void setTimeout(TimeUnit unit, int duration) + public void setTimeout(int duration, TimeUnit unit) { this.timeout = (int)TimeUnit.MILLISECONDS.convert(duration,unit); } @@ -705,14 +747,13 @@ public class BlockheadClient implements IncomingFrames, OutgoingFrames, Connecti LOG.debug("write(Frame->{}) to {}",frame,outgoing); if (LOG.isDebugEnabled()) { - frame.setMask(new byte[] - { 0x00, 0x00, 0x00, 0x00 }); + frame.setMask(new byte[] { 0x00, 0x00, 0x00, 0x00 }); } else { frame.setMask(clientmask); } - extensionStack.outgoingFrame(frame,null, BatchMode.OFF); + extensionStack.outgoingFrame(frame,null,BatchMode.OFF); } public void writeRaw(ByteBuffer buf) throws IOException diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzer.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzer.java index 8ebfc946463..1a72c897328 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzer.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/Fuzzer.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.websocket.common.test; +import static org.hamcrest.Matchers.*; + import java.io.IOException; import java.net.SocketException; import java.nio.ByteBuffer; @@ -25,8 +27,8 @@ import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -39,9 +41,6 @@ import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.io.IOState; import org.junit.Assert; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; - /** * Fuzzing utility for the AB tests. */ @@ -95,6 +94,7 @@ public class Fuzzer implements AutoCloseable policy.setIdleTimeout(5000); this.client = new BlockheadClient(policy,testcase.getServerURI()); + this.client.setTimeout(2,TimeUnit.SECONDS); this.generator = testcase.getLaxGenerator(); this.testname = testcase.getTestMethodName(); } @@ -140,28 +140,24 @@ public class Fuzzer implements AutoCloseable } } - public void expect(List expect) throws IOException, TimeoutException + public void expect(List expect) throws Exception { - expect(expect,TimeUnit.SECONDS,10); + expect(expect,10,TimeUnit.SECONDS); } - public void expect(List expect, TimeUnit unit, int duration) throws IOException, TimeoutException + public void expect(List expect, int duration, TimeUnit unit) throws Exception { int expectedCount = expect.size(); LOG.debug("expect() {} frame(s)",expect.size()); // Read frames - IncomingFramesCapture capture = client.readFrames(expect.size(),unit,duration); - if (LOG.isDebugEnabled()) - { - capture.dump(); - } - + EventQueue frames = client.readFrames(expect.size(),duration,unit); + String prefix = ""; for (int i = 0; i < expectedCount; i++) { WebSocketFrame expected = expect.get(i); - WebSocketFrame actual = capture.getFrames().poll(); + WebSocketFrame actual = frames.poll(); prefix = "Frame[" + i + "]"; @@ -183,7 +179,7 @@ public class Fuzzer implements AutoCloseable } } - public void expect(WebSocketFrame expect) throws IOException, TimeoutException + public void expect(WebSocketFrame expect) throws Exception { expect(Collections.singletonList(expect)); } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java index b07597e1e60..e6ca4dcfd37 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/AnnotatedMaxMessageSizeTest.java @@ -28,6 +28,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.common.CloseInfo; @@ -36,7 +37,6 @@ import org.eclipse.jetty.websocket.common.Parser; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.server.examples.echo.BigEchoSocket; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; import org.junit.AfterClass; @@ -100,8 +100,8 @@ public class AnnotatedMaxMessageSizeTest client.write(new TextFrame().setPayload(msg)); // Read frame (hopefully text frame) - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,500,TimeUnit.MILLISECONDS); + WebSocketFrame tf = frames.poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally @@ -127,8 +127,8 @@ public class AnnotatedMaxMessageSizeTest client.write(new TextFrame().setPayload(ByteBuffer.wrap(buf))); // Read frame (hopefully close frame saying its too large) - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,500,TimeUnit.MILLISECONDS); + WebSocketFrame tf = frames.poll(); Assert.assertThat("Frame is close", tf.getOpCode(), is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(tf); Assert.assertThat("Close Code", close.getStatusCode(), is(StatusCode.MESSAGE_TOO_LARGE)); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java index 1abe09bb5e1..38ab89072c0 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java @@ -22,19 +22,17 @@ import static org.hamcrest.Matchers.*; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; import org.eclipse.jetty.websocket.common.test.HttpResponse; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.server.examples.MyEchoServlet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; -import org.junit.Ignore; import org.junit.Test; -@Ignore("Bug 395444") public class ChromeTest { private static SimpleServletServer server; @@ -70,8 +68,8 @@ public class ChromeTest client.write(new TextFrame().setPayload(msg)); // Read frame (hopefully text frame) - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,500,TimeUnit.MILLISECONDS); + WebSocketFrame tf = frames.poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FirefoxTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FirefoxTest.java index 46dbdd92405..771cdd4bb58 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FirefoxTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FirefoxTest.java @@ -18,20 +18,20 @@ package org.eclipse.jetty.websocket.server; +import static org.hamcrest.Matchers.*; + import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.server.examples.MyEchoServlet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import static org.hamcrest.Matchers.is; - public class FirefoxTest { private static SimpleServletServer server; @@ -65,8 +65,8 @@ public class FirefoxTest client.write(new TextFrame().setPayload(msg)); // Read frame (hopefully text frame) - IncomingFramesCapture capture = client.readFrames(1, TimeUnit.MILLISECONDS, 500); - WebSocketFrame tf = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1, 500, TimeUnit.MILLISECONDS); + WebSocketFrame tf = frames.poll(); Assert.assertThat("Text Frame.status code", tf.getPayloadAsUTF8(), is(msg)); } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java index 045b96c01ce..be15bac9a25 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FragmentExtensionTest.java @@ -22,11 +22,11 @@ import static org.hamcrest.Matchers.*; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; import org.eclipse.jetty.websocket.common.test.HttpResponse; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.server.helper.EchoServlet; import org.junit.AfterClass; import org.junit.Assert; @@ -79,7 +79,7 @@ public class FragmentExtensionTest try { // Make sure the read times out if there are problems with the implementation - client.setTimeout(TimeUnit.SECONDS,1); + client.setTimeout(1,TimeUnit.SECONDS); client.connect(); client.sendStandardRequest(); HttpResponse resp = client.expectUpgradeResponse(); @@ -90,10 +90,10 @@ public class FragmentExtensionTest client.write(new TextFrame().setPayload(msg)); String parts[] = split(msg,fragSize); - IncomingFramesCapture capture = client.readFrames(parts.length,TimeUnit.MILLISECONDS,1000); + EventQueue frames = client.readFrames(parts.length,1000,TimeUnit.MILLISECONDS); for (int i = 0; i < parts.length; i++) { - WebSocketFrame frame = capture.getFrames().poll(); + WebSocketFrame frame = frames.poll(); Assert.assertThat("text[" + i + "].payload",frame.getPayloadAsUTF8(),is(parts[i])); } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java index 607ba243c3b..8811cc66cc4 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/FrameCompressionExtensionTest.java @@ -22,11 +22,11 @@ import static org.hamcrest.Matchers.*; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; import org.eclipse.jetty.websocket.common.test.HttpResponse; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.server.helper.EchoServlet; import org.junit.AfterClass; import org.junit.Assert; @@ -61,7 +61,7 @@ public class FrameCompressionExtensionTest try { // Make sure the read times out if there are problems with the implementation - client.setTimeout(TimeUnit.SECONDS,1); + client.setTimeout(1,TimeUnit.SECONDS); client.connect(); client.sendStandardRequest(); HttpResponse resp = client.expectUpgradeResponse(); @@ -73,8 +73,8 @@ public class FrameCompressionExtensionTest // Client sends first message client.write(new TextFrame().setPayload(msg)); - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000); - WebSocketFrame frame = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,1000,TimeUnit.MILLISECONDS); + WebSocketFrame frame = frames.poll(); Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString())); // Client sends second message @@ -82,8 +82,8 @@ public class FrameCompressionExtensionTest msg = "There"; client.write(new TextFrame().setPayload(msg)); - capture = client.readFrames(1,TimeUnit.SECONDS,1); - frame = capture.getFrames().poll(); + frames = client.readFrames(1,1,TimeUnit.SECONDS); + frame = frames.poll(); Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString())); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java index c0ca20f2371..5c127130832 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdentityExtensionTest.java @@ -22,11 +22,11 @@ import static org.hamcrest.Matchers.*; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; import org.eclipse.jetty.websocket.common.test.HttpResponse; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.server.helper.EchoServlet; import org.junit.AfterClass; import org.junit.Assert; @@ -64,7 +64,7 @@ public class IdentityExtensionTest try { // Make sure the read times out if there are problems with the implementation - client.setTimeout(TimeUnit.SECONDS,1); + client.setTimeout(1,TimeUnit.SECONDS); client.connect(); client.sendStandardRequest(); HttpResponse resp = client.expectUpgradeResponse(); @@ -73,8 +73,8 @@ public class IdentityExtensionTest client.write(new TextFrame().setPayload("Hello")); - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000); - WebSocketFrame frame = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,1000,TimeUnit.MILLISECONDS); + WebSocketFrame frame = frames.poll(); Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is("Hello")); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java index 7fdada494e0..4b360048124 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/IdleTimeoutTest.java @@ -22,13 +22,13 @@ import static org.hamcrest.Matchers.*; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.server.helper.RFCSocket; import org.eclipse.jetty.websocket.servlet.WebSocketServlet; import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; @@ -73,7 +73,7 @@ public class IdleTimeoutTest { BlockheadClient client = new BlockheadClient(server.getServerUri()); client.setProtocols("onConnect"); - client.setTimeout(TimeUnit.MILLISECONDS,2500); + client.setTimeout(2500,TimeUnit.MILLISECONDS); try { client.connect(); @@ -92,8 +92,8 @@ public class IdleTimeoutTest client.write(new TextFrame().setPayload("Hello")); // Expect server to have closed due to its own timeout - IncomingFramesCapture capture = client.readFrames(1,500,TimeUnit.MILLISECONDS); - WebSocketFrame frame = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,500,TimeUnit.MILLISECONDS); + WebSocketFrame frame = frames.poll(); Assert.assertThat("frame opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("close code",close.getStatusCode(),is(StatusCode.SHUTDOWN)); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java index 4747e8ad6a5..d06dd75ca99 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java @@ -18,22 +18,21 @@ package org.eclipse.jetty.websocket.server; +import static org.hamcrest.Matchers.*; + import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; import org.eclipse.jetty.websocket.common.test.HttpResponse; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.server.helper.EchoServlet; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; - public class PerMessageDeflateExtensionTest { private static SimpleServletServer server; @@ -65,7 +64,7 @@ public class PerMessageDeflateExtensionTest try { // Make sure the read times out if there are problems with the implementation - client.setTimeout(TimeUnit.SECONDS,1); + client.setTimeout(1,TimeUnit.SECONDS); client.connect(); client.sendStandardRequest(); HttpResponse resp = client.expectUpgradeResponse(); @@ -77,8 +76,8 @@ public class PerMessageDeflateExtensionTest // Client sends first message client.write(new TextFrame().setPayload(msg)); - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000); - WebSocketFrame frame = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,1000,TimeUnit.MILLISECONDS); + WebSocketFrame frame = frames.poll(); Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString())); // Client sends second message @@ -86,8 +85,8 @@ public class PerMessageDeflateExtensionTest msg = "There"; client.write(new TextFrame().setPayload(msg)); - capture = client.readFrames(1,TimeUnit.SECONDS,1); - frame = capture.getFrames().poll(); + frames = client.readFrames(1,1,TimeUnit.SECONDS); + frame = frames.poll(); Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString())); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java index 6cc9950fc23..47268588763 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/RequestHeadersTest.java @@ -107,7 +107,7 @@ public class RequestHeadersTest public void testAccessRequestCookies() throws Exception { BlockheadClient client = new BlockheadClient(server.getServerUri()); - client.setTimeout(TimeUnit.SECONDS,1); + client.setTimeout(1,TimeUnit.SECONDS); try { diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java index 5a4707c1af1..0ec98a3ca25 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketCloseTest.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.log.StacklessLogging; @@ -36,7 +37,6 @@ import org.eclipse.jetty.websocket.common.OpCode; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.events.AbstractEventDriver; import org.eclipse.jetty.websocket.common.test.BlockheadClient; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.server.helper.RFCSocket; import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest; import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse; @@ -162,14 +162,14 @@ public class WebSocketCloseTest try (BlockheadClient client = new BlockheadClient(server.getServerUri())) { client.setProtocols("fastclose"); - client.setTimeout(TimeUnit.SECONDS,1); + client.setTimeout(1,TimeUnit.SECONDS); client.connect(); client.sendStandardRequest(); client.expectUpgradeResponse(); // Verify that client got close frame - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); + WebSocketFrame frame = frames.poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.NORMAL)); @@ -192,15 +192,15 @@ public class WebSocketCloseTest try (BlockheadClient client = new BlockheadClient(server.getServerUri())) { client.setProtocols("fastfail"); - client.setTimeout(TimeUnit.SECONDS,1); + client.setTimeout(1,TimeUnit.SECONDS); try (StacklessLogging scope = new StacklessLogging(AbstractEventDriver.class)) { client.connect(); client.sendStandardRequest(); client.expectUpgradeResponse(); - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); + WebSocketFrame frame = frames.poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.SERVER_ERROR)); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java index 8b3732bfb71..4d3568bc768 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServerSessionTest.java @@ -18,15 +18,16 @@ package org.eclipse.jetty.websocket.server; +import static org.hamcrest.Matchers.*; + import java.net.URI; -import java.util.Queue; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.toolchain.test.AdvancedRunner; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.server.helper.SessionServlet; import org.junit.AfterClass; import org.junit.Assert; @@ -34,8 +35,6 @@ import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; -import static org.hamcrest.Matchers.is; - /** * Testing various aspects of the server side support for WebSocket {@link Session} */ @@ -90,8 +89,7 @@ public class WebSocketServerSessionTest client.write(new TextFrame().setPayload("getParameterMap|cost")); // intentionally invalid // Read frame (hopefully text frame) - IncomingFramesCapture capture = client.readFrames(4, TimeUnit.SECONDS, 5); - Queue frames = capture.getFrames(); + EventQueue frames = client.readFrames(4,5,TimeUnit.SECONDS); WebSocketFrame tf = frames.poll(); Assert.assertThat("Parameter Map[snack]", tf.getPayloadAsUTF8(), is("[cashews]")); tf = frames.poll(); diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java index fe27a9d1166..70718384cbb 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/WebSocketServletRFCTest.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.toolchain.test.AdvancedRunner; +import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.util.Utf8Appendable.NotUtf8Exception; import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.log.StacklessLogging; @@ -41,7 +42,6 @@ import org.eclipse.jetty.websocket.common.frames.BinaryFrame; import org.eclipse.jetty.websocket.common.frames.ContinuationFrame; import org.eclipse.jetty.websocket.common.frames.TextFrame; import org.eclipse.jetty.websocket.common.test.BlockheadClient; -import org.eclipse.jetty.websocket.common.test.IncomingFramesCapture; import org.eclipse.jetty.websocket.common.test.UnitGenerator; import org.eclipse.jetty.websocket.common.util.Hex; import org.eclipse.jetty.websocket.server.helper.RFCServlet; @@ -116,8 +116,8 @@ public class WebSocketServletRFCTest client.write(bin); // write buf3 (fin=true) // Read frame echo'd back (hopefully a single binary frame) - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,1000); - Frame binmsg = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,1000,TimeUnit.MILLISECONDS); + Frame binmsg = frames.poll(); int expectedSize = buf1.length + buf2.length + buf3.length; Assert.assertThat("BinaryFrame.payloadLength",binmsg.getPayloadLength(),is(expectedSize)); @@ -182,8 +182,8 @@ public class WebSocketServletRFCTest client.write(new TextFrame().setPayload(msg)); // Read frame (hopefully text frame) - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,500,TimeUnit.MILLISECONDS); + WebSocketFrame tf = frames.poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally @@ -215,8 +215,8 @@ public class WebSocketServletRFCTest client.write(new TextFrame().setPayload("CRASH")); // Read frame (hopefully close frame) - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - Frame cf = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,500,TimeUnit.MILLISECONDS); + Frame cf = frames.poll(); CloseInfo close = new CloseInfo(cf); Assert.assertThat("Close Frame.status code",close.getStatusCode(),is(StatusCode.SERVER_ERROR)); } @@ -261,8 +261,8 @@ public class WebSocketServletRFCTest client.write(new TextFrame().setPayload(msg)); // Read frame (hopefully text frame) - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,500,TimeUnit.MILLISECONDS); + WebSocketFrame tf = frames.poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally @@ -294,8 +294,8 @@ public class WebSocketServletRFCTest client.writeRaw(bbHeader); client.writeRaw(txt.getPayload()); - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.SECONDS,1); - WebSocketFrame frame = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); + WebSocketFrame frame = frames.poll(); Assert.assertThat("frames[0].opcode",frame.getOpCode(),is(OpCode.CLOSE)); CloseInfo close = new CloseInfo(frame); Assert.assertThat("Close Status Code",close.getStatusCode(),is(StatusCode.BAD_PAYLOAD)); @@ -340,8 +340,8 @@ public class WebSocketServletRFCTest client.write(new TextFrame().setPayload(msg)); // Read frame (hopefully text frame) - IncomingFramesCapture capture = client.readFrames(1,TimeUnit.MILLISECONDS,500); - WebSocketFrame tf = capture.getFrames().poll(); + EventQueue frames = client.readFrames(1,500,TimeUnit.MILLISECONDS); + WebSocketFrame tf = frames.poll(); Assert.assertThat("Text Frame.status code",tf.getPayloadAsUTF8(),is(msg)); } finally diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java index 1b117f4bd05..274e91dfea3 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/TestABCase9.java @@ -101,7 +101,7 @@ public class TestABCase9 extends AbstractABCase fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); - fuzzer.expect(expect,TimeUnit.SECONDS,8); + fuzzer.expect(expect,8,TimeUnit.SECONDS); } } @@ -125,7 +125,7 @@ public class TestABCase9 extends AbstractABCase fuzzer.setSendMode(Fuzzer.SendMode.SLOW); fuzzer.setSlowSendSegmentSize(segmentSize); fuzzer.send(send); - fuzzer.expect(expect,TimeUnit.SECONDS,8); + fuzzer.expect(expect,8,TimeUnit.SECONDS); } } @@ -187,7 +187,6 @@ public class TestABCase9 extends AbstractABCase * Echo 1MB text message (1 frame) */ @Test - @Stress("High I/O use") public void testCase9_1_3() throws Exception { byte utf[] = new byte[1 * MBYTE]; @@ -207,7 +206,7 @@ public class TestABCase9 extends AbstractABCase fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); - fuzzer.expect(expect,TimeUnit.SECONDS,4); + fuzzer.expect(expect,4,TimeUnit.SECONDS); } } @@ -215,7 +214,6 @@ public class TestABCase9 extends AbstractABCase * Echo 4MB text message (1 frame) */ @Test - @Stress("High I/O use") public void testCase9_1_4() throws Exception { byte utf[] = new byte[4 * MBYTE]; @@ -235,7 +233,7 @@ public class TestABCase9 extends AbstractABCase fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); - fuzzer.expect(expect,TimeUnit.SECONDS,8); + fuzzer.expect(expect,8,TimeUnit.SECONDS); } } @@ -263,7 +261,7 @@ public class TestABCase9 extends AbstractABCase fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); - fuzzer.expect(expect,TimeUnit.SECONDS,16); + fuzzer.expect(expect,16,TimeUnit.SECONDS); } } @@ -291,7 +289,7 @@ public class TestABCase9 extends AbstractABCase fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); - fuzzer.expect(expect,TimeUnit.SECONDS,32); + fuzzer.expect(expect,32,TimeUnit.SECONDS); } } @@ -372,7 +370,7 @@ public class TestABCase9 extends AbstractABCase fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); - fuzzer.expect(expect,TimeUnit.SECONDS,4); + fuzzer.expect(expect,4,TimeUnit.SECONDS); } } @@ -400,7 +398,7 @@ public class TestABCase9 extends AbstractABCase fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); - fuzzer.expect(expect,TimeUnit.SECONDS,8); + fuzzer.expect(expect,8,TimeUnit.SECONDS); } } @@ -428,7 +426,7 @@ public class TestABCase9 extends AbstractABCase fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); - fuzzer.expect(expect,TimeUnit.SECONDS,16); + fuzzer.expect(expect,16,TimeUnit.SECONDS); } } @@ -456,7 +454,7 @@ public class TestABCase9 extends AbstractABCase fuzzer.connect(); fuzzer.setSendMode(Fuzzer.SendMode.BULK); fuzzer.send(send); - fuzzer.expect(expect,TimeUnit.SECONDS,32); + fuzzer.expect(expect,32,TimeUnit.SECONDS); } } From b4542a031b1873f91f5f75a5b2147913191b4f1a Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Fri, 25 Apr 2014 16:34:14 +0200 Subject: [PATCH 072/135] 433512 Jetty throws RuntimeException when webapp compiled with jdk8 -parameters --- .../org/eclipse/jetty/annotations/AnnotationParser.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 5587c2fd6d5..e34d8a5c0e9 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 @@ -373,7 +373,7 @@ public class AnnotationParser final String signature, final String[] exceptions) { - super(Opcodes.ASM4); + super(Opcodes.ASM5); _handlers = handlers; _mi = new MethodInfo(classInfo, name, access, methodDesc,signature, exceptions); } @@ -417,7 +417,7 @@ public class AnnotationParser final String signature, final Object value) { - super(Opcodes.ASM4); + super(Opcodes.ASM5); _handlers = handlers; _fieldInfo = new FieldInfo(classInfo, fieldName, access, fieldType, signature, value); } @@ -456,7 +456,7 @@ public class AnnotationParser public MyClassVisitor(Set handlers, Resource containingResource) { - super(Opcodes.ASM4); + super(Opcodes.ASM5); _handlers = handlers; _containingResource = containingResource; } @@ -702,6 +702,7 @@ public class AnnotationParser } catch (Exception ex) { + if (LOG.isDebugEnabled()) LOG.debug("Error scanning file "+files[f], ex); me.add(new RuntimeException("Error scanning file "+files[f],ex)); } } From 87c5b30d1cc4f878c44f089a4b3d05aaa121bbad Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 25 Apr 2014 17:26:27 +0200 Subject: [PATCH 073/135] 432901 ensure a single onError callback only in pending and unready states --- .../org/eclipse/jetty/server/HttpOutput.java | 144 ++++++++++-------- .../jetty/servlet/AsyncIOServletTest.java | 86 ++++++++++- 2 files changed, 168 insertions(+), 62 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 610db58cbf9..085fbeb67df 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 @@ -74,7 +74,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable write completed - - - ASYNC READY->owp - */ - enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, CLOSED } + enum OutputState { OPEN, ASYNC, READY, PENDING, UNREADY, ERROR, CLOSED } private final AtomicReference _state=new AtomicReference<>(OutputState.OPEN); public HttpOutput(HttpChannel channel) @@ -146,7 +146,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable break loop; case UNREADY: - throw new WritePendingException(); // TODO ? + if (_state.compareAndSet(state,OutputState.ERROR)) + _writeListener.onError(_onError==null?new EofException("Async close"):_onError); + continue; default: if (_state.compareAndSet(state,OutputState.CLOSED)) @@ -179,7 +181,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable break loop; case UNREADY: - throw new WritePendingException(); // TODO ? + if (_state.compareAndSet(state,OutputState.ERROR)) + _writeListener.onError(_onError==null?new EofException("Async closed"):_onError); + continue; default: if (_state.compareAndSet(state,OutputState.CLOSED)) @@ -238,6 +242,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable case UNREADY: throw new WritePendingException(); + case ERROR: + throw new EofException(_onError); + case CLOSED: return; } @@ -298,6 +305,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable case UNREADY: throw new WritePendingException(); + case ERROR: + throw new EofException(_onError); + case CLOSED: throw new EofException("Closed"); } @@ -396,6 +406,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable case UNREADY: throw new WritePendingException(); + case ERROR: + throw new EofException(_onError); + case CLOSED: throw new EofException("Closed"); } @@ -476,6 +489,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable case UNREADY: throw new WritePendingException(); + case ERROR: + throw new EofException(_onError); + case CLOSED: throw new EofException("Closed"); } @@ -615,6 +631,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable if (!_state.compareAndSet(OutputState.OPEN, OutputState.PENDING)) continue; break; + case ERROR: + throw new EofException(_onError); case CLOSED: throw new EofException("Closed"); default: @@ -706,6 +724,9 @@ public class HttpOutput extends ServletOutputStream implements Runnable return false; case UNREADY: return false; + + case ERROR: + return true; case CLOSED: return true; @@ -716,45 +737,54 @@ public class HttpOutput extends ServletOutputStream implements Runnable @Override public void run() { - if(_onError!=null) + loop: while (true) { - Throwable th=_onError; - _onError=null; - _writeListener.onError(new IOException(th)); - close(); - } - - switch(_state.get()) - { - case READY: - try + OutputState state = _state.get(); + + if(_onError!=null) + { + switch(state) { - _writeListener.onWritePossible(); + case CLOSED: + case ERROR: + _onError=null; + break loop; + + default: + if (_state.compareAndSet(state, OutputState.ERROR)) + { + Throwable th=_onError; + _onError=null; + _writeListener.onError(new IOException(th)); + close(); + + break loop; + } + } - catch (Throwable e) - { - _writeListener.onError(e); - close(); - } - break; - - case CLOSED: - try - { - new Throwable().printStackTrace(); + continue loop; + } + + switch(_state.get()) + { + case READY: + case CLOSED: // even though a write is not possible, because a close has // occurred, we need to call onWritePossible to tell async // producer that the last write completed. - _writeListener.onWritePossible(); - } - catch (Throwable e) - { - _writeListener.onError(e); - } - break; - - default: - + try + { + _writeListener.onWritePossible(); + break loop; + } + catch (Throwable e) + { + _onError=e; + } + break; + default: + + } } } @@ -769,37 +799,29 @@ public class HttpOutput extends ServletOutputStream implements Runnable @Override protected void completed() { - try + while(true) { - while(true) + OutputState last=_state.get(); + switch(last) { - HttpOutput.OutputState last=_state.get(); - switch(last) - { - case PENDING: - if (!_state.compareAndSet(HttpOutput.OutputState.PENDING, HttpOutput.OutputState.ASYNC)) - continue; - break; + case PENDING: + if (!_state.compareAndSet(OutputState.PENDING, OutputState.ASYNC)) + continue; + break; - case UNREADY: - if (!_state.compareAndSet(HttpOutput.OutputState.UNREADY, HttpOutput.OutputState.READY)) - continue; - _channel.getState().onWritePossible(); - break; + case UNREADY: + if (!_state.compareAndSet(OutputState.UNREADY, OutputState.READY)) + continue; + _channel.getState().onWritePossible(); + break; - case CLOSED: - break; + case CLOSED: + break; - default: - throw new IllegalStateException(); - } - break; + default: + throw new IllegalStateException(); } - } - catch (Exception e) - { - _onError=e; - _channel.getState().onWritePossible(); + break; } } diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java index 563b895921e..0abff4f1584 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java @@ -18,11 +18,16 @@ package org.eclipse.jetty.servlet; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.Socket; +import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -30,6 +35,7 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.AsyncContext; import javax.servlet.ReadListener; import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -39,6 +45,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser; import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Test; @@ -56,7 +63,7 @@ public class AsyncIOServletTest connector = new ServerConnector(server); server.addConnector(connector); - context = new ServletContextHandler(server, "", false, false); + context = new ServletContextHandler(server, "/", false, false); ServletHolder holder = new ServletHolder(servlet); holder.setAsyncSupported(true); context.addServlet(holder, path); @@ -257,4 +264,81 @@ public class AsyncIOServletTest Assert.assertEquals("500", response.getCode()); } } + + + @Test + public void testAsyncWriteClosed() throws Exception + { + final CountDownLatch latch = new CountDownLatch(1); + String text = "Now is the winter of our discontent. How Now Brown Cow. The quick brown fox jumped over the lazy dog.\n"; + for (int i=0;i<10;i++) + text=text+text; + final byte[] data = text.getBytes(StandardCharsets.ISO_8859_1); + + startServer(new HttpServlet() + { + @Override + protected void service(HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException + { + response.flushBuffer(); + + final AsyncContext async = request.startAsync(); + final ServletOutputStream out = response.getOutputStream(); + out.setWriteListener(new WriteListener() + { + @Override + public void onWritePossible() throws IOException + { + while (out.isReady()) + { + try + { + Thread.sleep(100); + out.write(data); + } + catch(IOException e) + { + throw e; + } + catch(Exception e) + { + e.printStackTrace(); + } + } + } + + @Override + public void onError(Throwable t) + { + async.complete(); + latch.countDown(); + } + }); + } + }); + + String request = "GET " + path + " HTTP/1.1\r\n" + + "Host: localhost:" + connector.getLocalPort() + "\r\n" + + "\r\n"; + + try (Socket client = new Socket("localhost", connector.getLocalPort())) + { + OutputStream output = client.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream())); + String line=in.readLine(); + assertThat(line, containsString("200 OK")); + while (line.length()>0) + line=in.readLine(); + line=in.readLine(); + assertThat(line, not(containsString(" "))); + line=in.readLine(); + assertThat(line, containsString("discontent. How Now Brown Cow. The ")); + } + + if (!latch.await(5, TimeUnit.SECONDS)) + Assert.fail(); + } } From eade9a3d25d9837dc391e18fd02b55516731422e Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 25 Apr 2014 18:23:54 +0200 Subject: [PATCH 074/135] 432901 ensure a single onError callback only in pending and unready states --- .../src/main/java/org/eclipse/jetty/server/HttpOutput.java | 3 ++- .../java/org/eclipse/jetty/servlet/AsyncIOServletTest.java | 2 +- 2 files changed, 3 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 085fbeb67df..7c0fd899a41 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 @@ -755,7 +755,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable { Throwable th=_onError; _onError=null; - _writeListener.onError(new IOException(th)); + LOG.debug("onError",th); + _writeListener.onError(th); close(); break loop; diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java index 0abff4f1584..b28e145b7d9 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java @@ -238,10 +238,10 @@ public class AsyncIOServletTest @Override public void onError(Throwable t) { - Assert.assertSame(throwable, t); latch.countDown(); response.setStatus(500); asyncContext.complete(); + Assert.assertSame(throwable, t); } }); } From 718017af948be23204a548093f6079339f0a5e46 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 25 Apr 2014 12:36:08 -0700 Subject: [PATCH 075/135] Minor tweak to throwable assertion to be more reliable --- .../java/org/eclipse/jetty/servlet/AsyncIOServletTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java index b28e145b7d9..b37f94c2557 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncIOServletTest.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.servlet; +import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertThat; @@ -118,7 +119,8 @@ public class AsyncIOServletTest @Override public void onError(Throwable t) { - Assert.assertSame(throwable, t); + Assert.assertThat("onError type",t,instanceOf(throwable.getClass())); + Assert.assertThat("onError message",t.getMessage(),is(throwable.getMessage())); latch.countDown(); response.setStatus(500); asyncContext.complete(); From c7dd3481c653dbbd3c39a7dc0dd6ca134e86377f Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 25 Apr 2014 12:37:00 -0700 Subject: [PATCH 076/135] Turning on websocket-common/io debug for jenkins testing --- .../src/test/resources/jetty-logging.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties index 994503cc1b9..fda5d883a0a 100644 --- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties @@ -4,7 +4,7 @@ org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=INFO -# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG +org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG # org.eclipse.jetty.websocket.server.ab.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG From 703deea1dfcfd8586955328736b5d0b35a48709a Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Fri, 25 Apr 2014 12:48:04 -0700 Subject: [PATCH 077/135] Tidying up ws-test-client read operations (in hope that it will address test failures on jenkins) --- .../common/test/BlockheadClient.java | 95 ++++++------------- 1 file changed, 30 insertions(+), 65 deletions(-) diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java index d8973d9ec9a..9f5f17e7f9f 100644 --- a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/test/BlockheadClient.java @@ -101,6 +101,14 @@ public class BlockheadClient implements OutgoingFrames, ConnectionStateListener, byte buf[] = new byte[BUFFER_SIZE]; try { + if ((remainingBuffer != null) && (remainingBuffer.remaining() > 0)) + { + LOG.debug("Reading bytes received during response header parse: {}",BufferUtil.toDetailString(remainingBuffer)); + totalBytes += remainingBuffer.remaining(); + totalReadOps++; + parser.parse(remainingBuffer); + } + int len = 0; int available = 0; while (!eof) @@ -239,35 +247,6 @@ public class BlockheadClient implements OutgoingFrames, ConnectionStateListener, return disconnectedLatch.await(timeout,unit); } - protected int blockingRead(ByteBuffer buf) throws IOException - { - if (eof) - { - return -1; - } - - if ((remainingBuffer != null) && (remainingBuffer.remaining() > 0)) - { - return BufferUtil.put(remainingBuffer,buf); - } - - int len = -1; - int b; - while (buf.remaining() > 0) - { - b = in.read(); - if (b == (-1)) - { - eof = true; - break; - } - buf.put((byte)b); - len++; - } - - return len; - } - public void clearCaptured() { frameReader.clear(); @@ -595,35 +574,6 @@ public class BlockheadClient implements OutgoingFrames, ConnectionStateListener, } } - public int read(ByteBuffer buf) throws IOException - { - if (eof) - { - throw new EOFException("Hit EOF"); - } - - if ((remainingBuffer != null) && (remainingBuffer.remaining() > 0)) - { - return BufferUtil.put(remainingBuffer,buf); - } - - int len = -1; - int b; - while ((in.available() > 0) && (buf.remaining() > 0)) - { - b = in.read(); - if (b == (-1)) - { - eof = true; - break; - } - buf.put((byte)b); - len++; - } - - return len; - } - public EventQueue readFrames(int expectedFrameCount, int timeoutDuration, TimeUnit timeoutUnit) throws Exception { frameReader.frames.awaitEventCount(expectedFrameCount,timeoutDuration,timeoutUnit); @@ -633,17 +583,32 @@ public class BlockheadClient implements OutgoingFrames, ConnectionStateListener, public HttpResponse readResponseHeader() throws IOException { HttpResponse response = new HttpResponse(); - HttpResponseHeaderParser parser = new HttpResponseHeaderParser(response); + HttpResponseHeaderParser respParser = new HttpResponseHeaderParser(response); - ByteBuffer buf = BufferUtil.allocate(512); + byte buf[] = new byte[512]; - do + while (!eof) { - BufferUtil.flipToFill(buf); - read(buf); - BufferUtil.flipToFlush(buf,0); + int available = in.available(); + int len = in.read(buf,0,Math.min(available,buf.length)); + if (len < 0) + { + eof = true; + break; + } + else if (len > 0) + { + ByteBuffer bbuf = ByteBuffer.wrap(buf,0,len); + if (LOG.isDebugEnabled()) + { + LOG.debug("Read {} bytes: {}",len,BufferUtil.toDetailString(bbuf)); + } + if (respParser.parse(bbuf) != null) + { + break; + } + } } - while (parser.parse(buf) == null); remainingBuffer = response.getRemainingBuffer(); From b186d3eb6e254171542052583a010f411519df85 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 28 Apr 2014 12:16:45 +0200 Subject: [PATCH 078/135] 428904 Add logging of which webapp has path with uncovered http methods --- .../eclipse/jetty/security/ConstraintSecurityHandler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java index d8ad6391806..201618d89d1 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java @@ -45,6 +45,7 @@ import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.UserIdentity; +import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.security.Constraint; @@ -820,7 +821,9 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr if (paths != null && !paths.isEmpty()) { for (String p:paths) - LOG.warn("Path with uncovered http methods: {}",p); + LOG.warn("{} has uncovered http methods for path: {}",ContextHandler.getCurrentContext(), p); + if (LOG.isDebugEnabled()) + LOG.debug(new Throwable()); return true; } return false; From 1e8d8b8b3ec6e9f4e8ebb032b11ced00f100581b Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 28 Apr 2014 14:50:37 +0200 Subject: [PATCH 079/135] 433572 default to sending date header --- .../main/java/org/eclipse/jetty/server/HttpConfiguration.java | 2 +- .../java/org/eclipse/jetty/server/AsyncRequestReadTest.java | 1 + .../java/org/eclipse/jetty/server/HttpServerTestFixture.java | 1 + .../test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java | 1 + .../test/java/org/eclipse/jetty/servlet/AsyncContextTest.java | 2 ++ .../src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java | 1 + .../src/test/java/org/eclipse/jetty/servlet/InvokerTest.java | 1 + 7 files changed, 8 insertions(+), 1 deletion(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java index 87f62c2b96d..352af25c750 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java @@ -52,7 +52,7 @@ public class HttpConfiguration private String _secureScheme = HttpScheme.HTTPS.asString(); private boolean _sendServerVersion = true; //send Server: header private boolean _sendXPoweredBy = false; //send X-Powered-By: header - private boolean _sendDateHeader = false; //send Date: header + private boolean _sendDateHeader = true; //send Date: header public interface Customizer { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java index c8a43211f1b..2c9fd571a4e 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java @@ -59,6 +59,7 @@ public class AsyncRequestReadTest server = new Server(); connector = new ServerConnector(server); connector.setIdleTimeout(10000); + connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false); server.addConnector(connector); } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java index 1636cd4ab8d..abce05f7b45 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java @@ -68,6 +68,7 @@ public class HttpServerTestFixture protected void startServer(ServerConnector connector) throws Exception { _connector = connector; + _connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false); _server.addConnector(_connector); _server.setHandler(new HandlerWrapper()); _server.start(); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java index 29db142e7dd..564e9c14807 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java @@ -109,6 +109,7 @@ public class SSLEngineTest http.getHttpConfiguration().setRequestHeaderSize(512); connector=new ServerConnector(server, sslContextFactory, http); connector.setPort(0); + connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false); server.addConnector(connector); } 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 bbcb708de63..abfe5558ea5 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 @@ -40,6 +40,7 @@ import javax.servlet.http.HttpServletResponseWrapper; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.LocalConnector; import org.eclipse.jetty.server.QuietServletException; import org.eclipse.jetty.server.Request; @@ -70,6 +71,7 @@ public class AsyncContextTest _contextHandler = new ServletContextHandler(ServletContextHandler.NO_SESSIONS); _connector = new LocalConnector(_server); _connector.setIdleTimeout(5000); + _connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false); _server.setConnectors(new Connector[] { _connector }); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java index 6bc920f362d..8ed551d5d47 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DispatcherTest.java @@ -78,6 +78,7 @@ public class DispatcherTest _server = new Server(); _connector = new LocalConnector(_server); _connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false); + _connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false); _contextCollection = new ContextHandlerCollection(); _contextHandler = new ServletContextHandler(); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java index 13dfacebecb..d94f6cd7ca5 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/InvokerTest.java @@ -49,6 +49,7 @@ public class InvokerTest _server = new Server(); _connector = new LocalConnector(_server); _connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendServerVersion(false); + _connector.getConnectionFactory(HttpConfiguration.ConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false); ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); _server.addConnector(_connector); From 00d61ce1339f71d0efee44bcd5bd20ba1ec62393 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 28 Apr 2014 15:35:57 +0200 Subject: [PATCH 080/135] fixed jsp demo distribution --- jetty-distribution/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-distribution/pom.xml b/jetty-distribution/pom.xml index 5003e023f8e..d81eb575abf 100644 --- a/jetty-distribution/pom.xml +++ b/jetty-distribution/pom.xml @@ -570,7 +570,7 @@ jetty.home=${assembly-directory} jetty.base=${assembly-directory}/demo-base --add-to-start=server,continuation,deploy,websocket,ext,resources,client,annotations,jndi,servlets - --add-to-startd-ini=jsp,jstl,http,https + --add-to-startd=jsp,jstl,http,https From 609945fe7c6f0f5a48a77b3c77e31201d439fd37 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 28 Apr 2014 15:36:03 +0200 Subject: [PATCH 081/135] 433656 Change to Opcode.ASM5 breaks jetty-osgi --- .../org/eclipse/jetty/annotations/AnnotationParser.java | 8 +++++--- .../eclipse/jetty/osgi/annotations/AnnotationParser.java | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) 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 e34d8a5c0e9..41b1627f6e0 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 @@ -69,6 +69,8 @@ public class AnnotationParser private static final Logger LOG = Log.getLogger(AnnotationParser.class); protected Set _parsedClassNames = new ConcurrentHashSet(); + + protected static int ASM_OPCODE_VERSION = Opcodes.ASM5; //compatibility of api /** @@ -373,7 +375,7 @@ public class AnnotationParser final String signature, final String[] exceptions) { - super(Opcodes.ASM5); + super(ASM_OPCODE_VERSION); _handlers = handlers; _mi = new MethodInfo(classInfo, name, access, methodDesc,signature, exceptions); } @@ -417,7 +419,7 @@ public class AnnotationParser final String signature, final Object value) { - super(Opcodes.ASM5); + super(ASM_OPCODE_VERSION); _handlers = handlers; _fieldInfo = new FieldInfo(classInfo, fieldName, access, fieldType, signature, value); } @@ -456,7 +458,7 @@ public class AnnotationParser public MyClassVisitor(Set handlers, Resource containingResource) { - super(Opcodes.ASM5); + super(ASM_OPCODE_VERSION); _handlers = handlers; _containingResource = containingResource; } diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java index b1ccba263a8..8887036f595 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/annotations/AnnotationParser.java @@ -35,6 +35,7 @@ import org.eclipse.jetty.annotations.ClassNameResolver; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelper; import org.eclipse.jetty.util.ConcurrentHashSet; import org.eclipse.jetty.util.resource.Resource; +import org.objectweb.asm.Opcodes; import org.osgi.framework.Bundle; import org.osgi.framework.Constants; @@ -50,6 +51,14 @@ public class AnnotationParser extends org.eclipse.jetty.annotations.AnnotationPa private ConcurrentHashMap _resourceToBundle = new ConcurrentHashMap(); private ConcurrentHashMap _bundleToUri = new ConcurrentHashMap(); + static + { + //As of jetty 9.2.0, the impl of asm visitor classes is compatible with both asm4 and asm5. + //We need to use asm4 with osgi, because we need to use aries spifly to support annotations, + //and currently this only supports asm4. Therefore, we set the asm api version to be 4 for osgi. + ASM_OPCODE_VERSION = Opcodes.ASM4; + } + /** * Keep track of a jetty URI Resource and its associated OSGi bundle. * @param uri From 1de043d6c3b95f50ab4f51c003ea5ed5606e8b02 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 28 Apr 2014 13:59:55 -0700 Subject: [PATCH 082/135] 433563 - Jetty fails to startup on windows - InvalidPathException + Adjusted PathMatchers.isAbsolute() to only consider the search root, and not the whole path. --- .../main/java/org/eclipse/jetty/start/PathMatchers.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java b/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java index 609c8b211aa..58ce0ca9756 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java @@ -47,7 +47,7 @@ public class PathMatchers } } } - + private static final char GLOB_CHARS[] = "*?".toCharArray(); private static final char SYNTAXED_GLOB_CHARS[] = "{}[]|:".toCharArray(); private static final Path EMPTY_PATH = new File(".").toPath(); @@ -167,7 +167,12 @@ public class PathMatchers */ public static boolean isAbsolute(final String pattern) { - return asPath(pattern).isAbsolute(); + Path searchRoot = getSearchRoot(pattern); + if (searchRoot == EMPTY_PATH) + { + return false; + } + return searchRoot.isAbsolute(); } /** From b4d1060e886c309befb7fee61cd735cb39d3f3d7 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 29 Apr 2014 15:15:55 +0200 Subject: [PATCH 083/135] Avoid to null out the cookieStore field in doStop(), to prevent random NPEs while stopping. --- .../src/main/java/org/eclipse/jetty/client/HttpClient.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 15cd719d983..5649b2992ee 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -228,7 +228,6 @@ public class HttpClient extends ContainerLifeCycle protected void doStop() throws Exception { cookieStore.removeAll(); - cookieStore = null; decoderFactories.clear(); handlers.clear(); From 2e261b75d6dd749b5ea354e16750c64b81faa42c Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 29 Apr 2014 20:35:49 +0200 Subject: [PATCH 084/135] 433692 improved buffer resizing --- .../jetty/client/GZIPContentDecoder.java | 6 +- .../util/BufferingResponseListener.java | 90 +++++++++++-------- .../eclipse/jetty/client/HttpClientTest.java | 25 ++++-- .../org/eclipse/jetty/http/HttpGenerator.java | 10 +-- .../org/eclipse/jetty/io/WriteFlusher.java | 6 +- .../jetty/server/handler/ContextHandler.java | 11 +-- .../spdy/generator/HeadersBlockGenerator.java | 4 +- .../jetty/spdy/parser/HeadersBlockParser.java | 13 ++- .../eclipse/jetty/util/ArrayTernaryTrie.java | 10 ++- .../org/eclipse/jetty/util/BufferUtil.java | 24 ++++- .../jetty/util/ByteArrayISO8859Writer.java | 10 +-- .../eclipse/jetty/util/BufferUtilTest.java | 35 ++++++++ .../websocket/client/masks/FixedMasker.java | 5 +- .../websocket/common/WebSocketFrame.java | 5 +- .../websocket/server/ab/AbstractABCase.java | 5 +- 15 files changed, 161 insertions(+), 98 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/GZIPContentDecoder.java b/jetty-client/src/main/java/org/eclipse/jetty/client/GZIPContentDecoder.java index 8a457a6e89f..ab8f2a40b5f 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/GZIPContentDecoder.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/GZIPContentDecoder.java @@ -246,14 +246,12 @@ public class GZIPContentDecoder implements ContentDecoder if (output == null) { // Save the inflated bytes and loop to see if we have finished - output = new byte[decoded]; - System.arraycopy(bytes, 0, output, 0, decoded); + output = Arrays.copyOf(bytes, decoded); } else { // Accumulate inflated bytes and loop to see if we have finished - byte[] newOutput = new byte[output.length + decoded]; - System.arraycopy(output, 0, newOutput, 0, output.length); + byte[] newOutput = Arrays.copyOf(output, output.length+decoded); System.arraycopy(bytes, 0, newOutput, output.length, decoded); output = newOutput; } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java index bc5f8146f8e..b15083973e4 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java @@ -18,11 +18,14 @@ package org.eclipse.jetty.client.util; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; +import java.util.Arrays; import java.util.Locale; import org.eclipse.jetty.client.api.Response; @@ -30,6 +33,7 @@ import org.eclipse.jetty.client.api.Response.Listener; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.util.BufferUtil; /** *

    Implementation of {@link Listener} that buffers the content up to a maximum length @@ -40,7 +44,7 @@ import org.eclipse.jetty.http.HttpHeader; public abstract class BufferingResponseListener extends Listener.Adapter { private final int maxLength; - private volatile byte[] buffer = new byte[0]; + private volatile ByteBuffer buffer; private volatile String encoding; /** @@ -58,53 +62,57 @@ public abstract class BufferingResponseListener extends Listener.Adapter */ public BufferingResponseListener(int maxLength) { - this.maxLength = maxLength; + this.maxLength=maxLength; } @Override public void onHeaders(Response response) { + super.onHeaders(response); + HttpFields headers = response.getHeaders(); long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString()); if (length > maxLength) { response.abort(new IllegalArgumentException("Buffering capacity exceeded")); + return; } - else + + buffer=BufferUtil.allocate((length > 0)?(int)length:1024); + + String contentType = headers.get(HttpHeader.CONTENT_TYPE); + if (contentType != null) { - String contentType = headers.get(HttpHeader.CONTENT_TYPE); - if (contentType != null) + String charset = "charset="; + int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset); + if (index > 0) { - String charset = "charset="; - int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset); + String encoding = contentType.substring(index + charset.length()); + // Sometimes charsets arrive with an ending semicolon + index = encoding.indexOf(';'); if (index > 0) - { - String encoding = contentType.substring(index + charset.length()); - // Sometimes charsets arrive with an ending semicolon - index = encoding.indexOf(';'); - if (index > 0) - encoding = encoding.substring(0, index); - this.encoding = encoding; - } + encoding = encoding.substring(0, index); + this.encoding = encoding; } } } @Override public void onContent(Response response, ByteBuffer content) - { - long newLength = buffer.length + content.remaining(); - if (newLength > maxLength) + { + int length = content.remaining(); + if (length>BufferUtil.space(buffer)) { - response.abort(new IllegalArgumentException("Buffering capacity exceeded")); - } - else - { - byte[] newBuffer = new byte[(int)newLength]; - System.arraycopy(buffer, 0, newBuffer, 0, buffer.length); - content.get(newBuffer, buffer.length, content.remaining()); - buffer = newBuffer; + int requiredCapacity = buffer.capacity()+length; + if (requiredCapacity>maxLength) + response.abort(new IllegalArgumentException("Buffering capacity exceeded")); + + int newCapacity = Math.min(Integer.highestOneBit(requiredCapacity) << 1, maxLength); + buffer = BufferUtil.ensureCapacity(buffer,newCapacity); } + + BufferUtil.append(buffer, content); + } @Override @@ -121,7 +129,9 @@ public abstract class BufferingResponseListener extends Listener.Adapter */ public byte[] getContent() { - return buffer; + if (buffer==null) + return new byte[0]; + return BufferUtil.toArray(buffer); } /** @@ -144,14 +154,9 @@ public abstract class BufferingResponseListener extends Listener.Adapter */ public String getContentAsString(String encoding) { - try - { - return new String(getContent(), encoding); - } - catch (UnsupportedEncodingException x) - { - throw new UnsupportedCharsetException(encoding); - } + if (buffer==null) + return null; + return BufferUtil.toString(buffer, Charset.forName(encoding)); } /** @@ -161,6 +166,19 @@ public abstract class BufferingResponseListener extends Listener.Adapter */ public String getContentAsString(Charset encoding) { - return new String(getContent(), encoding); + if (buffer==null) + return null; + return BufferUtil.toString(buffer, encoding); + } + + /* ------------------------------------------------------------ */ + /** + * @return Content as InputStream + */ + public InputStream getContentAsInputStream() + { + if (buffer==null) + return new ByteArrayInputStream(new byte[]{}); + return new ByteArrayInputStream(buffer.array(), buffer.arrayOffset(), buffer.remaining()); } } 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 bedd3482776..fb596783310 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 @@ -37,12 +37,14 @@ import java.util.List; import java.util.NoSuchElementException; import java.util.Random; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Exchanger; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.zip.GZIPOutputStream; + import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; @@ -1038,26 +1040,33 @@ public class HttpClientTest extends AbstractHttpClientServerTest } }); - final AtomicInteger complete = new AtomicInteger(); + final Exchanger ex = new Exchanger(); BufferingResponseListener listener = new BufferingResponseListener() { @Override public void onComplete(Result result) { - complete.incrementAndGet(); + try + { + ex.exchange(result.getResponse()); + } + catch (InterruptedException e) + { + e.printStackTrace(); + } } }; - ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + + client.newRequest("localhost", connector.getLocalPort()) .scheme(scheme) - .onResponseContent(listener) - .onComplete(listener) - .send(); + .send(listener); + + Response response = ex.exchange(null); Assert.assertEquals(200, response.getStatus()); - Assert.assertEquals(1, complete.get()); Assert.assertArrayEquals(content, listener.getContent()); - Assert.assertArrayEquals(content, response.getContent()); + } @Test diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index e99d3cc8153..a51e4ba7b85 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import org.eclipse.jetty.http.HttpTokens.EndOfContent; import org.eclipse.jetty.util.BufferUtil; @@ -916,10 +917,8 @@ public class HttpGenerator line[versionLength+6+reason.length()]=HttpTokens.LINE_FEED; __preprepared[i] = new PreparedResponse(); - __preprepared[i]._reason=new byte[line.length-versionLength-7] ; - System.arraycopy(line,versionLength+5,__preprepared[i]._reason,0,line.length-versionLength-7); - __preprepared[i]._schemeCode=new byte[versionLength+5]; - System.arraycopy(line,0,__preprepared[i]._schemeCode,0,versionLength+5); + __preprepared[i]._schemeCode = Arrays.copyOfRange(line, 0,versionLength+5); + __preprepared[i]._reason = Arrays.copyOfRange(line, versionLength+5, line.length-2); __preprepared[i]._responseLine=line; } } @@ -1091,8 +1090,7 @@ public class HttpGenerator { super(header,value); int cbl=header.getBytesColonSpace().length; - _bytes=new byte[cbl+value.length()+2]; - System.arraycopy(header.getBytesColonSpace(),0,_bytes,0,cbl); + _bytes=Arrays.copyOf(header.getBytesColonSpace(), cbl+value.length()+2); System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length()); _bytes[_bytes.length-2]=(byte)'\r'; _bytes[_bytes.length-1]=(byte)'\n'; diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java index dd44e531e32..fccc6229552 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.WritePendingException; +import java.util.Arrays; import java.util.EnumMap; import java.util.EnumSet; import java.util.Set; @@ -301,10 +302,7 @@ abstract public class WriteFlusher if (consumed == length) return EMPTY_BUFFERS; - int newLength = length - consumed; - ByteBuffer[] result = new ByteBuffer[newLength]; - System.arraycopy(buffers, consumed, result, 0, newLength); - return result; + return Arrays.copyOfRange(buffers,consumed,length); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 333aecdcc97..397cde1fd13 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -1214,20 +1214,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu _protectedTargets = null; return; } - - _protectedTargets = new String[targets.length]; - System.arraycopy(targets, 0, _protectedTargets, 0, targets.length); + + _protectedTargets = Arrays.copyOf(targets, targets.length); } /* ------------------------------------------------------------ */ - public String[] getProtectedTargets () + public String[] getProtectedTargets() { if (_protectedTargets == null) return null; - String[] tmp = new String[_protectedTargets.length]; - System.arraycopy(_protectedTargets, 0, tmp, 0, _protectedTargets.length); - return tmp; + return Arrays.copyOf(_protectedTargets, _protectedTargets.length); } diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java index e5cf6be9762..bf3dfa668a3 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/generator/HeadersBlockGenerator.java @@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -62,8 +63,7 @@ public class HeadersBlockGenerator for (int i = 1; i < values.size(); ++i) { byte[] moreValueBytes = values.get(i).getBytes(iso1); - byte[] newValueBytes = new byte[valueBytes.length + 1 + moreValueBytes.length]; - System.arraycopy(valueBytes, 0, newValueBytes, 0, valueBytes.length); + byte[] newValueBytes = Arrays.copyOf(valueBytes,valueBytes.length + 1 + moreValueBytes.length); newValueBytes[valueBytes.length] = 0; System.arraycopy(moreValueBytes, 0, newValueBytes, valueBytes.length + 1, moreValueBytes.length); valueBytes = newValueBytes; diff --git a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java index e0fdd564711..f597fbf15bc 100644 --- a/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java +++ b/jetty-spdy/spdy-core/src/main/java/org/eclipse/jetty/spdy/parser/HeadersBlockParser.java @@ -21,6 +21,7 @@ package org.eclipse.jetty.spdy.parser; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.zip.ZipException; import org.eclipse.jetty.spdy.CompressionDictionary; @@ -114,16 +115,14 @@ public abstract class HeadersBlockParser int needed = length - accumulated; if (remaining < needed) { - byte[] local = new byte[accumulated + remaining]; - System.arraycopy(data, 0, local, 0, accumulated); + byte[] local = Arrays.copyOf(data,accumulated + remaining); buffer.get(local, accumulated, remaining); data = local; return false; } else { - byte[] local = new byte[length]; - System.arraycopy(data, 0, local, 0, accumulated); + byte[] local = Arrays.copyOf(data,length); buffer.get(local, accumulated, needed); data = local; return true; @@ -199,8 +198,7 @@ public abstract class HeadersBlockParser else { // Last pass needed to decompress, merge decompressed bytes - byte[] result = new byte[decompressed.length + count]; - System.arraycopy(decompressed, 0, result, 0, decompressed.length); + byte[] result = Arrays.copyOf(decompressed,decompressed.length+count); System.arraycopy(buffer, 0, result, decompressed.length, count); return ByteBuffer.wrap(result); } @@ -214,8 +212,7 @@ public abstract class HeadersBlockParser } else { - byte[] result = new byte[decompressed.length + buffer.length]; - System.arraycopy(decompressed, 0, result, 0, decompressed.length); + byte[] result = Arrays.copyOf(decompressed,decompressed.length+buffer.length); System.arraycopy(buffer, 0, result, decompressed.length, buffer.length); decompressed = result; } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java index d397716bcc0..fdca4ce1350 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.util; import java.nio.ByteBuffer; +import java.util.Arrays; import java.util.HashSet; import java.util.Set; @@ -107,11 +108,12 @@ public class ArrayTernaryTrie extends AbstractTrie */ public ArrayTernaryTrie(ArrayTernaryTrie trie, double factor) { - this(trie.isCaseInsensitive(),(int)(trie._value.length*factor)); + super(trie.isCaseInsensitive()); + int capacity=(int)(trie._value.length*factor); _rows=trie._rows; - System.arraycopy(trie._value,0,_value,0,trie._value.length); - System.arraycopy(trie._tree,0,_tree,0,trie._tree.length); - System.arraycopy(trie._key,0,_key,0,trie._key.length); + _value=Arrays.copyOf(trie._value, capacity); + _tree=Arrays.copyOf(trie._tree, capacity*ROW_SIZE); + _key=Arrays.copyOf(trie._key, capacity); } /* ------------------------------------------------------------ */ diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java index 16d3701a2da..bd7cb06ecd1 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java @@ -30,6 +30,7 @@ import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import org.eclipse.jetty.util.resource.Resource; @@ -215,15 +216,18 @@ public class BufferUtil */ public static byte[] toArray(ByteBuffer buffer) { - byte[] to = new byte[buffer.remaining()]; if (buffer.hasArray()) { byte[] array = buffer.array(); - System.arraycopy(array, buffer.arrayOffset() + buffer.position(), to, 0, to.length); + int from=buffer.arrayOffset() + buffer.position(); + return Arrays.copyOfRange(array,from,from+buffer.remaining()); } else + { + byte[] to = new byte[buffer.remaining()]; buffer.slice().get(to); - return to; + return to; + } } /* ------------------------------------------------------------ */ @@ -1022,6 +1026,20 @@ public class BufferUtil return true; } + public static ByteBuffer ensureCapacity(ByteBuffer buffer, int capacity) + { + if (buffer==null) + return allocate(capacity); + + if (buffer.capacity()>=capacity) + return buffer; + + if (buffer.hasArray()) + return ByteBuffer.wrap(Arrays.copyOfRange(buffer.array(), buffer.arrayOffset(), buffer.arrayOffset()+capacity),buffer.position(),buffer.remaining()); + + throw new UnsupportedOperationException(); + } + } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java index cf1e23ffea1..80df88198be 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ByteArrayISO8859Writer.java @@ -22,6 +22,7 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; import java.nio.charset.StandardCharsets; +import java.util.Arrays; /* ------------------------------------------------------------ */ @@ -253,19 +254,14 @@ public class ByteArrayISO8859Writer extends Writer { if (_fixed) throw new IOException("Buffer overflow: "+_buf.length); - byte[] buf = new byte[(_buf.length+n)*4/3]; - System.arraycopy(_buf,0,buf,0,_size); - _buf=buf; + _buf=Arrays.copyOf(_buf,(_buf.length+n)*4/3); } } - /* ------------------------------------------------------------ */ public byte[] getByteArray() { - byte[] data=new byte[_size]; - System.arraycopy(_buf,0,data,0,_size); - return data; + return Arrays.copyOf(_buf,_size); } } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java index 042d6b28b41..fbe60670368 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java @@ -289,6 +289,41 @@ public class BufferUtilTest int capacity = BufferUtil.TEMP_BUFFER_SIZE*2+1024; testWriteToWithBufferThatDoesNotExposeArray(capacity); } + + + @Test + public void testEnsureCapacity() throws Exception + { + ByteBuffer b = BufferUtil.toBuffer("Goodbye Cruel World"); + assertTrue(b==BufferUtil.ensureCapacity(b, 0)); + assertTrue(b==BufferUtil.ensureCapacity(b, 10)); + assertTrue(b==BufferUtil.ensureCapacity(b, b.capacity())); + + + ByteBuffer b1 = BufferUtil.ensureCapacity(b, 64); + assertTrue(b!=b1); + assertEquals(64, b1.capacity()); + assertEquals("Goodbye Cruel World", BufferUtil.toString(b1)); + + b1.position(8); + b1.limit(13); + assertEquals("Cruel", BufferUtil.toString(b1)); + ByteBuffer b2 = b1.slice(); + assertEquals("Cruel", BufferUtil.toString(b2)); + System.err.println(BufferUtil.toDetailString(b2)); + assertEquals(8, b2.arrayOffset()); + assertEquals(5, b2.capacity()); + + assertTrue(b2==BufferUtil.ensureCapacity(b2, 5)); + + ByteBuffer b3 = BufferUtil.ensureCapacity(b2, 64); + assertTrue(b2!=b3); + assertEquals(64, b3.capacity()); + assertEquals("Cruel", BufferUtil.toString(b3)); + assertEquals(0, b3.arrayOffset()); + + } + private void testWriteToWithBufferThatDoesNotExposeArray(int capacity) throws IOException { diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/FixedMasker.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/FixedMasker.java index 6f44aef8c59..2b3fc76f3b5 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/FixedMasker.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/masks/FixedMasker.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.websocket.client.masks; +import java.util.Arrays; + import org.eclipse.jetty.websocket.common.WebSocketFrame; public class FixedMasker implements Masker @@ -32,10 +34,9 @@ public class FixedMasker implements Masker public FixedMasker(byte[] mask) { - this.mask = new byte[4]; // Copy to avoid that external code keeps a reference // to the array parameter to modify masking on-the-fly - System.arraycopy(mask,0,mask,0,4); + this.mask=Arrays.copyOf(mask, 4); } @Override diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketFrame.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketFrame.java index b4b811cd789..4ed27a08237 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketFrame.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/WebSocketFrame.java @@ -154,10 +154,7 @@ public abstract class WebSocketFrame implements Frame masked = copy.masked; mask = null; if (copy.mask != null) - { - mask = new byte[copy.mask.length]; - System.arraycopy(copy.mask,0,mask,0,mask.length); - } + mask = Arrays.copyOf(copy.mask, copy.mask.length); } @Override diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java index 1b282b93026..4d0930c2396 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ab/AbstractABCase.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.websocket.server.ab; import java.net.URI; import java.nio.ByteBuffer; +import java.util.Arrays; import org.eclipse.jetty.io.MappedByteBufferPool; import org.eclipse.jetty.util.BufferUtil; @@ -113,9 +114,7 @@ public abstract class AbstractABCase implements Fuzzed */ protected ByteBuffer copyOf(byte[] payload) { - byte copy[] = new byte[payload.length]; - System.arraycopy(payload,0,copy,0,payload.length); - return ByteBuffer.wrap(copy); + return ByteBuffer.wrap(Arrays.copyOf(payload,payload.length)); } /** From 800a6639a36378e9df891ac40a60ae16a3224865 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Tue, 29 Apr 2014 20:55:09 +0200 Subject: [PATCH 085/135] Ensure annotation-related code is optional for tests with jdk8 --- jetty-osgi/jetty-osgi-boot/pom.xml | 1 + jetty-websocket/javax-websocket-server-impl/pom.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/jetty-osgi/jetty-osgi-boot/pom.xml b/jetty-osgi/jetty-osgi-boot/pom.xml index 5a02af5fdbf..5a074b92430 100644 --- a/jetty-osgi/jetty-osgi-boot/pom.xml +++ b/jetty-osgi/jetty-osgi-boot/pom.xml @@ -112,6 +112,7 @@ javax.servlet.http;version="[3.1,3.2)", javax.transaction;version="1.1.0";resolution:=optional, javax.transaction.xa;version="1.1.0";resolution:=optional, + org.objectweb.asm;version=4;resolution:=optional, org.eclipse.jetty.annotations;version="9.0.0";resolution:=optional, org.osgi.framework, org.osgi.service.cm;version="1.2.0", diff --git a/jetty-websocket/javax-websocket-server-impl/pom.xml b/jetty-websocket/javax-websocket-server-impl/pom.xml index d03b6885fdb..32e39012fdc 100644 --- a/jetty-websocket/javax-websocket-server-impl/pom.xml +++ b/jetty-websocket/javax-websocket-server-impl/pom.xml @@ -63,7 +63,7 @@ javax.websocket.server Implementation org.eclipse.jetty.websocket.jsr356.server.*;version="${parsedVersion.majorVersion}.${parsedVersion.minorVersion}.${parsedVersion.incrementalVersion}" - osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)" + osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)";resolution:=optional osgi.serviceloader; osgi.serviceloader=javax.servlet.ServletContainerInitializer From 5cc6833f5001317f3effb47b337a1d8b43f37cbb Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 29 Apr 2014 14:25:06 -0700 Subject: [PATCH 086/135] 433563 - Jetty fails to startup on windows - InvalidPathException + PathMatchers.isAbsolute() and .getSearchRoot() adjusted to not rely on Path object behavior differences between Unix and Windows + Updated StartArgs processing of --lib to automatically expand extra lib references prior to submitting them to the internal BaseHome for processing as pattern for paths. + Updated test cases for new behavior. --- .../org/eclipse/jetty/start/PathMatchers.java | 119 ++++++++++++------ .../org/eclipse/jetty/start/StartArgs.java | 5 +- .../org/eclipse/jetty/start/MainTest.java | 28 ++--- .../jetty/start/PathMatchersAbsoluteTest.java | 82 ++++++++++++ .../start/PathMatchersSearchRootTest.java | 89 +++++++++++++ .../eclipse/jetty/start/PathMatchersTest.java | 105 ---------------- 6 files changed, 269 insertions(+), 159 deletions(-) create mode 100755 jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersAbsoluteTest.java create mode 100644 jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersSearchRootTest.java delete mode 100644 jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersTest.java diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java b/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java index 58ce0ca9756..74a9a42210f 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/PathMatchers.java @@ -59,7 +59,7 @@ public class PathMatchers * the raw pattern (can contain "glob:" or "regex:" syntax indicator) * @return the Path version of the pattern provided. */ - private static Path asPath(String pattern) + private static Path asPath(final String pattern) { String test = pattern; if (test.startsWith("glob:")) @@ -119,43 +119,89 @@ public class PathMatchers */ public static Path getSearchRoot(final String pattern) { - Path path = asPath(pattern); - Path test = path.getRoot(); + StringBuilder root = new StringBuilder(); - boolean isSyntaxed = pattern.startsWith("glob:") || pattern.startsWith("regex:"); - - int len = path.getNameCount(); - for (int i = 0; i < len; i++) + int start = 0; + boolean syntaxed = false; + if (pattern.startsWith("glob:")) { - Path part = path.getName(i); - if (isGlob(part.toString(),isSyntaxed)) + start = "glob:".length(); + syntaxed = true; + } + else if (pattern.startsWith("regex:")) + { + start = "regex:".length(); + syntaxed = true; + } + int len = pattern.length(); + int lastSep = 0; + for (int i = start; i < len; i++) + { + int cp = pattern.codePointAt(i); + if (cp < 127) { - // found a glob part, return prior parts now - break; - } + char c = (char)cp; - // is this the last entry? - if (i == (len - 1)) - { - // always return prior entries - break; - } + // unix path case + if (c == '/') + { + root.append(c); + lastSep = root.length(); + } + else if (c == '\\') + { + root.append("\\"); + lastSep = root.length(); - if (test == null) - { - test = part; + // possible escaped sequence. + // only really interested in windows escape sequences "\\" + int count = countChars(pattern,i+1,'\\'); + if (count > 0) + { + // skip extra slashes + i += count; + } + } + else + { + if (isGlob(c,syntaxed)) + { + break; + } + root.append(c); + } } else { - test = test.resolve(part); + root.appendCodePoint(cp); } } - if (test == null) + String rootPath = root.substring(0,lastSep); + if (rootPath.length() <= 0) { return EMPTY_PATH; } - return test; + + return asPath(rootPath); + } + + private static int countChars(String pattern, int offset, char c) + { + int count = 0; + int len = pattern.length(); + for (int i = offset; i < len; i++) + { + if (pattern.charAt(i) == c) + { + count++; + } + else + { + break; + } + } + return count; } /** @@ -184,29 +230,24 @@ public class PathMatchers * true if overall pattern is syntaxed with "glob:" or "regex:" * @return true if part has glob characters */ - private static boolean isGlob(String part, boolean syntaxed) + private static boolean isGlob(char c, boolean syntaxed) { - int len = part.length(); - for (int i = 0; i < len; i++) + for (char g : GLOB_CHARS) { - char c = part.charAt(i); - for (char g : GLOB_CHARS) + if (c == g) + { + return true; + } + } + if (syntaxed) + { + for (char g : SYNTAXED_GLOB_CHARS) { if (c == g) { return true; } } - if (syntaxed) - { - for (char g : SYNTAXED_GLOB_CHARS) - { - if (c == g) - { - return true; - } - } - } } return false; } diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java index cde9f562f36..0a433806f5d 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java @@ -361,7 +361,10 @@ public class StartArgs StartLog.debug("rawlibref = " + rawlibref); String libref = properties.expand(rawlibref); StartLog.debug("expanded = " + libref); - + + // perform path escaping (needed by windows) + libref = libref.replaceAll("\\\\([^\\\\])","\\\\\\\\$1"); + for (Path libpath : baseHome.getPaths(libref)) { classpath.addComponent(libpath.toFile()); diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java index a8003c3bdf7..2043d6b5ae9 100644 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java @@ -18,7 +18,7 @@ package org.eclipse.jetty.start; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.is; import java.io.File; import java.util.ArrayList; @@ -38,7 +38,7 @@ public class MainTest System.setProperty("jetty.home",""); System.setProperty("jetty.base",""); } - + @Test public void testBasicProcessing() throws Exception { @@ -69,12 +69,12 @@ public class MainTest StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()])); System.err.println(args); - //Assert.assertEquals("--stop should not build module tree", 0, args.getEnabledModules().size()); + // Assert.assertEquals("--stop should not build module tree", 0, args.getEnabledModules().size()); Assert.assertEquals("--stop missing port","10000",args.getProperties().getString("STOP.PORT")); Assert.assertEquals("--stop missing key","foo",args.getProperties().getString("STOP.KEY")); Assert.assertEquals("--stop missing wait","300",args.getProperties().getString("STOP.WAIT")); } - + @Test @Ignore("Just a bit noisy for general testing") public void testListConfig() throws Exception @@ -90,7 +90,7 @@ public class MainTest StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()])); main.listConfig(args); } - + @Test @Ignore("Just a bit noisy for general testing") public void testHelp() throws Exception @@ -107,12 +107,12 @@ public class MainTest File homePath = MavenTestingUtils.getTestResourceDir("usecases/home").getAbsoluteFile(); cmdLineArgs.add("jetty.home=" + homePath); cmdLineArgs.add("user.dir=" + homePath); - + // JVM args cmdLineArgs.add("--exec"); cmdLineArgs.add("-Xms1024m"); cmdLineArgs.add("-Xmx1024m"); - + // Arbitrary Libs File extraJar = MavenTestingUtils.getTestResourceFile("extra-libs/example.jar"); File extraDir = MavenTestingUtils.getTestResourceDir("extra-resources"); @@ -127,9 +127,9 @@ public class MainTest StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()])); BaseHome baseHome = main.getBaseHome(); - - Assert.assertThat("jetty.home", baseHome.getHome(), is(homePath.getAbsolutePath())); - Assert.assertThat("jetty.base", baseHome.getBase(), is(homePath.getAbsolutePath())); + + Assert.assertThat("jetty.home",baseHome.getHome(),is(homePath.getAbsolutePath())); + Assert.assertThat("jetty.base",baseHome.getBase(),is(homePath.getAbsolutePath())); ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home-with-jvm.txt"); } @@ -146,10 +146,10 @@ public class MainTest Main main = new Main(); StartArgs args = main.processCommandLine(cmdLineArgs.toArray(new String[cmdLineArgs.size()])); BaseHome baseHome = main.getBaseHome(); - - Assert.assertThat("jetty.home", baseHome.getHome(), is(homePath.getAbsolutePath())); - Assert.assertThat("jetty.base", baseHome.getBase(), is(homePath.getAbsolutePath())); - + + Assert.assertThat("jetty.home",baseHome.getHome(),is(homePath.getAbsolutePath())); + Assert.assertThat("jetty.base",baseHome.getBase(),is(homePath.getAbsolutePath())); + ConfigurationAssert.assertConfiguration(baseHome,args,"assert-home-with-spaces.txt"); } } diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersAbsoluteTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersAbsoluteTest.java new file mode 100755 index 00000000000..c1324807903 --- /dev/null +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersAbsoluteTest.java @@ -0,0 +1,82 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.start; + +import static org.hamcrest.Matchers.is; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.toolchain.test.OS; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PathMatchersAbsoluteTest +{ + @Parameters(name="{0} -> {1}") + public static List data() + { + List cases = new ArrayList<>(); + + if(OS.IS_UNIX) + { + cases.add(new Object[]{"/opt/app",true}); + cases.add(new Object[]{"/opt/app",true}); + cases.add(new Object[]{"/opt/florb",true}); + cases.add(new Object[]{"/home/user/benfranklin",true}); + cases.add(new Object[]{"glob:/home/user/benfranklin/*.jar",true}); + cases.add(new Object[]{"glob:/**/*.jar",true}); + cases.add(new Object[]{"regex:/*-[^dev].ini",true}); + } + + if(OS.IS_WINDOWS) + { + // normal declaration + cases.add(new Object[]{"D:\\code\\jetty\\jetty-start\\src\\test\\resources\\extra-libs\\example.jar",true}); + // escaped declaration + cases.add(new Object[]{"C:\\\\System32",true}); + cases.add(new Object[]{"C:\\\\Program Files",true}); + } + + cases.add(new Object[]{"etc",false}); + cases.add(new Object[]{"lib",false}); + cases.add(new Object[]{"${user.dir}",false}); + cases.add(new Object[]{"**/*.jar",false}); + cases.add(new Object[]{"glob:*.ini",false}); + cases.add(new Object[]{"regex:*-[^dev].ini",false}); + + return cases; + } + + @Parameter(value=0) + public String pattern; + @Parameter(value=1) + public boolean expected; + + @Test + public void testIsAbsolute() + { + Assert.assertThat("isAbsolute(\""+pattern+"\")",PathMatchers.isAbsolute(pattern),is(expected)); + } +} diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersSearchRootTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersSearchRootTest.java new file mode 100644 index 00000000000..7fff06e1bb7 --- /dev/null +++ b/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersSearchRootTest.java @@ -0,0 +1,89 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.start; + +import static org.hamcrest.Matchers.is; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jetty.toolchain.test.OS; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PathMatchersSearchRootTest +{ + @Parameters(name="{0}") + public static List data() + { + List cases = new ArrayList<>(); + + if (OS.IS_UNIX) + { + // absolute first + cases.add(new String[]{"/opt/app/*.jar","/opt/app"}); + cases.add(new String[]{"/lib/jvm/**/jre/lib/*.jar","/lib/jvm"}); + cases.add(new String[]{"glob:/var/lib/*.xml","/var/lib"}); + cases.add(new String[]{"glob:/var/lib/*.{xml,java}","/var/lib"}); + cases.add(new String[]{"glob:/opt/corporate/lib-{dev,prod}/*.ini","/opt/corporate"}); + cases.add(new String[]{"regex:/opt/jetty/.*/lib-(dev|prod)/*.ini","/opt/jetty"}); + + cases.add(new String[]{"/*.ini","/"}); + cases.add(new String[]{"/etc/jetty.conf","/etc"}); + cases.add(new String[]{"/common.conf","/"}); + } + + if (OS.IS_WINDOWS) + { + // absolute declaration + cases.add(new String[]{"D:\\code\\jetty\\jetty-start\\src\\test\\resources\\extra-libs\\example.jar", + "D:\\code\\jetty\\jetty-start\\src\\test\\resources\\extra-libs"}); + // escaped declaration + // absolute patterns (complete with required windows slash escaping) + cases.add(new String[]{"C:\\\\corp\\\\lib\\\\*.jar","C:\\corp\\lib"}); + cases.add(new String[]{"D:\\\\lib\\\\**\\\\jre\\\\lib\\\\*.jar","D:\\lib"}); + } + + // some relative paths + cases.add(new String[]{"lib/*.jar","lib"}); + cases.add(new String[]{"etc/jetty.xml","etc"}); + cases.add(new String[]{"start.ini","."}); + cases.add(new String[]{"start.d/","start.d"}); + return cases; + } + + @Parameter(value=0) + public String pattern; + @Parameter(value=1) + public String expectedSearchRoot; + + @Test + public void testSearchRoot() + { + Path actual = PathMatchers.getSearchRoot(pattern); + String expectedNormal = FS.separators(expectedSearchRoot); + Assert.assertThat(".getSearchRoot(\"" + pattern + "\")",actual.toString(),is(expectedNormal)); + } +} diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersTest.java deleted file mode 100644 index 460f88d2a65..00000000000 --- a/jetty-start/src/test/java/org/eclipse/jetty/start/PathMatchersTest.java +++ /dev/null @@ -1,105 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2014 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.start; - -import static org.hamcrest.Matchers.*; - -import java.nio.file.Path; - -import org.eclipse.jetty.toolchain.test.OS; -import org.junit.Assert; -import org.junit.Test; - -public class PathMatchersTest -{ - private void assertIsAbsolute(String pattern, boolean expected) - { - Assert.assertThat("isAbsolute(\"" + pattern + "\")",PathMatchers.isAbsolute(pattern),is(expected)); - } - - @Test - public void testIsAbsolute() - { - if (OS.IS_UNIX) - { - assertIsAbsolute("/opt/app",true); - assertIsAbsolute("/opt/florb",true); - assertIsAbsolute("/home/user/benfranklin",true); - assertIsAbsolute("glob:/home/user/benfranklin/*.jar",true); - assertIsAbsolute("glob:/**/*.jar",true); - assertIsAbsolute("regex:/*-[^dev].ini",true); - } - - if (OS.IS_WINDOWS) - { - assertIsAbsolute("C:\\\\System32",true); - assertIsAbsolute("C:\\\\Program Files",true); - } - } - - @Test - public void testIsNotAbsolute() - { - assertIsAbsolute("etc",false); - assertIsAbsolute("lib",false); - assertIsAbsolute("${user.dir}",false); - assertIsAbsolute("**/*.jar",false); - assertIsAbsolute("glob:*.ini",false); - assertIsAbsolute("regex:*-[^dev].ini",false); - } - - private void assertSearchRoot(String pattern, String expectedSearchRoot) - { - Path actual = PathMatchers.getSearchRoot(pattern); - String expectedNormal = FS.separators(expectedSearchRoot); - Assert.assertThat(".getSearchRoot(\"" + pattern + "\")",actual.toString(),is(expectedNormal)); - } - - @Test - public void testGetSearchRoot() - { - if (OS.IS_UNIX) - { - // absolute first - assertSearchRoot("/opt/app/*.jar","/opt/app"); - assertSearchRoot("/lib/jvm/**/jre/lib/*.jar","/lib/jvm"); - assertSearchRoot("glob:/var/lib/*.xml","/var/lib"); - assertSearchRoot("glob:/var/lib/*.{xml,java}","/var/lib"); - assertSearchRoot("glob:/opt/corporate/lib-{dev,prod}/*.ini","/opt/corporate"); - assertSearchRoot("regex:/opt/jetty/.*/lib-(dev|prod)/*.ini","/opt/jetty"); - - assertSearchRoot("/*.ini","/"); - assertSearchRoot("/etc/jetty.conf","/etc"); - assertSearchRoot("/common.conf","/"); - } - - if (OS.IS_WINDOWS) - { - // absolute patterns (complete with required windows slash escaping) - assertSearchRoot("C:\\\\corp\\\\lib\\\\*.jar","C:\\corp\\lib"); - assertSearchRoot("D:\\\\lib\\\\**\\\\jre\\\\lib\\\\*.jar","C:\\lib"); - } - - // some relative paths - assertSearchRoot("lib/*.jar","lib"); - assertSearchRoot("etc/jetty.xml","etc"); - assertSearchRoot("start.ini","."); - assertSearchRoot("start.d/","."); - } -} From f65e40f8e7fdb2c7b87f7580d8870a06b11a9771 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 29 Apr 2014 15:44:00 -0700 Subject: [PATCH 087/135] Fixing bad EOL assumptions based on running OS (should be based on git checkout state) --- .../server/handler/ResourceHandlerTest.java | 81 +++++++++++++------ 1 file changed, 55 insertions(+), 26 deletions(-) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java index 9e5745b7e0a..5dd6aa28765 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.server.handler; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -25,6 +26,8 @@ import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpConfiguration; @@ -43,12 +46,12 @@ import org.junit.Test; /** * Resource Handler test - * + * * TODO: increase the testing going on here */ public class ResourceHandlerTest { - private static final String LN = System.getProperty("line.separator"); + private static String LN = System.getProperty("line.separator"); private static Server _server; private static HttpConfiguration _config; private static ServerConnector _connector; @@ -60,29 +63,56 @@ public class ResourceHandlerTest { File dir = MavenTestingUtils.getTargetFile("test-classes/simple"); File huge = new File(dir,"huge.txt"); - File big=new File(dir,"big.txt"); - try (OutputStream out = new FileOutputStream(huge)) { - for (int i=0;i<100;i++) + File big = new File(dir,"big.txt"); + try (OutputStream out = new FileOutputStream(huge)) + { + for (int i = 0; i < 100; i++) { - try (InputStream in=new FileInputStream(big)) + try (InputStream in = new FileInputStream(big)) { IO.copy(in,out); } } } huge.deleteOnExit(); - + + // determine how the SCM of choice checked out the big.txt EOL + // we can't just use whatever is the OS default. + // because, for example, a windows system using git can be configured for EOL handling using + // local, remote, file lists, patterns, etc, rendering assumptions about the OS EOL choice + // wrong for unit tests. + LN = System.getProperty("line.separator"); + try (BufferedReader reader = Files.newBufferedReader(big.toPath(),StandardCharsets.UTF_8)) + { + // a buffer large enough to capture at least 1 EOL + char cbuf[] = new char[128]; + reader.read(cbuf); + String sample = new String(cbuf); + if (sample.contains("\r\n")) + { + LN = "\r\n"; + } + else if (sample.contains("\n\r")) + { + LN = "\n\r"; + } + else + { + LN = "\n"; + } + } + _server = new Server(); - _config=new HttpConfiguration(); + _config = new HttpConfiguration(); _config.setOutputBufferSize(2048); _connector = new ServerConnector(_server,new HttpConnectionFactory(_config)); - + _server.setConnectors(new Connector[] { _connector }); _resourceHandler = new ResourceHandler(); _resourceHandler.setMinAsyncContentLength(4096); _resourceHandler.setMinMemoryMappedContentLength(8192); - + _resourceHandler.setResourceBase(MavenTestingUtils.getTargetFile("test-classes/simple").getAbsolutePath()); _contextHandler = new ContextHandler("/resource"); @@ -96,7 +126,7 @@ public class ResourceHandlerTest { _server.stop(); } - + @Before public void before() { @@ -107,45 +137,44 @@ public class ResourceHandlerTest public void testMissing() throws Exception { SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort())); - Assert.assertNotNull("missing jetty.css" , sr.getString("/resource/jetty-dir.css")); + Assert.assertNotNull("missing jetty.css",sr.getString("/resource/jetty-dir.css")); } - + @Test public void testSimple() throws Exception { SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort())); - Assert.assertEquals("simple text", sr.getString("/resource/simple.txt")); + Assert.assertEquals("simple text",sr.getString("/resource/simple.txt")); } - @Test public void testBigFile() throws Exception { _config.setOutputBufferSize(2048); SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort())); - String response=sr.getString("/resource/big.txt"); + String response = sr.getString("/resource/big.txt"); Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file")); - Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file"+LN)); + Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file" + LN)); } - + @Test public void testBigFileBigBuffer() throws Exception { - _config.setOutputBufferSize(16*1024); + _config.setOutputBufferSize(16 * 1024); SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort())); - String response=sr.getString("/resource/big.txt"); + String response = sr.getString("/resource/big.txt"); Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file")); - Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file"+LN)); + Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file" + LN)); } - + @Test public void testBigFileLittleBuffer() throws Exception { _config.setOutputBufferSize(8); SimpleRequest sr = new SimpleRequest(new URI("http://localhost:" + _connector.getLocalPort())); - String response=sr.getString("/resource/big.txt"); + String response = sr.getString("/resource/big.txt"); Assert.assertThat(response,Matchers.startsWith(" 1\tThis is a big file")); - Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file"+LN)); + Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file" + LN)); } @Test @@ -157,8 +186,8 @@ public class ResourceHandlerTest Thread.sleep(1000); String response = IO.toString(socket.getInputStream()); Assert.assertThat(response,Matchers.startsWith("HTTP/1.1 200 OK")); - Assert.assertThat(response,Matchers.containsString(" 400\tThis is a big file"+LN+" 1\tThis is a big file")); - Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file"+LN)); + Assert.assertThat(response,Matchers.containsString(" 400\tThis is a big file" + LN + " 1\tThis is a big file")); + Assert.assertThat(response,Matchers.endsWith(" 400\tThis is a big file" + LN)); } } } From 1c97fd3c4224360d532972279bd1b69de1aff66b Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 30 Apr 2014 11:57:50 +0200 Subject: [PATCH 088/135] 433692 improved buffer resizing --- .../eclipse/jetty/client/util/BufferingResponseListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java index b15083973e4..e30c6b90155 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/util/BufferingResponseListener.java @@ -103,10 +103,10 @@ public abstract class BufferingResponseListener extends Listener.Adapter int length = content.remaining(); if (length>BufferUtil.space(buffer)) { - int requiredCapacity = buffer.capacity()+length; + int requiredCapacity = buffer==null?0:buffer.capacity()+length; if (requiredCapacity>maxLength) response.abort(new IllegalArgumentException("Buffering capacity exceeded")); - + int newCapacity = Math.min(Integer.highestOneBit(requiredCapacity) << 1, maxLength); buffer = BufferUtil.ensureCapacity(buffer,newCapacity); } From 95343c3535184a0a935be664b80dde4d82a6fcd4 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 30 Apr 2014 13:55:50 +0200 Subject: [PATCH 089/135] Added ASYNC_WOKEN state to HttpChannelState --- .../jetty/server/HttpChannelState.java | 79 ++++++++++--------- 1 file changed, 41 insertions(+), 38 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java index 455edff54fc..1e90812bc27 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java @@ -35,21 +35,6 @@ import org.eclipse.jetty.util.thread.Scheduler; /** * Implementation of AsyncContext interface that holds the state of request-response cycle. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
    STATEACTION
    handling() startAsync()unhandle() dispatch() complete() completed()
    IDLE: DISPATCHED COMPLETECALLED??
    DISPATCHED: ASYNCSTARTEDCOMPLETING
    ASYNCSTARTED: ASYNCWAIT REDISPATCHINGCOMPLETECALLED
    REDISPATCHING: REDISPATCHED
    ASYNCWAIT: REDISPATCH COMPLETECALLED
    REDISPATCH: REDISPATCHED
    REDISPATCHED: ASYNCSTARTEDCOMPLETING
    COMPLETECALLED:COMPLETING COMPLETING
    COMPLETING: COMPLETING COMPLETED
    COMPLETED:
    */ public class HttpChannelState { @@ -59,12 +44,13 @@ public class HttpChannelState public enum State { - IDLE, // Idle request - DISPATCHED, // Request dispatched to filter/servlet - ASYNCWAIT, // Suspended and parked - ASYNCIO, // Has been dispatched for async IO - COMPLETING, // Request is completable - COMPLETED // Request is complete + IDLE, // Idle request + DISPATCHED, // Request dispatched to filter/servlet + ASYNC_WAIT, // Suspended and parked + ASYNC_WOKEN, // A thread has been dispatch to handle from ASYNCWAIT + ASYNC_IO, // Has been dispatched for async IO + COMPLETING, // Request is completable + COMPLETED // Request is complete } public enum Action @@ -188,16 +174,18 @@ public class HttpChannelState case COMPLETED: return Action.WAIT; - case ASYNCWAIT: + case ASYNC_WAIT: + LOG.warn("How did I get here?", new Throwable()); + case ASYNC_WOKEN: if (_asyncRead) { - _state=State.ASYNCIO; + _state=State.ASYNC_IO; _asyncRead=false; return Action.READ_CALLBACK; } if (_asyncWrite) { - _state=State.ASYNCIO; + _state=State.ASYNC_IO; _asyncWrite=false; return Action.WRITE_CALLBACK; } @@ -293,7 +281,7 @@ public class HttpChannelState switch(_state) { case DISPATCHED: - case ASYNCIO: + case ASYNC_IO: break; default: throw new IllegalStateException(this.getStatusString()); @@ -301,7 +289,7 @@ public class HttpChannelState if (_asyncRead) { - _state=State.ASYNCIO; + _state=State.ASYNC_IO; _asyncRead=false; return Action.READ_CALLBACK; } @@ -309,7 +297,7 @@ public class HttpChannelState if (_asyncWrite) { _asyncWrite=false; - _state=State.ASYNCIO; + _state=State.ASYNC_IO; return Action.WRITE_CALLBACK; } @@ -333,7 +321,7 @@ public class HttpChannelState case EXPIRING: case STARTED: scheduleTimeout(); - _state=State.ASYNCWAIT; + _state=State.ASYNC_WAIT; return Action.WAIT; } } @@ -360,7 +348,7 @@ public class HttpChannelState switch(_state) { case DISPATCHED: - case ASYNCIO: + case ASYNC_IO: dispatch=false; break; default: @@ -411,8 +399,11 @@ public class HttpChannelState if (_async==Async.EXPIRING) { _async=Async.EXPIRED; - if (_state==State.ASYNCWAIT) + if (_state==State.ASYNC_WAIT) + { + _state=State.ASYNC_WOKEN; dispatch=true; + } } } @@ -423,13 +414,17 @@ public class HttpChannelState public void complete() { // just like resume, except don't set _dispatched=true; - boolean handle; + boolean handle=false; synchronized (this) { if (_async!=Async.STARTED && _async!=Async.EXPIRING) throw new IllegalStateException(this.getStatusString()); _async=Async.COMPLETE; - handle=_state==State.ASYNCWAIT; + if (_state==State.ASYNC_WAIT) + { + handle=true; + _state=State.ASYNC_WOKEN; + } } cancelTimeout(); @@ -511,7 +506,7 @@ public class HttpChannelState switch(_state) { case DISPATCHED: - case ASYNCIO: + case ASYNC_IO: throw new IllegalStateException(getStatusString()); default: break; @@ -571,7 +566,7 @@ public class HttpChannelState { synchronized(this) { - return _state==State.ASYNCWAIT || _state==State.DISPATCHED && _async==Async.STARTED; + return _state==State.ASYNC_WAIT || _state==State.DISPATCHED && _async==Async.STARTED; } } @@ -665,12 +660,16 @@ public class HttpChannelState public void onReadPossible() { - boolean handle; + boolean handle=false; synchronized (this) { _asyncRead=true; - handle=_state==State.ASYNCWAIT; + if (_state==State.ASYNC_WAIT) + { + _state=State.ASYNC_WOKEN; + handle=true; + } } if (handle) @@ -679,12 +678,16 @@ public class HttpChannelState public void onWritePossible() { - boolean handle; + boolean handle=false; synchronized (this) { _asyncWrite=true; - handle=_state==State.ASYNCWAIT; + if (_state==State.ASYNC_WAIT) + { + _state=State.ASYNC_WOKEN; + handle=true; + } } if (handle) From d17547cf96720229075842fdf80da4b87c0e5eb3 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 30 Apr 2014 14:04:28 +0200 Subject: [PATCH 090/135] Added ASYNC_WOKEN state to HttpChannelState --- .../eclipse/jetty/server/HttpChannelState.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java index 1e90812bc27..9ff6b37a855 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java @@ -42,17 +42,22 @@ public class HttpChannelState private final static long DEFAULT_TIMEOUT=30000L; + /** The dispatched state of the HttpChannel, used to control the overall livecycle + */ public enum State { IDLE, // Idle request DISPATCHED, // Request dispatched to filter/servlet - ASYNC_WAIT, // Suspended and parked - ASYNC_WOKEN, // A thread has been dispatch to handle from ASYNCWAIT - ASYNC_IO, // Has been dispatched for async IO + ASYNC_WAIT, // Suspended and parked + ASYNC_WOKEN, // A thread has been dispatch to handle from ASYNCWAIT + ASYNC_IO, // Has been dispatched for async IO COMPLETING, // Request is completable COMPLETED // Request is complete } + /** + * The actions to take as the channel moves from state to state. + */ public enum Action { REQUEST_DISPATCH, // handle a normal request dispatch @@ -64,6 +69,11 @@ public class HttpChannelState COMPLETE // Complete the channel } + /** + * The state of the servlet async API. This can lead or follow the + * channel dispatch state and also includes reasons such as expired, + * dispatched or completed. + */ public enum Async { STARTED, From 95c6bad6545604ffa23024ac0092bd9629a520d7 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 30 Apr 2014 14:58:14 +0200 Subject: [PATCH 091/135] Improved handling of return values from parser callback, by returning as early as possible to avoid race conditions with application code that may have returned true and reentered the parsing code. --- .../org/eclipse/jetty/http/HttpParser.java | 48 +++++++++++-------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index ee40a74af16..79f1c28d90d 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -58,7 +58,7 @@ import org.eclipse.jetty.util.log.Logger; * For performance, the parse is heavily dependent on the * {@link Trie#getBest(ByteBuffer, int, int)} method to look ahead in a * single pass for both the structure ( : and CRLF ) and semantic (which - * header and value) of a header. Specifically the static {@link HttpField#CACHE} + * header and value) of a header. Specifically the static {@link HttpHeader#CACHE} * is used to lookup common combinations of headers and values * (eg. "Connection: close"), or just header names (eg. "Connection:" ). * For headers who's value is not known statically (eg. Host, COOKIE) then a @@ -186,7 +186,7 @@ public class HttpParser for (String charset : new String[]{"UTF-8","ISO-8859-1"}) { - CACHE.put(field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset)); + CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset)); CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset)); } } @@ -813,7 +813,7 @@ public class HttpParser { if (_valueString.endsWith(HttpHeaderValue.CHUNKED.toString())) _endOfContent=EndOfContent.CHUNKED_CONTENT; - else if (_valueString.indexOf(HttpHeaderValue.CHUNKED.toString()) >= 0) + else if (_valueString.contains(HttpHeaderValue.CHUNKED.toString())) { throw new BadMessage(HttpStatus.BAD_REQUEST_400,"Bad chunking"); } @@ -872,7 +872,7 @@ public class HttpParser case CONNECTION: // Don't cache if not persistent - if (_valueString!=null && _valueString.indexOf("close")>=0) + if (_valueString!=null && _valueString.contains("close")) { _closed=true; _connectionFields=null; @@ -1223,8 +1223,6 @@ public class HttpParser LOG.debug("parseNext s={} {}",_state,BufferUtil.toDetailString(buffer)); try { - boolean handle=false; - // Start a request/response if (_state==State.START) { @@ -1233,28 +1231,39 @@ public class HttpParser _methodString=null; _endOfContent=EndOfContent.UNKNOWN_CONTENT; _header=null; - handle=quickStart(buffer); + if (quickStart(buffer)) + return true; } // Request/response line - if (!handle && _state.ordinal()>= State.START.ordinal() && _state.ordinal()= State.START.ordinal() && _state.ordinal()= State.HEADER.ordinal() && _state.ordinal()= State.HEADER.ordinal() && _state.ordinal()= State.CONTENT.ordinal() && _state.ordinal()= State.CONTENT.ordinal() && _state.ordinal()0 && _headResponse) { setState(State.END); - handle=_handler.messageComplete(); + if (_handler.messageComplete()) + return true; } else - handle=parseContent(buffer); + { + if (parseContent(buffer)) + return true; + } } // handle end states @@ -1288,8 +1297,8 @@ public class HttpParser break; case START: - _handler.earlyEOF(); setState(State.CLOSED); + _handler.earlyEOF(); break; case END: @@ -1297,29 +1306,28 @@ public class HttpParser break; case EOF_CONTENT: - handle=_handler.messageComplete()||handle; setState(State.CLOSED); - break; + return _handler.messageComplete(); case CONTENT: case CHUNKED_CONTENT: case CHUNK_SIZE: case CHUNK_PARAMS: case CHUNK: - _handler.earlyEOF(); setState(State.CLOSED); + _handler.earlyEOF(); break; default: if (DEBUG) LOG.debug("{} EOF in {}",this,_state); - _handler.badMessage(400,null); setState(State.CLOSED); + _handler.badMessage(400,null); break; } } - return handle; + return false; } catch(BadMessage e) { From 906dc8369103e78b2f895b6c7145e54bc7d63864 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Wed, 30 Apr 2014 15:34:30 +0200 Subject: [PATCH 092/135] 431642 - Implement ProxyServlet using Servlet 3.1 async I/O. Implemented correctly AsyncProxyServlet, tests passing. --- .../jetty/client/ResponseNotifier.java | 3 + .../client/http/HttpReceiverOverHTTP.java | 39 ++- .../jetty/client/http/HttpSenderOverHTTP.java | 2 +- .../jetty/proxy/AsyncProxyServlet.java | 129 ++++++--- .../org/eclipse/jetty/proxy/ProxyServlet.java | 71 +++-- .../eclipse/jetty/proxy/EmptyHttpServlet.java | 38 +++ .../jetty/proxy/ProxyServletFailureTest.java | 245 ++++++++++++++++++ .../eclipse/jetty/proxy/ProxyServletTest.java | 140 +--------- 8 files changed, 456 insertions(+), 211 deletions(-) create mode 100644 jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EmptyHttpServlet.java create mode 100644 jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java index 1b40103cf63..b100ea81114 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/ResponseNotifier.java @@ -110,6 +110,9 @@ public class ResponseNotifier public void notifyContent(List listeners, Response response, ByteBuffer buffer, Callback callback) { + // Here we use an IteratingNestedCallback not to avoid the stack overflow, but to + // invoke the listeners one after the other. When all of them have invoked the + // callback they got passed, the callback passed to this method is finally invoked. ContentCallback contentCallback = new ContentCallback(listeners, response, buffer, callback); contentCallback.iterate(); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java index 7d5af9eb566..5f499bd0c9f 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java @@ -63,15 +63,22 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res HttpClient client = getHttpDestination().getHttpClient(); ByteBufferPool bufferPool = client.getByteBufferPool(); buffer = bufferPool.acquire(client.getResponseBufferSize(), true); - if (process()) + process(); + } + + private void process() + { + if (readAndParse()) { + HttpClient client = getHttpDestination().getHttpClient(); + ByteBufferPool bufferPool = client.getByteBufferPool(); bufferPool.release(buffer); // Don't linger the buffer around if we are idle. buffer = null; } } - private boolean process() + private boolean readAndParse() { HttpConnectionOverHTTP connection = getHttpConnection(); EndPoint endPoint = connection.getEndPoint(); @@ -80,7 +87,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res { try { - // Connection may be closed in a parser callback + // Connection may be closed in a parser callback. if (connection.isClosed()) { if (LOG.isDebugEnabled()) @@ -121,19 +128,29 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res } } + /** + * Parses a HTTP response from the given {@code buffer}. + * + * @param buffer the response bytes + * @return true to indicate that the parsing may proceed (for example with another response), + * false to indicate that the parsing should be interrupted (and will be resumed by another thread). + */ private boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) + // Must parse even if the buffer is fully consumed, to allow the + // parser to advance from asynchronous content to response complete. + if (parser.parseNext(buffer)) { - if (parser.parseNext(buffer)) - return parser.isStart(); + // If the parser returns true, we need to differentiate two cases: + // A) the response is completed, so the parser is in START state; + // B) the content is handled asynchronously, so the parser is in CONTENT state. + return parser.isStart(); } return true; } private void fillInterested() { - // TODO: do we need to call fillInterested() only if we are not failed (or we have an exchange) ? getHttpChannel().getHttpConnection().fillInterested(); } @@ -218,10 +235,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res if (!completed.compareAndSet(false, true)) { LOG.debug("Content consumed asynchronously, resuming processing"); - if (process()) - { - // TODO: release the buffer to the pool ! - } + process(); } } @@ -232,9 +246,6 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res } }; responseContent(exchange, buffer, callback); - // Return false to have the parser continue parsing. - // TODO: there is a race here: when this thread returns true, the parser is still running - // TODO: some stateful code that may be changed concurrently by the callback thread. return completed.compareAndSet(false, true); } diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java index 543bd17c14f..a00be54cac2 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpSenderOverHTTP.java @@ -114,7 +114,7 @@ public class HttpSenderOverHTTP extends HttpSender } } } - catch (Exception x) + catch (Throwable x) { LOG.debug(x); callback.failed(x); diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java index b59dff351ff..1127ecd177a 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.proxy; import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.channels.WritePendingException; import javax.servlet.AsyncContext; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; @@ -29,6 +30,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.api.ContentProvider; +import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.util.Callback; @@ -38,34 +40,52 @@ public class AsyncProxyServlet extends ProxyServlet private static final String WRITE_LISTENER_ATTRIBUTE = AsyncProxyServlet.class.getName() + ".writeListener"; @Override - protected ContentProvider proxyRequestContent(AsyncContext asyncContext, final int requestId) throws IOException + protected ContentProvider proxyRequestContent(Request proxyRequest, HttpServletRequest request) throws IOException { - ServletInputStream input = asyncContext.getRequest().getInputStream(); + ServletInputStream input = request.getInputStream(); DeferredContentProvider provider = new DeferredContentProvider(); - input.setReadListener(new StreamReader(input, requestId, provider)); + input.setReadListener(new StreamReader(input, getRequestId(request), provider)); return provider; } @Override - protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) throws IOException + protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) { - StreamWriter writeListener = (StreamWriter)request.getAttribute(WRITE_LISTENER_ATTRIBUTE); - if (writeListener == null) + try { - writeListener = new StreamWriter(request.getAsyncContext()); - request.setAttribute(WRITE_LISTENER_ATTRIBUTE, writeListener); - response.getOutputStream().setWriteListener(writeListener); + int requestId = getRequestId(request); + _log.debug("{} proxying content to downstream: {} bytes", requestId, length); + StreamWriter writeListener = (StreamWriter)request.getAttribute(WRITE_LISTENER_ATTRIBUTE); + if (writeListener == null) + { + writeListener = new StreamWriter(request.getAsyncContext(), requestId); + request.setAttribute(WRITE_LISTENER_ATTRIBUTE, writeListener); + + // Set the data to write before calling setWriteListener(), because + // setWriteListener() may trigger the call to onWritePossible() on + // a different thread and we would have a race. + writeListener.data(buffer, offset, length, callback); + + // Setting the WriteListener triggers an invocation to onWritePossible(). + response.getOutputStream().setWriteListener(writeListener); + } + else + { + writeListener.data(buffer, offset, length, callback); + writeListener.onWritePossible(); + } + } + catch (Throwable x) + { + // TODO: who calls asyncContext.complete() in this case ? + callback.failed(x); } - _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length); - if (writeListener.data(buffer, offset, length, callback)) - writeListener.onWritePossible(); - else - ;// TODO: fail callback } private class StreamReader implements ReadListener, Callback { private final byte[] buffer = new byte[512]; +// private final byte[] buffer = new byte[getHttpClient().getRequestBufferSize()]; private final ServletInputStream input; private final int requestId; private final DeferredContentProvider provider; @@ -80,14 +100,14 @@ public class AsyncProxyServlet extends ProxyServlet @Override public void onDataAvailable() throws IOException { - _log.debug("Asynchronous read start from {}", input); + _log.debug("{} asynchronous read start on {}", requestId, input); // First check for isReady() because it has // side effects, and then for isFinished(). while (input.isReady() && !input.isFinished()) { int read = input.read(buffer); - _log.debug("Asynchronous read {} bytes from {}", read, input); + _log.debug("{} asynchronous read {} bytes on {}", requestId, read, input); if (read > 0) { _log.debug("{} proxying content to upstream: {} bytes", requestId, read); @@ -97,7 +117,7 @@ public class AsyncProxyServlet extends ProxyServlet } } if (!input.isFinished()) - _log.debug("Asynchronous read pending from {}", input); + _log.debug("{} asynchronous read pending on {}", requestId, input); } @Override @@ -116,8 +136,15 @@ public class AsyncProxyServlet extends ProxyServlet @Override public void succeeded() { - // Notify the container that it may call onDataAvailable() again. - input.isReady(); + try + { + if (input.isReady()) + onDataAvailable(); + } + catch (Throwable x) + { + failed(x); + } } @Override @@ -131,60 +158,86 @@ public class AsyncProxyServlet extends ProxyServlet private class StreamWriter implements WriteListener { private final AsyncContext asyncContext; + private final int requestId; + private WriteState state; private byte[] buffer; private int offset; private int length; - private volatile Callback callback; + private Callback callback; - private StreamWriter(AsyncContext asyncContext) + private StreamWriter(AsyncContext asyncContext, int requestId) { this.asyncContext = asyncContext; + this.requestId = requestId; + this.state = WriteState.IDLE; } - private boolean data(byte[] bytes, int offset, int length, Callback callback) + private void data(byte[] bytes, int offset, int length, Callback callback) { - if (this.callback != null) - return false; - + if (state != WriteState.IDLE) + throw new WritePendingException(); + this.state = WriteState.READY; this.buffer = bytes; this.offset = offset; this.length = length; this.callback = callback; - return true; } @Override public void onWritePossible() throws IOException { - if (callback == null) + ServletOutputStream output = asyncContext.getResponse().getOutputStream(); + if (state == WriteState.READY) { - ServletOutputStream output = asyncContext.getResponse().getOutputStream(); + // There is data to write. + _log.debug("{} asynchronous write start of {} bytes on {}", requestId, length, output); output.write(buffer, offset, length); + state = WriteState.PENDING; if (output.isReady()) + { + _log.debug("{} asynchronous write of {} bytes completed on {}", requestId, length, output); complete(); + } + else + { + _log.debug("{} asynchronous write of {} bytes pending on {}", requestId, length, output); + } + } + else if (state == WriteState.PENDING) + { + // The write blocked but is now complete. + _log.debug("{} asynchronous write of {} bytes completing on {}", requestId, length, output); + complete(); } else { - // If we have a pending callback, it means - // that the write blocked but is now complete. - complete(); + throw new IllegalStateException(); } } private void complete() { - this.buffer = null; - this.offset = 0; - this.length = 0; - Callback callback = this.callback; - this.callback = null; - callback.succeeded(); + buffer = null; + offset = 0; + length = 0; + Callback c = callback; + callback = null; + state = WriteState.IDLE; + // Call the callback only after the whole state has been reset, + // because the callback may trigger a reentrant call and the + // state would be the old one + c.succeeded(); } @Override - public void onError(Throwable t) + public void onError(Throwable failure) { // TODO: } } + + private enum WriteState + { + READY, PENDING, IDLE + } } 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 8d7c5432969..046562939e5 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 @@ -80,7 +80,6 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool; */ public class ProxyServlet extends HttpServlet { - protected static final String ASYNC_CONTEXT = ProxyServlet.class.getName() + ".asyncContext"; private static final Set HOP_HEADERS = new HashSet<>(); static { @@ -175,14 +174,21 @@ public class ProxyServlet extends HttpServlet } } + protected HttpClient getHttpClient() + { + return _client; + } + /** * @return a logger instance with a name derived from this servlet's name. */ protected Logger createLogger() { - String name = getServletConfig().getServletName(); - name = name.replace('-', '.'); - return Log.getLogger(name); + String servletName = getServletConfig().getServletName(); + servletName = servletName.replace('-', '.'); + if (!servletName.startsWith(getClass().getPackage().getName())) + servletName = getClass().getName() + "." + servletName; + return Log.getLogger(servletName); } public void destroy() @@ -395,22 +401,21 @@ public class ProxyServlet extends HttpServlet .version(HttpVersion.fromString(request.getProtocol())); // Copy headers - boolean hasContent = false; + boolean hasContent = request.getContentLength() > 0 || request.getContentType() != null; for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) { String headerName = headerNames.nextElement(); String lowerHeaderName = headerName.toLowerCase(Locale.ENGLISH); - // Remove hop-by-hop headers - if (HOP_HEADERS.contains(lowerHeaderName)) - continue; + if (HttpHeader.TRANSFER_ENCODING.is(headerName)) + hasContent = true; if (_hostHeader != null && HttpHeader.HOST.is(headerName)) continue; - if (request.getContentLength() > 0 || request.getContentType() != null || - HttpHeader.TRANSFER_ENCODING.is(headerName)) - hasContent = true; + // Remove hop-by-hop headers + if (HOP_HEADERS.contains(lowerHeaderName)) + continue; for (Enumeration headerValues = request.getHeaders(headerName); headerValues.hasMoreElements();) { @@ -431,7 +436,7 @@ public class ProxyServlet extends HttpServlet final AsyncContext asyncContext = request.startAsync(); // We do not timeout the continuation, but the proxy request asyncContext.setTimeout(0); - request.setAttribute(ASYNC_CONTEXT, asyncContext); + proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS); if (hasContent) proxyRequest.content(proxyRequestContent(proxyRequest, request)); @@ -471,7 +476,6 @@ public class ProxyServlet extends HttpServlet proxyRequest.getHeaders().toString().trim()); } - proxyRequest.timeout(getTimeout(), TimeUnit.MILLISECONDS); proxyRequest.send(new ProxyResponseListener(request, response)); } @@ -529,15 +533,23 @@ public class ProxyServlet extends HttpServlet } } - protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) throws IOException + protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) { - response.getOutputStream().write(buffer, offset, length); - _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length); + try + { + _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length); + response.getOutputStream().write(buffer, offset, length); + callback.succeeded(); + } + catch (Throwable x) + { + callback.failed(x); + } } protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse) { - AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT); + AsyncContext asyncContext = request.getAsyncContext(); asyncContext.complete(); _log.debug("{} proxying successful", getRequestId(request)); } @@ -552,7 +564,7 @@ public class ProxyServlet extends HttpServlet else response.setStatus(HttpServletResponse.SC_BAD_GATEWAY); } - AsyncContext asyncContext = (AsyncContext)request.getAttribute(ASYNC_CONTEXT); + AsyncContext asyncContext = request.getAsyncContext(); asyncContext.complete(); } @@ -731,7 +743,7 @@ public class ProxyServlet extends HttpServlet } @Override - public void onContent(Response proxyResponse, ByteBuffer content, Callback callback) + public void onContent(final Response proxyResponse, ByteBuffer content, final Callback callback) { byte[] buffer; int offset; @@ -748,14 +760,21 @@ public class ProxyServlet extends HttpServlet offset = 0; } - try + onResponseContent(request, response, proxyResponse, buffer, offset, length, new Callback() { - onResponseContent(request, response, proxyResponse, buffer, offset, length, callback); - } - catch (IOException x) - { - proxyResponse.abort(x); - } + @Override + public void succeeded() + { + callback.succeeded(); + } + + @Override + public void failed(Throwable x) + { + callback.failed(x); + proxyResponse.abort(x); + } + }); } @Override diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EmptyHttpServlet.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EmptyHttpServlet.java new file mode 100644 index 00000000000..382ba7c42c2 --- /dev/null +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EmptyHttpServlet.java @@ -0,0 +1,38 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.proxy; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class EmptyHttpServlet extends HttpServlet +{ + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + } +} diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java new file mode 100644 index 00000000000..567bffd3460 --- /dev/null +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java @@ -0,0 +1,245 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.proxy; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.HttpProxy; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.toolchain.test.TestTracker; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.StdErrLog; +import org.eclipse.jetty.util.thread.QueuedThreadPool; +import org.junit.After; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class ProxyServletFailureTest +{ + private static final String PROXIED_HEADER = "X-Proxied"; + + @Parameterized.Parameters + public static Iterable data() + { + return Arrays.asList(new Object[][]{ + {ProxyServlet.class}, + {AsyncProxyServlet.class} + }); + } + + @Rule + public final TestTracker tracker = new TestTracker(); + private HttpClient client; + private Server proxy; + private ServerConnector proxyConnector; + private ProxyServlet proxyServlet; + private Server server; + private ServerConnector serverConnector; + + public ProxyServletFailureTest(Class proxyServletClass) throws Exception + { + this.proxyServlet = (ProxyServlet)proxyServletClass.newInstance(); + } + + private void prepareProxy() throws Exception + { + prepareProxy(new HashMap()); + } + + private void prepareProxy(Map initParams) throws Exception + { + QueuedThreadPool executor = new QueuedThreadPool(); + executor.setName("proxy"); + proxy = new Server(); + proxyConnector = new ServerConnector(proxy); + proxy.addConnector(proxyConnector); + + ServletContextHandler proxyCtx = new ServletContextHandler(proxy, "/", true, false); + ServletHolder proxyServletHolder = new ServletHolder(proxyServlet); + proxyServletHolder.setInitParameters(initParams); + proxyCtx.addServlet(proxyServletHolder, "/*"); + + proxy.start(); + + client = prepareClient(); + } + + private HttpClient prepareClient() throws Exception + { + HttpClient result = new HttpClient(); + QueuedThreadPool executor = new QueuedThreadPool(); + executor.setName("client"); + result.setExecutor(executor); + result.getProxyConfiguration().getProxies().add(new HttpProxy("localhost", proxyConnector.getLocalPort())); + result.start(); + return result; + } + + private void prepareServer(HttpServlet servlet) throws Exception + { + QueuedThreadPool executor = new QueuedThreadPool(); + executor.setName("server"); + server = new Server(executor); + serverConnector = new ServerConnector(server); + server.addConnector(serverConnector); + + ServletContextHandler appCtx = new ServletContextHandler(server, "/", true, false); + ServletHolder appServletHolder = new ServletHolder(servlet); + appCtx.addServlet(appServletHolder, "/*"); + + server.start(); + } + + @After + public void disposeProxy() throws Exception + { + client.stop(); + proxy.stop(); + } + + @After + public void disposeServer() throws Exception + { + server.stop(); + } + + @Test(expected = TimeoutException.class) + public void testClientRequestExpired() throws Exception + { + prepareProxy(); + final long timeout = 1000; + proxyServlet.setTimeout(3 * timeout); + prepareServer(new HttpServlet() + { + @Override + protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException + { + try + { + TimeUnit.MILLISECONDS.sleep(2 * timeout); + } + catch (InterruptedException x) + { + throw new ServletException(x); + } + } + }); + + client.newRequest("localhost", serverConnector.getLocalPort()) + .timeout(timeout, TimeUnit.MILLISECONDS) + .send(); + Assert.fail(); + } + + @Test + public void testProxyRequestExpired() throws Exception + { + prepareProxy(); + final long timeout = 1000; + proxyServlet.setTimeout(timeout); + prepareServer(new HttpServlet() + { + @Override + protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException + { + if (request.getHeader("Via") != null) + response.addHeader(PROXIED_HEADER, "true"); + try + { + TimeUnit.MILLISECONDS.sleep(2 * timeout); + } + catch (InterruptedException x) + { + throw new ServletException(x); + } + } + }); + + Response response = client.newRequest("localhost", serverConnector.getLocalPort()) + .timeout(3 * timeout, TimeUnit.MILLISECONDS) + .send(); + Assert.assertEquals(504, response.getStatus()); + Assert.assertFalse(response.getHeaders().containsKey(PROXIED_HEADER)); + } + + @Test + public void testServerDown() throws Exception + { + prepareProxy(); + prepareServer(new EmptyHttpServlet()); + + // Shutdown the server + int serverPort = serverConnector.getLocalPort(); + server.stop(); + + ContentResponse response = client.newRequest("localhost", serverPort) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertEquals(502, response.getStatus()); + } + + @Test + public void testServerException() throws Exception + { + ((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(true); + try + { + prepareProxy(); + prepareServer(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + throw new ServletException("Expected Test Exception"); + } + }); + + ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) + .timeout(5, TimeUnit.SECONDS) + .send(); + + Assert.assertEquals(500, response.getStatus()); + } + finally + { + ((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(false); + } + } +} diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java index 42257a3446c..30c8b382eb1 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java @@ -61,15 +61,12 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.TestTracker; import org.eclipse.jetty.toolchain.test.annotation.Slow; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.log.Log; -import org.eclipse.jetty.util.log.StdErrLog; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; import org.junit.After; @@ -88,8 +85,8 @@ public class ProxyServletTest public static Iterable data() { return Arrays.asList(new Object[][]{ - {new ProxyServlet()}, - {new AsyncProxyServlet()} + {ProxyServlet.class}, + {AsyncProxyServlet.class} }); } @@ -102,9 +99,9 @@ public class ProxyServletTest private Server server; private ServerConnector serverConnector; - public ProxyServletTest(ProxyServlet proxyServlet) + public ProxyServletTest(Class proxyServletClass) throws Exception { - this.proxyServlet = proxyServlet; + this.proxyServlet = (ProxyServlet)proxyServletClass.newInstance(); } private void prepareProxy() throws Exception @@ -184,51 +181,6 @@ public class ProxyServletTest } } - @Test - public void testServerDown() throws Exception - { - prepareProxy(); - prepareServer(new EmptyHttpServlet()); - - // Shutdown the server - int serverPort = serverConnector.getLocalPort(); - server.stop(); - - ContentResponse response = client.newRequest("localhost", serverPort) - .timeout(5, TimeUnit.SECONDS) - .send(); - - Assert.assertEquals(502, response.getStatus()); - } - - @Test - public void testServerException() throws Exception - { - ((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(true); - try - { - prepareProxy(); - prepareServer(new HttpServlet() - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - throw new ServletException("Expected Test Exception"); - } - }); - - ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) - .timeout(5, TimeUnit.SECONDS) - .send(); - - Assert.assertEquals(500, response.getStatus()); - } - finally - { - ((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(false); - } - } - @Test public void testProxyWithoutContent() throws Exception { @@ -379,7 +331,7 @@ public class ProxyServletTest ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) .method(HttpMethod.POST) .content(new BytesContentProvider(content)) - .timeout(5, TimeUnit.SECONDS) + .timeout(555, TimeUnit.SECONDS) .send(); Assert.assertEquals(200, response.getStatus()); @@ -520,69 +472,6 @@ public class ProxyServletTest Assert.assertTrue(response.getHeaders().containsKey(PROXIED_HEADER)); } - @Slow - @Test - public void testProxyRequestExpired() throws Exception - { - prepareProxy(); - final long timeout = 1000; - proxyServlet.setTimeout(timeout); - prepareServer(new HttpServlet() - { - @Override - protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException - { - if (request.getHeader("Via") != null) - response.addHeader(PROXIED_HEADER, "true"); - try - { - TimeUnit.MILLISECONDS.sleep(2 * timeout); - } - catch (InterruptedException x) - { - throw new ServletException(x); - } - } - }); - - Response response = client.newRequest("localhost", serverConnector.getLocalPort()) - .timeout(3 * timeout, TimeUnit.MILLISECONDS) - .send(); - Assert.assertEquals(504, response.getStatus()); - Assert.assertFalse(response.getHeaders().containsKey(PROXIED_HEADER)); - } - - @Slow - @Test(expected = TimeoutException.class) - public void testClientRequestExpired() throws Exception - { - prepareProxy(); - final long timeout = 1000; - proxyServlet.setTimeout(3 * timeout); - prepareServer(new HttpServlet() - { - @Override - protected void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException - { - if (request.getHeader("Via") != null) - response.addHeader(PROXIED_HEADER, "true"); - try - { - TimeUnit.MILLISECONDS.sleep(2 * timeout); - } - catch (InterruptedException x) - { - throw new ServletException(x); - } - } - }); - - client.newRequest("localhost", serverConnector.getLocalPort()) - .timeout(timeout, TimeUnit.MILLISECONDS) - .send(); - Assert.fail(); - } - @Test public void testProxyXForwardedHostHeaderIsPresent() throws Exception { @@ -827,7 +716,7 @@ public class ProxyServletTest } @Override - protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) throws IOException + protected void onResponseContent(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, byte[] buffer, int offset, int length, Callback callback) { // Accumulate the response content ByteArrayOutputStream baos = temp.get(request.getRequestURI()); @@ -921,7 +810,7 @@ public class ProxyServletTest } @Test(expected = TimeoutException.class) - public void shouldHandleWrongContentLength() throws Exception + public void testWrongContentLength() throws Exception { prepareProxy(); prepareServer(new HttpServlet() @@ -937,7 +826,7 @@ public class ProxyServletTest }); client.newRequest("localhost", serverConnector.getLocalPort()) - .timeout(5, TimeUnit.SECONDS) + .timeout(1, TimeUnit.SECONDS) .send(); Assert.fail(); @@ -1005,17 +894,4 @@ public class ProxyServletTest } // TODO: test proxy authentication - - private static class EmptyHttpServlet extends HttpServlet - { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - } - } } From d8c8db5b83413c3e39985c3f0e757dbca41c4e76 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 30 Apr 2014 09:00:18 -0700 Subject: [PATCH 093/135] Setting up junit Assume on OS Alias / Alternate Filename Reference support. + Windows 8 no longer supports the 8.3 alternate filename reference techniques, rendering the test cases for this behavior no longer valid. --- .../handler/ContextHandlerGetResourceTest.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java index 19e322ab4e8..5534dedb52a 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java @@ -33,14 +33,17 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.util.resource.Resource; import org.junit.AfterClass; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; public class ContextHandlerGetResourceTest { + private static boolean OS_ALIAS_SUPPORTED; private static Server server; private static ContextHandler context; private static File docroot; @@ -51,8 +54,9 @@ public class ContextHandlerGetResourceTest @BeforeClass public static void beforeClass() throws Exception { - docroot = new File("target/tests/docroot").getCanonicalFile().getAbsoluteFile(); - FS.ensureDirExists(docroot); + File testRoot = MavenTestingUtils.getTargetTestingDir(ContextHandlerGetResourceTest.class.getSimpleName()); + FS.ensureEmpty(testRoot); + docroot = new File(testRoot,"docroot").getCanonicalFile().getAbsoluteFile(); FS.ensureEmpty(docroot); File index = new File(docroot,"index.html"); index.createNewFile(); @@ -63,8 +67,7 @@ public class ContextHandlerGetResourceTest File verylong = new File(sub,"TextFile.Long.txt"); verylong.createNewFile(); - otherroot = new File("target/tests/otherroot").getCanonicalFile().getAbsoluteFile(); - FS.ensureDirExists(otherroot); + otherroot = new File(testRoot, "otherroot").getCanonicalFile().getAbsoluteFile(); FS.ensureEmpty(otherroot); File other = new File(otherroot,"other.txt"); other.createNewFile(); @@ -83,6 +86,7 @@ public class ContextHandlerGetResourceTest Files.createSymbolicLink(transit.toPath(),otherroot.toPath()); } + OS_ALIAS_SUPPORTED = new File(sub, "TEXTFI~1.TXT").exists(); server = new Server(); context =new ContextHandler("/"); @@ -313,6 +317,7 @@ public class ContextHandlerGetResourceTest @Test public void testAliasedFile() throws Exception { + Assume.assumeTrue("OS Supports 8.3 Aliased / Alternate References",OS_ALIAS_SUPPORTED); final String path="/subdir/TEXTFI~1.TXT"; Resource resource=context.getResource(path); @@ -325,6 +330,7 @@ public class ContextHandlerGetResourceTest @Test public void testAliasedFileAllowed() throws Exception { + Assume.assumeTrue("OS Supports 8.3 Aliased / Alternate References",OS_ALIAS_SUPPORTED); try { allowAliases.set(true); From 30652825fa10c84c959ab8a21796e0fb073fdaee Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 30 Apr 2014 22:13:24 +0200 Subject: [PATCH 094/135] Added ASYNC_WOKEN state to HttpChannelState --- .../org/eclipse/jetty/server/HttpChannelState.java | 13 ++++++++++--- .../eclipse/jetty/util/SharedBlockingCallback.java | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java index 9ff6b37a855..7ff4d5a2a47 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java @@ -184,8 +184,6 @@ public class HttpChannelState case COMPLETED: return Action.WAIT; - case ASYNC_WAIT: - LOG.warn("How did I get here?", new Throwable()); case ASYNC_WOKEN: if (_asyncRead) { @@ -219,6 +217,7 @@ public class HttpChannelState _async=null; return Action.ASYNC_EXPIRED; case STARTED: + // TODO if (DEBUG) LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this .getStatusString())); @@ -361,9 +360,17 @@ public class HttpChannelState case ASYNC_IO: dispatch=false; break; - default: + case ASYNC_WAIT: + _state=State.ASYNC_WOKEN; dispatch=true; break; + case ASYNC_WOKEN: + dispatch=false; + break; + default: + LOG.warn("async dispatched when complete {}",this); + dispatch=false; + break; } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java index 9b353b1bdb5..c969f949111 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java @@ -222,7 +222,7 @@ public class SharedBlockingCallback if (_state == IDLE) throw new IllegalStateException("IDLE"); if (_state == null) - LOG.warn(new Throwable()); + LOG.debug("Blocker not complete",new Throwable()); } finally { From a1d18a157da2a1e7addd559743a40a7cbd1043a0 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 2 May 2014 22:45:56 +0200 Subject: [PATCH 095/135] Added onReadFailure() callback to allow subclasses to perform actions in case of read failures. --- .../jetty/client/util/InputStreamContentProvider.java | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 bd0606530d4..89d3022ae27 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 @@ -102,6 +102,16 @@ public class InputStreamContentProvider implements ContentProvider return ByteBuffer.wrap(buffer, offset, length); } + /** + * Callback method invoked when an exception is thrown while reading + * from the stream. + * + * @param failure the exception thrown while reading from the stream. + */ + protected void onReadFailure(Throwable failure) + { + } + @Override public Iterator iterator() { @@ -166,6 +176,7 @@ public class InputStreamContentProvider implements ContentProvider if (failure == null) { failure = x; + onReadFailure(x); // Signal we have more content to cause a call to // next() which will throw NoSuchElementException. hasNext = Boolean.TRUE; From 976d86b3757211d5f6c992847ed4b8e1915f2703 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 2 May 2014 23:01:33 +0200 Subject: [PATCH 096/135] 434027 - ReadListener.onError() not invoked in case of read failures. --- .../src/main/java/org/eclipse/jetty/server/HttpInput.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index e5d31f082a7..833ecdede7f 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.server; import java.io.IOException; import java.util.Objects; - import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; @@ -363,7 +362,7 @@ public abstract class HttpInput extends ServletInputStream implements Runnabl { synchronized (lock()) { - if (_onError == null) + if (_onError != null) LOG.warn(x); else _onError = x; From a405454276c1943fe0edd0812eb47411a8d4703c Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 2 May 2014 23:09:21 +0200 Subject: [PATCH 097/135] 431642 - Implement ProxyServlet using Servlet 3.1 async I/O. Added more tests for failure cases. --- .../jetty/proxy/AsyncProxyServlet.java | 50 +++---- .../org/eclipse/jetty/proxy/ProxyServlet.java | 19 ++- .../eclipse/jetty/proxy/EchoHttpServlet.java | 36 +++++ .../eclipse/jetty/proxy/EmptyHttpServlet.java | 7 +- .../jetty/proxy/ProxyServletFailureTest.java | 140 +++++++++++++++++- .../test/resources/jetty-logging.properties | 1 + 6 files changed, 218 insertions(+), 35 deletions(-) create mode 100644 jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EchoHttpServlet.java diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java index 1127ecd177a..a1a9eedc091 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java @@ -21,7 +21,6 @@ package org.eclipse.jetty.proxy; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritePendingException; -import javax.servlet.AsyncContext; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; @@ -44,7 +43,7 @@ public class AsyncProxyServlet extends ProxyServlet { ServletInputStream input = request.getInputStream(); DeferredContentProvider provider = new DeferredContentProvider(); - input.setReadListener(new StreamReader(input, getRequestId(request), provider)); + input.setReadListener(new StreamReader(request, provider)); return provider; } @@ -53,12 +52,11 @@ public class AsyncProxyServlet extends ProxyServlet { try { - int requestId = getRequestId(request); - _log.debug("{} proxying content to downstream: {} bytes", requestId, length); + _log.debug("{} proxying content to downstream: {} bytes", getRequestId(request), length); StreamWriter writeListener = (StreamWriter)request.getAttribute(WRITE_LISTENER_ATTRIBUTE); if (writeListener == null) { - writeListener = new StreamWriter(request.getAsyncContext(), requestId); + writeListener = new StreamWriter(request, proxyResponse); request.setAttribute(WRITE_LISTENER_ATTRIBUTE, writeListener); // Set the data to write before calling setWriteListener(), because @@ -77,29 +75,28 @@ public class AsyncProxyServlet extends ProxyServlet } catch (Throwable x) { - // TODO: who calls asyncContext.complete() in this case ? callback.failed(x); + onResponseFailure(request, response, proxyResponse, x); } } private class StreamReader implements ReadListener, Callback { - private final byte[] buffer = new byte[512]; -// private final byte[] buffer = new byte[getHttpClient().getRequestBufferSize()]; - private final ServletInputStream input; - private final int requestId; + private final byte[] buffer = new byte[getHttpClient().getRequestBufferSize()]; + private final HttpServletRequest request; private final DeferredContentProvider provider; - public StreamReader(ServletInputStream input, int requestId, DeferredContentProvider provider) + public StreamReader(HttpServletRequest request, DeferredContentProvider provider) { - this.input = input; - this.requestId = requestId; + this.request = request; this.provider = provider; } @Override public void onDataAvailable() throws IOException { + int requestId = getRequestId(request); + ServletInputStream input = request.getInputStream(); _log.debug("{} asynchronous read start on {}", requestId, input); // First check for isReady() because it has @@ -123,7 +120,7 @@ public class AsyncProxyServlet extends ProxyServlet @Override public void onAllDataRead() throws IOException { - _log.debug("{} proxying content to upstream completed", requestId); + _log.debug("{} proxying content to upstream completed", getRequestId(request)); provider.close(); } @@ -138,7 +135,7 @@ public class AsyncProxyServlet extends ProxyServlet { try { - if (input.isReady()) + if (request.getInputStream().isReady()) onDataAvailable(); } catch (Throwable x) @@ -150,25 +147,24 @@ public class AsyncProxyServlet extends ProxyServlet @Override public void failed(Throwable x) { - // TODO: send a response error ? - // complete the async context since we cannot throw an exception from here. + onClientRequestFailure(request, x); } } private class StreamWriter implements WriteListener { - private final AsyncContext asyncContext; - private final int requestId; + private final HttpServletRequest request; + private final Response proxyResponse; private WriteState state; private byte[] buffer; private int offset; private int length; private Callback callback; - private StreamWriter(AsyncContext asyncContext, int requestId) + private StreamWriter(HttpServletRequest request, Response proxyResponse) { - this.asyncContext = asyncContext; - this.requestId = requestId; + this.request = request; + this.proxyResponse = proxyResponse; this.state = WriteState.IDLE; } @@ -186,7 +182,8 @@ public class AsyncProxyServlet extends ProxyServlet @Override public void onWritePossible() throws IOException { - ServletOutputStream output = asyncContext.getResponse().getOutputStream(); + int requestId = getRequestId(request); + ServletOutputStream output = request.getAsyncContext().getResponse().getOutputStream(); if (state == WriteState.READY) { // There is data to write. @@ -224,15 +221,16 @@ public class AsyncProxyServlet extends ProxyServlet callback = null; state = WriteState.IDLE; // Call the callback only after the whole state has been reset, - // because the callback may trigger a reentrant call and the - // state would be the old one + // because the callback may trigger a reentrant call and + // the state must already be the new one that we reset here. c.succeeded(); } @Override public void onError(Throwable failure) { - // TODO: + HttpServletResponse response = (HttpServletResponse)request.getAsyncContext().getResponse(); + onResponseFailure(request, response, proxyResponse, failure); } } 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 046562939e5..5b2fa5676e7 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 @@ -48,6 +48,7 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.util.InputStreamContentProvider; import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.HttpCookieStore; @@ -495,9 +496,24 @@ public class ProxyServlet extends HttpServlet _log.debug("{} proxying content to upstream: {} bytes", getRequestId(request), length); return super.onRead(buffer, offset, length); } + + @Override + protected void onReadFailure(Throwable failure) + { + onClientRequestFailure(request, failure); + } }; } + protected void onClientRequestFailure(HttpServletRequest request, Throwable failure) + { + AsyncContext asyncContext = request.getAsyncContext(); + HttpServletResponse response = (HttpServletResponse)asyncContext.getResponse(); + response.setStatus(500); + response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString()); + asyncContext.complete(); + } + protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException { response.sendError(HttpServletResponse.SC_FORBIDDEN); @@ -529,7 +545,7 @@ public class ProxyServlet extends HttpServlet if (newHeaderValue == null || newHeaderValue.trim().length() == 0) continue; - response.addHeader(headerName, newHeaderValue); + response.setHeader(headerName, newHeaderValue); } } @@ -563,6 +579,7 @@ public class ProxyServlet extends HttpServlet response.setStatus(HttpServletResponse.SC_GATEWAY_TIMEOUT); else response.setStatus(HttpServletResponse.SC_BAD_GATEWAY); + response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString()); } AsyncContext asyncContext = request.getAsyncContext(); asyncContext.complete(); diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EchoHttpServlet.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EchoHttpServlet.java new file mode 100644 index 00000000000..16667a7728c --- /dev/null +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EchoHttpServlet.java @@ -0,0 +1,36 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.proxy; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.util.IO; + +public class EchoHttpServlet extends HttpServlet +{ + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException + { + IO.copy(request.getInputStream(), response.getOutputStream()); + } +} diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EmptyHttpServlet.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EmptyHttpServlet.java index 382ba7c42c2..c9257e1dcc5 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EmptyHttpServlet.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/EmptyHttpServlet.java @@ -27,12 +27,7 @@ import javax.servlet.http.HttpServletResponse; public class EmptyHttpServlet extends HttpServlet { @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException - { - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } } diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java index 567bffd3460..b0ff68c5358 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletFailureTest.java @@ -18,7 +18,13 @@ package org.eclipse.jetty.proxy; +import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.Socket; +import java.nio.ByteBuffer; import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -31,14 +37,21 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpProxy; +import org.eclipse.jetty.client.api.ContentProvider; import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.api.Request; import org.eclipse.jetty.client.api.Response; +import org.eclipse.jetty.client.util.BytesContentProvider; +import org.eclipse.jetty.client.util.DeferredContentProvider; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.toolchain.test.TestTracker; +import org.eclipse.jetty.toolchain.test.http.SimpleHttpParser; +import org.eclipse.jetty.toolchain.test.http.SimpleHttpResponse; +import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.StdErrLog; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -86,7 +99,7 @@ public class ProxyServletFailureTest { QueuedThreadPool executor = new QueuedThreadPool(); executor.setName("proxy"); - proxy = new Server(); + proxy = new Server(executor); proxyConnector = new ServerConnector(proxy); proxy.addConnector(proxyConnector); @@ -139,8 +152,126 @@ public class ProxyServletFailureTest server.stop(); } + @Test + public void testClientRequestStallsHeadersProxyIdlesTimeout() throws Exception + { + prepareProxy(); + int idleTimeout = 2000; + proxyConnector.setIdleTimeout(idleTimeout); + + prepareServer(new EchoHttpServlet()); + + try (Socket socket = new Socket("localhost", proxyConnector.getLocalPort())) + { + String serverHostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "GET http://" + serverHostPort + " HTTP/1.1\r\n" + + "Host: " + serverHostPort + "\r\n"; + // Don't sent the \r\n that would signal the end of the headers. + OutputStream output = socket.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Wait for idle timeout to fire. + + socket.setSoTimeout(2 * idleTimeout); + InputStream input = socket.getInputStream(); + Assert.assertEquals(-1, input.read()); + } + } + + @Test + public void testClientRequestStallsContentProxyIdlesTimeout() throws Exception + { + prepareProxy(); + int idleTimeout = 2000; + proxyConnector.setIdleTimeout(idleTimeout); + + prepareServer(new EchoHttpServlet()); + + try (Socket socket = new Socket("localhost", proxyConnector.getLocalPort())) + { + String serverHostPort = "localhost:" + serverConnector.getLocalPort(); + String request = "" + + "GET http://" + serverHostPort + " HTTP/1.1\r\n" + + "Host: " + serverHostPort + "\r\n" + + "Content-Length: 1\r\n" + + "\r\n"; + OutputStream output = socket.getOutputStream(); + output.write(request.getBytes("UTF-8")); + output.flush(); + + // Do not send the promised content, wait to idle timeout. + + socket.setSoTimeout(2 * idleTimeout); + SimpleHttpParser parser = new SimpleHttpParser(); + BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); + SimpleHttpResponse response = parser.readResponse(reader); + Assert.assertTrue(Integer.parseInt(response.getCode()) >= 500); + String connectionHeader = response.getHeaders().get("connection"); + Assert.assertNotNull(connectionHeader); + Assert.assertTrue(connectionHeader.contains("close")); + Assert.assertEquals(-1, reader.read()); + } + } + + @Test + public void testProxyRequestStallsContentServerIdlesTimeout() throws Exception + { + final byte[] content = new byte[]{'C', '0', 'F', 'F', 'E', 'E'}; + if (proxyServlet instanceof AsyncProxyServlet) + { + proxyServlet = new AsyncProxyServlet() + { + @Override + protected ContentProvider proxyRequestContent(Request proxyRequest, HttpServletRequest request) throws IOException + { + return new DeferredContentProvider() + { + @Override + public boolean offer(ByteBuffer buffer, Callback callback) + { + // Ignore all content to trigger the test condition. + return true; + } + }; + } + }; + } + else + { + proxyServlet = new ProxyServlet() + { + @Override + protected ContentProvider proxyRequestContent(Request proxyRequest, HttpServletRequest request) throws IOException + { + return new BytesContentProvider(content) + { + @Override + public long getLength() + { + // Increase the content length to trigger the test condition. + return content.length + 1; + } + }; + } + }; + } + + prepareProxy(); + prepareServer(new EchoHttpServlet()); + long idleTimeout = 1000; + serverConnector.setIdleTimeout(idleTimeout); + + ContentResponse response = client.newRequest("localhost", serverConnector.getLocalPort()) + .content(new BytesContentProvider(content)) + .send(); + + Assert.assertEquals(500, response.getStatus()); + } + @Test(expected = TimeoutException.class) - public void testClientRequestExpired() throws Exception + public void testClientRequestExpires() throws Exception { prepareProxy(); final long timeout = 1000; @@ -242,4 +373,9 @@ public class ProxyServletFailureTest ((StdErrLog)Log.getLogger(ServletHandler.class)).setHideStacks(false); } } + + private interface Function + { + public R apply(T arg); + } } diff --git a/jetty-proxy/src/test/resources/jetty-logging.properties b/jetty-proxy/src/test/resources/jetty-logging.properties index 1c19e5331e5..e0c669f1be3 100644 --- a/jetty-proxy/src/test/resources/jetty-logging.properties +++ b/jetty-proxy/src/test/resources/jetty-logging.properties @@ -1,3 +1,4 @@ org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog #org.eclipse.jetty.LEVEL=DEBUG #org.eclipse.jetty.client.LEVEL=DEBUG +#org.eclipse.jetty.proxy.LEVEL=DEBUG From 1956ceffded0c27df345b6b1754206c37aac8dc4 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 30 Apr 2014 09:00:18 -0700 Subject: [PATCH 098/135] Setting up junit Assume on OS Alias / Alternate Filename Reference support. + Windows 8 no longer supports the 8.3 alternate filename reference techniques, rendering the test cases for this behavior no longer valid. --- .../handler/ContextHandlerGetResourceTest.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java index 19e322ab4e8..5534dedb52a 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java @@ -33,14 +33,17 @@ import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.util.resource.Resource; import org.junit.AfterClass; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; public class ContextHandlerGetResourceTest { + private static boolean OS_ALIAS_SUPPORTED; private static Server server; private static ContextHandler context; private static File docroot; @@ -51,8 +54,9 @@ public class ContextHandlerGetResourceTest @BeforeClass public static void beforeClass() throws Exception { - docroot = new File("target/tests/docroot").getCanonicalFile().getAbsoluteFile(); - FS.ensureDirExists(docroot); + File testRoot = MavenTestingUtils.getTargetTestingDir(ContextHandlerGetResourceTest.class.getSimpleName()); + FS.ensureEmpty(testRoot); + docroot = new File(testRoot,"docroot").getCanonicalFile().getAbsoluteFile(); FS.ensureEmpty(docroot); File index = new File(docroot,"index.html"); index.createNewFile(); @@ -63,8 +67,7 @@ public class ContextHandlerGetResourceTest File verylong = new File(sub,"TextFile.Long.txt"); verylong.createNewFile(); - otherroot = new File("target/tests/otherroot").getCanonicalFile().getAbsoluteFile(); - FS.ensureDirExists(otherroot); + otherroot = new File(testRoot, "otherroot").getCanonicalFile().getAbsoluteFile(); FS.ensureEmpty(otherroot); File other = new File(otherroot,"other.txt"); other.createNewFile(); @@ -83,6 +86,7 @@ public class ContextHandlerGetResourceTest Files.createSymbolicLink(transit.toPath(),otherroot.toPath()); } + OS_ALIAS_SUPPORTED = new File(sub, "TEXTFI~1.TXT").exists(); server = new Server(); context =new ContextHandler("/"); @@ -313,6 +317,7 @@ public class ContextHandlerGetResourceTest @Test public void testAliasedFile() throws Exception { + Assume.assumeTrue("OS Supports 8.3 Aliased / Alternate References",OS_ALIAS_SUPPORTED); final String path="/subdir/TEXTFI~1.TXT"; Resource resource=context.getResource(path); @@ -325,6 +330,7 @@ public class ContextHandlerGetResourceTest @Test public void testAliasedFileAllowed() throws Exception { + Assume.assumeTrue("OS Supports 8.3 Aliased / Alternate References",OS_ALIAS_SUPPORTED); try { allowAliases.set(true); From 11096e8a0d241da802a3793d670e5768b0a45529 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 30 Apr 2014 22:13:24 +0200 Subject: [PATCH 099/135] Added ASYNC_WOKEN state to HttpChannelState --- .../org/eclipse/jetty/server/HttpChannelState.java | 13 ++++++++++--- .../eclipse/jetty/util/SharedBlockingCallback.java | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java index 9ff6b37a855..7ff4d5a2a47 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java @@ -184,8 +184,6 @@ public class HttpChannelState case COMPLETED: return Action.WAIT; - case ASYNC_WAIT: - LOG.warn("How did I get here?", new Throwable()); case ASYNC_WOKEN: if (_asyncRead) { @@ -219,6 +217,7 @@ public class HttpChannelState _async=null; return Action.ASYNC_EXPIRED; case STARTED: + // TODO if (DEBUG) LOG.debug("TODO Fix this double dispatch",new IllegalStateException(this .getStatusString())); @@ -361,9 +360,17 @@ public class HttpChannelState case ASYNC_IO: dispatch=false; break; - default: + case ASYNC_WAIT: + _state=State.ASYNC_WOKEN; dispatch=true; break; + case ASYNC_WOKEN: + dispatch=false; + break; + default: + LOG.warn("async dispatched when complete {}",this); + dispatch=false; + break; } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java index 9b353b1bdb5..c969f949111 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java @@ -222,7 +222,7 @@ public class SharedBlockingCallback if (_state == IDLE) throw new IllegalStateException("IDLE"); if (_state == null) - LOG.warn(new Throwable()); + LOG.debug("Blocker not complete",new Throwable()); } finally { From 6fc645514e232c007af590089e308cd53419d5fb Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 2 May 2014 23:08:19 +0200 Subject: [PATCH 100/135] Fixed tests that broke due to changes in handling of white spaces between HTTP messages introduced by commit 95c6bad6545604ffa23024ac0092bd9629a520d7. --- .../support/rawhttp/HttpResponseTesterTest.java | 15 +++++++-------- .../jetty/test/support/rawhttp/HttpTesting.java | 16 ---------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java index 50550425b2e..5c6178e344f 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpResponseTesterTest.java @@ -18,10 +18,6 @@ package org.eclipse.jetty.test.support.rawhttp; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertThat; - import java.io.IOException; import java.util.List; @@ -31,6 +27,10 @@ import org.eclipse.jetty.http.HttpTester; import org.junit.Assert; import org.junit.Test; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertThat; + public class HttpResponseTesterTest { @Test @@ -85,7 +85,8 @@ public class HttpResponseTesterTest rawResponse.append("\n"); rawResponse.append("Host=Default\n"); rawResponse.append("Resource=R1\n"); - + rawResponse.append("\n"); + rawResponse.append("HTTP/1.1 200 OK\n"); rawResponse.append("Date: Mon, 08 Jun 2009 23:05:26 GMT\n"); rawResponse.append("Content-Type: text/plain\n"); @@ -96,9 +97,7 @@ public class HttpResponseTesterTest rawResponse.append("\n"); rawResponse.append("Host=Default\n"); rawResponse.append("Resource=R2\n"); - rawResponse.append("\n"); - - + List responses = HttpTesting.readResponses(rawResponse.toString()); Assert.assertNotNull("Responses should not be null",responses); diff --git a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java index 3bd3646c666..5afeaad3220 100644 --- a/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java +++ b/tests/test-integration/src/test/java/org/eclipse/jetty/test/support/rawhttp/HttpTesting.java @@ -117,22 +117,6 @@ public class HttpTesting } - - - public static List readResponses(ByteBuffer buffer) throws IOException - { - List list = new ArrayList<>(); - - while(BufferUtil.hasContent(buffer)) - { - HttpTester.Response response = HttpTester.parseResponse(buffer); - if (response == null) - break; - list.add(HttpTester.parseResponse(buffer)); - } - return list; - } - public static List readResponses(String string) throws IOException { List list = new ArrayList<>(); From 4369e1d242087cecc5d5a256e32a7799a9b297fa Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sun, 4 May 2014 15:27:09 +0200 Subject: [PATCH 101/135] Better fix for the duplicate Date header. --- .../src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 5b2fa5676e7..e37bd297064 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 @@ -534,6 +534,10 @@ public class ProxyServlet extends HttpServlet protected void onResponseHeaders(HttpServletRequest request, HttpServletResponse response, Response proxyResponse) { + // Clear the response headers in case it comes with predefined ones. + for (String name : response.getHeaderNames()) + response.setHeader(name, null); + for (HttpField field : proxyResponse.getHeaders()) { String headerName = field.getName(); @@ -545,7 +549,7 @@ public class ProxyServlet extends HttpServlet if (newHeaderValue == null || newHeaderValue.trim().length() == 0) continue; - response.setHeader(headerName, newHeaderValue); + response.addHeader(headerName, newHeaderValue); } } From f62824187513c3d92ade12c4f46345843ccdfa00 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sun, 4 May 2014 16:03:26 +0200 Subject: [PATCH 102/135] 433916 - HttpChannelOverHttp handles HTTP 1.0 connection reuse incorrectly. Fixed by properly checking the HTTP version and whether the Connection: keep-alive header is present. --- .../client/http/HttpChannelOverHTTP.java | 12 ++++-- .../client/HttpConnectionLifecycleTest.java | 38 ++++++++++++++++++- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java index cd5bcf274f6..b89a32bdb7a 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java @@ -20,10 +20,12 @@ package org.eclipse.jetty.client.http; import org.eclipse.jetty.client.HttpChannel; import org.eclipse.jetty.client.HttpExchange; +import org.eclipse.jetty.client.api.Response; import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.http.HttpVersion; public class HttpChannelOverHTTP extends HttpChannel { @@ -77,10 +79,12 @@ public class HttpChannelOverHTTP extends HttpChannel public void exchangeTerminated(Result result) { super.exchangeTerminated(result); - HttpFields responseHeaders = result.getResponse().getHeaders(); - boolean close = result.isFailed() || - responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()) || - receiver.isShutdown(); + Response response = result.getResponse(); + HttpFields responseHeaders = response.getHeaders(); + boolean implicitClose = response.getVersion().compareTo(HttpVersion.HTTP_1_1) < 0 && + !responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()); + boolean explicitClose = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); + boolean close = result.isFailed() || implicitClose || explicitClose || receiver.isShutdown(); if (close) connection.close(); else diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java index 3313e224de0..98c59c85305 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java @@ -24,7 +24,6 @@ import java.util.Arrays; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; - import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -37,6 +36,7 @@ import org.eclipse.jetty.client.api.Result; import org.eclipse.jetty.client.http.HttpDestinationOverHTTP; import org.eclipse.jetty.client.util.ByteBufferContentProvider; import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.annotation.Slow; @@ -489,4 +489,40 @@ public class HttpConnectionLifecycleTest extends AbstractHttpClientServerTest Assert.assertEquals(0, idleConnections.size()); Assert.assertEquals(0, activeConnections.size()); } + + @Test + public void testConnectionForHTTP10ResponseIsRemoved() throws Exception + { + start(new EmptyServerHandler()); + + String host = "localhost"; + int port = connector.getLocalPort(); + HttpDestinationOverHTTP destination = (HttpDestinationOverHTTP)client.getDestination(scheme, host, port); + ConnectionPool connectionPool = destination.getConnectionPool(); + + final BlockingQueue idleConnections = connectionPool.getIdleConnections(); + Assert.assertEquals(0, idleConnections.size()); + + final BlockingQueue activeConnections = connectionPool.getActiveConnections(); + Assert.assertEquals(0, activeConnections.size()); + + client.setStrictEventOrdering(false); + ContentResponse response = client.newRequest(host, port) + .scheme(scheme) + .onResponseBegin(new Response.BeginListener() + { + @Override + public void onBegin(Response response) + { + // Simulate a HTTP 1.0 response has been received. + ((HttpResponse)response).version(HttpVersion.HTTP_1_0); + } + }) + .send(); + + Assert.assertEquals(200, response.getStatus()); + + Assert.assertEquals(0, idleConnections.size()); + Assert.assertEquals(0, activeConnections.size()); + } } From 9b388750fae307e9780ed96da4845cb9170012d0 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sun, 4 May 2014 19:42:22 +0200 Subject: [PATCH 103/135] 433916 - HttpChannelOverHttp handles HTTP 1.0 connection reuse incorrectly. Fixed by properly checking the HTTP version and whether the Connection: keep-alive header is present. --- .../client/http/HttpChannelOverHTTP.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java index b89a32bdb7a..912c058b202 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpChannelOverHTTP.java @@ -81,10 +81,21 @@ public class HttpChannelOverHTTP extends HttpChannel super.exchangeTerminated(result); Response response = result.getResponse(); HttpFields responseHeaders = response.getHeaders(); - boolean implicitClose = response.getVersion().compareTo(HttpVersion.HTTP_1_1) < 0 && - !responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()); - boolean explicitClose = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); - boolean close = result.isFailed() || implicitClose || explicitClose || receiver.isShutdown(); + boolean close = result.isFailed() || receiver.isShutdown(); + // Only check HTTP headers if there are no failures. + if (!close) + { + if (response.getVersion().compareTo(HttpVersion.HTTP_1_1) < 0) + { + // HTTP 1.0 must close the connection unless it has an explicit keep alive. + close = !responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.asString()); + } + else + { + // HTTP 1.1 or greater closes only if it has an explicit close. + close = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); + } + } if (close) connection.close(); else From c4b5e3d3d7521bd6786ff0f1e542f257132a4c4a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sun, 4 May 2014 23:14:45 +0200 Subject: [PATCH 104/135] Introduced CompletableCallback to handle asynchronous content on clients. --- .../client/http/HttpReceiverOverHTTP.java | 20 ++-- .../client/HttpClientAsyncContentTest.java | 13 +-- .../jetty/util/CompletableCallback.java | 100 ++++++++++++++++++ 3 files changed, 109 insertions(+), 24 deletions(-) create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/CompletableCallback.java diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java index 5f499bd0c9f..f98f4828942 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/http/HttpReceiverOverHTTP.java @@ -20,7 +20,6 @@ package org.eclipse.jetty.client.http; import java.io.EOFException; import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicBoolean; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.client.HttpExchange; @@ -34,7 +33,7 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.CompletableCallback; public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.ResponseHandler { @@ -226,27 +225,22 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res if (exchange == null) return false; - final AtomicBoolean completed = new AtomicBoolean(); - Callback callback = new Callback() + CompletableCallback callback = new CompletableCallback() { @Override - public void succeeded() + public void resume() { - if (!completed.compareAndSet(false, true)) - { - LOG.debug("Content consumed asynchronously, resuming processing"); - process(); - } + LOG.debug("Content consumed asynchronously, resuming processing"); + process(); } - @Override - public void failed(Throwable x) + public void abort(Throwable x) { failAndClose(x); } }; responseContent(exchange, buffer, callback); - return completed.compareAndSet(false, true); + return callback.tryComplete(); } @Override diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java index 56fcd1a9220..4ca0db19ccf 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientAsyncContentTest.java @@ -19,7 +19,6 @@ package org.eclipse.jetty.client; import java.io.IOException; -import java.net.Socket; import java.nio.ByteBuffer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -86,7 +85,7 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest } }); - Assert.assertTrue(contentLatch.get().await(555, TimeUnit.SECONDS)); + Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS)); Callback callback = callbackRef.get(); // Wait a while to be sure that the parsing does not proceed. @@ -99,7 +98,7 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest contentLatch.set(new CountDownLatch(1)); callback.succeeded(); - Assert.assertTrue(contentLatch.get().await(555, TimeUnit.SECONDS)); + Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS)); callback = callbackRef.get(); // Wait a while to be sure that the parsing does not proceed. @@ -116,12 +115,4 @@ public class HttpClientAsyncContentTest extends AbstractHttpClientServerTest Assert.assertTrue(completeLatch.await(5, TimeUnit.SECONDS)); Assert.assertEquals(2, contentCount.get()); } - - public void test() throws Exception - { - try (Socket socket = new Socket()) - { - System.out.println("socket = " + socket); - } - } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/CompletableCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/CompletableCallback.java new file mode 100644 index 00000000000..ec5a30a84e6 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/CompletableCallback.java @@ -0,0 +1,100 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A callback to be used by driver code that needs to know whether the callback has been + * succeeded or failed (that is, completed) just after the asynchronous operation or not, + * typically because further processing depends on the callback being completed. + * The driver code competes with the asynchronous operation to complete the callback. + *

    + * If the callback is already completed, the driver code continues the processing, + * otherwise it suspends it. If it is suspended, the callback will be completed some time + * later, and {@link #resume()} or {@link #abort(Throwable)} will be called to allow the + * application to resume the processing. + *

    + * Typical usage: + *

    + * CompletableCallback callback = new CompletableCallback()
    + * {
    + *     @Override
    + *     public void resume()
    + *     {
    + *         // continue processing
    + *     }
    + *
    + *     @Override
    + *     public void abort(Throwable failure)
    + *     {
    + *         // abort processing
    + *     }
    + * }
    + * asyncOperation(callback);
    + * boolean completed = callback.tryComplete();
    + * if (completed)
    + *     // suspend processing, async operation not done yet
    + * else
    + *     // continue processing, async operation already done
    + * 
    + */ +public abstract class CompletableCallback implements Callback +{ + private final AtomicBoolean completed = new AtomicBoolean(); + + @Override + public void succeeded() + { + if (!tryComplete()) + resume(); + } + + @Override + public void failed(Throwable x) + { + if (!tryComplete()) + abort(x); + } + + /** + * Callback method invoked when this callback is succeeded + * after a first call to {@link #tryComplete()}. + */ + public abstract void resume(); + + /** + * Callback method invoked when this callback is failed + * after a first call to {@link #tryComplete()}. + */ + public abstract void abort(Throwable failure); + + /** + * Tries to complete this callback; driver code should call + * this method once after the asynchronous operation + * to detect whether the asynchronous operation has already + * completed or not. + * + * @return whether the attempt to complete was successful. + */ + public boolean tryComplete() + { + return completed.compareAndSet(false, true); + } +} From 871b330ba8c1dfc8b65d7536134f15ca632a8176 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Sun, 4 May 2014 23:15:54 +0200 Subject: [PATCH 105/135] 434056 - Support content consumed asynchronously. Implemented content consumed asynchronously for FastCGI (on the client). --- .../fcgi/client/http/HttpChannelOverFCGI.java | 8 +- .../client/http/HttpConnectionOverFCGI.java | 81 ++++++++++++++----- .../parser/BeginRequestContentParser.java | 8 +- .../jetty/fcgi/parser/ClientParser.java | 4 +- .../jetty/fcgi/parser/ContentParser.java | 7 +- .../fcgi/parser/EndRequestContentParser.java | 8 +- .../jetty/fcgi/parser/HeaderParser.java | 6 ++ .../fcgi/parser/ParamsContentParser.java | 6 +- .../org/eclipse/jetty/fcgi/parser/Parser.java | 18 +++-- .../fcgi/parser/ResponseContentParser.java | 15 ++-- .../fcgi/parser/StreamContentParser.java | 14 ++-- .../fcgi/generator/ClientGeneratorTest.java | 3 +- .../jetty/fcgi/parser/ClientParserTest.java | 9 ++- .../fcgi/server/ServerFCGIConnection.java | 3 +- .../server/proxy/FastCGIProxyServlet.java | 3 +- .../jetty/fcgi/server/HttpClientTest.java | 75 ++++++++++++++++- .../jetty/proxy/AsyncProxyServlet.java | 21 +++++ .../org/eclipse/jetty/proxy/ProxyServlet.java | 40 +++++---- .../eclipse/jetty/proxy/ProxyServletTest.java | 14 +++- 19 files changed, 259 insertions(+), 84 deletions(-) diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java index 36d91e076ab..59775694ebb 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpChannelOverFCGI.java @@ -103,11 +103,13 @@ public class HttpChannelOverFCGI extends HttpChannel return exchange != null && receiver.responseHeaders(exchange); } - protected boolean content(ByteBuffer buffer) + protected boolean content(ByteBuffer buffer, Callback callback) { HttpExchange exchange = getHttpExchange(); - // TODO: handle callback properly - return exchange != null && receiver.responseContent(exchange, buffer, new Callback.Adapter()); + if (exchange != null) + return receiver.responseContent(exchange, buffer, callback); + callback.succeeded(); + return false; } protected boolean responseSuccess() diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java index f14143bfb0c..caafebaff80 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpConnectionOverFCGI.java @@ -45,6 +45,7 @@ import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.BufferUtil; +import org.eclipse.jetty.util.CompletableCallback; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -60,6 +61,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec private final boolean multiplexed; private final Delegate delegate; private final ClientParser parser; + private ByteBuffer buffer; public HttpConnectionOverFCGI(EndPoint endPoint, HttpDestination destination, boolean multiplexed) { @@ -98,48 +100,66 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec @Override public void onFillable() { - EndPoint endPoint = getEndPoint(); HttpClient client = destination.getHttpClient(); ByteBufferPool bufferPool = client.getByteBufferPool(); - ByteBuffer buffer = bufferPool.acquire(client.getResponseBufferSize(), true); - try + buffer = bufferPool.acquire(client.getResponseBufferSize(), true); + process(); + } + + private void process() + { + if (readAndParse()) { - while (true) + HttpClient client = destination.getHttpClient(); + ByteBufferPool bufferPool = client.getByteBufferPool(); + bufferPool.release(buffer); + // Don't linger the buffer around if we are idle. + buffer = null; + } + } + + private boolean readAndParse() + { + EndPoint endPoint = getEndPoint(); + ByteBuffer buffer = this.buffer; + while (true) + { + try { + if (!parse(buffer)) + return false; + int read = endPoint.fill(buffer); if (LOG.isDebugEnabled()) // Avoid boxing of variable 'read'. LOG.debug("Read {} bytes from {}", read, endPoint); if (read > 0) { - parse(buffer); + if (!parse(buffer)) + return false; } else if (read == 0) { fillInterested(); - break; + return true; } else { shutdown(); - break; + return true; } } - } - catch (Exception x) - { - LOG.debug(x); - close(x); - } - finally - { - bufferPool.release(buffer); + catch (Exception x) + { + LOG.debug(x); + close(x); + return false; + } } } - private void parse(ByteBuffer buffer) + private boolean parse(ByteBuffer buffer) { - while (buffer.hasRemaining()) - parser.parse(buffer); + return !parser.parse(buffer); } private void shutdown() @@ -313,7 +333,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec } @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { switch (stream) { @@ -321,7 +341,25 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec { HttpChannelOverFCGI channel = channels.get(request); if (channel != null) - channel.content(buffer); + { + CompletableCallback callback = new CompletableCallback() + { + @Override + public void resume() + { + LOG.debug("Content consumed asynchronously, resuming processing"); + process(); + } + + @Override + public void abort(Throwable x) + { + close(x); + } + }; + channel.content(buffer, callback); + return callback.tryComplete(); + } else noChannel(request); break; @@ -336,6 +374,7 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec throw new IllegalArgumentException(); } } + return false; } @Override diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java index 09c0755c10c..50911bc0d7e 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/BeginRequestContentParser.java @@ -37,7 +37,7 @@ public class BeginRequestContentParser extends ContentParser } @Override - public boolean parse(ByteBuffer buffer) + public Result parse(ByteBuffer buffer) { while (buffer.hasRemaining()) { @@ -78,7 +78,7 @@ public class BeginRequestContentParser extends ContentParser buffer.position(buffer.position() + 5); onStart(); reset(); - return true; + return Result.COMPLETE; } else { @@ -94,7 +94,7 @@ public class BeginRequestContentParser extends ContentParser { onStart(); reset(); - return true; + return Result.COMPLETE; } break; } @@ -104,7 +104,7 @@ public class BeginRequestContentParser extends ContentParser } } } - return false; + return Result.PENDING; } private void onStart() diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java index d7a7dc6604d..88a5e7a2e72 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ClientParser.java @@ -86,9 +86,9 @@ public class ClientParser extends Parser } @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { - listener.onContent(request, stream, buffer); + return listener.onContent(request, stream, buffer); } @Override diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java index 500396ae9ef..55bf6d2c30d 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ContentParser.java @@ -29,7 +29,7 @@ public abstract class ContentParser this.headerParser = headerParser; } - public abstract boolean parse(ByteBuffer buffer); + public abstract Result parse(ByteBuffer buffer); public void noContent() { @@ -45,4 +45,9 @@ public abstract class ContentParser { return headerParser.getContentLength(); } + + public enum Result + { + PENDING, ASYNC, COMPLETE + } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java index 1f77eaf0ea6..b2198ad59d6 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/EndRequestContentParser.java @@ -35,7 +35,7 @@ public class EndRequestContentParser extends ContentParser } @Override - public boolean parse(ByteBuffer buffer) + public Result parse(ByteBuffer buffer) { while (buffer.hasRemaining()) { @@ -76,7 +76,7 @@ public class EndRequestContentParser extends ContentParser buffer.position(buffer.position() + 3); onEnd(); reset(); - return true; + return Result.COMPLETE; } else { @@ -92,7 +92,7 @@ public class EndRequestContentParser extends ContentParser { onEnd(); reset(); - return true; + return Result.COMPLETE; } break; } @@ -102,7 +102,7 @@ public class EndRequestContentParser extends ContentParser } } } - return false; + return Result.PENDING; } private void onEnd() diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java index 496275a49fc..85c34d0c3eb 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/HeaderParser.java @@ -32,6 +32,12 @@ public class HeaderParser private int length; private int padding; + /** + * Parses the bytes in the given {@code buffer} as FastCGI header bytes + * + * @param buffer the bytes to parse + * @return whether there were enough bytes for a FastCGI header + */ public boolean parse(ByteBuffer buffer) { while (buffer.hasRemaining()) diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java index 7f2805a2b27..42cbee811da 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ParamsContentParser.java @@ -45,7 +45,7 @@ public class ParamsContentParser extends ContentParser } @Override - public boolean parse(ByteBuffer buffer) + public Result parse(ByteBuffer buffer) { while (buffer.hasRemaining() || state == State.PARAM) { @@ -185,7 +185,7 @@ public class ParamsContentParser extends ContentParser if (length == 0) { reset(); - return true; + return Result.COMPLETE; } break; } @@ -195,7 +195,7 @@ public class ParamsContentParser extends ContentParser } } } - return false; + return Result.PENDING; } @Override diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java index 5739ef32012..45a348f1396 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/Parser.java @@ -29,7 +29,7 @@ public abstract class Parser private State state = State.HEADER; private int padding; - public void parse(ByteBuffer buffer) + public boolean parse(ByteBuffer buffer) { while (true) { @@ -38,7 +38,7 @@ public abstract class Parser case HEADER: { if (!headerParser.parse(buffer)) - return; + return false; state = State.CONTENT; break; } @@ -51,8 +51,11 @@ public abstract class Parser } else { - if (!contentParser.parse(buffer)) - return; + ContentParser.Result result = contentParser.parse(buffer); + if (result == ContentParser.Result.PENDING) + return false; + else if (result == ContentParser.Result.ASYNC) + return true; } padding = headerParser.getPaddingLength(); state = State.PADDING; @@ -70,7 +73,7 @@ public abstract class Parser { padding -= buffer.remaining(); buffer.position(buffer.limit()); - return; + return false; } } default: @@ -96,7 +99,7 @@ public abstract class Parser public void onHeaders(int request); - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer); + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer); public void onEnd(int request); @@ -115,8 +118,9 @@ public abstract class Parser } @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { + return false; } @Override diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java index b130c1374ee..26983fe0e7c 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/ResponseContentParser.java @@ -52,7 +52,7 @@ public class ResponseContentParser extends StreamContentParser } @Override - protected void onContent(ByteBuffer buffer) + protected boolean onContent(ByteBuffer buffer) { int request = getRequest(); ResponseParser parser = parsers.get(request); @@ -61,7 +61,7 @@ public class ResponseContentParser extends StreamContentParser parser = new ResponseParser(listener, request); parsers.put(request, parser); } - parser.parse(buffer); + return parser.parse(buffer); } @Override @@ -87,7 +87,7 @@ public class ResponseContentParser extends StreamContentParser this.httpParser = new FCGIHttpParser(this); } - public void parse(ByteBuffer buffer) + public boolean parse(ByteBuffer buffer) { LOG.debug("Response {} {} content {} {}", request, FCGI.StreamType.STD_OUT, state, buffer); @@ -117,7 +117,8 @@ public class ResponseContentParser extends StreamContentParser } case RAW_CONTENT: { - notifyContent(buffer); + if (notifyContent(buffer)) + return true; remaining = 0; break; } @@ -133,6 +134,7 @@ public class ResponseContentParser extends StreamContentParser } } } + return false; } @Override @@ -253,15 +255,16 @@ public class ResponseContentParser extends StreamContentParser return false; } - private void notifyContent(ByteBuffer buffer) + private boolean notifyContent(ByteBuffer buffer) { try { - listener.onContent(request, FCGI.StreamType.STD_OUT, buffer); + return listener.onContent(request, FCGI.StreamType.STD_OUT, buffer); } catch (Throwable x) { logger.debug("Exception while invoking listener " + listener, x); + return false; } } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java index b59593dd25a..655a4d24bf6 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/parser/StreamContentParser.java @@ -41,7 +41,7 @@ public class StreamContentParser extends ContentParser } @Override - public boolean parse(ByteBuffer buffer) + public Result parse(ByteBuffer buffer) { while (buffer.hasRemaining()) { @@ -59,14 +59,15 @@ public class StreamContentParser extends ContentParser int limit = buffer.limit(); buffer.limit(buffer.position() + length); ByteBuffer slice = buffer.slice(); - onContent(slice); buffer.position(buffer.limit()); buffer.limit(limit); contentLength -= length; + if (onContent(slice)) + return Result.ASYNC; if (contentLength > 0) break; state = State.LENGTH; - return true; + return Result.COMPLETE; } default: { @@ -74,7 +75,7 @@ public class StreamContentParser extends ContentParser } } } - return false; + return Result.PENDING; } @Override @@ -90,15 +91,16 @@ public class StreamContentParser extends ContentParser } } - protected void onContent(ByteBuffer buffer) + protected boolean onContent(ByteBuffer buffer) { try { - listener.onContent(getRequest(), streamType, buffer); + return listener.onContent(getRequest(), streamType, buffer); } catch (Throwable x) { logger.debug("Exception while invoking listener " + listener, x); + return false; } } diff --git a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java index 5756c01e512..c1307599f73 100644 --- a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java +++ b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/generator/ClientGeneratorTest.java @@ -158,10 +158,11 @@ public class ClientGeneratorTest ServerParser parser = new ServerParser(new ServerParser.Listener.Adapter() { @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); totalLength.addAndGet(buffer.remaining()); + return false; } @Override diff --git a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java index 826ed84b43e..fdd2acb2f8b 100644 --- a/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java +++ b/jetty-fcgi/fcgi-client/src/test/java/org/eclipse/jetty/fcgi/parser/ClientParserTest.java @@ -117,10 +117,11 @@ public class ClientParserTest ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); verifier.addAndGet(2); + return false; } @Override @@ -168,11 +169,12 @@ public class ClientParserTest ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); Assert.assertEquals(contentLength, buffer.remaining()); verifier.addAndGet(2); + return false; } @Override @@ -221,10 +223,11 @@ public class ClientParserTest ClientParser parser = new ClientParser(new ClientParser.Listener.Adapter() { @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { Assert.assertEquals(id, request); totalLength.addAndGet(buffer.remaining()); + return false; } @Override diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java index 2e8187966e0..1aa253a1fb1 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/ServerFCGIConnection.java @@ -151,7 +151,7 @@ public class ServerFCGIConnection extends AbstractConnection } @Override - public void onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) + public boolean onContent(int request, FCGI.StreamType stream, ByteBuffer buffer) { HttpChannelOverFCGI channel = channels.get(request); if (LOG.isDebugEnabled()) @@ -161,6 +161,7 @@ public class ServerFCGIConnection extends AbstractConnection if (channel.content(buffer)) channel.dispatch(); } + return false; } @Override diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java index f8926e96bb9..2daa22132c6 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java @@ -32,6 +32,7 @@ import org.eclipse.jetty.fcgi.FCGI; import org.eclipse.jetty.fcgi.client.http.HttpClientTransportOverFCGI; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.proxy.AsyncProxyServlet; import org.eclipse.jetty.proxy.ProxyServlet; /** @@ -57,7 +58,7 @@ import org.eclipse.jetty.proxy.ProxyServlet; * * @see TryFilesFilter */ -public class FastCGIProxyServlet extends ProxyServlet.Transparent +public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent { public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot"; public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern"; diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java index 2146f6305be..e97750f8b50 100644 --- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java @@ -30,6 +30,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; @@ -47,6 +48,7 @@ import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.toolchain.test.IO; import org.eclipse.jetty.toolchain.test.annotation.Slow; +import org.eclipse.jetty.util.Callback; import org.junit.Assert; import org.junit.Test; @@ -567,7 +569,7 @@ public class HttpClientTest extends AbstractHttpClientServerTest // Promise some content, then flush the headers, then fail to send the content. response.setContentLength(16); response.flushBuffer(); - throw new NullPointerException(); + throw new NullPointerException("Explicitly thrown by test"); } }); @@ -627,4 +629,75 @@ public class HttpClientTest extends AbstractHttpClientServerTest Assert.assertEquals(200, response.getStatus()); Assert.assertArrayEquals(data, response.getContent()); } + + @Test + public void testSmallAsyncContent() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + ServletOutputStream output = response.getOutputStream(); + output.write(65); + output.flush(); + output.write(66); + } + }); + + final AtomicInteger contentCount = new AtomicInteger(); + final AtomicReference callbackRef = new AtomicReference<>(); + final AtomicReference contentLatch = new AtomicReference<>(new CountDownLatch(1)); + final CountDownLatch completeLatch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .onResponseContentAsync(new Response.AsyncContentListener() + { + @Override + public void onContent(Response response, ByteBuffer content, Callback callback) + { + contentCount.incrementAndGet(); + callbackRef.set(callback); + contentLatch.get().countDown(); + } + }) + .send(new Response.CompleteListener() + { + @Override + public void onComplete(Result result) + { + completeLatch.countDown(); + } + }); + + Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS)); + Callback callback = callbackRef.get(); + + // Wait a while to be sure that the parsing does not proceed. + TimeUnit.MILLISECONDS.sleep(1000); + + Assert.assertEquals(1, contentCount.get()); + + // Succeed the content callback to proceed with parsing. + callbackRef.set(null); + contentLatch.set(new CountDownLatch(1)); + callback.succeeded(); + + Assert.assertTrue(contentLatch.get().await(5, TimeUnit.SECONDS)); + callback = callbackRef.get(); + + // Wait a while to be sure that the parsing does not proceed. + TimeUnit.MILLISECONDS.sleep(1000); + + Assert.assertEquals(2, contentCount.get()); + Assert.assertEquals(1, completeLatch.getCount()); + + // Succeed the content callback to proceed with parsing. + callbackRef.set(null); + contentLatch.set(new CountDownLatch(1)); + callback.succeeded(); + + Assert.assertTrue(completeLatch.await(555, TimeUnit.SECONDS)); + Assert.assertEquals(2, contentCount.get()); + } } diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java index a1a9eedc091..7e22db075e1 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java @@ -19,9 +19,12 @@ package org.eclipse.jetty.proxy; import java.io.IOException; +import java.net.URI; import java.nio.ByteBuffer; import java.nio.channels.WritePendingException; import javax.servlet.ReadListener; +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; @@ -79,6 +82,24 @@ public class AsyncProxyServlet extends ProxyServlet onResponseFailure(request, response, proxyResponse, x); } } + + public static class Transparent extends AsyncProxyServlet + { + private final TransparentDelegate delegate = new TransparentDelegate(this); + + @Override + public void init(ServletConfig config) throws ServletException + { + super.init(config); + delegate.init(config); + } + + @Override + protected URI rewriteURI(HttpServletRequest request) + { + return delegate.rewriteURI(request); + } + } private class StreamReader implements ReadListener, Callback { 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 e37bd297064..7f1fe069f96 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 @@ -646,26 +646,35 @@ public class ProxyServlet extends HttpServlet */ public static class Transparent extends ProxyServlet { - private String _proxyTo; - private String _prefix; + private final TransparentDelegate delegate = new TransparentDelegate(this); - public Transparent() + @Override + public void init(ServletConfig config) throws ServletException { - } - - public Transparent(String proxyTo, String prefix) - { - _proxyTo = URI.create(proxyTo).normalize().toString(); - _prefix = URI.create(prefix).normalize().toString(); + super.init(config); + delegate.init(config); } @Override - public void init() throws ServletException + protected URI rewriteURI(HttpServletRequest request) { - super.init(); + return delegate.rewriteURI(request); + } + } - ServletConfig config = getServletConfig(); + protected static class TransparentDelegate + { + private final ProxyServlet proxyServlet; + private String _proxyTo; + private String _prefix; + protected TransparentDelegate(ProxyServlet proxyServlet) + { + this.proxyServlet = proxyServlet; + } + + protected void init(ServletConfig config) throws ServletException + { String proxyTo = config.getInitParameter("proxyTo"); _proxyTo = proxyTo == null ? _proxyTo : proxyTo; @@ -681,13 +690,12 @@ public class ProxyServlet extends HttpServlet } // Adjust prefix value to account for context path - String contextPath = getServletContext().getContextPath(); + String contextPath = config.getServletContext().getContextPath(); _prefix = _prefix == null ? contextPath : (contextPath + _prefix); - _log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo); + proxyServlet._log.debug(config.getServletName() + " @ " + _prefix + " to " + _proxyTo); } - @Override protected URI rewriteURI(HttpServletRequest request) { String path = request.getRequestURI(); @@ -706,7 +714,7 @@ public class ProxyServlet extends HttpServlet uri.append("?").append(query); URI rewrittenURI = URI.create(uri.toString()).normalize(); - if (!validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort())) + if (!proxyServlet.validateDestination(rewrittenURI.getHost(), rewrittenURI.getPort())) return null; return rewrittenURI; diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java index 30c8b382eb1..c599fd61228 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java @@ -593,8 +593,11 @@ public class ProxyServletTest }); String proxyTo = "http://localhost:" + serverConnector.getLocalPort(); - proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix); - prepareProxy(); + proxyServlet = new ProxyServlet.Transparent(); + Map params = new HashMap<>(); + params.put("proxyTo", proxyTo); + params.put("prefix", prefix); + prepareProxy(params); // Make the request to the proxy, it should transparently forward to the server ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()) @@ -632,8 +635,11 @@ public class ProxyServletTest String proxyTo = "http://localhost:" + serverConnector.getLocalPort(); String prefix = "/proxy"; - proxyServlet = new ProxyServlet.Transparent(proxyTo, prefix); - prepareProxy(); + proxyServlet = new ProxyServlet.Transparent(); + Map params = new HashMap<>(); + params.put("proxyTo", proxyTo); + params.put("prefix", prefix); + prepareProxy(params); // Make the request to the proxy, it should transparently forward to the server ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()) From c6f394ab1441b201a7011f96672fd75d93f10f63 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 5 May 2014 10:00:06 +0200 Subject: [PATCH 106/135] 434009 Improved javadoc for accessing HttpChannel and HttpConnection --- .../main/java/org/eclipse/jetty/server/HttpChannel.java | 5 +++++ .../main/java/org/eclipse/jetty/server/HttpConnection.java | 7 +++++++ 2 files changed, 12 insertions(+) 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 cd4a169398f..07b09ab7241 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 @@ -73,6 +73,11 @@ public class HttpChannel implements HttpParser.RequestHandler, Runnable, H private static final Logger LOG = Log.getLogger(HttpChannel.class); private static final ThreadLocal> __currentChannel = new ThreadLocal<>(); + /* ------------------------------------------------------------ */ + /** Get the current channel that this thread is dispatched to. + * @see Request#getAttribute(String) for a more general way to access the HttpChannel + * @return the current HttpChannel or null + */ public static HttpChannel getCurrentHttpChannel() { return __currentChannel.get(); 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 0e7e0b9d2fd..10c9a1dd4de 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 @@ -62,6 +62,13 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http private volatile ByteBuffer _chunk = null; + /* ------------------------------------------------------------ */ + /** Get the current connection that this thread is dispatched to. + * Note that a thread may be processing a request asynchronously and + * thus not be dispatched to the connection. + * @see Request#getAttribute(String) for a more general way to access the HttpConnection + * @return the current HttpConnection or null + */ public static HttpConnection getCurrentConnection() { return __currentConnection.get(); From 51c5a4c833f1ada8629de3daa0a393bd80c13829 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 5 May 2014 14:53:53 +0200 Subject: [PATCH 107/135] 425421 ContainerLifeCycle does not start added beans in started state --- .../org/eclipse/jetty/client/HttpClient.java | 2 +- .../server/handler/HandlerCollection.java | 4 +- .../jetty/server/handler/HandlerWrapper.java | 3 +- .../eclipse/jetty/servlet/ServletHandler.java | 1 - .../util/component/ContainerLifeCycle.java | 52 ++++++++++++++++--- .../jetty/util/thread/QueuedThreadPool.java | 2 + .../component/ContainerLifeCycleTest.java | 30 +++++++---- .../jsr356/server/BasicEndpointTest.java | 1 - .../server/ExtensionStackProcessingTest.java | 4 +- .../websocket/jsr356/server/WSServer.java | 2 + ...asicEchoEndpointConfigContextListener.java | 3 ++ .../websocket/client/WebSocketClient.java | 20 ++----- .../client/io/UpgradeConnection.java | 2 +- 13 files changed, 84 insertions(+), 42 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 5649b2992ee..8e323bc734a 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -201,8 +201,8 @@ public class HttpClient extends ContainerLifeCycle scheduler = new ScheduledExecutorScheduler(name + "-scheduler", false); addBean(scheduler); - addBean(transport); transport.setHttpClient(this); + addBean(transport); resolver = new SocketAddressResolver(executor, scheduler, getAddressResolutionTimeout()); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java index aacfaf39f2e..c118c7ec1da 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java @@ -85,9 +85,9 @@ public class HandlerCollection extends AbstractHandlerContainer for (Handler handler:handlers) if (handler.getServer()!=getServer()) handler.setServer(getServer()); - - updateBeans(_handlers, handlers); + Handler[] old=_handlers;; _handlers = handlers; + updateBeans(old, handlers); } /* ------------------------------------------------------------ */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java index e91c824421a..dcbf6ff5d54 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java @@ -84,8 +84,9 @@ public class HandlerWrapper extends AbstractHandlerContainer if (handler!=null) handler.setServer(getServer()); - updateBean(_handler,handler); + Handler old=_handler; _handler=handler; + updateBean(old,_handler); } /* ------------------------------------------------------------ */ 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 6f0e5b170fb..f19bcd9eb0b 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 @@ -814,7 +814,6 @@ public class ServletHandler extends ScopedHandler /* ------------------------------------------------------------ */ /** Initialize filters and load-on-startup servlets. - * Called automatically from start if autoInitializeServlet is true. */ public void initialize() throws Exception diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java index 3fe7c448929..464c0f7e4a8 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java @@ -38,8 +38,11 @@ import org.eclipse.jetty.util.log.Logger; *

    * When a {@link LifeCycle} bean is added without a managed state being specified the state is determined heuristically: *

      - *
    • If when added, a bean is already started, then it is assumed to be an unmanaged bean. - *
    • If when added, a bean is not started, then it is added in Auto state. + *
    • If the added bean is running, it will be added as an unmanaged bean. + *
    • If the added bean is !running and the container is !running, it will be added as an AUTO bean (see below). + *
    • If the added bean is !running and the container is starting, it will be added as an managed bean and will be started (this handles the frequent case of + * new beans added during calls to doStart). + *
    • If the added bean is !running and the container is started, it will be added as an unmanaged bean. *
    * When the container is started, then all contained managed beans will also be started. Any contained Auto beans * will be check for their status and if already started will be switched unmanaged beans, else they will be @@ -64,13 +67,17 @@ import org.eclipse.jetty.util.log.Logger; * +? referenced AUTO object that could become MANAGED or UNMANAGED. * */ + +/* ------------------------------------------------------------ */ +/** + */ @ManagedObject("Implementation of Container and LifeCycle") public class ContainerLifeCycle extends AbstractLifeCycle implements Container, Destroyable, Dumpable { private static final Logger LOG = Log.getLogger(ContainerLifeCycle.class); private final List _beans = new CopyOnWriteArrayList<>(); private final List _listeners = new CopyOnWriteArrayList<>(); - private boolean _started = false; + private boolean _doStarted = false; public ContainerLifeCycle() @@ -84,7 +91,7 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container, protected void doStart() throws Exception { // indicate that we are started, so that addBean will start other beans added. - _started = true; + _doStarted = true; // start our managed and auto beans for (Bean b : _beans) @@ -142,7 +149,7 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container, @Override protected void doStop() throws Exception { - _started = false; + _doStarted = false; super.doStop(); List reverse = new ArrayList<>(_beans); Collections.reverse(reverse); @@ -267,7 +274,7 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container, case MANAGED: manage(new_bean); - if (_started) + if (isStarting() && _doStarted) { LifeCycle l = (LifeCycle)o; if (!l.isRunning()) @@ -279,16 +286,20 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container, if (o instanceof LifeCycle) { LifeCycle l = (LifeCycle)o; - if (_started) + if (isStarting()) { if (l.isRunning()) unmanage(new_bean); - else + else if (_doStarted) { manage(new_bean); start(l); } + else + new_bean._managed=Managed.AUTO; } + else if (isStarted()) + unmanage(new_bean); else new_bean._managed=Managed.AUTO; } @@ -315,6 +326,31 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container, } + /* ------------------------------------------------------------ */ + /** Add a managed lifecycle. + *

    This is a conveniance method that uses addBean(lifecycle,true) + * and then ensures that the added bean is started iff this container + * is running. Exception from nested calls to start are caught and + * wrapped as RuntimeExceptions + * @param lifecycle + */ + public void addManaged(LifeCycle lifecycle) + { + addBean(lifecycle,true); + try + { + if (isRunning() && !lifecycle.isRunning()) + start(lifecycle); + } + catch (RuntimeException | Error e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } @Override public void addEventListener(Container.Listener listener) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java index 1918ad15c73..5cc75121c0b 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java @@ -661,4 +661,6 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo } return null; } + + } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/component/ContainerLifeCycleTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/component/ContainerLifeCycleTest.java index 0eb37a4fd13..ddc152be381 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/component/ContainerLifeCycleTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/component/ContainerLifeCycleTest.java @@ -108,7 +108,16 @@ public class ContainerLifeCycleTest Assert.assertEquals(1,destroyed.get()); a0.addBean(a1); + Assert.assertEquals(2,started.get()); + Assert.assertEquals(2,stopped.get()); + Assert.assertEquals(1,destroyed.get()); + Assert.assertFalse(a0.isManaged(a1)); a0.start(); + Assert.assertEquals(2,started.get()); + Assert.assertEquals(2,stopped.get()); + Assert.assertEquals(1,destroyed.get()); + a1.start(); + a0.manage(a1); Assert.assertEquals(3,started.get()); Assert.assertEquals(2,stopped.get()); Assert.assertEquals(1,destroyed.get()); @@ -285,16 +294,16 @@ public class ContainerLifeCycleTest dump=trim(a0.dump()); dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); - dump=check(dump," | += org.eclipse.jetty.util.component.Container"); + dump=check(dump," | +~ org.eclipse.jetty.util.component.Container"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); dump=check(dump,""); ContainerLifeCycle aa10 = new ContainerLifeCycle(); - aa1.addBean(aa10); + aa1.addBean(aa10,true); dump=trim(a0.dump()); dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); - dump=check(dump," | += org.eclipse.jetty.util.component.Container"); + dump=check(dump," | +~ org.eclipse.jetty.util.component.Container"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); dump=check(dump," += org.eclipse.jetty.util.component.Container"); dump=check(dump,""); @@ -314,11 +323,11 @@ public class ContainerLifeCycleTest dump(out,indent,TypeUtil.asList(new Object[]{a1,a2}),TypeUtil.asList(new Object[]{a3,a4})); } }; - a0.addBean(aa); + a0.addBean(aa,true); dump=trim(a0.dump()); dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); - dump=check(dump," | += org.eclipse.jetty.util.component.Container"); + dump=check(dump," | +~ org.eclipse.jetty.util.component.Container"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); dump=check(dump," | += org.eclipse.jetty.util.component.Container"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); @@ -332,14 +341,14 @@ public class ContainerLifeCycleTest dump=trim(a0.dump()); dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); - dump=check(dump," | += org.eclipse.jetty.util.component.Container"); + dump=check(dump," | +~ org.eclipse.jetty.util.component.Container"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); dump=check(dump," | += org.eclipse.jetty.util.component.Container"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); dump=check(dump," +- org.eclipse.jetty.util.component.Container"); dump=check(dump," +- org.eclipse.jetty.util.component.Container"); dump=check(dump," | += org.eclipse.jetty.util.component.Conta"); - dump=check(dump," | += org.eclipse.jetty.util.component.C"); + dump=check(dump," | +~ org.eclipse.jetty.util.component.C"); dump=check(dump," +- org.eclipse.jetty.util.component.Container"); dump=check(dump," +- org.eclipse.jetty.util.component.Container"); dump=check(dump,""); @@ -348,7 +357,7 @@ public class ContainerLifeCycleTest dump=trim(a0.dump()); dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); - dump=check(dump," | += org.eclipse.jetty.util.component.Container"); + dump=check(dump," | +~ org.eclipse.jetty.util.component.Container"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); dump=check(dump," | += org.eclipse.jetty.util.component.Container"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); @@ -363,7 +372,7 @@ public class ContainerLifeCycleTest dump=trim(a0.dump()); dump=check(dump,"org.eclipse.jetty.util.component.ContainerLifeCycl"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); - dump=check(dump," | += org.eclipse.jetty.util.component.Container"); + dump=check(dump," | +~ org.eclipse.jetty.util.component.Container"); dump=check(dump," += org.eclipse.jetty.util.component.ContainerLife"); dump=check(dump," | += org.eclipse.jetty.util.component.Container"); dump=check(dump," +~ org.eclipse.jetty.util.component.ContainerLife"); @@ -523,7 +532,10 @@ public class ContainerLifeCycleTest c0.addBean(c00); c0.start(); c0.addBean(inherited); + c0.manage(inherited); c0.addBean(c01); + c01.start(); + c0.manage(c01); Assert.assertTrue(c0.isManaged(inherited)); Assert.assertFalse(c00.isManaged(inherited)); diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicEndpointTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicEndpointTest.java index 03359878e97..10ac6e56abd 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicEndpointTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/BasicEndpointTest.java @@ -64,7 +64,6 @@ public class BasicEndpointTest WebAppContext webapp = wsb.createWebAppContext(); wsb.deployWebapp(webapp); - // wsb.dump(); WebSocketClient client = new WebSocketClient(bufferPool); try diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java index d2fbcd7c853..82f094e74af 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java @@ -68,10 +68,10 @@ public class ExtensionStackProcessingTest ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class, "/").build(); container.addEndpoint(config); - server.start(); - client = ContainerProvider.getWebSocketContainer(); server.addBean(client, true); + + server.start(); } @After diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/WSServer.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/WSServer.java index 00813197b58..9297c7f3d2f 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/WSServer.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/WSServer.java @@ -110,6 +110,7 @@ public class WSServer WebAppContext context = new WebAppContext(); context.setContextPath(this.contextPath); context.setBaseResource(Resource.newResource(this.contextDir)); + context.setAttribute("org.eclipse.jetty.websocket.jsr356",Boolean.TRUE); // @formatter:off context.setConfigurations(new Configuration[] { @@ -133,6 +134,7 @@ public class WSServer public void deployWebapp(WebAppContext webapp) throws Exception { contexts.addHandler(webapp); + contexts.manage(webapp); webapp.start(); if (LOG.isDebugEnabled()) { diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointConfigContextListener.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointConfigContextListener.java index 05b239f4b88..e224de8ab1f 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointConfigContextListener.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/samples/echo/BasicEchoEndpointConfigContextListener.java @@ -39,6 +39,9 @@ public class BasicEchoEndpointConfigContextListener implements ServletContextLis public void contextInitialized(ServletContextEvent sce) { ServerContainer container = (ServerContainer)sce.getServletContext().getAttribute(ServerContainer.class.getName()); + if (container==null) + throw new IllegalStateException("No Websocket ServerContainer in "+sce.getServletContext()); + // Build up a configuration with a specific path String path = "/echo"; ServerEndpointConfig.Builder builder = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class,path); diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java index 387874a52ec..8ab24ec9063 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java @@ -36,6 +36,7 @@ import org.eclipse.jetty.io.SelectorManager; import org.eclipse.jetty.util.HttpCookieStore; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.component.ContainerLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -430,30 +431,17 @@ public class WebSocketClient extends ContainerLifeCycle implements SessionListen threadPool.setName(name); threadPool.setDaemon(daemon); executor = threadPool; - addBean(executor,true); + addManaged(threadPool); } else { addBean(executor,false); } - if (connectionManager != null) - { - return; - } - try + if (connectionManager == null) { connectionManager = newConnectionManager(); - addBean(connectionManager); - connectionManager.start(); - } - catch (IOException e) - { - throw e; - } - catch (Exception e) - { - throw new IOException(e); + addManaged(connectionManager); } } diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java index 9bc359b1daf..fe385d3756e 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/io/UpgradeConnection.java @@ -260,7 +260,7 @@ public class UpgradeConnection extends AbstractConnection extensionStack.setNextOutgoing(connection); session.addBean(extensionStack); - connectPromise.getClient().addBean(session); + connectPromise.getClient().addManaged(session); // Now swap out the connection endp.setConnection(connection); From dea8d684a61a5b54605259c350abd3e9e4dfdb2c Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 5 May 2014 15:12:45 +0200 Subject: [PATCH 108/135] 434077 Avoid close race in websocket echo test --- .../jsr356/server/JettyEchoSocket.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java index c7bb1e03a37..8383bb17694 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java @@ -18,6 +18,7 @@ package org.eclipse.jetty.websocket.jsr356.server; +import java.io.EOFException; import java.io.IOException; import java.util.Queue; import java.util.concurrent.TimeUnit; @@ -46,8 +47,8 @@ public class JettyEchoSocket private static final Logger LOG = Log.getLogger(JettyEchoSocket.class); @SuppressWarnings("unused") private Session session; - private RemoteEndpoint remote; - private Boolean closed = null; + private volatile RemoteEndpoint remote; + private volatile Boolean closed = null; private EventQueue incomingMessages = new EventQueue<>(); public Queue awaitMessages(int expected) throws TimeoutException, InterruptedException @@ -92,8 +93,14 @@ public class JettyEchoSocket public void sendMessage(String msg) throws IOException { - remote.sendStringByFuture(msg); - if (remote.getBatchMode() == BatchMode.ON) - remote.flush(); + RemoteEndpoint r = remote; + // TODO there is a race with onClose here and no sufficient memory barrier. Taking a local copy + // stops the worst of the errors, but is probably not the best solution. + if (r==null) + throw new EOFException("Closed="+closed); + + r.sendStringByFuture(msg); + if (r.getBatchMode() == BatchMode.ON) + r.flush(); } } From 0e594e55c38b4c5c8474e15ab36e12fd8cd3531d Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 5 May 2014 15:35:11 +0200 Subject: [PATCH 109/135] Dispatching execution of blocking code to a different thread to avoid to block the selector thread. --- .../eclipse/jetty/proxy/ConnectHandler.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java index 49dd797c26b..5a550145976 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/ConnectHandler.java @@ -28,7 +28,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executor; - import javax.servlet.AsyncContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -432,8 +431,7 @@ public class ConnectHandler extends HandlerWrapper protected class ConnectManager extends SelectorManager { - - private ConnectManager(Executor executor, Scheduler scheduler, int selectors) + protected ConnectManager(Executor executor, Scheduler scheduler, int selectors) { super(executor, scheduler, selectors); } @@ -455,10 +453,16 @@ public class ConnectHandler extends HandlerWrapper } @Override - protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment) + protected void connectionFailed(SocketChannel channel, final Throwable ex, final Object attachment) { - ConnectContext connectContext = (ConnectContext)attachment; - onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex); + getExecutor().execute(new Runnable() + { + public void run() + { + ConnectContext connectContext = (ConnectContext)attachment; + onConnectFailure(connectContext.request, connectContext.response, connectContext.asyncContext, ex); + } + }); } } @@ -518,8 +522,14 @@ public class ConnectHandler extends HandlerWrapper public void onOpen() { super.onOpen(); - onConnectSuccess(connectContext, this); - fillInterested(); + getExecutor().execute(new Runnable() + { + public void run() + { + onConnectSuccess(connectContext, UpstreamConnection.this); + fillInterested(); + } + }); } @Override From 943fb64176ac5efffc5ac6f701add5f429ffb179 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Mon, 5 May 2014 15:45:00 +0200 Subject: [PATCH 110/135] 433841 Resource.newResource() declares an exception it does not throw --- .../jetty/server/handler/ResourceHandler.java | 16 ++++------------ .../eclipse/jetty/util/resource/Resource.java | 12 ++++++------ 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java index f59dcd5ac64..10d1e051301 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java @@ -248,15 +248,7 @@ public class ResourceHandler extends HandlerWrapper { if(_defaultStylesheet == null) { - try - { - _defaultStylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css")); - } - catch(IOException e) - { - LOG.warn(e.toString()); - LOG.debug(e); - } + _defaultStylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css")); } return _defaultStylesheet; } @@ -279,9 +271,9 @@ public class ResourceHandler extends HandlerWrapper } catch(Exception e) { - LOG.warn(e.toString()); - LOG.debug(e); - throw new IllegalArgumentException(stylesheet); + LOG.warn(e.toString()); + LOG.debug(e); + throw new IllegalArgumentException(stylesheet); } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java index 623731a33e6..b36a9355823 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java @@ -78,10 +78,10 @@ public abstract class Resource implements ResourceFactory, Closeable /** Construct a resource from a uri. * @param uri A URI. * @return A Resource object. - * @throws IOException Problem accessing URI + * @throws MalformedURLException Problem accessing URI */ public static Resource newResource(URI uri) - throws IOException + throws MalformedURLException { return newResource(uri.toURL()); } @@ -90,10 +90,8 @@ public abstract class Resource implements ResourceFactory, Closeable /** Construct a resource from a url. * @param url A URL. * @return A Resource object. - * @throws IOException Problem accessing URL */ public static Resource newResource(URL url) - throws IOException { return newResource(url, __defaultUseCaches); } @@ -142,10 +140,11 @@ public abstract class Resource implements ResourceFactory, Closeable /* ------------------------------------------------------------ */ /** Construct a resource from a string. * @param resource A URL or filename. + * @throws MalformedURLException Problem accessing URI * @return A Resource object. */ public static Resource newResource(String resource) - throws MalformedURLException, IOException + throws MalformedURLException { return newResource(resource, __defaultUseCaches); } @@ -155,9 +154,10 @@ public abstract class Resource implements ResourceFactory, Closeable * @param resource A URL or filename. * @param useCaches controls URLConnection caching * @return A Resource object. + * @throws MalformedURLException Problem accessing URI */ public static Resource newResource(String resource, boolean useCaches) - throws MalformedURLException, IOException + throws MalformedURLException { URL url=null; try From a9134a9b94b3ca9a8d600862f2a08e4076956cea Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 5 May 2014 16:18:10 +0200 Subject: [PATCH 111/135] Made handling of client error more robust, avoiding IllegalStateExceptions when trying to complete the response. --- .../jetty/proxy/AsyncProxyServlet.java | 8 +++-- .../org/eclipse/jetty/proxy/ProxyServlet.java | 35 +++++++------------ 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java index 7e22db075e1..ed2477107cf 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AsyncProxyServlet.java @@ -46,7 +46,7 @@ public class AsyncProxyServlet extends ProxyServlet { ServletInputStream input = request.getInputStream(); DeferredContentProvider provider = new DeferredContentProvider(); - input.setReadListener(new StreamReader(request, provider)); + input.setReadListener(new StreamReader(proxyRequest, request, provider)); return provider; } @@ -104,11 +104,13 @@ public class AsyncProxyServlet extends ProxyServlet private class StreamReader implements ReadListener, Callback { private final byte[] buffer = new byte[getHttpClient().getRequestBufferSize()]; + private final Request proxyRequest; private final HttpServletRequest request; private final DeferredContentProvider provider; - public StreamReader(HttpServletRequest request, DeferredContentProvider provider) + public StreamReader(Request proxyRequest, HttpServletRequest request, DeferredContentProvider provider) { + this.proxyRequest = proxyRequest; this.request = request; this.provider = provider; } @@ -168,7 +170,7 @@ public class AsyncProxyServlet extends ProxyServlet @Override public void failed(Throwable x) { - onClientRequestFailure(request, x); + onClientRequestFailure(proxyRequest, request, x); } } 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 7f1fe069f96..cdab7289c7b 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 @@ -480,7 +480,7 @@ public class ProxyServlet extends HttpServlet proxyRequest.send(new ProxyResponseListener(request, response)); } - protected ContentProvider proxyRequestContent(Request proxyRequest, final HttpServletRequest request) throws IOException + protected ContentProvider proxyRequestContent(final Request proxyRequest, final HttpServletRequest request) throws IOException { return new InputStreamContentProvider(request.getInputStream()) { @@ -500,18 +500,15 @@ public class ProxyServlet extends HttpServlet @Override protected void onReadFailure(Throwable failure) { - onClientRequestFailure(request, failure); + onClientRequestFailure(proxyRequest, request, failure); } }; } - protected void onClientRequestFailure(HttpServletRequest request, Throwable failure) + protected void onClientRequestFailure(Request proxyRequest, HttpServletRequest request, Throwable failure) { - AsyncContext asyncContext = request.getAsyncContext(); - HttpServletResponse response = (HttpServletResponse)asyncContext.getResponse(); - response.setStatus(500); - response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString()); - asyncContext.complete(); + _log.debug(getRequestId(request) + " client request failure", failure); + proxyRequest.abort(failure); } protected void onRewriteFailed(HttpServletRequest request, HttpServletResponse response) throws IOException @@ -569,9 +566,9 @@ public class ProxyServlet extends HttpServlet protected void onResponseSuccess(HttpServletRequest request, HttpServletResponse response, Response proxyResponse) { + _log.debug("{} proxying successful", getRequestId(request)); AsyncContext asyncContext = request.getAsyncContext(); asyncContext.complete(); - _log.debug("{} proxying successful", getRequestId(request)); } protected void onResponseFailure(HttpServletRequest request, HttpServletResponse response, Response proxyResponse, Throwable failure) @@ -584,9 +581,9 @@ public class ProxyServlet extends HttpServlet else response.setStatus(HttpServletResponse.SC_BAD_GATEWAY); response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString()); + AsyncContext asyncContext = request.getAsyncContext(); + asyncContext.complete(); } - AsyncContext asyncContext = request.getAsyncContext(); - asyncContext.complete(); } protected int getRequestId(HttpServletRequest request) @@ -806,21 +803,13 @@ public class ProxyServlet extends HttpServlet }); } - @Override - public void onSuccess(Response proxyResponse) - { - onResponseSuccess(request, response, proxyResponse); - } - - @Override - public void onFailure(Response proxyResponse, Throwable failure) - { - onResponseFailure(request, response, proxyResponse, failure); - } - @Override public void onComplete(Result result) { + if (result.isSucceeded()) + onResponseSuccess(request, response, result.getResponse()); + else + onResponseFailure(request, response, result.getResponse(), result.getFailure()); _log.debug("{} proxying complete", getRequestId(request)); } } From 894fb6bfc076e3bb86761ab1f327d34da1673609 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 5 May 2014 19:09:19 +0200 Subject: [PATCH 112/135] increment Version --- jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java index baeaf0a79c5..5a9b25705e1 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Jetty.java @@ -30,7 +30,7 @@ public class Jetty pkg.getImplementationVersion() != null) VERSION = pkg.getImplementationVersion(); else - VERSION = System.getProperty("jetty.version", "9.1.z-SNAPSHOT"); + VERSION = System.getProperty("jetty.version", "9.2.z-SNAPSHOT"); } private Jetty() From 0abda0f35a10929c1cad64349666e8f05c19893c Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Mon, 5 May 2014 19:49:58 +0200 Subject: [PATCH 113/135] 434074 Avoid double dispatch by returning false from messageComplete --- .../java/org/eclipse/jetty/server/HttpConnection.java | 8 ++++++++ .../jetty/server/SlowClientWithPipelinedRequestTest.java | 1 - .../server/handler/ContextHandlerGetResourceTest.java | 1 - .../jetty/spdy/server/http/HttpChannelOverSPDY.java | 7 +++++++ 4 files changed, 15 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 10c9a1dd4de..c56222fb462 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 @@ -478,6 +478,14 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http { getEndPoint().shutdownOutput(); } + + + @Override + public boolean messageComplete() + { + super.messageComplete(); + return false; + } } private class CommitCallback extends IteratingCallback diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java index b86badfa192..599c474d7d4 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/SlowClientWithPipelinedRequestTest.java @@ -93,7 +93,6 @@ public class SlowClientWithPipelinedRequestTest throws IOException, ServletException { baseRequest.setHandled(true); - System.err.println("target = " + target); if ("/content".equals(target)) { // We simulate what the DefaultServlet does, bypassing the blocking diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java index 5534dedb52a..3ba83f6ed9d 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ContextHandlerGetResourceTest.java @@ -73,7 +73,6 @@ public class ContextHandlerGetResourceTest other.createNewFile(); File transit = new File(docroot.getParentFile(),"transit"); - System.err.println("transit "+transit); transit.delete(); if (OS.IS_UNIX) diff --git a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpChannelOverSPDY.java b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpChannelOverSPDY.java index 575b940a93c..d032aa1c9e3 100644 --- a/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpChannelOverSPDY.java +++ b/jetty-spdy/spdy-http-server/src/main/java/org/eclipse/jetty/spdy/server/http/HttpChannelOverSPDY.java @@ -156,6 +156,13 @@ public class HttpChannelOverSPDY extends HttpChannel if (dispatch) dispatch(); } + + @Override + public boolean messageComplete() + { + super.messageComplete(); + return false; + } private boolean performBeginRequest(Fields headers) { From c03388b6400a7776d10e28313acff60b184f40ca Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 5 May 2014 09:28:03 -0700 Subject: [PATCH 114/135] 431459 - Jetty WebSocket compression extensions fails to handle big messages properly + Setting compression extensions to default unregistered state until they can be stablized. --- .../jsr356/server/ExtensionStackProcessingTest.java | 13 +++++++++++++ .../jetty/websocket/client/WebSocketClient.java | 6 ++++++ .../websocket/server/WebSocketServerFactory.java | 6 ++++++ .../server/PerMessageDeflateExtensionTest.java | 4 +++- 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java index 82f094e74af..da1a99efad6 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ExtensionStackProcessingTest.java @@ -22,6 +22,7 @@ import java.net.URI; import java.util.Arrays; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; + import javax.websocket.ClientEndpointConfig; import javax.websocket.ContainerProvider; import javax.websocket.Endpoint; @@ -37,6 +38,7 @@ import javax.websocket.server.ServerEndpointConfig; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory; import org.eclipse.jetty.websocket.api.extensions.OutgoingFrames; import org.eclipse.jetty.websocket.client.io.WebSocketClientConnection; import org.eclipse.jetty.websocket.common.extensions.ExtensionStack; @@ -45,8 +47,10 @@ import org.eclipse.jetty.websocket.jsr356.JsrExtension; import org.eclipse.jetty.websocket.jsr356.JsrSession; import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; import org.eclipse.jetty.websocket.jsr356.server.samples.echo.BasicEchoEndpoint; +import org.eclipse.jetty.websocket.server.WebSocketUpgradeFilter; import org.junit.After; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -54,6 +58,7 @@ public class ExtensionStackProcessingTest { private Server server; private ServerConnector connector; + private ExtensionFactory serverExtensionFactory; private WebSocketContainer client; @Before @@ -65,6 +70,10 @@ public class ExtensionStackProcessingTest ServletContextHandler context = new ServletContextHandler(server, "/", true, false); ServerContainer container = WebSocketServerContainerInitializer.configureContext(context); + + WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)context.getAttribute(WebSocketUpgradeFilter.class.getName()); + serverExtensionFactory = filter.getFactory().getExtensionFactory(); + ServerEndpointConfig config = ServerEndpointConfig.Builder.create(BasicEchoEndpoint.class, "/").build(); container.addEndpoint(config); @@ -83,6 +92,8 @@ public class ExtensionStackProcessingTest @Test public void testDeflateFrameExtension() throws Exception { + Assume.assumeTrue("Server has deflate-frame extension registered",serverExtensionFactory.isAvailable("deflate-frame")); + ClientEndpointConfig config = ClientEndpointConfig.Builder.create() .extensions(Arrays.asList(new JsrExtension("deflate-frame"))) .build(); @@ -129,6 +140,8 @@ public class ExtensionStackProcessingTest @Test public void testPerMessageDeflateExtension() throws Exception { + Assume.assumeTrue("Server has permessage-deflate extension registered",serverExtensionFactory.isAvailable("permessage-deflate")); + ClientEndpointConfig config = ClientEndpointConfig.Builder.create() .extensions(Arrays.asList(new JsrExtension("permessage-deflate"))) .build(); diff --git a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java index 8ab24ec9063..7ccb4f55c9e 100644 --- a/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java +++ b/jetty-websocket/websocket-client/src/main/java/org/eclipse/jetty/websocket/client/WebSocketClient.java @@ -116,6 +116,12 @@ public class WebSocketClient extends ContainerLifeCycle implements SessionListen this.policy = WebSocketPolicy.newClientPolicy(); this.bufferPool = bufferPool; this.extensionRegistry = new WebSocketExtensionFactory(policy,bufferPool); + + // Bug #431459 - unregistering compression extensions till they are more stable + this.extensionRegistry.unregister("deflate-frame"); + this.extensionRegistry.unregister("permessage-deflate"); + this.extensionRegistry.unregister("x-webkit-deflate-frame"); + this.masker = new RandomMasker(); this.eventDriverFactory = new EventDriverFactory(policy); this.sessionFactory = new WebSocketSessionFactory(this); diff --git a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java index f6a2f2c7bd8..b85806ef007 100644 --- a/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java +++ b/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/WebSocketServerFactory.java @@ -113,6 +113,12 @@ public class WebSocketServerFactory extends ContainerLifeCycle implements WebSoc this.eventDriverFactory = new EventDriverFactory(defaultPolicy); this.bufferPool = bufferPool; this.extensionFactory = new WebSocketExtensionFactory(defaultPolicy, this.bufferPool); + + // Bug #431459 - unregistering compression extensions till they are more stable + this.extensionFactory.unregister("deflate-frame"); + this.extensionFactory.unregister("permessage-deflate"); + this.extensionFactory.unregister("x-webkit-deflate-frame"); + this.sessionFactories = new ArrayList<>(); this.sessionFactories.add(new WebSocketSessionFactory(this)); this.creator = this; diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java index d06dd75ca99..6f2a4fec1d2 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java @@ -30,6 +30,7 @@ import org.eclipse.jetty.websocket.common.test.HttpResponse; import org.eclipse.jetty.websocket.server.helper.EchoServlet; import org.junit.AfterClass; import org.junit.Assert; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; @@ -69,7 +70,8 @@ public class PerMessageDeflateExtensionTest client.sendStandardRequest(); HttpResponse resp = client.expectUpgradeResponse(); - Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("permessage-deflate")); + // Stop test here if server doesn't have permessage-deflate enabled. + Assume.assumeThat("Response",resp.getExtensionsHeader(),containsString("permessage-deflate")); String msg = "Hello"; From c0e0f9bb83221f6e914e86abb5f61c773c00bbc0 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 5 May 2014 11:37:35 -0700 Subject: [PATCH 115/135] 431459 - Jetty WebSocket compression extensions fails to handle big messages properly + Setting up Assume for deregistered compression extension tests --- .../eclipse/jetty/websocket/server/ChromeTest.java | 3 +++ .../server/PerMessageDeflateExtensionTest.java | 6 ++++-- .../websocket/server/SimpleServletServer.java | 14 ++++++++++++++ .../jetty/websocket/servlet/WebSocketServlet.java | 2 ++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java index 38ab89072c0..48b4664ca05 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java @@ -30,6 +30,7 @@ import org.eclipse.jetty.websocket.common.test.HttpResponse; import org.eclipse.jetty.websocket.server.examples.MyEchoServlet; import org.junit.AfterClass; import org.junit.Assert; +import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; @@ -53,6 +54,8 @@ public class ChromeTest @Test public void testUpgradeWithWebkitDeflateExtension() throws Exception { + Assume.assumeTrue("Server has x-webkit-deflate-frame registered",server.getWebSocketServletFactory().getExtensionFactory().isAvailable("x-webkit-deflate-frame")); + BlockheadClient client = new BlockheadClient(server.getServerUri()); try { diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java index 6f2a4fec1d2..9de6b938b91 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/PerMessageDeflateExtensionTest.java @@ -57,6 +57,9 @@ public class PerMessageDeflateExtensionTest @Test public void testPerMessageDeflateDefault() throws Exception { + Assume.assumeTrue("Server has x-webkit-deflate-frame registered", + server.getWebSocketServletFactory().getExtensionFactory().isAvailable("permessage-deflate")); + BlockheadClient client = new BlockheadClient(server.getServerUri()); client.clearExtensions(); client.addExtensions("permessage-deflate"); @@ -70,8 +73,7 @@ public class PerMessageDeflateExtensionTest client.sendStandardRequest(); HttpResponse resp = client.expectUpgradeResponse(); - // Stop test here if server doesn't have permessage-deflate enabled. - Assume.assumeThat("Response",resp.getExtensionsHeader(),containsString("permessage-deflate")); + Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("permessage-deflate")); String msg = "Hello"; diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/SimpleServletServer.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/SimpleServletServer.java index 6d6c730460b..4ec28fb229a 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/SimpleServletServer.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/SimpleServletServer.java @@ -34,6 +34,7 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory; public class SimpleServletServer { @@ -147,4 +148,17 @@ public class SimpleServletServer e.printStackTrace(System.err); } } + + public WebSocketServletFactory getWebSocketServletFactory() + { + // Try filter approach first + WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)this.servlet.getServletContext().getAttribute(WebSocketUpgradeFilter.class.getName()); + if (filter != null) + { + return filter.getFactory(); + } + + // Try servlet next + return (WebSocketServletFactory)this.servlet.getServletContext().getAttribute(WebSocketServletFactory.class.getName()); + } } diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java index f5a96865f73..85b44f64f7b 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/WebSocketServlet.java @@ -130,6 +130,8 @@ public abstract class WebSocketServlet extends HttpServlet configure(factory); factory.init(); + + getServletContext().setAttribute(WebSocketServletFactory.class.getName(),factory); } catch (Exception x) { From 8e957b5a234a0668523cffb2edc44a63d7d1bd0c Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 5 May 2014 12:09:48 -0700 Subject: [PATCH 116/135] Reducing the noise on websocket testing --- .../jetty/websocket/jsr356/server/EchoTest.java | 1 - .../websocket/jsr356/server/PingPongTest.java | 1 - .../jetty/websocket/client/ClientCloseTest.java | 15 ++++++++++----- .../common/io/AbstractWebSocketConnection.java | 2 +- .../jetty/websocket/server/ChromeTest.java | 3 ++- .../src/test/resources/jetty-logging.properties | 5 +++-- 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoTest.java index 0732ffc20a1..693b9a423b9 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/EchoTest.java @@ -223,7 +223,6 @@ public class EchoTest WebAppContext webapp = server.createWebAppContext(); server.deployWebapp(webapp); - server.dump(); } @BeforeClass diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/PingPongTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/PingPongTest.java index a034478acf0..aee8854b0a5 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/PingPongTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/PingPongTest.java @@ -60,7 +60,6 @@ public class PingPongTest WebAppContext webapp = server.createWebAppContext(); server.deployWebapp(webapp); - server.dump(); } @BeforeClass diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java index 2b1f824883b..0242d0a33e9 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java @@ -41,6 +41,7 @@ import org.eclipse.jetty.toolchain.test.TestTracker; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.websocket.api.ProtocolException; import org.eclipse.jetty.websocket.api.Session; @@ -50,6 +51,7 @@ import org.eclipse.jetty.websocket.client.io.ConnectionManager; import org.eclipse.jetty.websocket.client.io.WebSocketClientSelectorManager; import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.OpCode; +import org.eclipse.jetty.websocket.common.Parser; import org.eclipse.jetty.websocket.common.WebSocketFrame; import org.eclipse.jetty.websocket.common.WebSocketSession; import org.eclipse.jetty.websocket.common.frames.TextFrame; @@ -458,13 +460,16 @@ public class ClientCloseTest bad.putShort((short)StatusCode.NORMAL); bad.put(msg); BufferUtil.flipToFlush(bad,0); - serverConn.write(bad); + try (StacklessLogging quiet = new StacklessLogging(Parser.class)) + { + serverConn.write(bad); - // client should have noticed the error - clientSocket.assertReceivedError(ProtocolException.class,containsString("Invalid control frame")); + // client should have noticed the error + clientSocket.assertReceivedError(ProtocolException.class,containsString("Invalid control frame")); - // client parse invalid frame, notifies server of close (protocol error) - confirmServerReceivedCloseFrame(serverConn,StatusCode.PROTOCOL,allOf(containsString("Invalid control frame"),containsString("length"))); + // client parse invalid frame, notifies server of close (protocol error) + confirmServerReceivedCloseFrame(serverConn,StatusCode.PROTOCOL,allOf(containsString("Invalid control frame"),containsString("length"))); + } // server disconnects serverConn.disconnect(); diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java index 7494abac802..87fdd62955f 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java @@ -581,7 +581,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp } catch (CloseException e) { - LOG.warn(e); + LOG.debug(e); close(e.getStatusCode(),e.getMessage()); return -1; } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java index 48b4664ca05..40b865cc1bf 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/ChromeTest.java @@ -54,7 +54,8 @@ public class ChromeTest @Test public void testUpgradeWithWebkitDeflateExtension() throws Exception { - Assume.assumeTrue("Server has x-webkit-deflate-frame registered",server.getWebSocketServletFactory().getExtensionFactory().isAvailable("x-webkit-deflate-frame")); + Assume.assumeTrue("Server has x-webkit-deflate-frame registered", + server.getWebSocketServletFactory().getExtensionFactory().isAvailable("x-webkit-deflate-frame")); BlockheadClient client = new BlockheadClient(server.getServerUri()); try diff --git a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties index fda5d883a0a..9165916af5d 100644 --- a/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties +++ b/jetty-websocket/websocket-server/src/test/resources/jetty-logging.properties @@ -4,7 +4,7 @@ org.eclipse.jetty.LEVEL=WARN # org.eclipse.jetty.io.WriteFlusher.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=DEBUG # org.eclipse.jetty.websocket.LEVEL=INFO -org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG +# org.eclipse.jetty.websocket.common.io.LEVEL=DEBUG # org.eclipse.jetty.websocket.server.ab.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.Parser.LEVEL=DEBUG # org.eclipse.jetty.websocket.common.Generator.LEVEL=DEBUG @@ -19,5 +19,6 @@ org.eclipse.jetty.websocket.server.browser.LEVEL=DEBUG ### Disabling intentional error out of RFCSocket org.eclipse.jetty.websocket.server.helper.RFCSocket.LEVEL=OFF -### Hiding Stack Traces from ABSocket +### Hiding Stack Traces from various test cases org.eclipse.jetty.websocket.server.ab.ABSocket.STACKS=OFF +org.eclipse.jetty.websocket.server.WebSocketCloseTest$FastFailSocket.STACKS=OFF \ No newline at end of file From 5635f02235bc5aee869765259b3a936f8b20d77a Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 5 May 2014 13:46:58 -0700 Subject: [PATCH 117/135] 433793 - WebSocket / empty protocol list in ServerEndpointConfig.Configurator when using non-exact header name + Marking header (and parameter) maps as case-insensitive. --- .../jsr356/server/ConfiguratorTest.java | 126 ++++++++++++++++++ .../servlet/UpgradeHttpServletRequest.java | 6 +- 2 files changed, 130 insertions(+), 2 deletions(-) diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java index f6cdb3b19da..8a78de9ec53 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/ConfiguratorTest.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import javax.websocket.Extension; import javax.websocket.HandshakeResponse; @@ -130,6 +131,38 @@ public class ConfiguratorTest } } + public static class ProtocolsConfigurator extends ServerEndpointConfig.Configurator + { + public static AtomicReference seenProtocols = new AtomicReference<>(); + + @Override + public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) + { + super.modifyHandshake(sec,request,response); + } + + @Override + public String getNegotiatedSubprotocol(List supported, List requested) + { + LOG.warn(new Throwable()); + String seen = QuoteUtil.join(requested,","); + seenProtocols.compareAndSet(null,seen); + return super.getNegotiatedSubprotocol(supported,requested); + } + } + + @ServerEndpoint(value = "/protocols", configurator = ProtocolsConfigurator.class) + public static class ProtocolsSocket + { + @OnMessage + public String onMessage(Session session, String msg) + { + StringBuilder response = new StringBuilder(); + response.append("Requested Protocols: [").append(ProtocolsConfigurator.seenProtocols.get()).append("]"); + return response.toString(); + } + } + private static Server server; private static URI baseServerUri; @@ -149,6 +182,7 @@ public class ConfiguratorTest container.addEndpoint(CaptureHeadersSocket.class); container.addEndpoint(EmptySocket.class); container.addEndpoint(NoExtensionsSocket.class); + container.addEndpoint(ProtocolsSocket.class); server.start(); String host = connector.getHost(); @@ -215,4 +249,96 @@ public class ConfiguratorTest Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Request Header [X-Dummy]: \"Bogus\"")); } } + + /** + * Test of Sec-WebSocket-Protocol, as seen in RFC-6455, 1 protocol + */ + @Test + public void testProtocol_Single() throws Exception + { + URI uri = baseServerUri.resolve("/protocols"); + ProtocolsConfigurator.seenProtocols.set(null); + + try (BlockheadClient client = new BlockheadClient(uri)) + { + client.addHeader("Sec-WebSocket-Protocol: echo\r\n"); + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + client.write(new TextFrame().setPayload("getProtocols")); + EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); + WebSocketFrame frame = frames.poll(); + Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\"]")); + } + } + + /** + * Test of Sec-WebSocket-Protocol, as seen in RFC-6455, 3 protocols + */ + @Test + public void testProtocol_Triple() throws Exception + { + URI uri = baseServerUri.resolve("/protocols"); + ProtocolsConfigurator.seenProtocols.set(null); + + try (BlockheadClient client = new BlockheadClient(uri)) + { + client.addHeader("Sec-WebSocket-Protocol: echo, chat, status\r\n"); + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + client.write(new TextFrame().setPayload("getProtocols")); + EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); + WebSocketFrame frame = frames.poll(); + Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]")); + } + } + + /** + * Test of Sec-WebSocket-Protocol, using all lowercase header + */ + @Test + public void testProtocol_LowercaseHeader() throws Exception + { + URI uri = baseServerUri.resolve("/protocols"); + ProtocolsConfigurator.seenProtocols.set(null); + + try (BlockheadClient client = new BlockheadClient(uri)) + { + client.addHeader("sec-websocket-protocol: echo, chat, status\r\n"); + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + client.write(new TextFrame().setPayload("getProtocols")); + EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); + WebSocketFrame frame = frames.poll(); + Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]")); + } + } + + /** + * Test of Sec-WebSocket-Protocol, using non-spec case header + */ + @Test + public void testProtocol_AltHeaderCase() throws Exception + { + URI uri = baseServerUri.resolve("/protocols"); + ProtocolsConfigurator.seenProtocols.set(null); + + try (BlockheadClient client = new BlockheadClient(uri)) + { + client.addHeader("Sec-Websocket-Protocol: echo, chat, status\r\n"); + client.connect(); + client.sendStandardRequest(); + client.expectUpgradeResponse(); + + client.write(new TextFrame().setPayload("getProtocols")); + EventQueue frames = client.readFrames(1,1,TimeUnit.SECONDS); + WebSocketFrame frame = frames.poll(); + Assert.assertThat("Frame Response", frame.getPayloadAsUTF8(), is("Requested Protocols: [\"echo\",\"chat\",\"status\"]")); + } + } } diff --git a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/UpgradeHttpServletRequest.java b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/UpgradeHttpServletRequest.java index cc672d8f2b6..a2796b88160 100644 --- a/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/UpgradeHttpServletRequest.java +++ b/jetty-websocket/websocket-servlet/src/main/java/org/eclipse/jetty/websocket/servlet/UpgradeHttpServletRequest.java @@ -31,6 +31,8 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.TreeMap; + import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; import javax.servlet.RequestDispatcher; @@ -68,8 +70,8 @@ public class UpgradeHttpServletRequest implements HttpServletRequest private final String remoteUser; private final Principal principal; - private final Map> headers = new HashMap<>(8); - private final Map parameters = new HashMap<>(2); + private final Map> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private final Map parameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); private final Map attributes = new HashMap<>(2); private final List locales = new ArrayList<>(2); From f8c1f70b7501e0055912bccb89ac8b3d5237557b Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Tue, 6 May 2014 10:00:40 +0200 Subject: [PATCH 118/135] 433708 Improve WebAppClassLoader.addClassPath() IllegalStateException message --- .../java/org/eclipse/jetty/webapp/WebAppClassLoader.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java index ad05984fb86..6e9186ebd06 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebAppClassLoader.java @@ -242,7 +242,11 @@ public class WebAppClassLoader extends URLClassLoader else if (resource.isDirectory()) addURL(resource.getURL()); else - throw new IllegalArgumentException("!file: "+resource); + { + if (LOG.isDebugEnabled()) + LOG.debug("Check file exists and is not nested jar: "+resource); + throw new IllegalArgumentException("File not resolvable or incompatible with URLClassloader: "+resource); + } } } } From f26291f5b600a1053045d9e8bbfd1bbfc2dcd526 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 6 May 2014 10:45:10 +0200 Subject: [PATCH 119/135] 433849 FileResource string compare fix --- .../java/org/eclipse/jetty/util/resource/FileResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java index 136ce4cfc88..449884241b6 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/resource/FileResource.java @@ -108,8 +108,8 @@ public class FileResource extends Resource _file=file; URI file_uri=_file.toURI(); _uri=normalizeURI(_file,uri); - - if (!_uri.equals(file_uri) && !_uri.toString().equals(file_uri.toString())) + + if (!_uri.equals(file_uri.toString())) { // URI and File URI are different. Is it just an encoding difference? if (!file_uri.toString().equals(URIUtil.decodePath(uri.toString()))) From e67df3677323d77eb4e469998221d72100756e4f Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 6 May 2014 13:58:14 +0200 Subject: [PATCH 120/135] Removed redundant assignment. --- .../src/main/java/org/eclipse/jetty/proxy/ProxyServlet.java | 4 +--- 1 file changed, 1 insertion(+), 3 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 cdab7289c7b..1c7cc26b2de 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 @@ -672,9 +672,7 @@ public class ProxyServlet extends HttpServlet protected void init(ServletConfig config) throws ServletException { - String proxyTo = config.getInitParameter("proxyTo"); - _proxyTo = proxyTo == null ? _proxyTo : proxyTo; - + _proxyTo = config.getInitParameter("proxyTo"); if (_proxyTo == null) throw new UnavailableException("Init parameter 'proxyTo' is required."); From b966c025017c7dbb93a9cc5d9a148a8c4e586645 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 6 May 2014 13:58:59 +0200 Subject: [PATCH 121/135] Tests for FastCGIProxyServlet. --- .../server/proxy/FastCGIProxyServletTest.java | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java new file mode 100644 index 00000000000..b1a85c191fe --- /dev/null +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServletTest.java @@ -0,0 +1,112 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.fcgi.server.proxy; + +import java.io.IOException; +import java.net.URI; +import java.util.Random; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.fcgi.server.ServerFCGIConnectionFactory; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +public class FastCGIProxyServletTest +{ + private Server server; + private ServerConnector httpConnector; + private ServerConnector fcgiConnector; + private HttpClient client; + + public void prepare(HttpServlet servlet) throws Exception + { + server = new Server(); + httpConnector = new ServerConnector(server); + server.addConnector(httpConnector); + + fcgiConnector = new ServerConnector(server, new ServerFCGIConnectionFactory(new HttpConfiguration())); + server.addConnector(fcgiConnector); + + final String contextPath = "/"; + ServletContextHandler context = new ServletContextHandler(server, contextPath); + + final String servletPath = "/script"; + FastCGIProxyServlet fcgiServlet = new FastCGIProxyServlet() + { + @Override + protected URI rewriteURI(HttpServletRequest request) + { + return URI.create("http://localhost:" + fcgiConnector.getLocalPort() + servletPath + request.getServletPath()); + } + }; + ServletHolder fcgiServletHolder = new ServletHolder(fcgiServlet); + context.addServlet(fcgiServletHolder, "*.php"); + fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_ROOT_INIT_PARAM, "/scriptRoot"); + fcgiServletHolder.setInitParameter("proxyTo", "http://localhost"); + fcgiServletHolder.setInitParameter(FastCGIProxyServlet.SCRIPT_PATTERN_INIT_PARAM, "(.+?\\.php)"); + + context.addServlet(new ServletHolder(servlet), servletPath + "/*"); + + client = new HttpClient(); + server.addBean(client); + + server.start(); + } + + @After + public void dispose() throws Exception + { + server.stop(); + } + + @Test + public void testGETWithSmallResponseContent() throws Exception + { + final byte[] data = new byte[1024]; + new Random().nextBytes(data); + + final String path = "/foo/index.php"; + prepare(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + Assert.assertTrue(req.getRequestURI().endsWith(path)); + resp.getOutputStream().write(data); + } + }); + + ContentResponse response = client.newRequest("localhost", httpConnector.getLocalPort()) + .path(path) + .send(); + Assert.assertEquals(200, response.getStatus()); + Assert.assertArrayEquals(data, response.getContent()); + } +} From e983e00a4572d4841aa4876f986fe2d7c19a2ce8 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 6 May 2014 16:24:34 +0200 Subject: [PATCH 122/135] Tests for TryFilesFilter. --- .../fcgi/server/proxy/TryFilesFilterTest.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilterTest.java diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilterTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilterTest.java new file mode 100644 index 00000000000..e1d78c79b44 --- /dev/null +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilterTest.java @@ -0,0 +1,107 @@ +// +// ======================================================================== +// Copyright (c) 1995-2014 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.fcgi.server.proxy; + +import java.io.IOException; +import java.util.EnumSet; +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.FilterHolder; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +public class TryFilesFilterTest +{ + private Server server; + private ServerConnector connector; + private ServerConnector sslConnector; + private HttpClient client; + private String forwardPath; + + public void prepare(HttpServlet servlet) throws Exception + { + server = new Server(); + connector = new ServerConnector(server); + server.addConnector(connector); + + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setEndpointIdentificationAlgorithm(""); + sslContextFactory.setKeyStorePath("src/test/resources/keystore.jks"); + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setTrustStorePath("src/test/resources/truststore.jks"); + sslContextFactory.setTrustStorePassword("storepwd"); + sslConnector = new ServerConnector(server, sslContextFactory); + server.addConnector(sslConnector); + + ServletContextHandler context = new ServletContextHandler(server, "/"); + + FilterHolder filterHolder = context.addFilter(TryFilesFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); + forwardPath = "/index.php"; + filterHolder.setInitParameter(TryFilesFilter.FILES_INIT_PARAM, "$path " + forwardPath + "?p=$path"); + + context.addServlet(new ServletHolder(servlet), "/*"); + + client = new HttpClient(sslContextFactory); + server.addBean(client); + + server.start(); + } + + @After + public void dispose() throws Exception + { + server.stop(); + } + + @Test + public void testHTTPSRequestIsForwarded() throws Exception + { + final String path = "/one/"; + prepare(new HttpServlet() + { + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException + { + Assert.assertTrue("https".equalsIgnoreCase(req.getScheme())); + Assert.assertTrue(req.isSecure()); + Assert.assertEquals(forwardPath, req.getRequestURI()); + Assert.assertTrue(req.getQueryString().endsWith(path)); + } + }); + + ContentResponse response = client.newRequest("localhost", sslConnector.getLocalPort()) + .scheme("https") + .path(path) + .send(); + + Assert.assertEquals(200, response.getStatus()); + } +} From 9d33817e71f9b6c4696da9de543f928b38e260a7 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 6 May 2014 19:36:51 +0200 Subject: [PATCH 123/135] 434247 - Redirect loop in FastCGI proxying for HTTPS sites. --- .../jetty/fcgi/server/proxy/FastCGIProxyServlet.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java index 2daa22132c6..d107011752d 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/FastCGIProxyServlet.java @@ -54,6 +54,8 @@ import org.eclipse.jetty.proxy.ProxyServlet; *

  • the FastCGI SCRIPT_NAME parameter
  • *
  • the FastCGI PATH_INFO parameter
  • * + *
  • fastCGI.HTTPS, optional, defaults to false, that specifies whether + * to force the FastCGI HTTPS parameter to the value on
  • * * * @see TryFilesFilter @@ -62,6 +64,8 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent { public static final String SCRIPT_ROOT_INIT_PARAM = "scriptRoot"; public static final String SCRIPT_PATTERN_INIT_PARAM = "scriptPattern"; + public static final String FASTCGI_HTTPS_INIT_PARAM = "fastCGI.HTTPS"; + private static final String REMOTE_ADDR_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remoteAddr"; private static final String REMOTE_PORT_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".remotePort"; private static final String SERVER_NAME_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".serverName"; @@ -71,6 +75,7 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent private static final String REQUEST_URI_ATTRIBUTE = FastCGIProxyServlet.class.getName() + ".requestURI"; private Pattern scriptPattern; + private boolean fcgiHTTPS; @Override public void init() throws ServletException @@ -81,6 +86,8 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent if (value == null) value = "(.+?\\.php)"; scriptPattern = Pattern.compile(value); + + fcgiHTTPS = Boolean.parseBoolean(getInitParameter(FASTCGI_HTTPS_INIT_PARAM)); } @Override @@ -131,7 +138,7 @@ public class FastCGIProxyServlet extends AsyncProxyServlet.Transparent fastCGIHeaders.put(FCGI.Headers.SERVER_ADDR, (String)proxyRequest.getAttributes().get(SERVER_ADDR_ATTRIBUTE)); fastCGIHeaders.put(FCGI.Headers.SERVER_PORT, (String)proxyRequest.getAttributes().get(SERVER_PORT_ATTRIBUTE)); - if (HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE))) + if (fcgiHTTPS || HttpScheme.HTTPS.is((String)proxyRequest.getAttributes().get(SCHEME_ATTRIBUTE))) fastCGIHeaders.put(FCGI.Headers.HTTPS, "on"); URI proxyRequestURI = proxyRequest.getURI(); From d6e5d827f694726eac784d68b1c00bedc9470742 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 6 May 2014 19:44:03 +0200 Subject: [PATCH 124/135] Removed obsolete FastCGI modules. --- jetty-fcgi/fcgi-distribution/pom.xml | 70 ------------------- .../src/main/assembly/distribution.xml | 31 -------- .../main/config/webapps/wordpress-example.xml | 68 ------------------ jetty-fcgi/fcgi-http-client-transport/pom.xml | 43 ------------ .../test/resources/jetty-logging.properties | 3 - jetty-fcgi/fcgi-proxy/pom.xml | 55 --------------- jetty-fcgi/pom.xml | 3 - 7 files changed, 273 deletions(-) delete mode 100644 jetty-fcgi/fcgi-distribution/pom.xml delete mode 100644 jetty-fcgi/fcgi-distribution/src/main/assembly/distribution.xml delete mode 100644 jetty-fcgi/fcgi-distribution/src/main/config/webapps/wordpress-example.xml delete mode 100644 jetty-fcgi/fcgi-http-client-transport/pom.xml delete mode 100644 jetty-fcgi/fcgi-http-client-transport/src/test/resources/jetty-logging.properties delete mode 100644 jetty-fcgi/fcgi-proxy/pom.xml diff --git a/jetty-fcgi/fcgi-distribution/pom.xml b/jetty-fcgi/fcgi-distribution/pom.xml deleted file mode 100644 index 81d03d9e236..00000000000 --- a/jetty-fcgi/fcgi-distribution/pom.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - fcgi-parent - org.eclipse.jetty.fcgi - 9.2.0-SNAPSHOT - - - 4.0.0 - fcgi-distribution - pom - Jetty :: FastCGI :: Distribution - - - ${project.build.directory}/distribution - - - - - - maven-dependency-plugin - - - copy-jars - generate-resources - - copy-dependencies - - - org.eclipse.jetty.fcgi - fcgi-server - jar - ${distribution-directory}/lib/fcgi - - - - - - maven-assembly-plugin - - - assemble - package - - assembly - - - jetty-fcgi-${project.version} - - src/main/assembly/distribution.xml - - gnu - - - - - - - - - - org.eclipse.jetty.fcgi - fcgi-proxy - ${project.version} - - - - diff --git a/jetty-fcgi/fcgi-distribution/src/main/assembly/distribution.xml b/jetty-fcgi/fcgi-distribution/src/main/assembly/distribution.xml deleted file mode 100644 index df1f093682d..00000000000 --- a/jetty-fcgi/fcgi-distribution/src/main/assembly/distribution.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - distribution - - - tar.gz - - - false - - - - ${project.basedir}/src/main/config/modules - /modules - - *.mod - - - - ${distribution-directory} - / - - lib/** - - - - - diff --git a/jetty-fcgi/fcgi-distribution/src/main/config/webapps/wordpress-example.xml b/jetty-fcgi/fcgi-distribution/src/main/config/webapps/wordpress-example.xml deleted file mode 100644 index 9ec42697473..00000000000 --- a/jetty-fcgi/fcgi-distribution/src/main/config/webapps/wordpress-example.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - /var/www/wordpress-3.7.1 - - - /wp - - - index.php - - - - org.eclipse.jetty.fcgi.proxy.TryFilesFilter - /* - - - - - - - files - $path /index.php?p=$path - - - - - - - default - - - org.eclipse.jetty.servlet.DefaultServlet - - - - dirAllowed - false - - - - / - - - - org.eclipse.jetty.fcgi.proxy.FastCGIProxyServlet - *.php - - proxyTo - http://localhost:9000 - - - prefix - / - - - scriptRoot - - - - scriptPattern - (.+?\\.php) - - - - diff --git a/jetty-fcgi/fcgi-http-client-transport/pom.xml b/jetty-fcgi/fcgi-http-client-transport/pom.xml deleted file mode 100644 index d911c12528c..00000000000 --- a/jetty-fcgi/fcgi-http-client-transport/pom.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - org.eclipse.jetty.fcgi - fcgi-parent - 9.2.0-SNAPSHOT - - - 4.0.0 - fcgi-http-client-transport - Jetty :: FastCGI :: HTTP Client Transport - - - ${project.groupId}.client.http - - - - - org.eclipse.jetty.fcgi - fcgi-core - ${project.version} - - - org.eclipse.jetty - jetty-client - ${project.version} - - - - org.eclipse.jetty.fcgi - fcgi-server - ${project.version} - test - - - org.eclipse.jetty.toolchain - jetty-test-helper - - - - diff --git a/jetty-fcgi/fcgi-http-client-transport/src/test/resources/jetty-logging.properties b/jetty-fcgi/fcgi-http-client-transport/src/test/resources/jetty-logging.properties deleted file mode 100644 index b8df62d071d..00000000000 --- a/jetty-fcgi/fcgi-http-client-transport/src/test/resources/jetty-logging.properties +++ /dev/null @@ -1,3 +0,0 @@ -org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog -#org.eclipse.jetty.client.LEVEL=DEBUG -#org.eclipse.jetty.fcgi.LEVEL=DEBUG diff --git a/jetty-fcgi/fcgi-proxy/pom.xml b/jetty-fcgi/fcgi-proxy/pom.xml deleted file mode 100644 index 149d189df73..00000000000 --- a/jetty-fcgi/fcgi-proxy/pom.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - fcgi-parent - org.eclipse.jetty.fcgi - 9.2.0-SNAPSHOT - - - 4.0.0 - fcgi-proxy - Jetty :: FastCGI :: Proxy - - - ${project.groupId}.proxy - - - - - javax.servlet - javax.servlet-api - - - org.eclipse.jetty.fcgi - fcgi-http-client-transport - ${project.version} - - - org.eclipse.jetty - jetty-proxy - ${project.version} - - - - org.eclipse.jetty - jetty-server - ${project.version} - test - - - org.eclipse.jetty - jetty-servlet - ${project.version} - test - - - org.eclipse.jetty.spdy - spdy-http-server - ${project.version} - test - - - - diff --git a/jetty-fcgi/pom.xml b/jetty-fcgi/pom.xml index d309fec8a21..ff2f37caad5 100644 --- a/jetty-fcgi/pom.xml +++ b/jetty-fcgi/pom.xml @@ -14,10 +14,7 @@ fcgi-client - fcgi-server - - From f5648a209620980106f8bcb1e1ae1b800cb7c81b Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 5 May 2014 16:01:20 -0700 Subject: [PATCH 125/135] Removing yet another noisy dump --- .../jetty/websocket/jsr356/server/OnMessageReturnTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnMessageReturnTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnMessageReturnTest.java index 5185b6acdf2..35d8cff95d7 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnMessageReturnTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/OnMessageReturnTest.java @@ -56,7 +56,6 @@ public class OnMessageReturnTest WebAppContext webapp = wsb.createWebAppContext(); wsb.deployWebapp(webapp); - wsb.dump(); WebSocketClient client = new WebSocketClient(bufferPool); try From 7d662360f1ec4ab79b17f7b21f9efb7aa27871ec Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 6 May 2014 12:12:42 -0700 Subject: [PATCH 126/135] 434077 - AnnotatedServerEndpointTest emits strange exception Adding lock around use of remote, eliminating artificial EofException. --- .../jsr356/server/JettyEchoSocket.java | 67 ++++++++++++++----- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java index 8383bb17694..d44d8bbdc8c 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/JettyEchoSocket.java @@ -18,11 +18,12 @@ package org.eclipse.jetty.websocket.jsr356.server; -import java.io.EOFException; import java.io.IOException; import java.util.Queue; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.util.log.Log; @@ -47,8 +48,8 @@ public class JettyEchoSocket private static final Logger LOG = Log.getLogger(JettyEchoSocket.class); @SuppressWarnings("unused") private Session session; - private volatile RemoteEndpoint remote; - private volatile Boolean closed = null; + private Lock remoteLock = new ReentrantLock(); + private RemoteEndpoint remote; private EventQueue incomingMessages = new EventQueue<>(); public Queue awaitMessages(int expected) throws TimeoutException, InterruptedException @@ -57,17 +58,32 @@ public class JettyEchoSocket return incomingMessages; } - public Boolean getClosed() + public boolean getClosed() { - return closed; + remoteLock.lock(); + try + { + return (remote == null); + } + finally + { + remoteLock.unlock(); + } } @OnWebSocketClose public void onClose(int code, String reason) { - closed = true; session = null; - remote = null; + remoteLock.lock(); + try + { + remote = null; + } + finally + { + remoteLock.unlock(); + } } @OnWebSocketError @@ -86,21 +102,36 @@ public class JettyEchoSocket @OnWebSocketConnect public void onOpen(Session session) { - this.closed = false; this.session = session; - this.remote = session.getRemote(); + remoteLock.lock(); + try + { + this.remote = session.getRemote(); + } + finally + { + remoteLock.unlock(); + } } public void sendMessage(String msg) throws IOException { - RemoteEndpoint r = remote; - // TODO there is a race with onClose here and no sufficient memory barrier. Taking a local copy - // stops the worst of the errors, but is probably not the best solution. - if (r==null) - throw new EOFException("Closed="+closed); - - r.sendStringByFuture(msg); - if (r.getBatchMode() == BatchMode.ON) - r.flush(); + remoteLock.lock(); + try + { + RemoteEndpoint r = remote; + if (r == null) + { + return; + } + + r.sendStringByFuture(msg); + if (r.getBatchMode() == BatchMode.ON) + r.flush(); + } + finally + { + remoteLock.unlock(); + } } } From 7db7620c3656c89b70868e584445248838795233 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Tue, 6 May 2014 21:20:41 +0200 Subject: [PATCH 127/135] 367680 jsp-file with load-on-startup not precompiled --- .../eclipse/jetty/servlet/ServletHandler.java | 5 +- .../eclipse/jetty/servlet/ServletHolder.java | 105 ++++++++++++++++-- 2 files changed, 98 insertions(+), 12 deletions(-) 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 f19bcd9eb0b..81243e01e78 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 @@ -846,7 +846,7 @@ public class ServletHandler extends ScopedHandler { try { - if (servlet.getClassName() == null && servlet.getForcedPath() != null) + /* if (servlet.getClassName() == null && servlet.getForcedPath() != null) { ServletHolder forced_holder = _servletPathMap.match(servlet.getForcedPath()); if (forced_holder == null || forced_holder.getClassName() == null) @@ -854,8 +854,9 @@ public class ServletHandler extends ScopedHandler mx.add(new IllegalStateException("No forced path servlet for " + servlet.getForcedPath())); continue; } + System.err.println("ServletHandler setting forced path classname to "+forced_holder.getClassName()+ " for "+servlet.getForcedPath()); servlet.setClassName(forced_holder.getClassName()); - } + }*/ servlet.start(); servlet.initialize(); diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java index 3ae2bc50226..c5f6e8a3af5 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -19,6 +19,7 @@ package org.eclipse.jetty.servlet; import java.io.IOException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -86,6 +87,8 @@ public class ServletHolder extends Holder implements UserIdentity.Scope private transient long _unavailable; private transient boolean _enabled = true; private transient UnavailableException _unavailableEx; + + public static final String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.jspPackagePrefix"; public static final Map NO_MAPPED_ROLES = Collections.emptyMap(); /* ---------------------------------------------------------------- */ @@ -277,8 +280,6 @@ public class ServletHolder extends Holder implements UserIdentity.Scope public void doStart() throws Exception { - - _unavailable=0; if (!_enabled) return; @@ -287,8 +288,8 @@ public class ServletHolder extends Holder implements UserIdentity.Scope if (_forcedPath != null) { // Look for a precompiled JSP Servlet - String precompiled="org.apache.jsp"+_forcedPath.replace('.','_').replace('/','.'); - + String precompiled=getClassNameForJsp(_forcedPath); + LOG.debug("Checking for precompiled servlet {} for jsp {}", precompiled, _forcedPath); ServletHolder jsp=getServletHandler().getServlet(precompiled); if (jsp!=null) { @@ -297,13 +298,25 @@ public class ServletHolder extends Holder implements UserIdentity.Scope setClassName(jsp.getClassName()); } else - { - // Look for normal JSP servlet - jsp=getServletHandler().getServlet("jsp"); - if (jsp!=null) + { + if (getClassName() == null) { - LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName()); - setClassName(jsp.getClassName()); + // Look for normal JSP servlet + jsp=getServletHandler().getServlet("jsp"); + if (jsp!=null) + { + LOG.debug("JSP file {} for {} mapped to Servlet {}",_forcedPath, getName(),jsp.getClassName()); + setClassName(jsp.getClassName()); + //copy jsp init params that don't exist for this servlet + for (Map.Entry entry:jsp.getInitParameters().entrySet()) + { + if (!_initParams.containsKey(entry.getKey())) + setInitParameter(entry.getKey(), entry.getValue()); + } + //jsp specific: set up the jsp-file on the JspServlet so it can precompile iff load-on-startup is >=0 + if (_initOnStartup) + setInitParameter("jspFile", _forcedPath); + } } } } @@ -784,6 +797,78 @@ public class ServletHolder extends Holder implements UserIdentity.Scope return false; return ("org.apache.jasper.servlet.JspServlet".equals(classname)); } + + + /* ------------------------------------------------------------ */ + private String getNameOfJspClass (String jsp) + { + if (jsp == null) + return ""; + + int i = jsp.lastIndexOf('/') + 1; + jsp = jsp.substring(i); + try + { + Class jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil"); + Method makeJavaIdentifier = jspUtil.getMethod("makeJavaIdentifier", String.class); + return (String)makeJavaIdentifier.invoke(null, jsp); + } + catch (Exception e) + { + String tmp = jsp.replace('.','_'); + LOG.warn("Unable to make identifier for jsp "+jsp +" trying "+tmp+" instead"); + if (LOG.isDebugEnabled()) + LOG.warn(e); + return tmp; + } + } + + + /* ------------------------------------------------------------ */ + private String getPackageOfJspClass (String jsp) + { + if (jsp == null) + return ""; + + int i = jsp.lastIndexOf('/'); + if (i <= 0) + return ""; + try + { + Class jspUtil = Loader.loadClass(Holder.class, "org.apache.jasper.compiler.JspUtil"); + Method makeJavaPackage = jspUtil.getMethod("makeJavaPackage", String.class); + return (String)makeJavaPackage.invoke(null, jsp.substring(0,i)); + } + catch (Exception e) + { + String tmp = jsp.substring(1).replace('/','.'); + LOG.warn("Unable to make package for jsp "+jsp +" trying "+tmp+" instead"); + if (LOG.isDebugEnabled()) + LOG.warn(e); + return tmp; + } + } + + + /* ------------------------------------------------------------ */ + private String getJspPackagePrefix () + { + String jspPackageName = (String)getServletHandler().getServletContext().getAttribute(JSP_GENERATED_PACKAGE_NAME ); + if (jspPackageName == null) + jspPackageName = "org.apache.jsp"; + + return jspPackageName; + } + + + /* ------------------------------------------------------------ */ + private String getClassNameForJsp (String jsp) + { + if (jsp == null) + return null; + + return getJspPackagePrefix() + "." +getPackageOfJspClass(jsp) + "." + getNameOfJspClass(jsp); + } /* ------------------------------------------------------------ */ From eecd986058ee1f23a9981edebc04adfd042b8ed8 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 7 May 2014 12:18:18 +0200 Subject: [PATCH 128/135] RequestBuffer handling code and javadoc cleanup --- .../eclipse/jetty/server/HttpConnection.java | 219 +++++++++++------- .../jetty/server/HttpInputOverHTTP.java | 37 +-- 2 files changed, 142 insertions(+), 114 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 c56222fb462..16cbbf93d6b 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 @@ -18,6 +18,7 @@ package org.eclipse.jetty.server; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.RejectedExecutionException; @@ -217,6 +218,8 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http else { // Get a buffer + // We are not in a race here for the request buffer as we have not yet received a request, + // so there are not an possible legal threads calling #parseContent or #completed. _requestBuffer = getRequestBuffer(); // fill @@ -236,11 +239,15 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http // The parser returned true, which indicates the channel is ready to handle a request. // Call the channel and this will either handle the request/response to completion OR, // if the request suspends, the request/response will be incomplete so the outer loop will exit. + // Not that onFillable no longer manipulates the request buffer from this point and that is + // left to threads calling #completed or #parseContent (which may be this thread inside handle()) suspended = !_channel.handle(); } else { // We parsed what we could, recycle the request buffer + // We are not in a race here for the request buffer as we have not yet received a request, + // so there are not an possible legal threads calling #parseContent or #completed. releaseRequestBuffer(); } } @@ -266,7 +273,136 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http } } } + + /* ------------------------------------------------------------ */ + /** Fill and parse data looking for content + * @throws IOException + */ + protected void parseContent() throws IOException + { + // Not in a race here for the request buffer with #onFillable because an async consumer of + // content would only be started after onFillable has given up control. + // In a little bit of a race with #completed, but then not sure if it is legal to be doing + // async calls to IO and have a completed call at the same time. + ByteBuffer requestBuffer = getRequestBuffer(); + + while (_parser.inContentState()) + { + // Can the parser progress (even with an empty buffer) + boolean parsed = _parser.parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer); + + // No, we can we try reading some content? + if (BufferUtil.isEmpty(requestBuffer) && getEndPoint().isInputShutdown()) + { + _parser.atEOF(); + if (parsed) + break; + continue; + } + + if (parsed) + break; + + // OK lets read some data + int filled=getEndPoint().fill(requestBuffer); + if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled' + LOG.debug("{} filled {}",this,filled); + if (filled<=0) + { + if (filled<0) + { + _parser.atEOF(); + continue; + } + break; + } + } + } + @Override + public void completed() + { + // Handle connection upgrades + if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101) + { + Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE); + if (connection != null) + { + LOG.debug("Upgrade from {} to {}", this, connection); + onClose(); + getEndPoint().setConnection(connection); + connection.onOpen(); + _channel.reset(); + _parser.reset(); + _generator.reset(); + releaseRequestBuffer(); + return; + } + } + + // Finish consuming the request + // If we are still expecting + if (_channel.isExpecting100Continue()) + // close to seek EOF + _parser.close(); + else if (_parser.inContentState() && _generator.isPersistent()) + // Complete reading the request + _channel.getRequest().getHttpInput().consumeAll(); + + // Reset the channel, parsers and generator + _channel.reset(); + if (_generator.isPersistent() && !_parser.isClosed()) + _parser.reset(); + else + _parser.close(); + + // Not in a race here with onFillable, because it has given up control before calling handle. + // in a slight race with #completed, but not sure what to do with that anyway. + releaseRequestBuffer(); + if (_chunk!=null) + _bufferPool.release(_chunk); + _chunk=null; + _generator.reset(); + + // if we are not called from the onfillable thread, schedule completion + if (getCurrentConnection()!=this) + { + // If we are looking for the next request + if (_parser.isStart()) + { + // if the buffer is empty + if (BufferUtil.isEmpty(_requestBuffer)) + { + // look for more data + fillInterested(); + } + // else if we are still running + else if (getConnector().isRunning()) + { + // Dispatched to handle a pipelined request + try + { + getExecutor().execute(this); + } + catch (RejectedExecutionException e) + { + if (getConnector().isRunning()) + LOG.warn(e); + else + LOG.ignore(e); + getEndPoint().close(); + } + } + else + { + getEndPoint().close(); + } + } + // else the parser must be closed, so seek the EOF if we are still open + else if (getEndPoint().isOpen()) + fillInterested(); + } + } @Override protected void onFillInterestedFailed(Throwable cause) @@ -310,88 +446,7 @@ public class HttpConnection extends AbstractConnection implements Runnable, Http new ContentCallback(content,lastContent,callback).iterate(); } - @Override - public void completed() - { - // Handle connection upgrades - if (_channel.getResponse().getStatus() == HttpStatus.SWITCHING_PROTOCOLS_101) - { - Connection connection = (Connection)_channel.getRequest().getAttribute(UPGRADE_CONNECTION_ATTRIBUTE); - if (connection != null) - { - LOG.debug("Upgrade from {} to {}", this, connection); - onClose(); - getEndPoint().setConnection(connection); - connection.onOpen(); - _channel.reset(); - _parser.reset(); - _generator.reset(); - releaseRequestBuffer(); - return; - } - } - - // Finish consuming the request - // If we are still expecting - if (_channel.isExpecting100Continue()) - // close to seek EOF - _parser.close(); - else if (_parser.inContentState() && _generator.isPersistent()) - // Complete reading the request - _channel.getRequest().getHttpInput().consumeAll(); - - // Reset the channel, parsers and generator - _channel.reset(); - if (_generator.isPersistent() && !_parser.isClosed()) - _parser.reset(); - else - _parser.close(); - releaseRequestBuffer(); - if (_chunk!=null) - _bufferPool.release(_chunk); - _chunk=null; - _generator.reset(); - - // if we are not called from the onfillable thread, schedule completion - if (getCurrentConnection()!=this) - { - // If we are looking for the next request - if (_parser.isStart()) - { - // if the buffer is empty - if (_requestBuffer == null) - { - // look for more data - fillInterested(); - } - // else if we are still running - else if (getConnector().isRunning()) - { - // Dispatched to handle a pipelined request - try - { - getExecutor().execute(this); - } - catch (RejectedExecutionException e) - { - if (getConnector().isRunning()) - LOG.warn(e); - else - LOG.ignore(e); - getEndPoint().close(); - } - } - else - { - getEndPoint().close(); - } - } - // else the parser must be closed, so seek the EOF if we are still open - else if (getEndPoint().isOpen()) - fillInterested(); - } - } - + protected class HttpChannelOverHttp extends HttpChannel { public HttpChannelOverHttp(Connector connector, HttpConfiguration config, EndPoint endPoint, HttpTransport transport, HttpInput input) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java index fda404c9130..35524500492 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java @@ -86,38 +86,11 @@ public class HttpInputOverHTTP extends HttpInput implements Callback // No - then we are going to need to parse some more content _content=null; - ByteBuffer requestBuffer = _httpConnection.getRequestBuffer(); - - while (!_httpConnection.getParser().isComplete()) - { - // Can the parser progress (even with an empty buffer) - _httpConnection.getParser().parseNext(requestBuffer==null?BufferUtil.EMPTY_BUFFER:requestBuffer); - - // If we got some content, that will do for now! - if (BufferUtil.hasContent(_content)) - return _content; - - // No, we can we try reading some content? - if (BufferUtil.isEmpty(requestBuffer) && _httpConnection.getEndPoint().isInputShutdown()) - { - _httpConnection.getParser().atEOF(); - continue; - } - - // OK lets read some data - int filled=_httpConnection.getEndPoint().fill(requestBuffer); - if (LOG.isDebugEnabled()) // Avoid boxing of variable 'filled' - LOG.debug("{} filled {}",this,filled); - if (filled<=0) - { - if (filled<0) - { - _httpConnection.getParser().atEOF(); - continue; - } - return null; - } - } + _httpConnection.parseContent(); + + // If we have some content available, return it + if (BufferUtil.hasContent(_content)) + return _content; return null; From 8fb9baace16e05ab8f534170e65b54b820cfb50f Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Wed, 7 May 2014 15:28:49 +0200 Subject: [PATCH 129/135] Change org.eclipse.jetty.servlet.jspPackagePrefix to a context init-param --- .../main/java/org/eclipse/jetty/servlet/ServletHolder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java index c5f6e8a3af5..63e8b0898ec 100644 --- a/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java +++ b/jetty-servlet/src/main/java/org/eclipse/jetty/servlet/ServletHolder.java @@ -88,7 +88,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope private transient boolean _enabled = true; private transient UnavailableException _unavailableEx; - public static final String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.jspPackagePrefix"; + public static final String JSP_GENERATED_PACKAGE_NAME = "org.eclipse.jetty.servlet.jspPackagePrefix"; public static final Map NO_MAPPED_ROLES = Collections.emptyMap(); /* ---------------------------------------------------------------- */ @@ -853,7 +853,7 @@ public class ServletHolder extends Holder implements UserIdentity.Scope /* ------------------------------------------------------------ */ private String getJspPackagePrefix () { - String jspPackageName = (String)getServletHandler().getServletContext().getAttribute(JSP_GENERATED_PACKAGE_NAME ); + String jspPackageName = (String)getServletHandler().getServletContext().getInitParameter(JSP_GENERATED_PACKAGE_NAME ); if (jspPackageName == null) jspPackageName = "org.apache.jsp"; From 9c9eed1d33b29201d9cd98fc5c9795f03eb8e43a Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Wed, 7 May 2014 15:42:34 +0200 Subject: [PATCH 130/135] Make clear org.apache.jasper.compiler.disablejsr199 only applies to gla --- jetty-distribution/src/main/resources/modules/jsp.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-distribution/src/main/resources/modules/jsp.mod b/jetty-distribution/src/main/resources/modules/jsp.mod index 4924ed91030..29f3ee78cec 100644 --- a/jetty-distribution/src/main/resources/modules/jsp.mod +++ b/jetty-distribution/src/main/resources/modules/jsp.mod @@ -16,5 +16,5 @@ jsp-impl/${jsp-impl}-jsp # default jetty >= 9.2 jsp-impl=apache -# To use an non-jdk compiler for JSP compilation uncomment next line +# To use a non-jdk compiler for JSP compilation when using apache uncomment next line # -Dorg.apache.jasper.compiler.disablejsr199=true From dfb751c331a48be1a2e9b455aa427f45f7325c3b Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Wed, 7 May 2014 15:43:46 +0200 Subject: [PATCH 131/135] Make clear org.apache.jasper.compiler.disablejsr199 only applies to glassfish --- jetty-distribution/src/main/resources/modules/jsp.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-distribution/src/main/resources/modules/jsp.mod b/jetty-distribution/src/main/resources/modules/jsp.mod index 29f3ee78cec..fa5b9fdfa95 100644 --- a/jetty-distribution/src/main/resources/modules/jsp.mod +++ b/jetty-distribution/src/main/resources/modules/jsp.mod @@ -16,5 +16,5 @@ jsp-impl/${jsp-impl}-jsp # default jetty >= 9.2 jsp-impl=apache -# To use a non-jdk compiler for JSP compilation when using apache uncomment next line +# To use a non-jdk compiler for JSP compilation when using glassfish uncomment next line # -Dorg.apache.jasper.compiler.disablejsr199=true From 711c0a5335ed106731dbd9eebe0b8be2ef8bac9f Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Wed, 7 May 2014 21:38:28 +0200 Subject: [PATCH 132/135] include jetty-quickstart in jetty-all aggregate --- aggregates/jetty-all/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/aggregates/jetty-all/pom.xml b/aggregates/jetty-all/pom.xml index de451684512..c64b2bc8fa7 100644 --- a/aggregates/jetty-all/pom.xml +++ b/aggregates/jetty-all/pom.xml @@ -191,6 +191,12 @@ ${project.version} provided + + org.eclipse.jetty + jetty-quickstart + ${project.version} + provided + javax.websocket From fa5a5f3507987cf9563843bc8d509a5662b98ef4 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 7 May 2014 10:49:47 -0700 Subject: [PATCH 133/135] Adding javadoc --- .../jetty/websocket/common/io/AbstractWebSocketConnection.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java index 87fdd62955f..c88c4b3896b 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java @@ -504,6 +504,9 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp this.ioState.onOpened(); } + /** + * Event for no activity on connection (read or write) + */ @Override protected boolean onReadTimeout() { From 8ff1cec5703dfc8b9f37558761c4a374b88c758c Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 7 May 2014 13:10:48 -0700 Subject: [PATCH 134/135] 433262 - WebSocket / Advanced close use cases + AWSC.Flusher.onFailure() now uses IOState properly. + IOState now tracks the final CLOSED CloseInfo atomically + Renamed IOState.onReadEOF() to .onReadFailure(Throwable) + Added IOState.onWriteFailure(Throwable) --- .../io/AbstractWebSocketConnection.java | 35 +--- .../jetty/websocket/common/io/IOState.java | 198 ++++++++++++++---- 2 files changed, 162 insertions(+), 71 deletions(-) diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java index c88c4b3896b..9f0ead3d48d 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/AbstractWebSocketConnection.java @@ -34,7 +34,6 @@ import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.component.Dumpable; import org.eclipse.jetty.util.log.Log; @@ -42,7 +41,6 @@ import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.thread.Scheduler; import org.eclipse.jetty.websocket.api.BatchMode; import org.eclipse.jetty.websocket.api.CloseException; -import org.eclipse.jetty.websocket.api.CloseStatus; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.SuspendToken; import org.eclipse.jetty.websocket.api.WebSocketPolicy; @@ -72,6 +70,8 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp @Override protected void onFailure(Throwable x) { + session.notifyError(x); + if (ioState.wasAbnormalClose()) { LOG.ignore(x); @@ -79,34 +79,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp } LOG.debug("Write flush failure",x); - - // Unable to write? can't notify other side of close, so disconnect. - // This is an ABNORMAL closure - String reason = "Websocket write failure"; - - if (x instanceof EOFException) - { - reason = "EOF"; - Throwable cause = x.getCause(); - if ((cause != null) && (StringUtil.isNotBlank(cause.getMessage()))) - { - reason = "EOF: " + cause.getMessage(); - } - } - else - { - if (StringUtil.isNotBlank(x.getMessage())) - { - reason = x.getMessage(); - } - } - - // Abnormal Close - reason = CloseStatus.trimMaxReasonLength(reason); - session.notifyError(x); - session.notifyClose(StatusCode.ABNORMAL,reason); - - disconnect(); // disconnect endpoint & connection + ioState.onWriteFailure(x); } } @@ -563,7 +536,7 @@ public abstract class AbstractWebSocketConnection extends AbstractConnection imp else if (filled < 0) { LOG.debug("read - EOF Reached (remote: {})",getRemoteAddress()); - ioState.onReadEOF(); + ioState.onReadFailure(new EOFException("Remote Read EOF")); return -1; } else diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java index 815d2d30ad0..35c87d91786 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/io/IOState.java @@ -18,12 +18,16 @@ package org.eclipse.jetty.websocket.common.io; +import java.io.EOFException; import java.io.IOException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; +import org.eclipse.jetty.websocket.api.CloseStatus; import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.common.CloseInfo; import org.eclipse.jetty.websocket.common.ConnectionState; @@ -62,10 +66,38 @@ public class IOState private ConnectionState state; private final List listeners = new CopyOnWriteArrayList<>(); + /** + * Is input on websocket available (for reading frames). + * Used to determine close handshake completion, and track half-close states + */ private boolean inputAvailable; + /** + * Is output on websocket available (for writing frames). + * Used to determine close handshake completion, and track half-closed states. + */ private boolean outputAvailable; + /** + * Initiator of the close handshake. + * Used to determine who initiated a close handshake for reply reasons. + */ private CloseHandshakeSource closeHandshakeSource; + /** + * The close info for the initiator of the close handshake. + * It is possible in abnormal close scenarios to have a different + * final close info that is used to notify the WS-Endpoint's onClose() + * events with. + */ private CloseInfo closeInfo; + /** + * Atomic reference to the final close info. + * This can only be set once, and is used for the WS-Endpoint's onClose() + * event. + */ + private AtomicReference finalClose = new AtomicReference<>(); + /** + * Tracker for if the close handshake was completed successfully by + * both sides. False if close was sudden or abnormal. + */ private boolean cleanClose; /** @@ -104,6 +136,11 @@ public class IOState public CloseInfo getCloseInfo() { + CloseInfo ci = finalClose.get(); + if (ci != null) + { + return ci; + } return closeInfo; } @@ -137,6 +174,7 @@ public class IOState private void notifyStateListeners(ConnectionState state) { + LOG.debug("Notify State Listeners: {}",state); for (ConnectionStateListener listener : listeners) { if (LOG.isDebugEnabled()) @@ -170,7 +208,7 @@ public class IOState } this.state = ConnectionState.CLOSED; - this.closeInfo = close; + finalClose.compareAndSet(null,close); this.inputAvailable = false; this.outputAvailable = false; this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL; @@ -185,6 +223,7 @@ public class IOState public void onCloseLocal(CloseInfo close) { ConnectionState event = null; + ConnectionState abnormalEvent = null; ConnectionState initialState = this.state; LOG.debug("onCloseLocal({}) : {}",close,initialState); if (initialState == ConnectionState.CLOSED) @@ -223,6 +262,7 @@ public class IOState LOG.debug("Close Handshake satisfied, disconnecting"); cleanClose = true; this.state = ConnectionState.CLOSED; + finalClose.compareAndSet(null,close); event = this.state; } else if (this.state == ConnectionState.OPEN) @@ -230,30 +270,27 @@ public class IOState // We are now entering CLOSING (or half-closed) this.state = ConnectionState.CLOSING; event = this.state; + + // if abnormal, we don't expect an answer. + if (close.isAbnormal()) + { + abnormalEvent = ConnectionState.CLOSED; + finalClose.compareAndSet(null,close); + cleanClose = false; + outputAvailable = false; + inputAvailable = false; + closeHandshakeSource = CloseHandshakeSource.ABNORMAL; + } } } // Only notify on state change events if (event != null) { - LOG.debug("notifying state listeners: {}",event); notifyStateListeners(event); - - // if abnormal, we don't expect an answer. - if (close.isAbnormal()) - { - LOG.debug("Abnormal close, disconnecting"); - synchronized (this) - { - state = ConnectionState.CLOSED; - cleanClose = false; - outputAvailable = false; - inputAvailable = false; - closeHandshakeSource = CloseHandshakeSource.ABNORMAL; - event = this.state; - } - notifyStateListeners(event); - return; + + if(abnormalEvent != null) { + notifyStateListeners(abnormalEvent); } } } @@ -291,6 +328,7 @@ public class IOState LOG.debug("Close Handshake satisfied, disconnecting"); cleanClose = true; state = ConnectionState.CLOSED; + finalClose.compareAndSet(null,close); event = this.state; } else if (this.state == ConnectionState.OPEN) @@ -315,15 +353,15 @@ public class IOState */ public void onConnected() { - if (this.state != ConnectionState.CONNECTING) - { - LOG.debug("Unable to set to connected, not in CONNECTING state: {}",this.state); - return; - } - ConnectionState event = null; synchronized (this) { + if (this.state != ConnectionState.CONNECTING) + { + LOG.debug("Unable to set to connected, not in CONNECTING state: {}",this.state); + return; + } + this.state = ConnectionState.CONNECTED; inputAvailable = false; // cannot read (yet) outputAvailable = true; // write allowed @@ -355,21 +393,21 @@ public class IOState */ public void onOpened() { - if (this.state == ConnectionState.OPEN) - { - // already opened - return; - } - - if (this.state != ConnectionState.CONNECTED) - { - LOG.debug("Unable to open, not in CONNECTED state: {}",this.state); - return; - } - ConnectionState event = null; synchronized (this) { + if (this.state == ConnectionState.OPEN) + { + // already opened + return; + } + + if (this.state != ConnectionState.CONNECTED) + { + LOG.debug("Unable to open, not in CONNECTED state: {}",this.state); + return; + } + this.state = ConnectionState.OPEN; this.inputAvailable = true; this.outputAvailable = true; @@ -379,11 +417,11 @@ public class IOState } /** - * The local endpoint has reached a read EOF. + * The local endpoint has reached a read failure. *

    * This could be a normal result after a proper close handshake, or even a premature close due to a connection disconnect. */ - public void onReadEOF() + public void onReadFailure(Throwable t) { ConnectionState event = null; synchronized (this) @@ -394,7 +432,29 @@ public class IOState return; } - CloseInfo close = new CloseInfo(StatusCode.ABNORMAL,"Read EOF"); + // Build out Close Reason + String reason = "WebSocket Read Failure"; + if (t instanceof EOFException) + { + reason = "WebSocket Read EOF"; + Throwable cause = t.getCause(); + if ((cause != null) && (StringUtil.isNotBlank(cause.getMessage()))) + { + reason = "EOF: " + cause.getMessage(); + } + } + else + { + if (StringUtil.isNotBlank(t.getMessage())) + { + reason = t.getMessage(); + } + } + + reason = CloseStatus.trimMaxReasonLength(reason); + CloseInfo close = new CloseInfo(StatusCode.ABNORMAL,reason); + + finalClose.compareAndSet(null,close); this.cleanClose = false; this.state = ConnectionState.CLOSED; @@ -407,6 +467,56 @@ public class IOState notifyStateListeners(event); } + /** + * The local endpoint has reached a write failure. + *

    + * A low level I/O failure, or even a jetty side EndPoint close (from idle timeout) are a few scenarios + */ + public void onWriteFailure(Throwable t) + { + ConnectionState event = null; + synchronized (this) + { + if (this.state == ConnectionState.CLOSED) + { + // already closed + return; + } + + // Build out Close Reason + String reason = "WebSocket Write Failure"; + if (t instanceof EOFException) + { + reason = "WebSocket Write EOF"; + Throwable cause = t.getCause(); + if ((cause != null) && (StringUtil.isNotBlank(cause.getMessage()))) + { + reason = "EOF: " + cause.getMessage(); + } + } + else + { + if (StringUtil.isNotBlank(t.getMessage())) + { + reason = t.getMessage(); + } + } + + reason = CloseStatus.trimMaxReasonLength(reason); + CloseInfo close = new CloseInfo(StatusCode.ABNORMAL,reason); + + finalClose.compareAndSet(null,close); + + this.cleanClose = false; + this.state = ConnectionState.CLOSED; + this.inputAvailable = false; + this.outputAvailable = false; + this.closeHandshakeSource = CloseHandshakeSource.ABNORMAL; + event = this.state; + } + notifyStateListeners(event); + } + public void onDisconnected() { ConnectionState event = null; @@ -451,7 +561,15 @@ public class IOState str.append("out"); if ((state == ConnectionState.CLOSED) || (state == ConnectionState.CLOSING)) { - str.append(",close=").append(closeInfo); + CloseInfo ci = finalClose.get(); + if (ci != null) + { + str.append(",finalClose=").append(ci); + } + else + { + str.append(",close=").append(closeInfo); + } str.append(",clean=").append(cleanClose); str.append(",closeSource=").append(closeHandshakeSource); } From 74e719535603ee0f35c8dcbaee2952cdf01f7ae8 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Wed, 7 May 2014 16:14:50 -0700 Subject: [PATCH 135/135] 433262 - WebSocket / Advanced close use cases + because of the race condition in close on testNetworkCongestion(), the test has been modified to allow for either close condition as valid. --- .../eclipse/jetty/websocket/client/ClientCloseTest.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java index 0242d0a33e9..3852ca32b04 100644 --- a/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java +++ b/jetty-websocket/websocket-client/src/test/java/org/eclipse/jetty/websocket/client/ClientCloseTest.java @@ -31,6 +31,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; @@ -78,6 +79,7 @@ public class ClientCloseTest public int closeCode = -1; public String closeReason = null; public CountDownLatch closeLatch = new CountDownLatch(1); + public AtomicInteger closeCount = new AtomicInteger(0); public CountDownLatch openLatch = new CountDownLatch(1); public EventQueue messageQueue = new EventQueue<>(); @@ -95,6 +97,7 @@ public class ClientCloseTest long maxTimeout = clientTimeoutMs * 2; Assert.assertThat("Client Close Event Occurred",closeLatch.await(maxTimeout,TimeUnit.MILLISECONDS),is(true)); + Assert.assertThat("Client Close Event Count",closeCount.get(),is(1)); Assert.assertThat("Client Close Event Status Code",closeCode,statusCodeMatcher); if (reasonMatcher == null) { @@ -133,6 +136,7 @@ public class ClientCloseTest { LOG.debug("onWebSocketClose({},{})",statusCode,reason); super.onWebSocketClose(statusCode,reason); + closeCount.incrementAndGet(); closeCode = statusCode; closeReason = reason; closeLatch.countDown(); @@ -427,7 +431,9 @@ public class ClientCloseTest // client idle timeout triggers close event on client ws-endpoint // client close event on ws-endpoint - clientSocket.assertReceivedCloseEvent(timeout,is(StatusCode.SHUTDOWN),containsString("Timeout")); + clientSocket.assertReceivedCloseEvent(timeout, + anyOf(is(StatusCode.SHUTDOWN),is(StatusCode.ABNORMAL)), + anyOf(containsString("Timeout"),containsString("Write"))); } @Test