Merge branch 'master' into session-refactor

This commit is contained in:
Jan Bartel 2015-12-10 09:38:58 +11:00
commit 4793be634f
28 changed files with 1008 additions and 410 deletions

View File

@ -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

View File

@ -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 <servlet> 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<ServletMapping> 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<ServletMapping> 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<ServletMapping> getServletMappingsForServlet (String name)
{
ServletMapping[] allMappings = _context.getServletHandler().getServletMappings();
if (allMappings == null)
return Collections.emptyList();
List<ServletMapping> mappings = new ArrayList<ServletMapping>();
for (ServletMapping m:allMappings)
{
if (m.getServletName() != null && name.equals(m.getServletName()))
{
mappings.add(m);
}
}
return mappings;
}
/**
* @param mappings
* @return
*/
private boolean containsNonDefaultMappings (List<ServletMapping> mappings)
{
if (mappings == null)
return false;
for (ServletMapping m:mappings)
{
if (!m.isDefault())
return true;
}
return false;
}
}

View File

@ -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
{
}

View File

@ -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<DiscoveredAnnotation> results = new ArrayList<DiscoveredAnnotation>();
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
{

View File

@ -324,27 +324,34 @@ public abstract class HttpReceiver
}
else
{
List<ByteBuffer> 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<ByteBuffer> 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);
}
}

View File

@ -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

View File

@ -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

View File

@ -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
{

View File

@ -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;
}

View File

@ -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());
}
}
}

View File

@ -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());
}
}
}

View File

@ -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());
}
}

View File

@ -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

View File

@ -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());
}
}
}

View File

@ -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));
}
}
}
}

View File

@ -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());
}
}
}

View File

@ -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<Integer, Integer> 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<Integer, Integer> settings2 = frame.getSettings();
Assert.assertEquals(1, settings2.size());
Assert.assertEquals(value, settings2.get(key));
Assert.assertTrue(frame.isReply());
}
}
}

View File

@ -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());
}
}
}

View File

@ -660,8 +660,6 @@ public class ServletHolder extends Holder<Servlet> 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")))
{

View File

@ -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;i<buffer.length;i+=200)
buffer[i]=(byte)'\n';
// Once we read block, let's make ourselves write blocked
out.setWriteListener(new WriteListener()
{
@Override
public void onWritePossible() throws IOException
int c = in.read();
if (c<0)
throw new IllegalStateException();
if (c=='F')
readF=true;
}
if (readF)
{
onDA.set(0);
final byte[] buffer = new byte[64*1024];
Arrays.fill(buffer,(byte)'X');
for (int i=199;i<buffer.length;i+=200)
buffer[i]=(byte)'\n';
// Once we read block, let's make ourselves write blocked
out.setWriteListener(new WriteListener()
{
onWP.incrementAndGet();
if (onWP.get()>2)
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

View File

@ -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());

View File

@ -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<String, Object> 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();

View File

@ -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
{
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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<Object[]> modes()
{
List<Object[]> 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<Session> 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<WebSocketFrame> 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();
}
}

View File

@ -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();
}
}
}

View File

@ -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<String> 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<String> failures) throws InterruptedException
private void test(Random random, final CountDownLatch latch, final List<String> 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<String> 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<String> 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