diff --git a/VERSION.txt b/VERSION.txt index d68345e6a0d..57dd1679c57 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -31,6 +31,26 @@ jetty-9.3.6.v20151106 - 06 November 2015 + 481437 Port ConnectHandler connect and context functionality from Jetty 8. + 481554 DispatcherType reset race +jetty-9.2.14.v20151106 - 06 November 2015 + + 428474 Expose batch mode in the Jetty WebSocket API + + 471055 Restore legacy/experimental WebSocket extensions (deflate-frame) + + 472082 isOpen returns true on CLOSING Connection + + 474068 Update WebSocket Extension for permessage-deflate draft-22 + + 474319 Reintroduce blocking connect(). + + 474321 Allow synchronous address resolution. + + 474453 Tiny buffers (under 7 bytes) fail to compress in permessage-deflate + + 474454 Backport permessage-deflate from Jetty 9.3.x to 9.2.x + + 474936 WebSocketSessions are not always cleaned out from openSessions + + 476023 Incorrect trimming of WebSocket close reason + + 476049 When using WebSocket Session.close() there should be no status code + or reason sent + + 477385 Problem in MANIFEST.MF with version 9.2.10 / 9.2.13. + + 477817 Fixed memory leak in QueuedThreadPool + + 481006 SSL requests intermittently fail with EOFException when SSL + renegotiation is disallowed. + + 481236 Make ShutdownMonitor java security manager friendly + + 481437 Port ConnectHandler connect and context functionality from Jetty 8. + jetty-9.3.5.v20151012 - 12 October 2015 + 479343 calls to MetaData#orderFragments() with relative ordering adds duplicate jars diff --git a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java index 9ee43520287..493c767ac94 100644 --- a/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java +++ b/jetty-annotations/src/main/java/org/eclipse/jetty/annotations/WebServletAnnotation.java @@ -19,6 +19,8 @@ package org.eclipse.jetty.annotations; import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import javax.servlet.Servlet; import javax.servlet.annotation.WebInitParam; @@ -28,6 +30,7 @@ import javax.servlet.http.HttpServlet; import org.eclipse.jetty.servlet.Holder; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletMapping; +import org.eclipse.jetty.util.ArrayUtil; import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -50,13 +53,13 @@ public class WebServletAnnotation extends DiscoveredAnnotation { super(context, className); } - - + + public WebServletAnnotation (WebAppContext context, String className, Resource resource) { super(context, className, resource); } - + /** * @see DiscoveredAnnotation#apply() */ @@ -104,10 +107,11 @@ public class WebServletAnnotation extends DiscoveredAnnotation String servletName = (annotation.name().equals("")?clazz.getName():annotation.name()); MetaData metaData = _context.getMetaData(); + ServletMapping mapping = null; //the new mapping //Find out if a already exists with this name ServletHolder[] holders = _context.getServletHandler().getServlets(); - boolean isNew = true; + ServletHolder holder = null; if (holders != null) { @@ -116,13 +120,13 @@ public class WebServletAnnotation extends DiscoveredAnnotation if (h.getName() != null && servletName.equals(h.getName())) { holder = h; - isNew = false; break; } } } - if (isNew) + //handle creation/completion of a servlet + if (holder == null) { //No servlet of this name has already been defined, either by a descriptor //or another annotation (which would be impossible). @@ -147,11 +151,11 @@ public class WebServletAnnotation extends DiscoveredAnnotation } _context.getServletHandler().addServlet(holder); - ServletMapping mapping = new ServletMapping(); + + + mapping = new ServletMapping(); mapping.setServletName(holder.getName()); mapping.setPathSpecs( LazyList.toStringArray(urlPatternList)); - _context.getServletHandler().addServletMapping(mapping); - metaData.setOrigin(servletName+".servlet.mappings",annotation,clazz); } else { @@ -175,54 +179,102 @@ public class WebServletAnnotation extends DiscoveredAnnotation } } + //check the url-patterns //ServletSpec 3.0 p81 If a servlet already has url mappings from a - //webxml or fragment descriptor the annotation is ignored. However, we want to be able to - //replace mappings that were given in webdefault.xml - boolean mappingsExist = false; - boolean anyNonDefaults = false; - ServletMapping[] allMappings = _context.getServletHandler().getServletMappings(); - if (allMappings != null) - { - for (ServletMapping m:allMappings) - { - if (m.getServletName() != null && servletName.equals(m.getServletName())) - { - mappingsExist = true; - if (!m.isDefault()) - { - anyNonDefaults = true; - break; - } - } - } - } + //webxml or fragment descriptor the annotation is ignored. + //However, we want to be able to replace mappings that were given in webdefault.xml + List existingMappings = getServletMappingsForServlet(servletName); - if (anyNonDefaults) - return; //if any mappings already set by a descriptor that is not webdefault.xml, we're done - - boolean clash = false; - if (mappingsExist) + //if any mappings for this servlet already set by a descriptor that is not webdefault.xml forget + //about processing these url mappings + if (existingMappings.isEmpty() || !containsNonDefaultMappings(existingMappings)) { - for (String p:urlPatternList) - { - ServletMapping m = _context.getServletHandler().getServletMapping(p); - if (m != null && !m.isDefault()) - { - //trying to override a servlet-mapping that was added not by webdefault.xml - clash = true; - break; - } - } - } - - if (!mappingsExist || !clash) - { - ServletMapping m = new ServletMapping(); - m.setServletName(servletName); - m.setPathSpecs(LazyList.toStringArray(urlPatternList)); - _context.getServletHandler().addServletMapping(m); + mapping = new ServletMapping(); + mapping.setServletName(servletName); + mapping.setPathSpecs(LazyList.toStringArray(urlPatternList)); } } + + + //We also want to be able to replace mappings that were defined in webdefault.xml + //that were for a different servlet eg a mapping in webdefault.xml for / to the jetty + //default servlet should be able to be replaced by an annotation for / to a different + //servlet + if (mapping != null) + { + //url mapping was permitted by annotation processing rules + + //take a copy of the existing servlet mappings that we can iterate over and remove from. This is + //because the ServletHandler interface does not support removal of individual mappings. + List allMappings = ArrayUtil.asMutableList(_context.getServletHandler().getServletMappings()); + + //for each of the urls in the annotation, check if a mapping to same/different servlet exists + // if mapping exists and is from a default descriptor, it can be replaced. NOTE: we do not + // guard against duplicate path mapping here: that is the job of the ServletHandler + for (String p:urlPatternList) + { + ServletMapping existingMapping = _context.getServletHandler().getServletMapping(p); + if (existingMapping != null && existingMapping.isDefault()) + { + String[] updatedPaths = ArrayUtil.removeFromArray(existingMapping.getPathSpecs(), p); + //if we removed the last path from a servletmapping, delete the servletmapping + if (updatedPaths == null || updatedPaths.length == 0) + { + boolean success = allMappings.remove(existingMapping); + if (LOG.isDebugEnabled()) LOG.debug("Removed empty mapping {} from defaults descriptor success:{}",existingMapping, success); + } + else + { + existingMapping.setPathSpecs(updatedPaths); + if (LOG.isDebugEnabled()) LOG.debug("Removed path {} from mapping {} from defaults descriptor ", p,existingMapping); + } + } + _context.getMetaData().setOrigin(servletName+".servlet.mapping."+p, annotation, clazz); + } + allMappings.add(mapping); + _context.getServletHandler().setServletMappings(allMappings.toArray(new ServletMapping[allMappings.size()])); + } + } + + + + + /** + * @param name + * @return + */ + private List getServletMappingsForServlet (String name) + { + ServletMapping[] allMappings = _context.getServletHandler().getServletMappings(); + if (allMappings == null) + return Collections.emptyList(); + + List mappings = new ArrayList(); + for (ServletMapping m:allMappings) + { + if (m.getServletName() != null && name.equals(m.getServletName())) + { + mappings.add(m); + } + } + return mappings; + } + + + /** + * @param mappings + * @return + */ + private boolean containsNonDefaultMappings (List mappings) + { + if (mappings == null) + return false; + for (ServletMapping m:mappings) + { + if (!m.isDefault()) + return true; + } + return false; } } diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ServletD.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ServletD.java new file mode 100644 index 00000000000..4179e73c873 --- /dev/null +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/ServletD.java @@ -0,0 +1,31 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.annotations; + +import javax.servlet.annotation.WebInitParam; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; + + + +@WebServlet(urlPatterns = { "/", "/bah/*" }, name="DServlet", initParams={@WebInitParam(name="x", value="y")}, loadOnStartup=1, asyncSupported=false) +public class ServletD extends HttpServlet +{ + +} diff --git a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java index f4af95de772..b5088764082 100644 --- a/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java +++ b/jetty-annotations/src/test/java/org/eclipse/jetty/annotations/TestServletAnnotations.java @@ -18,12 +18,6 @@ package org.eclipse.jetty.annotations; -import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.assertTrue; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -37,6 +31,13 @@ import org.eclipse.jetty.webapp.DiscoveredAnnotation; import org.eclipse.jetty.webapp.WebAppContext; import org.junit.Test; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * TestServletAnnotations */ @@ -59,7 +60,7 @@ public class TestServletAnnotations _list.add(a); } } - + @Test public void testServletAnnotation() throws Exception { @@ -69,9 +70,9 @@ public class TestServletAnnotations WebAppContext wac = new WebAppContext(); List results = new ArrayList(); - + TestWebServletAnnotationHandler handler = new TestWebServletAnnotationHandler(wac, results); - + parser.parse(Collections.singleton(handler), classes, new ClassNameResolver () { public boolean isExcluded(String name) @@ -85,7 +86,7 @@ public class TestServletAnnotations } }); - + assertEquals(1, results.size()); assertTrue(results.get(0) instanceof WebServletAnnotation); @@ -94,14 +95,14 @@ public class TestServletAnnotations ServletHolder[] holders = wac.getServletHandler().getServlets(); assertNotNull(holders); assertEquals(1, holders.length); - + // Verify servlet annotations ServletHolder cholder = holders[0]; assertThat("Servlet Name", cholder.getName(), is("CServlet")); assertThat("InitParameter[x]", cholder.getInitParameter("x"), is("y")); assertThat("Init Order", cholder.getInitOrder(), is(2)); assertThat("Async Supported", cholder.isAsyncSupported(), is(false)); - + // Verify mappings ServletMapping[] mappings = wac.getServletHandler().getServletMappings(); assertNotNull(mappings); @@ -111,6 +112,195 @@ public class TestServletAnnotations assertEquals(2, paths.length); } + + @Test + public void testWebServletAnnotationOverrideDefault () throws Exception + { + //if the existing servlet mapping TO A DIFFERENT SERVLET IS from a default descriptor we + //DO allow the annotation to replace the mapping. + + WebAppContext wac = new WebAppContext(); + ServletHolder defaultServlet = new ServletHolder(); + defaultServlet.setClassName("org.eclipse.jetty.servlet.DefaultServlet"); + defaultServlet.setName("default"); + wac.getServletHandler().addServlet(defaultServlet); + + ServletMapping m = new ServletMapping(); + m.setPathSpec("/"); + m.setServletName("default"); + m.setDefault(true); //this mapping will be from a default descriptor + wac.getServletHandler().addServletMapping(m); + + WebServletAnnotation annotation = new WebServletAnnotation(wac, "org.eclipse.jetty.annotations.ServletD", null); + annotation.apply(); + + //test that as the original servlet mapping had only 1 pathspec, then the whole + //servlet mapping should be deleted as that pathspec will be remapped to the DServlet + ServletMapping[] resultMappings = wac.getServletHandler().getServletMappings(); + assertNotNull(resultMappings); + assertEquals(1, resultMappings.length); + assertEquals(2, resultMappings[0].getPathSpecs().length); + resultMappings[0].getServletName().equals("DServlet"); + for (String s:resultMappings[0].getPathSpecs()) + { + assertTrue (s.equals("/") || s.equals("/bah/*")); + } + } + + + + @Test + public void testWebServletAnnotationReplaceDefault () throws Exception + { + //if the existing servlet mapping TO A DIFFERENT SERVLET IS from a default descriptor we + //DO allow the annotation to replace the mapping. + WebAppContext wac = new WebAppContext(); + ServletHolder defaultServlet = new ServletHolder(); + defaultServlet.setClassName("org.eclipse.jetty.servlet.DefaultServlet"); + defaultServlet.setName("default"); + wac.getServletHandler().addServlet(defaultServlet); + + ServletMapping m = new ServletMapping(); + m.setPathSpec("/"); + m.setServletName("default"); + m.setDefault(true); //this mapping will be from a default descriptor + wac.getServletHandler().addServletMapping(m); + + ServletMapping m2 = new ServletMapping(); + m2.setPathSpec("/other"); + m2.setServletName("default"); + m2.setDefault(true); //this mapping will be from a default descriptor + wac.getServletHandler().addServletMapping(m2); + + WebServletAnnotation annotation = new WebServletAnnotation(wac, "org.eclipse.jetty.annotations.ServletD", null); + annotation.apply(); + + //test that only the mapping for "/" was removed from the mappings to the default servlet + ServletMapping[] resultMappings = wac.getServletHandler().getServletMappings(); + assertNotNull(resultMappings); + assertEquals(2, resultMappings.length); + for (ServletMapping r:resultMappings) + { + if (r.getServletName().equals("default")) + { + assertEquals(1,r.getPathSpecs().length); + assertEquals("/other", r.getPathSpecs()[0]); + } + else if (r.getServletName().equals("DServlet")) + { + assertEquals(2,r.getPathSpecs().length); + for (String p:r.getPathSpecs()) + { + if (!p.equals("/") && !p.equals("/bah/*")) + fail("Unexpected path"); + } + } + else + fail("Unexpected servlet mapping"); + } + + } + + + @Test + public void testWebServletAnnotationNotOverride () throws Exception + { + //if the existing servlet mapping TO A DIFFERENT SERVLET IS NOT from a default descriptor we + //DO NOT allow the annotation to replace the mapping + WebAppContext wac = new WebAppContext(); + ServletHolder servlet = new ServletHolder(); + servlet.setClassName("org.eclipse.jetty.servlet.FooServlet"); + servlet.setName("foo"); + wac.getServletHandler().addServlet(servlet); + ServletMapping m = new ServletMapping(); + m.setPathSpec("/"); + m.setServletName("foo"); + wac.getServletHandler().addServletMapping(m); + + WebServletAnnotation annotation = new WebServletAnnotation(wac, "org.eclipse.jetty.annotations.ServletD", null); + annotation.apply(); + + ServletMapping[] resultMappings = wac.getServletHandler().getServletMappings(); + assertEquals(2, resultMappings.length); + for (ServletMapping r:resultMappings) + { + if (r.getServletName().equals("DServlet")) + { + assertEquals(2, r.getPathSpecs().length); + } + else if (r.getServletName().equals("foo")) + { + assertEquals(1, r.getPathSpecs().length); + } + else + fail("Unexpected servlet name"); + } + } + + @Test + public void testWebServletAnnotationIgnore () throws Exception + { + //an existing servlet OF THE SAME NAME has even 1 non-default mapping we can't use + //any of the url mappings in the annotation + WebAppContext wac = new WebAppContext(); + ServletHolder servlet = new ServletHolder(); + servlet.setClassName("org.eclipse.jetty.servlet.OtherDServlet"); + servlet.setName("DServlet"); + wac.getServletHandler().addServlet(servlet); + + ServletMapping m = new ServletMapping(); + m.setPathSpec("/default"); + m.setDefault(true); + m.setServletName("DServlet"); + wac.getServletHandler().addServletMapping(m); + + ServletMapping m2 = new ServletMapping(); + m2.setPathSpec("/other"); + m2.setServletName("DServlet"); + wac.getServletHandler().addServletMapping(m2); + + WebServletAnnotation annotation = new WebServletAnnotation(wac, "org.eclipse.jetty.annotations.ServletD", null); + annotation.apply(); + + ServletMapping[] resultMappings = wac.getServletHandler().getServletMappings(); + assertEquals(2, resultMappings.length); + + for (ServletMapping r:resultMappings) + { + assertEquals(1, r.getPathSpecs().length); + if (!r.getPathSpecs()[0].equals("/default") && !r.getPathSpecs()[0].equals("/other")) + fail("Unexpected path in mapping"); + } + + } + + @Test + public void testWebServletAnnotationNoMappings () throws Exception + { + //an existing servlet OF THE SAME NAME has no mappings, therefore all mappings in the annotation + //should be accepted + WebAppContext wac = new WebAppContext(); + ServletHolder servlet = new ServletHolder(); + servlet.setName("foo"); + wac.getServletHandler().addServlet(servlet); + + + WebServletAnnotation annotation = new WebServletAnnotation(wac, "org.eclipse.jetty.annotations.ServletD", null); + annotation.apply(); + + ServletMapping[] resultMappings = wac.getServletHandler().getServletMappings(); + assertEquals(1, resultMappings.length); + assertEquals(2, resultMappings[0].getPathSpecs().length); + for (String s:resultMappings[0].getPathSpecs()) + { + if (!s.equals("/") && !s.equals("/bah/*")) + fail("Unexpected path mapping"); + } + } + + + + @Test public void testDeclareRoles () throws Exception { 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 04bdf627313..c711aca7956 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 @@ -324,27 +324,34 @@ public abstract class HttpReceiver } else { - List decodeds = new ArrayList<>(2); - while (buffer.hasRemaining()) + try { - ByteBuffer decoded = decoder.decode(buffer); - if (!decoded.hasRemaining()) - continue; - decodeds.add(decoded); - if (LOG.isDebugEnabled()) - LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.lineSeparator(), BufferUtil.toDetailString(decoded)); - } + List decodeds = new ArrayList<>(2); + while (buffer.hasRemaining()) + { + ByteBuffer decoded = decoder.decode(buffer); + if (!decoded.hasRemaining()) + continue; + decodeds.add(decoded); + if (LOG.isDebugEnabled()) + LOG.debug("Response content decoded ({}) {}{}{}", decoder, response, System.lineSeparator(), BufferUtil.toDetailString(decoded)); + } - if (decodeds.isEmpty()) - { - callback.succeeded(); + if (decodeds.isEmpty()) + { + callback.succeeded(); + } + else + { + int size = decodeds.size(); + CountingCallback counter = new CountingCallback(callback, size); + for (int i = 0; i < size; ++i) + notifier.notifyContent(listeners, response, decodeds.get(i), counter); + } } - else + catch (Throwable x) { - int size = decodeds.size(); - CountingCallback counter = new CountingCallback(callback, size); - for (int i = 0; i < size; ++i) - notifier.notifyContent(listeners, response, decodeds.get(i), counter); + callback.failed(x); } } 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 bf6c039d465..9c6d86b6223 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 @@ -218,16 +218,17 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res parser.setHeadResponse(HttpMethod.HEAD.is(method) || HttpMethod.CONNECT.is(method)); exchange.getResponse().version(version).status(status).reason(reason); - responseBegin(exchange); - return false; + return !responseBegin(exchange); } @Override public void parsedHeader(HttpField field) { HttpExchange exchange = getHttpExchange(); - if (exchange != null) - responseHeader(exchange, field); + if (exchange == null) + return; + + responseHeader(exchange, field); } @Override @@ -237,8 +238,7 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res if (exchange == null) return false; - responseHeaders(exchange); - return false; + return !responseHeaders(exchange); } @Override @@ -263,17 +263,20 @@ public class HttpReceiverOverHTTP extends HttpReceiver implements HttpParser.Res failAndClose(x); } }; - responseContent(exchange, buffer, callback); - return callback.tryComplete(); + // Do not short circuit these calls. + boolean proceed = responseContent(exchange, buffer, callback); + boolean async = callback.tryComplete(); + return !proceed || async; } @Override public boolean messageComplete() { HttpExchange exchange = getHttpExchange(); - if (exchange != null) - responseSuccess(exchange); - return false; + if (exchange == null) + return false; + + return !responseSuccess(exchange); } @Override diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientGZIPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientGZIPTest.java index 082690728f1..b198dadf849 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientGZIPTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientGZIPTest.java @@ -22,6 +22,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InterruptedIOException; import java.util.Arrays; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.zip.GZIPOutputStream; @@ -196,6 +197,33 @@ public class HttpClientGZIPTest extends AbstractHttpClientServerTest Assert.assertArrayEquals(data, response.getContent()); } + @Test + public void testGZIPContentCorrupted() throws Exception + { + start(new AbstractHandler() + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + baseRequest.setHandled(true); + response.setHeader("Content-Encoding", "gzip"); + // Not gzipped, will cause the client to blow up. + response.getOutputStream().print("0123456789"); + } + }); + + final CountDownLatch latch = new CountDownLatch(1); + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scheme) + .send(result -> + { + if (result.isFailed()) + latch.countDown(); + }); + + Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); + } + private static void sleep(long ms) throws IOException { try 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 4d9691de506..0d71cfd3eda 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 @@ -374,9 +374,10 @@ public class HttpConnectionOverFCGI extends AbstractConnection implements Connec close(x); } }; - if (!channel.content(buffer, callback)) - return true; - return callback.tryComplete(); + // Do not short circuit these calls. + boolean proceed = channel.content(buffer, callback); + boolean async = callback.tryComplete(); + return !proceed || async; } else { diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java index e66888fb92b..855e0899086 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/parser/HeaderBlockParser.java @@ -75,7 +75,12 @@ public class HeaderBlockParser MetaData result = hpackDecoder.decode(toDecode); buffer.limit(limit); - byteBufferPool.release(blockBuffer); + + if (blockBuffer != null) + { + byteBufferPool.release(blockBuffer); + blockBuffer = null; + } return result; } diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java index dd7f83760d8..2db99624324 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/DataGenerateParseTest.java @@ -130,18 +130,23 @@ public class DataGenerateParseTest } }, 4096, 8192); - ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - ByteBuffer data = ByteBuffer.wrap(largeContent); - generator.generateData(lease, 13, data.slice(), true, data.remaining()); - - for (ByteBuffer buffer : lease.getByteBuffers()) + // Iterate a few times to be sure generator and parser are properly reset. + for (int i = 0; i < 2; ++i) { - while (buffer.hasRemaining()) - { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); + ByteBuffer data = ByteBuffer.wrap(largeContent); + generator.generateData(lease, 13, data.slice(), true, data.remaining()); - Assert.assertEquals(largeContent.length, frames.size()); + frames.clear(); + for (ByteBuffer buffer : lease.getByteBuffers()) + { + while (buffer.hasRemaining()) + { + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + } + } + + Assert.assertEquals(largeContent.length, frames.size()); + } } } diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java index b53225a36ec..a51874170e5 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/GoAwayGenerateParseTest.java @@ -96,21 +96,26 @@ public class GoAwayGenerateParseTest byte[] payload = new byte[16]; new Random().nextBytes(payload); - ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - generator.generateGoAway(lease, lastStreamId, error, payload); - - for (ByteBuffer buffer : lease.getByteBuffers()) + // Iterate a few times to be sure generator and parser are properly reset. + for (int i = 0; i < 2; ++i) { - while (buffer.hasRemaining()) - { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); + generator.generateGoAway(lease, lastStreamId, error, payload); - Assert.assertEquals(1, frames.size()); - GoAwayFrame frame = frames.get(0); - Assert.assertEquals(lastStreamId, frame.getLastStreamId()); - Assert.assertEquals(error, frame.getError()); - Assert.assertArrayEquals(payload, frame.getPayload()); + frames.clear(); + for (ByteBuffer buffer : lease.getByteBuffers()) + { + while (buffer.hasRemaining()) + { + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + } + } + + Assert.assertEquals(1, frames.size()); + GoAwayFrame frame = frames.get(0); + Assert.assertEquals(lastStreamId, frame.getLastStreamId()); + Assert.assertEquals(error, frame.getError()); + Assert.assertArrayEquals(payload, frame.getPayload()); + } } } diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java index a7796f0a32f..25bb969f4de 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/HeadersGenerateParseTest.java @@ -114,41 +114,47 @@ public class HeadersGenerateParseTest } }, 4096, 8192); - int streamId = 13; - HttpFields fields = new HttpFields(); - fields.put("Accept", "text/html"); - fields.put("User-Agent", "Jetty"); - MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:8080"), "/path", HttpVersion.HTTP_2, fields); - - ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - PriorityFrame priorityFrame = new PriorityFrame(streamId, 3 * streamId, 200, true); - generator.generateHeaders(lease, streamId, metaData, priorityFrame, true); - - for (ByteBuffer buffer : lease.getByteBuffers()) + // Iterate a few times to be sure generator and parser are properly reset. + for (int i = 0; i < 2; ++i) { - while (buffer.hasRemaining()) + int streamId = 13; + HttpFields fields = new HttpFields(); + fields.put("Accept", "text/html"); + fields.put("User-Agent", "Jetty"); + MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:8080"), "/path", HttpVersion.HTTP_2, fields); + + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); + PriorityFrame priorityFrame = new PriorityFrame(streamId, 3 * streamId, 200, true); + generator.generateHeaders(lease, streamId, metaData, priorityFrame, true); + + frames.clear(); + for (ByteBuffer buffer : lease.getByteBuffers()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + buffer = buffer.slice(); + while (buffer.hasRemaining()) + { + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + } } - } - Assert.assertEquals(1, frames.size()); - HeadersFrame frame = frames.get(0); - Assert.assertEquals(streamId, frame.getStreamId()); - Assert.assertTrue(frame.isEndStream()); - MetaData.Request request = (MetaData.Request)frame.getMetaData(); - Assert.assertEquals(metaData.getMethod(), request.getMethod()); - Assert.assertEquals(metaData.getURI(), request.getURI()); - for (int j = 0; j < fields.size(); ++j) - { - HttpField field = fields.getField(j); - Assert.assertTrue(request.getFields().contains(field)); + Assert.assertEquals(1, frames.size()); + HeadersFrame frame = frames.get(0); + Assert.assertEquals(streamId, frame.getStreamId()); + Assert.assertTrue(frame.isEndStream()); + MetaData.Request request = (MetaData.Request)frame.getMetaData(); + Assert.assertEquals(metaData.getMethod(), request.getMethod()); + Assert.assertEquals(metaData.getURI(), request.getURI()); + for (int j = 0; j < fields.size(); ++j) + { + HttpField field = fields.getField(j); + Assert.assertTrue(request.getFields().contains(field)); + } + PriorityFrame priority = frame.getPriority(); + Assert.assertNotNull(priority); + Assert.assertEquals(priorityFrame.getStreamId(), priority.getStreamId()); + Assert.assertEquals(priorityFrame.getParentStreamId(), priority.getParentStreamId()); + Assert.assertEquals(priorityFrame.getWeight(), priority.getWeight()); + Assert.assertEquals(priorityFrame.isExclusive(), priority.isExclusive()); } - PriorityFrame priority = frame.getPriority(); - Assert.assertNotNull(priority); - Assert.assertEquals(priorityFrame.getStreamId(), priority.getStreamId()); - Assert.assertEquals(priorityFrame.getParentStreamId(), priority.getParentStreamId()); - Assert.assertEquals(priorityFrame.getWeight(), priority.getWeight()); - Assert.assertEquals(priorityFrame.isExclusive(), priority.isExclusive()); } } diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java index b3e85aeab48..96f20c17742 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PingGenerateParseTest.java @@ -93,21 +93,26 @@ public class PingGenerateParseTest byte[] payload = new byte[8]; new Random().nextBytes(payload); - ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - generator.generatePing(lease, payload, true); - - for (ByteBuffer buffer : lease.getByteBuffers()) + // Iterate a few times to be sure generator and parser are properly reset. + for (int i = 0; i < 2; ++i) { - while (buffer.hasRemaining()) - { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); + generator.generatePing(lease, payload, true); - Assert.assertEquals(1, frames.size()); - PingFrame frame = frames.get(0); - Assert.assertArrayEquals(payload, frame.getPayload()); - Assert.assertTrue(frame.isReply()); + frames.clear(); + for (ByteBuffer buffer : lease.getByteBuffers()) + { + while (buffer.hasRemaining()) + { + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + } + } + + Assert.assertEquals(1, frames.size()); + PingFrame frame = frames.get(0); + Assert.assertArrayEquals(payload, frame.getPayload()); + Assert.assertTrue(frame.isReply()); + } } @Test diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java index 44dbc0cebba..664c4dd181b 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PriorityGenerateParseTest.java @@ -98,22 +98,27 @@ public class PriorityGenerateParseTest int weight = 3; boolean exclusive = true; - ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - generator.generatePriority(lease, streamId, parentStreamId, weight, exclusive); - - for (ByteBuffer buffer : lease.getByteBuffers()) + // Iterate a few times to be sure generator and parser are properly reset. + for (int i = 0; i < 2; ++i) { - while (buffer.hasRemaining()) - { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); + generator.generatePriority(lease, streamId, parentStreamId, weight, exclusive); - Assert.assertEquals(1, frames.size()); - PriorityFrame frame = frames.get(0); - Assert.assertEquals(streamId, frame.getStreamId()); - Assert.assertEquals(parentStreamId, frame.getParentStreamId()); - Assert.assertEquals(weight, frame.getWeight()); - Assert.assertEquals(exclusive, frame.isExclusive()); + frames.clear(); + for (ByteBuffer buffer : lease.getByteBuffers()) + { + while (buffer.hasRemaining()) + { + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + } + } + + Assert.assertEquals(1, frames.size()); + PriorityFrame frame = frames.get(0); + Assert.assertEquals(streamId, frame.getStreamId()); + Assert.assertEquals(parentStreamId, frame.getParentStreamId()); + Assert.assertEquals(weight, frame.getWeight()); + Assert.assertEquals(exclusive, frame.isExclusive()); + } } } diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java index 78a08f9796d..fd3d50c68f4 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/PushPromiseGenerateParseTest.java @@ -115,28 +115,33 @@ public class PushPromiseGenerateParseTest fields.put("User-Agent", "Jetty"); MetaData.Request metaData = new MetaData.Request("GET", HttpScheme.HTTP, new HostPortHttpField("localhost:8080"), "/path", HttpVersion.HTTP_2, fields); - ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - generator.generatePushPromise(lease, streamId, promisedStreamId, metaData); - - for (ByteBuffer buffer : lease.getByteBuffers()) + // Iterate a few times to be sure generator and parser are properly reset. + for (int i = 0; i < 2; ++i) { - while (buffer.hasRemaining()) + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); + generator.generatePushPromise(lease, streamId, promisedStreamId, metaData); + + frames.clear(); + for (ByteBuffer buffer : lease.getByteBuffers()) { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + while (buffer.hasRemaining()) + { + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + } } - } - Assert.assertEquals(1, frames.size()); - PushPromiseFrame frame = frames.get(0); - Assert.assertEquals(streamId, frame.getStreamId()); - Assert.assertEquals(promisedStreamId, frame.getPromisedStreamId()); - MetaData.Request request = (MetaData.Request)frame.getMetaData(); - Assert.assertEquals(metaData.getMethod(), request.getMethod()); - Assert.assertEquals(metaData.getURI(), request.getURI()); - for (int j = 0; j < fields.size(); ++j) - { - HttpField field = fields.getField(j); - Assert.assertTrue(request.getFields().contains(field)); + Assert.assertEquals(1, frames.size()); + PushPromiseFrame frame = frames.get(0); + Assert.assertEquals(streamId, frame.getStreamId()); + Assert.assertEquals(promisedStreamId, frame.getPromisedStreamId()); + MetaData.Request request = (MetaData.Request)frame.getMetaData(); + Assert.assertEquals(metaData.getMethod(), request.getMethod()); + Assert.assertEquals(metaData.getURI(), request.getURI()); + for (int j = 0; j < fields.size(); ++j) + { + HttpField field = fields.getField(j); + Assert.assertTrue(request.getFields().contains(field)); + } } } } diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java index 66633e64e2f..8936c5372f1 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/ResetGenerateParseTest.java @@ -92,20 +92,25 @@ public class ResetGenerateParseTest int streamId = 13; int error = 17; - ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - generator.generateReset(lease, streamId, error); - - for (ByteBuffer buffer : lease.getByteBuffers()) + // Iterate a few times to be sure generator and parser are properly reset. + for (int i = 0; i < 2; ++i) { - while (buffer.hasRemaining()) - { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); + generator.generateReset(lease, streamId, error); - Assert.assertEquals(1, frames.size()); - ResetFrame frame = frames.get(0); - Assert.assertEquals(streamId, frame.getStreamId()); - Assert.assertEquals(error, frame.getError()); + frames.clear(); + for (ByteBuffer buffer : lease.getByteBuffers()) + { + while (buffer.hasRemaining()) + { + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + } + } + + Assert.assertEquals(1, frames.size()); + ResetFrame frame = frames.get(0); + Assert.assertEquals(streamId, frame.getStreamId()); + Assert.assertEquals(error, frame.getError()); + } } } diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java index 15343f406ca..c0a7ee633a7 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/SettingsGenerateParseTest.java @@ -155,22 +155,27 @@ public class SettingsGenerateParseTest Integer value = 17; settings1.put(key, value); - ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - generator.generateSettings(lease, settings1, true); - - for (ByteBuffer buffer : lease.getByteBuffers()) + // Iterate a few times to be sure generator and parser are properly reset. + for (int i = 0; i < 2; ++i) { - while (buffer.hasRemaining()) - { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); + generator.generateSettings(lease, settings1, true); - Assert.assertEquals(1, frames.size()); - SettingsFrame frame = frames.get(0); - Map settings2 = frame.getSettings(); - Assert.assertEquals(1, settings2.size()); - Assert.assertEquals(value, settings2.get(key)); - Assert.assertTrue(frame.isReply()); + frames.clear(); + for (ByteBuffer buffer : lease.getByteBuffers()) + { + while (buffer.hasRemaining()) + { + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + } + } + + Assert.assertEquals(1, frames.size()); + SettingsFrame frame = frames.get(0); + Map settings2 = frame.getSettings(); + Assert.assertEquals(1, settings2.size()); + Assert.assertEquals(value, settings2.get(key)); + Assert.assertTrue(frame.isReply()); + } } } diff --git a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java index 3cd2e6bdcc8..1a53d241e0f 100644 --- a/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java +++ b/jetty-http2/http2-common/src/test/java/org/eclipse/jetty/http2/frames/WindowUpdateGenerateParseTest.java @@ -92,20 +92,25 @@ public class WindowUpdateGenerateParseTest int streamId = 13; int windowUpdate = 17; - ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); - generator.generateWindowUpdate(lease, streamId, windowUpdate); - - for (ByteBuffer buffer : lease.getByteBuffers()) + // Iterate a few times to be sure generator and parser are properly reset. + for (int i = 0; i < 2; ++i) { - while (buffer.hasRemaining()) - { - parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); - } - } + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(byteBufferPool); + generator.generateWindowUpdate(lease, streamId, windowUpdate); - Assert.assertEquals(1, frames.size()); - WindowUpdateFrame frame = frames.get(0); - Assert.assertEquals(streamId, frame.getStreamId()); - Assert.assertEquals(windowUpdate, frame.getWindowDelta()); + frames.clear(); + for (ByteBuffer buffer : lease.getByteBuffers()) + { + while (buffer.hasRemaining()) + { + parser.parse(ByteBuffer.wrap(new byte[]{buffer.get()})); + } + } + + Assert.assertEquals(1, frames.size()); + WindowUpdateFrame frame = frames.get(0); + Assert.assertEquals(streamId, frame.getStreamId()); + Assert.assertEquals(windowUpdate, frame.getWindowDelta()); + } } } 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 0fed318d8b8..b9519021a0f 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 @@ -660,8 +660,6 @@ public class ServletHolder extends Holder implements UserIdentity.Scope /* Set the webapp's classpath for Jasper */ ch.setAttribute("org.apache.catalina.jsp_classpath", ch.getClassPath()); - System.err.println("JSP ("+ch+","+getName()+") CP="+ch.getClassPath()); - /* Set up other classpath attribute */ if ("?".equals(getInitParameter("classpath"))) { diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java index 460f3799d9f..0b1749e1aae 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/AsyncServletIOTest.java @@ -614,6 +614,9 @@ public class AsyncServletIOTest @Test public void testCompleteWhilePending() throws Exception { + _servlet4.onDA.set(0); + _servlet4.onWP.set(0); + StringBuilder request = new StringBuilder(512); request.append("POST /ctx/path4/info HTTP/1.1\r\n") .append("Host: localhost\r\n") @@ -674,7 +677,7 @@ public class AsyncServletIOTest } catch(IOException e) { - + // ignored } LOG.debug("last: "+last); @@ -686,8 +689,8 @@ public class AsyncServletIOTest assertTrue(_servlet4.completed.await(5, TimeUnit.SECONDS)); Thread.sleep(100); - assertEquals(2,_servlet4.onDA.get()); - assertEquals(2,_servlet4.onWP.get()); + assertEquals(0,_servlet4.onDA.get()); + assertEquals(0,_servlet4.onWP.get()); } @@ -708,7 +711,6 @@ public class AsyncServletIOTest in.setReadListener(new ReadListener() { - @Override public void onError(Throwable t) { @@ -719,59 +721,61 @@ public class AsyncServletIOTest public void onDataAvailable() throws IOException { onDA.incrementAndGet(); - if (onDA.get()>2) - return; - + + boolean readF=false; // Read all available content while(in.isReady()) - if (in.read()<0) - throw new IllegalStateException(); - - if (onDA.get()==1) - return; - - final byte[] buffer = new byte[64*1024]; - Arrays.fill(buffer,(byte)'X'); - for (int i=199;i2) - return; - - while (out.isReady()) - out.write(buffer); - - if (onWP.get()==1) - return; - - try + @Override + public void onWritePossible() throws IOException { - // As soon as we are write blocked, complete - async.complete(); + onWP.incrementAndGet(); + + while (out.isReady()) + out.write(buffer); + + try + { + // As soon as we are write blocked, complete + onWP.set(0); + async.complete(); + } + catch(Exception e) + { + e.printStackTrace(); + } + finally + { + completed.countDown(); + } } - catch(Exception e) + + @Override + public void onError(Throwable t) { - e.printStackTrace(); + t.printStackTrace(); } - finally - { - completed.countDown(); - } - } - - @Override - public void onError(Throwable t) - { - t.printStackTrace(); - } - }); + }); + } } @Override 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 index 156b4c20bc0..69fe60a301f 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/CompletableCallback.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/CompletableCallback.java @@ -84,6 +84,10 @@ public abstract class CompletableCallback implements Callback } break; } + case FAILED: + { + return; + } default: { throw new IllegalStateException(current.toString()); @@ -110,6 +114,10 @@ public abstract class CompletableCallback implements Callback } break; } + case FAILED: + { + return; + } default: { throw new IllegalStateException(current.toString()); diff --git a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java index c2f95755dec..60016f4d1db 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java +++ b/jetty-websocket/javax-websocket-server-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/server/JsrCreator.java @@ -20,7 +20,9 @@ package org.eclipse.jetty.websocket.jsr356.server; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; import javax.websocket.Extension; import javax.websocket.Extension.Parameter; @@ -45,6 +47,7 @@ public class JsrCreator implements WebSocketCreator { public static final String PROP_REMOTE_ADDRESS = "javax.websocket.endpoint.remoteAddress"; public static final String PROP_LOCAL_ADDRESS = "javax.websocket.endpoint.localAddress"; + public static final String PROP_LOCALES = "javax.websocket.upgrade.locales"; private static final Logger LOG = Log.getLogger(JsrCreator.class); private final WebSocketContainerScope containerScope; private final ServerEndpointMetadata metadata; @@ -74,8 +77,10 @@ public class JsrCreator implements WebSocketCreator // This is being implemented as an optional set of userProperties so that // it is not JSR api breaking. A few users on #jetty and a few from cometd // have asked for access to this information. - config.getUserProperties().put(PROP_LOCAL_ADDRESS,req.getLocalSocketAddress()); - config.getUserProperties().put(PROP_REMOTE_ADDRESS,req.getRemoteSocketAddress()); + Map userProperties = config.getUserProperties(); + userProperties.put(PROP_LOCAL_ADDRESS,req.getLocalSocketAddress()); + userProperties.put(PROP_REMOTE_ADDRESS,req.getRemoteSocketAddress()); + userProperties.put(PROP_LOCALES,Collections.list(req.getLocales())); // Get Configurator from config object (not guaranteed to be unique per endpoint upgrade) ServerEndpointConfig.Configurator configurator = config.getConfigurator(); diff --git a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StreamTest.java b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StreamTest.java index ca51ec54367..7a39a440964 100644 --- a/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StreamTest.java +++ b/jetty-websocket/javax-websocket-server-impl/src/test/java/org/eclipse/jetty/websocket/jsr356/server/StreamTest.java @@ -28,13 +28,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URI; -import java.security.DigestOutputStream; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.websocket.ClientEndpoint; import javax.websocket.CloseReason; @@ -63,7 +59,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.websocket.common.test.LeakTrackingBufferPoolRule; -import org.eclipse.jetty.websocket.common.util.Hex; +import org.eclipse.jetty.websocket.common.util.Sha1Sum; import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; import org.junit.AfterClass; import org.junit.Assert; @@ -176,33 +172,12 @@ public class StreamTest Assert.assertThat("Path should exist: " + file,file.exists(),is(true)); Assert.assertThat("Path should not be a directory:" + file,file.isDirectory(),is(false)); - String expectedSha1 = loadExpectedSha1Sum(sha1File); - String actualSha1 = calculateSha1Sum(file); + String expectedSha1 = Sha1Sum.loadSha1(sha1File); + String actualSha1 = Sha1Sum.calculate(file); Assert.assertThat("SHA1Sum of content: " + file,expectedSha1,equalToIgnoringCase(actualSha1)); } - private String calculateSha1Sum(File file) throws IOException, NoSuchAlgorithmException - { - MessageDigest digest = MessageDigest.getInstance("SHA1"); - try (FileInputStream fis = new FileInputStream(file); - NoOpOutputStream noop = new NoOpOutputStream(); - DigestOutputStream digester = new DigestOutputStream(noop,digest)) - { - IO.copy(fis,digester); - return Hex.asHex(digest.digest()); - } - } - - private String loadExpectedSha1Sum(File sha1File) throws IOException - { - String contents = IO.readToString(sha1File); - Pattern pat = Pattern.compile("^[0-9A-Fa-f]*"); - Matcher mat = pat.matcher(contents); - Assert.assertTrue("Should have found HEX code in SHA1 file: " + sha1File,mat.find()); - return mat.group(); - } - @ClientEndpoint public static class ClientSocket { @@ -317,32 +292,4 @@ public class StreamTest t.printStackTrace(System.err); } } - - private static class NoOpOutputStream extends OutputStream - { - @Override - public void write(byte[] b) throws IOException - { - } - - @Override - public void write(byte[] b, int off, int len) throws IOException - { - } - - @Override - public void flush() throws IOException - { - } - - @Override - public void close() throws IOException - { - } - - @Override - public void write(int b) throws IOException - { - } - } } diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java index 5c27104c594..ec73e3c384e 100644 --- a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/extensions/compress/CompressExtension.java @@ -154,30 +154,33 @@ public abstract class CompressExtension extends AbstractExtension return; } byte[] output = new byte[DECOMPRESS_BUF_SIZE]; - - if (inflater.needsInput() && !supplyInput(inflater,buf)) + + while(buf.hasRemaining() && inflater.needsInput()) { - LOG.debug("Needed input, but no buffer could supply input"); - return; - } - - int read = 0; - while ((read = inflater.inflate(output)) >= 0) - { - if (read == 0) + if (!supplyInput(inflater,buf)) { - LOG.debug("Decompress: read 0 {}",toDetail(inflater)); - break; + LOG.debug("Needed input, but no buffer could supply input"); + return; } - else + + int read = 0; + while ((read = inflater.inflate(output)) >= 0) { - // do something with output - if (LOG.isDebugEnabled()) + if (read == 0) { - LOG.debug("Decompressed {} bytes: {}",read,toDetail(inflater)); + LOG.debug("Decompress: read 0 {}",toDetail(inflater)); + break; + } + else + { + // do something with output + if (LOG.isDebugEnabled()) + { + LOG.debug("Decompressed {} bytes: {}",read,toDetail(inflater)); + } + + accumulator.copyChunk(output,0,read); } - - accumulator.copyChunk(output,0,read); } } diff --git a/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/Sha1Sum.java b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/Sha1Sum.java new file mode 100644 index 00000000000..f3771efb8a3 --- /dev/null +++ b/jetty-websocket/websocket-common/src/test/java/org/eclipse/jetty/websocket/common/util/Sha1Sum.java @@ -0,0 +1,110 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.DigestOutputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.jetty.toolchain.test.IO; +import org.junit.Assert; + +/** + * Calculate the sha1sum for various content + */ +public class Sha1Sum +{ + private static class NoOpOutputStream extends OutputStream + { + @Override + public void write(byte[] b) throws IOException + { + } + + @Override + public void write(byte[] b, int off, int len) throws IOException + { + } + + @Override + public void flush() throws IOException + { + } + + @Override + public void close() throws IOException + { + } + + @Override + public void write(int b) throws IOException + { + } + } + + public static String calculate(File file) throws NoSuchAlgorithmException, IOException + { + return calculate(file.toPath()); + } + + public static String calculate(Path path) throws NoSuchAlgorithmException, IOException + { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + try (InputStream in = Files.newInputStream(path,StandardOpenOption.READ); + NoOpOutputStream noop = new NoOpOutputStream(); + DigestOutputStream digester = new DigestOutputStream(noop,digest)) + { + IO.copy(in,digester); + return Hex.asHex(digest.digest()); + } + } + + public static String calculate(byte[] buf) throws NoSuchAlgorithmException + { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + digest.update(buf); + return Hex.asHex(digest.digest()); + } + + public static String calculate(byte[] buf, int offset, int len) throws NoSuchAlgorithmException + { + MessageDigest digest = MessageDigest.getInstance("SHA1"); + digest.update(buf,offset,len); + return Hex.asHex(digest.digest()); + } + + public static String loadSha1(File sha1File) throws IOException + { + String contents = IO.readToString(sha1File); + Pattern pat = Pattern.compile("^[0-9A-Fa-f]*"); + Matcher mat = pat.matcher(contents); + Assert.assertTrue("Should have found HEX code in SHA1 file: " + sha1File,mat.find()); + return mat.group(); + } + +} 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 5242339e6aa..1673d6086f1 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 @@ -20,34 +20,90 @@ package org.eclipse.jetty.websocket.server; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Future; 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.api.Session; +import org.eclipse.jetty.websocket.api.WebSocketPolicy; +import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig; +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.common.test.LeakTrackingBufferPoolRule; +import org.eclipse.jetty.websocket.common.util.Sha1Sum; +import org.eclipse.jetty.websocket.server.helper.CaptureSocket; import org.eclipse.jetty.websocket.server.helper.EchoServlet; -import org.junit.AfterClass; +import org.junit.After; import org.junit.Assert; import org.junit.Assume; -import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +@RunWith(Parameterized.class) public class PerMessageDeflateExtensionTest { - private static SimpleServletServer server; - - @BeforeClass - public static void startServer() throws Exception + private static enum TestCaseMessageSize { - server = new SimpleServletServer(new EchoServlet()); - server.start(); + TINY(10), + SMALL(1024), + MEDIUM(10*1024), + LARGE(100*1024), + HUGE(1024*1024); + + private int size; + + private TestCaseMessageSize(int size) + { + this.size = size; + } + } + + @Parameters(name = "{0} ({3}) (Input Buffer Size: {4} bytes)") + public static List modes() + { + List modes = new ArrayList<>(); + + for(TestCaseMessageSize size: TestCaseMessageSize.values()) + { + modes.add(new Object[] { "Normal HTTP/WS", false, "ws", size, -1 }); + modes.add(new Object[] { "Encrypted HTTPS/WSS", true, "wss", size, -1 }); + int altInputBufSize = 15*1024; + modes.add(new Object[] { "Normal HTTP/WS", false, "ws", size, altInputBufSize }); + modes.add(new Object[] { "Encrypted HTTPS/WSS", true, "wss", size, altInputBufSize }); + } + + return modes; } - @AfterClass - public static void stopServer() + @Rule + public LeakTrackingBufferPoolRule bufferPool = new LeakTrackingBufferPoolRule("Test"); + + private SimpleServletServer server; + private String scheme; + private int msgSize; + private int inputBufferSize; + + public PerMessageDeflateExtensionTest(String mode, boolean sslMode, String scheme, TestCaseMessageSize msgSize, int bufferSize) throws Exception + { + server = new SimpleServletServer(new EchoServlet()); + server.enableSsl(sslMode); + server.start(); + + this.scheme = scheme; + this.msgSize = msgSize.size; + this.inputBufferSize = bufferSize; + } + + @After + public void stopServer() { server.stop(); } @@ -62,42 +118,84 @@ public class PerMessageDeflateExtensionTest Assume.assumeTrue("Server has permessage-deflate registered", server.getWebSocketServletFactory().getExtensionFactory().isAvailable("permessage-deflate")); - BlockheadClient client = new BlockheadClient(server.getServerUri()); - client.clearExtensions(); - client.addExtensions("permessage-deflate"); - client.setProtocols("echo"); + Assert.assertThat("server scheme",server.getServerUri().getScheme(),is(scheme)); + int binBufferSize = (int) (msgSize * 1.5); + + WebSocketPolicy serverPolicy = server.getWebSocketServletFactory().getPolicy(); + + // Ensure binBufferSize is sane (not smaller then other buffers) + binBufferSize = Math.max(binBufferSize,serverPolicy.getMaxBinaryMessageSize()); + binBufferSize = Math.max(binBufferSize,serverPolicy.getMaxBinaryMessageBufferSize()); + binBufferSize = Math.max(binBufferSize,this.inputBufferSize); + + serverPolicy.setMaxBinaryMessageSize(binBufferSize); + serverPolicy.setMaxBinaryMessageBufferSize(binBufferSize); + + WebSocketClient client = new WebSocketClient(server.getSslContextFactory(),null,bufferPool); + WebSocketPolicy clientPolicy = client.getPolicy(); + clientPolicy.setMaxBinaryMessageSize(binBufferSize); + clientPolicy.setMaxBinaryMessageBufferSize(binBufferSize); + if (inputBufferSize > 0) + { + clientPolicy.setInputBufferSize(inputBufferSize); + } + try { + client.start(); // Make sure the read times out if there are problems with the implementation - client.setTimeout(1,TimeUnit.SECONDS); - client.connect(); - client.sendStandardRequest(); - HttpResponse resp = client.expectUpgradeResponse(); + client.setMaxIdleTimeout(TimeUnit.SECONDS.toMillis(1)); - Assert.assertThat("Response",resp.getExtensionsHeader(),containsString("permessage-deflate")); + CaptureSocket clientSocket = new CaptureSocket(); + ClientUpgradeRequest request = new ClientUpgradeRequest(); + request.addExtensions("permessage-deflate"); + request.setSubProtocols("echo"); - String msg = "Hello"; + Future fut = client.connect(clientSocket,server.getServerUri(),request); + // Wait for connect + Session session = fut.get(3,TimeUnit.SECONDS); + + assertThat("Response.extensions",getNegotiatedExtensionList(session),containsString("permessage-deflate")); + + // Create message + byte msg[] = new byte[msgSize]; + Random rand = new Random(); + rand.setSeed(8080); + rand.nextBytes(msg); + + // Calculate sha1 + String sha1 = Sha1Sum.calculate(msg); + // Client sends first message - client.write(new TextFrame().setPayload(msg)); + session.getRemote().sendBytes(ByteBuffer.wrap(msg)); - 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 - client.clearCaptured(); - msg = "There"; - client.write(new TextFrame().setPayload(msg)); - - frames = client.readFrames(1,1,TimeUnit.SECONDS); - frame = frames.poll(); - Assert.assertThat("TEXT.payload",frame.getPayloadAsUTF8(),is(msg.toString())); + clientSocket.messages.awaitEventCount(1,1,TimeUnit.SECONDS); + String echoMsg = clientSocket.messages.poll(); + Assert.assertThat("Echo'd Message",echoMsg,is("binary[sha1="+sha1+"]")); } finally { - client.close(); + client.stop(); } } + + private String getNegotiatedExtensionList(Session session) + { + StringBuilder actual = new StringBuilder(); + actual.append('['); + + boolean delim = false; + for (ExtensionConfig ext : session.getUpgradeResponse().getExtensions()) + { + if (delim) + actual.append(", "); + actual.append(ext.getName()); + delim = true; + } + actual.append(']'); + + return actual.toString(); + } } diff --git a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/CaptureSocket.java b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/CaptureSocket.java index 5218c94e09b..8e02950e776 100644 --- a/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/CaptureSocket.java +++ b/jetty-websocket/websocket-server/src/test/java/org/eclipse/jetty/websocket/server/helper/CaptureSocket.java @@ -18,12 +18,14 @@ package org.eclipse.jetty.websocket.server.helper; +import java.security.NoSuchAlgorithmException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.eclipse.jetty.toolchain.test.EventQueue; import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.WebSocketAdapter; +import org.eclipse.jetty.websocket.common.util.Sha1Sum; public class CaptureSocket extends WebSocketAdapter { @@ -58,4 +60,18 @@ public class CaptureSocket extends WebSocketAdapter // System.out.printf("Received Message \"%s\" [size %d]%n", message, message.length()); messages.add(message); } + + @Override + public void onWebSocketBinary(byte[] payload, int offset, int len) + { + try + { + messages.add("binary[sha1="+Sha1Sum.calculate(payload,offset,len)+"]"); + } + catch (NoSuchAlgorithmException e) + { + messages.add("ERROR: Unable to caclulate Binary SHA1: " + e.getMessage()); + e.printStackTrace(); + } + } } diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java index 0fab406c2f8..9ec3dc90469 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientLoadTest.java @@ -28,6 +28,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.IntStream; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -200,7 +201,20 @@ public class HttpClientLoadTest extends AbstractTest assertThat("Connection Leaks", connectionLeaks.get(), Matchers.is(0L)); } - private void run(Random random, int iterations) throws InterruptedException + @Test + public void testConcurrent() throws Exception + { + start(new LoadHandler()); + + Random random = new Random(); + int runs = 1; + int iterations = 256; + IntStream.range(0, 16).parallel().forEach(i -> + IntStream.range(0, runs).forEach(j -> + run(random, iterations))); + } + + private void run(Random random, int iterations) { CountDownLatch latch = new CountDownLatch(iterations); List failures = new ArrayList<>(); @@ -222,7 +236,7 @@ public class HttpClientLoadTest extends AbstractTest test(random, latch, failures); // test("http", "localhost", "GET", false, false, 64 * 1024, false, latch, failures); } - Assert.assertTrue(latch.await(iterations, TimeUnit.SECONDS)); + Assert.assertTrue(await(latch, iterations, TimeUnit.SECONDS)); long end = System.nanoTime(); task.cancel(); long elapsed = TimeUnit.NANOSECONDS.toMillis(end - begin); @@ -234,7 +248,7 @@ public class HttpClientLoadTest extends AbstractTest Assert.assertTrue(failures.toString(), failures.isEmpty()); } - private void test(Random random, final CountDownLatch latch, final List failures) throws InterruptedException + private void test(Random random, final CountDownLatch latch, final List failures) { // Choose a random destination String host = random.nextBoolean() ? "localhost" : "127.0.0.1"; @@ -257,7 +271,7 @@ public class HttpClientLoadTest extends AbstractTest test(ssl ? "https" : "http", host, method.asString(), clientClose, serverClose, contentLength, true, latch, failures); } - private void test(String scheme, String host, String method, boolean clientClose, boolean serverClose, int contentLength, final boolean checkContentLength, final CountDownLatch latch, final List failures) throws InterruptedException + private void test(String scheme, String host, String method, boolean clientClose, boolean serverClose, int contentLength, final boolean checkContentLength, final CountDownLatch latch, final List failures) { Request request = client.newRequest(host, connector.getLocalPort()) .scheme(scheme) @@ -318,7 +332,19 @@ public class HttpClientLoadTest extends AbstractTest latch.countDown(); } }); - requestLatch.await(5, TimeUnit.SECONDS); + await(requestLatch, 5, TimeUnit.SECONDS); + } + + private boolean await(CountDownLatch latch, long time, TimeUnit unit) + { + try + { + return latch.await(time, unit); + } + catch (InterruptedException x) + { + throw new RuntimeException(x); + } } private class LoadHandler extends AbstractHandler