Merge remote-tracking branch 'origin/master' into jetty-8
Conflicts: jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxConsoleWebSocketServlet.java jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterTest.java jetty-websocket/pom.xml
|
@ -19,11 +19,17 @@ jetty-7.5.0-SNAPSHOT
|
|||
+ 352421 HttpURI paths beginning with '.'
|
||||
+ 352684 Implemented spinning thread analyzer
|
||||
+ 352786 GzipFilter fails to pass parameters to GzipResponseWrapper
|
||||
+ 352999 ExpireTest running too long
|
||||
+ 353073 WebSocketClient
|
||||
+ 353095 maven-jetty-plugin: PermGen leak due to javax.el.BeanELResolver
|
||||
+ 353165 addJars can follow symbolic link jar files
|
||||
+ 353210 Bundle-Version in o.e.j.o.boot.logback fix
|
||||
+ 353465 JAASLoginService ignores callbackHandlerClass
|
||||
+ 353563 HttpDestinationQueueTest too slow
|
||||
+ 353862 Improve performance of QuotedStringTokenizer.quote()
|
||||
+ 354014 Content-Length is passed to wrapped response in GZipFilter
|
||||
+ 354204 Charset encodings property file not used
|
||||
+ 354466 Typo in example config of jetty-plus.xml
|
||||
|
||||
jetty-7.4.4.v20110707 July 7th 2011
|
||||
+ 308851 Converted all jetty-client module tests to JUnit 4
|
||||
|
|
|
@ -16,7 +16,6 @@ package org.eclipse.jetty.client;
|
|||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -105,7 +104,6 @@ public class ExpireTest
|
|||
exchange.setURL(baseUrl);
|
||||
|
||||
client.send(exchange);
|
||||
Thread.sleep(50);
|
||||
}
|
||||
|
||||
// Wait to be sure that all exchanges have expired
|
||||
|
|
|
@ -5,25 +5,34 @@ import java.net.Socket;
|
|||
import java.util.concurrent.RejectedExecutionException;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HttpDestinationQueueTest
|
||||
{
|
||||
private static HttpClient _httpClient;
|
||||
private static long _timeout = 200;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeOnce() throws Exception
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
_httpClient.setMaxConnectionsPerAddress(1);
|
||||
_httpClient.setMaxQueueSizePerAddress(1);
|
||||
_httpClient.setTimeout(_timeout);
|
||||
_httpClient.start();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDestinationMaxQueueSize() throws Exception
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.setMaxConnectionsPerAddress(1);
|
||||
client.setMaxQueueSizePerAddress(1);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket(0);
|
||||
|
||||
// This will keep the connection busy
|
||||
HttpExchange exchange1 = new HttpExchange();
|
||||
exchange1.setMethod("GET");
|
||||
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
||||
client.send(exchange1);
|
||||
_httpClient.send(exchange1);
|
||||
|
||||
// Read request so we are sure that this exchange is out of the queue
|
||||
Socket socket = server.accept();
|
||||
|
@ -32,7 +41,7 @@ public class HttpDestinationQueueTest
|
|||
while (true)
|
||||
{
|
||||
int read = socket.getInputStream().read(buffer);
|
||||
request.append(new String(buffer, 0, read, "UTF-8"));
|
||||
request.append(new String(buffer,0,read,"UTF-8"));
|
||||
if (request.toString().endsWith("\r\n\r\n"))
|
||||
break;
|
||||
}
|
||||
|
@ -42,7 +51,7 @@ public class HttpDestinationQueueTest
|
|||
HttpExchange exchange2 = new HttpExchange();
|
||||
exchange2.setMethod("GET");
|
||||
exchange2.setURL("http://localhost:" + server.getLocalPort() + "/exchange2");
|
||||
client.send(exchange2);
|
||||
_httpClient.send(exchange2);
|
||||
|
||||
// This will be rejected, since the connection is busy and the queue is full
|
||||
HttpExchange exchange3 = new HttpExchange();
|
||||
|
@ -50,7 +59,7 @@ public class HttpDestinationQueueTest
|
|||
exchange3.setURL("http://localhost:" + server.getLocalPort() + "/exchange3");
|
||||
try
|
||||
{
|
||||
client.send(exchange3);
|
||||
_httpClient.send(exchange3);
|
||||
Assert.fail();
|
||||
}
|
||||
catch (RejectedExecutionException x)
|
||||
|
@ -60,14 +69,14 @@ public class HttpDestinationQueueTest
|
|||
|
||||
// Send the response to avoid exceptions in the console
|
||||
socket.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes("UTF-8"));
|
||||
Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange1.waitForDone());
|
||||
Assert.assertEquals(HttpExchange.STATUS_COMPLETED,exchange1.waitForDone());
|
||||
|
||||
// Be sure that the second exchange can be sent
|
||||
request.setLength(0);
|
||||
while (true)
|
||||
{
|
||||
int read = socket.getInputStream().read(buffer);
|
||||
request.append(new String(buffer, 0, read, "UTF-8"));
|
||||
request.append(new String(buffer,0,read,"UTF-8"));
|
||||
if (request.toString().endsWith("\r\n\r\n"))
|
||||
break;
|
||||
}
|
||||
|
@ -75,31 +84,23 @@ public class HttpDestinationQueueTest
|
|||
|
||||
socket.getOutputStream().write("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n".getBytes("UTF-8"));
|
||||
socket.close();
|
||||
Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange2.waitForDone());
|
||||
Assert.assertEquals(HttpExchange.STATUS_COMPLETED,exchange2.waitForDone());
|
||||
|
||||
server.close();
|
||||
|
||||
client.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultTimeoutIncludesQueuingExchangeExpiresInQueue() throws Exception
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.setMaxConnectionsPerAddress(1);
|
||||
client.setMaxQueueSizePerAddress(1);
|
||||
long timeout = 1000;
|
||||
client.setTimeout(timeout);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket(0);
|
||||
|
||||
// This will keep the connection busy
|
||||
HttpExchange exchange1 = new HttpExchange();
|
||||
exchange1.setTimeout(timeout * 3); // Be sure it does not expire
|
||||
exchange1.setTimeout(_timeout * 3); // Be sure it does not expire
|
||||
exchange1.setMethod("GET");
|
||||
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
||||
client.send(exchange1);
|
||||
_httpClient.send(exchange1);
|
||||
|
||||
// Read request so we are sure that this exchange is out of the queue
|
||||
Socket socket = server.accept();
|
||||
|
@ -108,7 +109,7 @@ public class HttpDestinationQueueTest
|
|||
while (true)
|
||||
{
|
||||
int read = socket.getInputStream().read(buffer);
|
||||
request.append(new String(buffer, 0, read, "UTF-8"));
|
||||
request.append(new String(buffer,0,read,"UTF-8"));
|
||||
if (request.toString().endsWith("\r\n\r\n"))
|
||||
break;
|
||||
}
|
||||
|
@ -118,39 +119,30 @@ public class HttpDestinationQueueTest
|
|||
HttpExchange exchange2 = new HttpExchange();
|
||||
exchange2.setMethod("GET");
|
||||
exchange2.setURL("http://localhost:" + server.getLocalPort() + "/exchange2");
|
||||
client.send(exchange2);
|
||||
_httpClient.send(exchange2);
|
||||
|
||||
// Wait until the queued exchange times out in the queue
|
||||
Thread.sleep(timeout * 2);
|
||||
Thread.sleep(_timeout * 2);
|
||||
|
||||
Assert.assertEquals(HttpExchange.STATUS_EXPIRED, exchange2.getStatus());
|
||||
Assert.assertEquals(HttpExchange.STATUS_EXPIRED,exchange2.getStatus());
|
||||
|
||||
// Send the response to the first exchange to avoid exceptions in the console
|
||||
socket.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes("UTF-8"));
|
||||
Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange1.waitForDone());
|
||||
Assert.assertEquals(HttpExchange.STATUS_COMPLETED,exchange1.waitForDone());
|
||||
socket.close();
|
||||
|
||||
server.close();
|
||||
|
||||
client.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultTimeoutIncludesQueuingExchangeExpiresDuringRequest() throws Exception
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.setMaxConnectionsPerAddress(1);
|
||||
client.setMaxQueueSizePerAddress(1);
|
||||
long timeout = 1000;
|
||||
client.setTimeout(timeout);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket(0);
|
||||
|
||||
HttpExchange exchange1 = new HttpExchange();
|
||||
exchange1.setMethod("GET");
|
||||
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
||||
client.send(exchange1);
|
||||
_httpClient.send(exchange1);
|
||||
|
||||
// Read request so we are sure that this exchange is out of the queue
|
||||
Socket socket = server.accept();
|
||||
|
@ -159,32 +151,25 @@ public class HttpDestinationQueueTest
|
|||
while (true)
|
||||
{
|
||||
int read = socket.getInputStream().read(buffer);
|
||||
request.append(new String(buffer, 0, read, "UTF-8"));
|
||||
request.append(new String(buffer,0,read,"UTF-8"));
|
||||
if (request.toString().endsWith("\r\n\r\n"))
|
||||
break;
|
||||
}
|
||||
Assert.assertTrue(request.toString().contains("exchange1"));
|
||||
|
||||
// Wait until the exchange times out during the request
|
||||
Thread.sleep(timeout * 2);
|
||||
Thread.sleep(_timeout * 2);
|
||||
|
||||
Assert.assertEquals(HttpExchange.STATUS_EXPIRED, exchange1.getStatus());
|
||||
Assert.assertEquals(HttpExchange.STATUS_EXPIRED,exchange1.getStatus());
|
||||
|
||||
socket.close();
|
||||
|
||||
server.close();
|
||||
|
||||
client.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExchangeTimeoutIncludesQueuingExchangeExpiresDuringResponse() throws Exception
|
||||
{
|
||||
HttpClient client = new HttpClient();
|
||||
client.setMaxConnectionsPerAddress(1);
|
||||
client.setMaxQueueSizePerAddress(1);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket(0);
|
||||
|
||||
long timeout = 1000;
|
||||
|
@ -192,7 +177,7 @@ public class HttpDestinationQueueTest
|
|||
exchange1.setTimeout(timeout);
|
||||
exchange1.setMethod("GET");
|
||||
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
||||
client.send(exchange1);
|
||||
_httpClient.send(exchange1);
|
||||
|
||||
// Read request so we are sure that this exchange is out of the queue
|
||||
Socket socket = server.accept();
|
||||
|
@ -201,7 +186,7 @@ public class HttpDestinationQueueTest
|
|||
while (true)
|
||||
{
|
||||
int read = socket.getInputStream().read(buffer);
|
||||
request.append(new String(buffer, 0, read, "UTF-8"));
|
||||
request.append(new String(buffer,0,read,"UTF-8"));
|
||||
if (request.toString().endsWith("\r\n\r\n"))
|
||||
break;
|
||||
}
|
||||
|
@ -213,12 +198,10 @@ public class HttpDestinationQueueTest
|
|||
// Wait until the exchange times out during the response
|
||||
Thread.sleep(timeout * 2);
|
||||
|
||||
Assert.assertEquals(HttpExchange.STATUS_EXPIRED, exchange1.getStatus());
|
||||
Assert.assertEquals(HttpExchange.STATUS_EXPIRED,exchange1.getStatus());
|
||||
|
||||
socket.close();
|
||||
|
||||
server.close();
|
||||
|
||||
client.stop();
|
||||
}
|
||||
}
|
|
@ -103,14 +103,6 @@ public class WebSocketUpgradeTest
|
|||
_results.add("clientWS.onMessage");
|
||||
_results.add(data);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
_results.add("clientWS.onError");
|
||||
_results.add(message);
|
||||
_results.add(ex);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -253,18 +245,10 @@ public class WebSocketUpgradeTest
|
|||
_results.add(data);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
_results.add("serverWS.onError");
|
||||
_results.add(message);
|
||||
_results.add(ex);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void onClose(int code, String message)
|
||||
{
|
||||
_results.add("onDisconnect");
|
||||
_results.add("onClose");
|
||||
_webSockets.remove(this);
|
||||
}
|
||||
|
||||
|
|
|
@ -142,6 +142,8 @@ public class ScanningAppProviderRuntimeUpdatesTest
|
|||
// This test will not work on Windows as second war file would
|
||||
// not be written over the first one because of a file lock
|
||||
Assume.assumeTrue(!OS.IS_WINDOWS);
|
||||
Assume.assumeTrue(!OS.IS_OSX); // build server has issues with finding itself apparently
|
||||
|
||||
|
||||
jetty.copyWebapp("foo-webapp-1.war","foo.war");
|
||||
jetty.copyContext("foo.xml","foo.xml");
|
||||
|
|
|
@ -293,7 +293,6 @@ public class MimeTypes
|
|||
case TEXT_XML_8859_1_ORDINAL:
|
||||
return StringUtil.__ISO_8859_1;
|
||||
|
||||
case TEXT_JSON_ORDINAL:
|
||||
case TEXT_HTML_UTF_8_ORDINAL:
|
||||
case TEXT_PLAIN_UTF_8_ORDINAL:
|
||||
case TEXT_XML_UTF_8_ORDINAL:
|
||||
|
@ -363,6 +362,7 @@ public class MimeTypes
|
|||
|
||||
if (state==10)
|
||||
return CACHE.lookup(value.peek(start,i-start)).toString();
|
||||
return null;
|
||||
|
||||
return (String)__encodings.get(value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
|
@ -34,10 +35,13 @@ import org.eclipse.jetty.util.StringUtil;
|
|||
*/
|
||||
public class GzipResponseWrapper extends HttpServletResponseWrapper
|
||||
{
|
||||
public static int DEFAULT_BUFFER_SIZE = 8192;
|
||||
public static int DEFAULT_MIN_GZIP_SIZE = 256;
|
||||
|
||||
private HttpServletRequest _request;
|
||||
private Set<String> _mimeTypes;
|
||||
private int _bufferSize=8192;
|
||||
private int _minGzipSize=256;
|
||||
private int _bufferSize=DEFAULT_BUFFER_SIZE;
|
||||
private int _minGzipSize=DEFAULT_MIN_GZIP_SIZE;
|
||||
|
||||
private PrintWriter _writer;
|
||||
private GzipStream _gzStream;
|
||||
|
@ -137,10 +141,28 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
|
|||
* @see javax.servlet.ServletResponseWrapper#setContentLength(int)
|
||||
*/
|
||||
public void setContentLength(int length)
|
||||
{
|
||||
setContentLength((long)length);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
protected void setContentLength(long length)
|
||||
{
|
||||
_contentLength=length;
|
||||
if (_gzStream!=null)
|
||||
_gzStream.setContentLength(length);
|
||||
else if (_noGzip && _contentLength>=0)
|
||||
{
|
||||
HttpServletResponse response = (HttpServletResponse)getResponse();
|
||||
if(_contentLength<Integer.MAX_VALUE)
|
||||
{
|
||||
response.setContentLength((int)_contentLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
response.setHeader("Content-Length", Long.toString(_contentLength));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -179,9 +201,7 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
|
|||
{
|
||||
if ("content-length".equalsIgnoreCase(name))
|
||||
{
|
||||
_contentLength=Long.parseLong(value);
|
||||
if (_gzStream!=null)
|
||||
_gzStream.setContentLength(_contentLength);
|
||||
setContentLength(Long.parseLong(value));
|
||||
}
|
||||
else if ("content-type".equalsIgnoreCase(name))
|
||||
{
|
||||
|
@ -296,7 +316,10 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
|
|||
if (_gzStream==null)
|
||||
{
|
||||
if (getResponse().isCommitted() || _noGzip)
|
||||
{
|
||||
setContentLength(_contentLength);
|
||||
return getResponse().getOutputStream();
|
||||
}
|
||||
|
||||
_gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
|
||||
}
|
||||
|
@ -318,7 +341,10 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
|
|||
throw new IllegalStateException("getOutputStream() called");
|
||||
|
||||
if (getResponse().isCommitted() || _noGzip)
|
||||
{
|
||||
setContentLength(_contentLength);
|
||||
return getResponse().getWriter();
|
||||
}
|
||||
|
||||
_gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
|
||||
_writer=newWriter(_gzStream,getCharacterEncoding());
|
||||
|
|
|
@ -42,6 +42,7 @@ public class GzipStream extends ServletOutputStream
|
|||
protected int _bufferSize;
|
||||
protected int _minGzipSize;
|
||||
protected long _contentLength;
|
||||
protected boolean _doNotGzip;
|
||||
|
||||
/**
|
||||
* Instantiates a new gzip stream.
|
||||
|
@ -77,6 +78,7 @@ public class GzipStream extends ServletOutputStream
|
|||
if (_gzOut!=null)
|
||||
_response.setHeader("Content-Encoding",null);
|
||||
_gzOut=null;
|
||||
_doNotGzip=false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,6 +89,13 @@ public class GzipStream extends ServletOutputStream
|
|||
public void setContentLength(long length)
|
||||
{
|
||||
_contentLength=length;
|
||||
if (_doNotGzip && length>=0)
|
||||
{
|
||||
if(_contentLength<Integer.MAX_VALUE)
|
||||
_response.setContentLength((int)_contentLength);
|
||||
else
|
||||
_response.setHeader("Content-Length",Long.toString(_contentLength));
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -245,14 +254,10 @@ public class GzipStream extends ServletOutputStream
|
|||
throw new IllegalStateException();
|
||||
if (_out==null || _bOut!=null )
|
||||
{
|
||||
_doNotGzip = true;
|
||||
|
||||
_out=_response.getOutputStream();
|
||||
if (_contentLength>=0)
|
||||
{
|
||||
if(_contentLength<Integer.MAX_VALUE)
|
||||
_response.setContentLength((int)_contentLength);
|
||||
else
|
||||
_response.setHeader("Content-Length",Long.toString(_contentLength));
|
||||
}
|
||||
setContentLength(_contentLength);
|
||||
|
||||
if (_bOut!=null)
|
||||
_out.write(_bOut.getBuf(),0,_bOut.getCount());
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
text/html = ISO-8859-1
|
||||
text/plain = US-ASCII
|
||||
text/plain = ISO-8859-1
|
||||
text/xml = UTF-8
|
||||
text/json = UTF-8
|
||||
|
|
|
@ -77,6 +77,19 @@ public class ByteArrayBuffer extends AbstractBuffer
|
|||
_string = value;
|
||||
}
|
||||
|
||||
public ByteArrayBuffer(String value,boolean immutable)
|
||||
{
|
||||
super(READWRITE,NON_VOLATILE);
|
||||
_bytes = StringUtil.getBytes(value);
|
||||
setGetIndex(0);
|
||||
setPutIndex(_bytes.length);
|
||||
if (immutable)
|
||||
{
|
||||
_access=IMMUTABLE;
|
||||
_string = value;
|
||||
}
|
||||
}
|
||||
|
||||
public ByteArrayBuffer(String value,String encoding) throws UnsupportedEncodingException
|
||||
{
|
||||
super(READWRITE,NON_VOLATILE);
|
||||
|
|
|
@ -9,3 +9,5 @@ To run ThreadMonitor on a Jetty installation that doesn't include jetty-monitor
|
|||
java -jar start.jar etc/jetty-monitor.xml
|
||||
|
||||
If running Jetty on Java VM version 1.5, the -Dcom.sun.management.jmxremote option should be added to the command lines above in order to enable the JMX agent.
|
||||
|
||||
In order to log CPU utilization for threads that are above specified threshold, you need to follow instructions inside jetty-monitor.xml configuration file.
|
|
@ -9,6 +9,20 @@
|
|||
<Set name="scanInterval">2000</Set>
|
||||
<Set name="busyThreshold">90</Set>
|
||||
<Set name="stackDepth">3</Set>
|
||||
<Set name="trailLength">2</Set>
|
||||
<!-- To enable logging CPU utilization for threads above specified threshold, -->
|
||||
<!-- uncomment the following lines, changing log interval (in milliseconds) -->
|
||||
<!-- and log threshold (in percent) as desired. -->
|
||||
<!--
|
||||
<Set name="logInterval">10000</Arg>
|
||||
<Set name="logThreshold">1</Arg>
|
||||
-->
|
||||
|
||||
<!-- To enable detail dump of the server whenever a thread is detected as spinning, -->
|
||||
<!-- uncomment the following lines. -->
|
||||
<!--
|
||||
<Set name="dumpable"><Ref id="Server"/></Set>
|
||||
-->
|
||||
</New>
|
||||
</Arg>
|
||||
</Call>
|
||||
|
|
|
@ -15,15 +15,16 @@
|
|||
package org.eclipse.jetty.monitor;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.Logger;
|
||||
|
||||
|
@ -32,17 +33,20 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
||||
{
|
||||
private int _scanInterval;
|
||||
private int _logInterval;
|
||||
private int _busyThreshold;
|
||||
private int _logThreshold;
|
||||
private int _stackDepth;
|
||||
private int _trailLength;
|
||||
|
||||
private ThreadMXBean _threadBean;
|
||||
private Method findDeadlockedThreadsMethod;
|
||||
|
||||
private Thread _runner;
|
||||
private Logger _logger;
|
||||
private volatile boolean _done = true;
|
||||
private Dumpable _dumpable;
|
||||
|
||||
private Map<Long,ExtThreadInfo> _extInfo;
|
||||
private Map<Long,ThreadMonitorInfo> _monitorInfo;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
|
@ -52,7 +56,32 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
|||
*/
|
||||
public ThreadMonitor() throws Exception
|
||||
{
|
||||
this(5000, 95, 3);
|
||||
this(5000);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Instantiates a new thread monitor.
|
||||
*
|
||||
* @param intervalMs scan interval
|
||||
* @throws Exception
|
||||
*/
|
||||
public ThreadMonitor(int intervalMs) throws Exception
|
||||
{
|
||||
this(intervalMs, 95);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Instantiates a new thread monitor.
|
||||
*
|
||||
* @param intervalMs scan interval
|
||||
* @param threshold busy threshold
|
||||
* @throws Exception
|
||||
*/
|
||||
public ThreadMonitor(int intervalMs, int threshold) throws Exception
|
||||
{
|
||||
this(intervalMs, threshold, 3);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -65,53 +94,196 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
|||
* @throws Exception
|
||||
*/
|
||||
public ThreadMonitor(int intervalMs, int threshold, int depth) throws Exception
|
||||
{
|
||||
this(intervalMs, threshold, depth, 3);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Instantiates a new thread monitor.
|
||||
*
|
||||
* @param intervalMs scan interval
|
||||
* @param threshold busy threshold
|
||||
* @param depth stack compare depth
|
||||
* @param trail length of stack trail
|
||||
* @throws Exception
|
||||
*/
|
||||
public ThreadMonitor(int intervalMs, int threshold, int depth, int trail) throws Exception
|
||||
{
|
||||
_scanInterval = intervalMs;
|
||||
_busyThreshold = threshold;
|
||||
_stackDepth = depth;
|
||||
_trailLength = trail;
|
||||
|
||||
_logger = Log.getLogger(getClass().getName());
|
||||
_extInfo = new HashMap<Long, ExtThreadInfo>();
|
||||
_logger = Log.getLogger(ThreadMonitor.class.getName());
|
||||
_monitorInfo = new HashMap<Long, ThreadMonitorInfo>();
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Gets the scan interval.
|
||||
*
|
||||
* @return the scan interval
|
||||
*/
|
||||
public int getScanInterval()
|
||||
{
|
||||
return _scanInterval;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the scan interval.
|
||||
*
|
||||
* @param ms the new scan interval
|
||||
*/
|
||||
public void setScanInterval(int ms)
|
||||
{
|
||||
_scanInterval = ms;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public int getBusyThreshold()
|
||||
/**
|
||||
* Gets the log interval.
|
||||
*
|
||||
* @return the log interval
|
||||
*/
|
||||
public int getLogInterval()
|
||||
{
|
||||
return _logInterval;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the log interval.
|
||||
*
|
||||
* @param ms the new log interval
|
||||
*/
|
||||
public void setLogInterval(int ms)
|
||||
{
|
||||
_logInterval = ms;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Gets the busy threshold.
|
||||
*
|
||||
* @return the busy threshold
|
||||
*/
|
||||
public int getBusyThreshold()
|
||||
{
|
||||
return _busyThreshold;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the busy threshold.
|
||||
*
|
||||
* @param percent the new busy threshold
|
||||
*/
|
||||
public void setBusyThreshold(int percent)
|
||||
{
|
||||
_busyThreshold = percent;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Gets the log threshold.
|
||||
*
|
||||
* @return the log threshold
|
||||
*/
|
||||
public int getLogThreshold()
|
||||
{
|
||||
return _logThreshold;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the log threshold.
|
||||
*
|
||||
* @param percent the new log threshold
|
||||
*/
|
||||
public void setLogThreshold(int percent)
|
||||
{
|
||||
_logThreshold = percent;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Gets the stack depth.
|
||||
*
|
||||
* @return the stack depth
|
||||
*/
|
||||
public int getStackDepth()
|
||||
{
|
||||
return _stackDepth;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the stack depth.
|
||||
*
|
||||
* @param stackDepth the new stack depth
|
||||
*/
|
||||
public void setStackDepth(int stackDepth)
|
||||
{
|
||||
_stackDepth = stackDepth;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the stack trace trail length.
|
||||
*
|
||||
* @param trailLength the new trail length
|
||||
*/
|
||||
public void setTrailLength(int trailLength)
|
||||
{
|
||||
_trailLength = trailLength;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Gets the stack trace trail length.
|
||||
*
|
||||
* @return the trail length
|
||||
*/
|
||||
public int getTrailLength()
|
||||
{
|
||||
return _trailLength;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Enable logging of CPU usage.
|
||||
*
|
||||
* @param frequencyMs the logging frequency
|
||||
* @param thresholdPercent the logging threshold
|
||||
*/
|
||||
public void logCpuUsage(int frequencyMs, int thresholdPercent)
|
||||
{
|
||||
setLogInterval(frequencyMs);
|
||||
setLogThreshold(thresholdPercent);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return A {@link Dumpable} that is dumped whenever spinning threads are detected
|
||||
*/
|
||||
public Dumpable getDumpable()
|
||||
{
|
||||
return _dumpable;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param dumpable A {@link Dumpable} that is dumped whenever spinning threads are detected
|
||||
*/
|
||||
public void setDumpable(Dumpable dumpable)
|
||||
{
|
||||
_dumpable = dumpable;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
|
||||
|
@ -121,6 +293,7 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
|||
_done = false;
|
||||
|
||||
_runner = new Thread(this);
|
||||
_runner.setDaemon(true);
|
||||
_runner.start();
|
||||
|
||||
Log.info("Thread Monitor started successfully");
|
||||
|
@ -143,49 +316,6 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
|||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Initialize JMX objects.
|
||||
*/
|
||||
protected void init()
|
||||
{
|
||||
_threadBean = ManagementFactory.getThreadMXBean();
|
||||
if (_threadBean.isThreadCpuTimeSupported())
|
||||
{
|
||||
_threadBean.setThreadCpuTimeEnabled(true);
|
||||
}
|
||||
|
||||
String versionStr = System.getProperty("java.version");
|
||||
float version = Float.valueOf(versionStr.substring(0,versionStr.lastIndexOf('.')));
|
||||
try
|
||||
{
|
||||
if (version < 1.6)
|
||||
{
|
||||
findDeadlockedThreadsMethod = ThreadMXBean.class.getMethod("findMonitorDeadlockedThreads");
|
||||
}
|
||||
else
|
||||
{
|
||||
findDeadlockedThreadsMethod = ThreadMXBean.class.getMethod("findDeadlockedThreads");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.debug(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Find deadlocked threads.
|
||||
*
|
||||
* @return array of the deadlocked thread ids
|
||||
* @throws Exception the exception
|
||||
*/
|
||||
protected long[] findDeadlockedThreads() throws Exception
|
||||
{
|
||||
return (long[]) findDeadlockedThreadsMethod.invoke(_threadBean,(Object[])null);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Retrieve all avaliable thread ids
|
||||
|
@ -211,42 +341,14 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
|||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Retrieve thread info.
|
||||
*
|
||||
* @param id thread id
|
||||
* @param maxDepth maximum stack depth
|
||||
* @return thread info
|
||||
* Initialize JMX objects.
|
||||
*/
|
||||
protected ThreadInfo getThreadInfo(long id, int maxDepth)
|
||||
protected void init()
|
||||
{
|
||||
return _threadBean.getThreadInfo(id,maxDepth);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Output thread info to log.
|
||||
*
|
||||
* @param threads thread info list
|
||||
*/
|
||||
protected void dump(final List<ThreadInfo> threads)
|
||||
{
|
||||
if (threads != null && threads.size() > 0)
|
||||
_threadBean = ManagementFactory.getThreadMXBean();
|
||||
if (_threadBean.isThreadCpuTimeSupported())
|
||||
{
|
||||
for (ThreadInfo info : threads)
|
||||
{
|
||||
StringBuffer msg = new StringBuffer();
|
||||
if (info.getLockOwnerId() < 0)
|
||||
{
|
||||
msg.append(String.format("Thread %s[%d] is spinning", info.getThreadName(), info.getThreadId()));
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.append(String.format("Thread %s[%d] is %s", info.getThreadName(), info.getThreadId(), info.getThreadState()));
|
||||
msg.append(String.format(" on %s owned by %s[%d]", info.getLockName(), info.getLockOwnerName(), info.getLockOwnerId()));
|
||||
}
|
||||
|
||||
_logger.warn(new ThreadMonitorException(msg.toString(), info.getStackTrace()));
|
||||
}
|
||||
_threadBean.setThreadCpuTimeEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -256,34 +358,45 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
|||
*/
|
||||
public void run()
|
||||
{
|
||||
long currTime;
|
||||
long lastTime = 0;
|
||||
// Initialize repeat flag
|
||||
boolean repeat = false;
|
||||
boolean scanNow, logNow;
|
||||
|
||||
// Set next scan time and log time
|
||||
long nextScanTime = System.currentTimeMillis();
|
||||
long nextLogTime = nextScanTime + _logInterval;
|
||||
|
||||
while (!_done)
|
||||
{
|
||||
currTime = System.currentTimeMillis();
|
||||
if (currTime < lastTime + _scanInterval)
|
||||
long currTime = System.currentTimeMillis();
|
||||
scanNow = (currTime > nextScanTime);
|
||||
logNow = (_logInterval > 0 && currTime > nextLogTime);
|
||||
if (repeat || scanNow || logNow)
|
||||
{
|
||||
repeat = collectThreadInfo();
|
||||
logThreadInfo(logNow);
|
||||
|
||||
if (scanNow)
|
||||
{
|
||||
nextScanTime = System.currentTimeMillis() + _scanInterval;
|
||||
}
|
||||
if (logNow)
|
||||
{
|
||||
nextLogTime = System.currentTimeMillis() + _logInterval;
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep only if not going to repeat scanning immediately
|
||||
if (!repeat)
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(50);
|
||||
Thread.sleep(100);
|
||||
}
|
||||
catch (InterruptedException ex)
|
||||
{
|
||||
Log.ignore(ex);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
List<ThreadInfo> threadInfo = new ArrayList<ThreadInfo>();
|
||||
|
||||
findSpinningThreads(threadInfo);
|
||||
findDeadlockedThreads(threadInfo);
|
||||
|
||||
lastTime = System.currentTimeMillis();
|
||||
|
||||
if (threadInfo.size() > 0)
|
||||
{
|
||||
dump(threadInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -291,104 +404,158 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
|||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Find spinning threads.
|
||||
*
|
||||
* @param threadInfo thread info list to add the results
|
||||
* @return thread info list
|
||||
* Collect thread info.
|
||||
*/
|
||||
private List<ThreadInfo> findSpinningThreads(final List<ThreadInfo> threadInfo)
|
||||
private boolean collectThreadInfo()
|
||||
{
|
||||
if (threadInfo != null)
|
||||
boolean repeat = false;
|
||||
try
|
||||
{
|
||||
try
|
||||
// Retrieve stack traces for all threads at once as it
|
||||
// was proven to be an order of magnitude faster when
|
||||
// retrieving a single thread stack trace.
|
||||
Map<Thread,StackTraceElement[]> all = Thread.getAllStackTraces();
|
||||
|
||||
for (Map.Entry<Thread,StackTraceElement[]> entry : all.entrySet())
|
||||
{
|
||||
long[] allThreadId = getAllThreadIds();
|
||||
for (int idx=0; idx < allThreadId.length; idx++)
|
||||
Thread thread = entry.getKey();
|
||||
long threadId = thread.getId();
|
||||
|
||||
// Skip our own runner thread
|
||||
if (threadId == _runner.getId())
|
||||
{
|
||||
long currId = allThreadId[idx];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currId == _runner.getId())
|
||||
ThreadMonitorInfo currMonitorInfo = _monitorInfo.get(Long.valueOf(threadId));
|
||||
if (currMonitorInfo == null)
|
||||
{
|
||||
// Create thread info object for a new thread
|
||||
currMonitorInfo = new ThreadMonitorInfo(thread);
|
||||
currMonitorInfo.setStackTrace(entry.getValue());
|
||||
currMonitorInfo.setCpuTime(getThreadCpuTime(threadId));
|
||||
currMonitorInfo.setSampleTime(System.nanoTime());
|
||||
_monitorInfo.put(Long.valueOf(threadId), currMonitorInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update the existing thread info object
|
||||
currMonitorInfo.setStackTrace(entry.getValue());
|
||||
currMonitorInfo.setCpuTime(getThreadCpuTime(threadId));
|
||||
currMonitorInfo.setSampleTime(System.nanoTime());
|
||||
|
||||
// Stack trace count holds logging state
|
||||
int count = currMonitorInfo.getTraceCount();
|
||||
if (count >= 0 && currMonitorInfo.isSpinning())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
long currCpuTime = getThreadCpuTime(currId);
|
||||
long currNanoTime = System.nanoTime();
|
||||
|
||||
ExtThreadInfo currExtInfo = _extInfo.get(Long.valueOf(currId));
|
||||
if (currExtInfo != null)
|
||||
{
|
||||
long elapsedCpuTime = currCpuTime - currExtInfo.getLastCpuTime();
|
||||
long elapsedNanoTime = currNanoTime - currExtInfo.getLastSampleTime();
|
||||
|
||||
if (((elapsedCpuTime * 100.0) / elapsedNanoTime) > _busyThreshold)
|
||||
// Thread was spinning and was logged before
|
||||
if (count < _trailLength)
|
||||
{
|
||||
ThreadInfo currInfo = getThreadInfo(currId, Integer.MAX_VALUE);
|
||||
if (currInfo != null)
|
||||
{
|
||||
StackTraceElement[] lastStackTrace = currExtInfo.getStackTrace();
|
||||
currExtInfo.setStackTrace(currInfo.getStackTrace());
|
||||
// Log another stack trace
|
||||
currMonitorInfo.setTraceCount(count+1);
|
||||
repeat = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastStackTrace != null
|
||||
&& matchStackTraces(lastStackTrace, currInfo.getStackTrace())) {
|
||||
threadInfo.add(currInfo);
|
||||
}
|
||||
// Reset spin flag and trace count
|
||||
currMonitorInfo.setSpinning(false);
|
||||
currMonitorInfo.setTraceCount(-1);
|
||||
}
|
||||
if (currMonitorInfo.getCpuUtilization() > _busyThreshold)
|
||||
{
|
||||
// Thread is busy
|
||||
StackTraceElement[] lastStackTrace = currMonitorInfo.getStackTrace();
|
||||
|
||||
if (lastStackTrace != null
|
||||
&& matchStackTraces(lastStackTrace, entry.getValue()))
|
||||
{
|
||||
// Thread is spinning
|
||||
currMonitorInfo.setSpinning(true);
|
||||
if (count < 0)
|
||||
{
|
||||
// Enable logging of spin status and stack traces
|
||||
// only if the incoming trace count is negative
|
||||
// that indicates a new scan for this thread
|
||||
currMonitorInfo.setTraceCount(0);
|
||||
repeat = (_trailLength > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currExtInfo = new ExtThreadInfo(currId);
|
||||
_extInfo.put(Long.valueOf(currId), currExtInfo);
|
||||
}
|
||||
|
||||
currExtInfo.setLastCpuTime(currCpuTime);
|
||||
currExtInfo.setLastSampleTime(currNanoTime);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.debug(ex);
|
||||
}
|
||||
}
|
||||
|
||||
return threadInfo;
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.debug(ex);
|
||||
}
|
||||
return repeat;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Find deadlocked threads.
|
||||
*
|
||||
* @param threadInfo thread info list to add the results
|
||||
* @return thread info list
|
||||
*/
|
||||
private List<ThreadInfo> findDeadlockedThreads(final List<ThreadInfo> threadInfo)
|
||||
protected void logThreadInfo(boolean logAll)
|
||||
{
|
||||
if (threadInfo != null)
|
||||
if (_monitorInfo.size() > 0)
|
||||
{
|
||||
try
|
||||
// Select thread objects for all live threads
|
||||
long[] running = getAllThreadIds();
|
||||
List<ThreadMonitorInfo> all = new ArrayList<ThreadMonitorInfo>();
|
||||
for (int idx=0; idx<running.length; idx++)
|
||||
{
|
||||
long[] threads = findDeadlockedThreads();
|
||||
if (threads != null && threads.length > 0)
|
||||
ThreadMonitorInfo info = _monitorInfo.get(running[idx]);
|
||||
if (info != null)
|
||||
{
|
||||
ThreadInfo currInfo;
|
||||
for (int idx=0; idx < threads.length; idx++)
|
||||
{
|
||||
currInfo = getThreadInfo(threads[idx], Integer.MAX_VALUE);
|
||||
if (currInfo != null)
|
||||
{
|
||||
threadInfo.add(currInfo);
|
||||
}
|
||||
}
|
||||
all.add(info);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
// Sort selected thread objects by their CPU utilization
|
||||
Collections.sort(all, new Comparator<ThreadMonitorInfo>()
|
||||
{
|
||||
Log.debug(ex);
|
||||
/* ------------------------------------------------------------ */
|
||||
public int compare(ThreadMonitorInfo info1, ThreadMonitorInfo info2)
|
||||
{
|
||||
return (int)Math.signum(info2.getCpuUtilization()-info1.getCpuUtilization());
|
||||
}
|
||||
});
|
||||
|
||||
String format = "Thread '%2$s'[%3$s,id:%1$d,cpu:%4$.2f%%]%5$s";
|
||||
|
||||
// Log thread information for threads that exceed logging threshold
|
||||
// or log spinning threads if their trace count is zero
|
||||
boolean spinning=false;
|
||||
for (ThreadMonitorInfo info : all)
|
||||
{
|
||||
if (logAll && info.getCpuUtilization() > _logThreshold
|
||||
|| info.isSpinning() && info.getTraceCount() == 0)
|
||||
{
|
||||
String message = String.format(format,
|
||||
info.getThreadId(), info.getThreadName(),
|
||||
info.getThreadState(), info.getCpuUtilization(),
|
||||
info.isSpinning() ? " SPINNING" : "");
|
||||
_logger.info(message);
|
||||
spinning=true;
|
||||
}
|
||||
}
|
||||
|
||||
// Dump info
|
||||
if (spinning && _dumpable!=null)
|
||||
{
|
||||
System.err.println(_dumpable.dump());
|
||||
}
|
||||
|
||||
// Log stack traces for spinning threads with positive trace count
|
||||
for (ThreadMonitorInfo info : all)
|
||||
{
|
||||
if (info.isSpinning() && info.getTraceCount() >= 0)
|
||||
{
|
||||
String message = String.format(format,
|
||||
info.getThreadId(), info.getThreadName(),
|
||||
info.getThreadState(), info.getCpuUtilization(),
|
||||
" STACK TRACE");
|
||||
_logger.warn(new ThreadMonitorException(message, info.getStackTrace()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return threadInfo;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -414,91 +581,4 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
|||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
private class ExtThreadInfo
|
||||
{
|
||||
private long _threadId;
|
||||
|
||||
private long _lastCpuTime;
|
||||
private long _lastSampleTime;
|
||||
private StackTraceElement[] _stackTrace;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public ExtThreadInfo(long threadId)
|
||||
{
|
||||
_threadId = threadId;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return thread id associated with the instance
|
||||
*/
|
||||
public long getThreadId()
|
||||
{
|
||||
return _threadId;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return the last CPU time of the thread
|
||||
*/
|
||||
public long getLastCpuTime()
|
||||
{
|
||||
return _lastCpuTime;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Set the last CPU time.
|
||||
*
|
||||
* @param lastCpuTime new last CPU time
|
||||
*/
|
||||
public void setLastCpuTime(long lastCpuTime)
|
||||
{
|
||||
this._lastCpuTime = lastCpuTime;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return the time of last sample
|
||||
*/
|
||||
public long getLastSampleTime()
|
||||
{
|
||||
return _lastSampleTime;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the last sample time.
|
||||
*
|
||||
* @param lastSampleTime the time of last sample
|
||||
*/
|
||||
public void setLastSampleTime(long lastSampleTime)
|
||||
{
|
||||
_lastSampleTime = lastSampleTime;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Gets the stack trace.
|
||||
*
|
||||
* @return the stack trace
|
||||
*/
|
||||
public StackTraceElement[] getStackTrace()
|
||||
{
|
||||
return _stackTrace;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the stack trace.
|
||||
*
|
||||
* @param stackTrace the new stack trace
|
||||
*/
|
||||
public void setStackTrace(StackTraceElement[] stackTrace)
|
||||
{
|
||||
_stackTrace = stackTrace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
// ========================================================================
|
||||
// Copyright (c) Webtide LLC
|
||||
// ------------------------------------------------------------------------
|
||||
// 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.monitor;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
*/
|
||||
public class ThreadMonitorInfo
|
||||
{
|
||||
private Thread _thread;
|
||||
private StackTraceElement[] _stackTrace;
|
||||
|
||||
private boolean _threadSpinning = false;
|
||||
private int _traceCount = -1;
|
||||
|
||||
private long _prevCpuTime;
|
||||
private long _prevSampleTime;
|
||||
private long _currCpuTime;
|
||||
private long _currSampleTime;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Instantiates a new thread monitor info.
|
||||
*
|
||||
* @param threadInfo the thread info
|
||||
*/
|
||||
public ThreadMonitorInfo(Thread thread)
|
||||
{
|
||||
_thread = thread;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return Id of the thread
|
||||
*/
|
||||
public long getThreadId()
|
||||
{
|
||||
return _thread.getId();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Gets the thread name.
|
||||
*
|
||||
* @return the thread name
|
||||
*/
|
||||
public String getThreadName()
|
||||
{
|
||||
return _thread.getName();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Gets the thread state.
|
||||
*
|
||||
* @return the thread state
|
||||
*/
|
||||
public String getThreadState()
|
||||
{
|
||||
return _thread.getState().toString();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Gets the stack trace.
|
||||
*
|
||||
* @return the stack trace
|
||||
*/
|
||||
public StackTraceElement[] getStackTrace()
|
||||
{
|
||||
return _stackTrace;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the stack trace.
|
||||
*
|
||||
* @param stackTrace the new stack trace
|
||||
*/
|
||||
public void setStackTrace(StackTraceElement[] stackTrace)
|
||||
{
|
||||
_stackTrace = stackTrace;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Checks if is spinning.
|
||||
*
|
||||
* @return true, if is spinning
|
||||
*/
|
||||
public boolean isSpinning()
|
||||
{
|
||||
return _threadSpinning;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the spinning flag.
|
||||
*
|
||||
* @param value the new value
|
||||
*/
|
||||
public void setSpinning(boolean value)
|
||||
{
|
||||
_threadSpinning = value;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the trace count.
|
||||
*
|
||||
* @param traceCount the new trace count
|
||||
*/
|
||||
public void setTraceCount(int traceCount)
|
||||
{
|
||||
_traceCount = traceCount;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Gets the trace count.
|
||||
*
|
||||
* @return the trace count
|
||||
*/
|
||||
public int getTraceCount()
|
||||
{
|
||||
return _traceCount;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return the CPU time of the thread
|
||||
*/
|
||||
public long getCpuTime()
|
||||
{
|
||||
return _currCpuTime;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Set the CPU time.
|
||||
*
|
||||
* @param ns new CPU time
|
||||
*/
|
||||
public void setCpuTime(long ns)
|
||||
{
|
||||
_prevCpuTime = _currCpuTime;
|
||||
_currCpuTime = ns;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return the time of sample
|
||||
*/
|
||||
public long getSampleTime()
|
||||
{
|
||||
return _currSampleTime;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Sets the sample time.
|
||||
*
|
||||
* @param ns the time of sample
|
||||
*/
|
||||
public void setSampleTime(long ns)
|
||||
{
|
||||
_prevSampleTime = _currSampleTime;
|
||||
_currSampleTime = ns;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Gets the CPU utilization.
|
||||
*
|
||||
* @return the CPU utilization percentage
|
||||
*/
|
||||
public float getCpuUtilization()
|
||||
{
|
||||
long elapsedCpuTime = _currCpuTime - _prevCpuTime;
|
||||
long elapsedNanoTime = _currSampleTime - _prevSampleTime;
|
||||
|
||||
return elapsedNanoTime > 0 ? Math.min((elapsedCpuTime * 100.0f) / elapsedNanoTime, 100.0f) : 0;
|
||||
}
|
||||
}
|
|
@ -16,10 +16,13 @@ package org.eclipse.jetty.monitor;
|
|||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.util.List;
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.log.StdErrLog;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
|
@ -28,24 +31,55 @@ import org.junit.Test;
|
|||
*/
|
||||
public class ThreadMonitorTest
|
||||
{
|
||||
public final static int DURATION=9000;
|
||||
private AtomicInteger count=new AtomicInteger(0);
|
||||
public final static int DURATION=4000;
|
||||
|
||||
@Test
|
||||
public void monitorTest() throws Exception
|
||||
{
|
||||
((StdErrLog)Log.getLogger(ThreadMonitor.class.getName())).setHideStacks(true);
|
||||
((StdErrLog)Log.getLogger(ThreadMonitor.class.getName())).setSource(false);
|
||||
|
||||
ThreadMonitor monitor = new ThreadMonitor(1000,50,2)
|
||||
final AtomicInteger countLogs=new AtomicInteger(0);
|
||||
final AtomicInteger countSpin=new AtomicInteger(0);
|
||||
|
||||
ThreadMonitor monitor = new ThreadMonitor(1000,50,1,1)
|
||||
{
|
||||
@Override
|
||||
protected void dump(List<ThreadInfo> threads)
|
||||
protected void logThreadInfo(boolean logAll)
|
||||
{
|
||||
count.incrementAndGet();
|
||||
super.dump(threads);
|
||||
if (logAll)
|
||||
countLogs.incrementAndGet();
|
||||
else
|
||||
countSpin.incrementAndGet();
|
||||
super.logThreadInfo(logAll);
|
||||
}
|
||||
};
|
||||
monitor.setDumpable(new Dumpable()
|
||||
{
|
||||
public void dump(Appendable out, String indent) throws IOException
|
||||
{
|
||||
out.append(dump());
|
||||
}
|
||||
|
||||
public String dump()
|
||||
{
|
||||
return "Dump Spinning";
|
||||
}
|
||||
});
|
||||
|
||||
monitor.logCpuUsage(2000,0);
|
||||
monitor.start();
|
||||
|
||||
Random rnd = new Random();
|
||||
for (long cnt=0; cnt<100; cnt++)
|
||||
{
|
||||
long value = rnd.nextLong() % 50 + 50;
|
||||
Sleeper sleeper = new Sleeper(value);
|
||||
Thread runner = new Thread(sleeper);
|
||||
runner.setDaemon(true);
|
||||
runner.start();
|
||||
}
|
||||
|
||||
Spinner spinner = new Spinner();
|
||||
Thread runner = new Thread(spinner);
|
||||
runner.start();
|
||||
|
@ -55,7 +89,8 @@ public class ThreadMonitorTest
|
|||
spinner.setDone();
|
||||
monitor.stop();
|
||||
|
||||
assertTrue(count.get() >= 2);
|
||||
assertTrue(countLogs.get() >= 1);
|
||||
assertTrue(countSpin.get() >= 2);
|
||||
}
|
||||
|
||||
|
||||
|
@ -64,8 +99,6 @@ public class ThreadMonitorTest
|
|||
private volatile boolean done = false;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
*/
|
||||
public void setDone()
|
||||
{
|
||||
done = true;
|
||||
|
@ -91,6 +124,36 @@ public class ThreadMonitorTest
|
|||
if (result==42)
|
||||
System.err.println("Bingo!");
|
||||
}
|
||||
}
|
||||
|
||||
private class Sleeper implements Runnable
|
||||
{
|
||||
private long _value;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public Sleeper(long value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void run()
|
||||
{
|
||||
try
|
||||
{
|
||||
fn(_value);
|
||||
}
|
||||
catch (InterruptedException e) {}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public long fn(long value) throws InterruptedException
|
||||
{
|
||||
long result = value > 1 ? fn(value-1) : 1;
|
||||
|
||||
Thread.sleep(50);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<!-- the web.xml file. -->
|
||||
<!-- =========================================================== -->
|
||||
<!--
|
||||
<Call name="addLoginService">
|
||||
<Call name="addBean">
|
||||
<Arg>
|
||||
<New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
|
||||
<Set name="name">xyzrealm</Set>
|
||||
|
|
|
@ -667,8 +667,8 @@ public class Response implements HttpServletResponse
|
|||
if (encoding==null)
|
||||
{
|
||||
/* implementation of educated defaults */
|
||||
if(_mimeType!=null)
|
||||
encoding = null; // TODO getHttpContext().getEncodingByMimeType(_mimeType);
|
||||
if(_cachedMimeType != null)
|
||||
encoding = MimeTypes.getCharsetFromContentType(_cachedMimeType);
|
||||
|
||||
if (encoding==null)
|
||||
encoding = StringUtil.__ISO_8859_1;
|
||||
|
|
|
@ -48,7 +48,7 @@ import org.eclipse.jetty.util.log.Log;
|
|||
*
|
||||
* <p>
|
||||
* Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and CPU cycles. If this handler is used for static content,
|
||||
* then use of efficient direct NIO may be prevented, thus use of the gzip mechanism of the {@link org.eclipse.jetty.servlet.DefaultServlet} is advised instead.
|
||||
* then use of efficient direct NIO may be prevented, thus use of the gzip mechanism of the <code>org.eclipse.jetty.servlet.DefaultServlet</code> is advised instead.
|
||||
* </p>
|
||||
*/
|
||||
public class GzipHandler extends HandlerWrapper
|
||||
|
|
|
@ -81,7 +81,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
|
|||
|
||||
/* ------------------------------------------------------------- */
|
||||
/**
|
||||
* @return True is the session is invalid or passivated.
|
||||
* asserts that the session is valid
|
||||
*/
|
||||
protected void checkValid() throws IllegalStateException
|
||||
{
|
||||
|
|
|
@ -682,7 +682,7 @@ public class JDBCSessionManager extends AbstractSessionManager
|
|||
/**
|
||||
* Add a newly created session to our in-memory list for this node and persist it.
|
||||
*
|
||||
* @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSessionManager.AbstractSession)
|
||||
* @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
|
||||
*/
|
||||
@Override
|
||||
protected void addSession(AbstractSession session)
|
||||
|
|
|
@ -65,7 +65,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
|||
private static final String REQUEST2_HEADER=
|
||||
"POST / HTTP/1.0\n"+
|
||||
"Host: localhost\n"+
|
||||
"Content-Type: text/xml\n"+
|
||||
"Content-Type: text/xml;charset=ISO-8859-1\n"+
|
||||
"Content-Length: ";
|
||||
private static final String REQUEST2_CONTENT=
|
||||
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+
|
||||
|
|
|
@ -116,11 +116,20 @@ public class ResponseTest
|
|||
assertEquals("foo2/bar2;charset=ISO-8859-1",response.getContentType());
|
||||
|
||||
response.recycle();
|
||||
|
||||
response.setContentType("text/xml;charset=ISO-8859-7");
|
||||
response.getWriter();
|
||||
response.setContentType("text/html;charset=UTF-8");
|
||||
assertEquals("text/html;charset=ISO-8859-7",response.getContentType());
|
||||
|
||||
response.recycle();
|
||||
response.setContentType("text/html;charset=US-ASCII");
|
||||
response.getWriter();
|
||||
assertEquals("text/html;charset=US-ASCII",response.getContentType());
|
||||
|
||||
response.recycle();
|
||||
response.setContentType("text/json");
|
||||
response.getWriter();
|
||||
assertEquals("text/json;charset=UTF-8", response.getContentType());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
package org.eclipse.jetty.servlets;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.Servlet;
|
||||
|
||||
import org.eclipse.jetty.http.gzip.GzipResponseWrapper;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlets.gzip.GzipTester;
|
||||
import org.eclipse.jetty.servlets.gzip.TestServletLengthTypeStreamWrite;
|
||||
import org.eclipse.jetty.servlets.gzip.TestServletStreamLengthTypeWrite;
|
||||
import org.eclipse.jetty.servlets.gzip.TestServletStreamTypeLengthWrite;
|
||||
import org.eclipse.jetty.servlets.gzip.TestServletTypeLengthStreamWrite;
|
||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
/**
|
||||
* Test the GzipFilter support for Content-Length setting variations.
|
||||
*
|
||||
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class GzipFilterContentLengthTest
|
||||
{
|
||||
/**
|
||||
* These are the junit parameters for running this test.
|
||||
* <p>
|
||||
* We have 4 test servlets, that arrange the content-length/content-type/get stream in different orders so as to
|
||||
* simulate the real world scenario that caused the bug in <a
|
||||
* href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||
* <p>
|
||||
* This test case will be run with each entry in the array below as setup parameters for the test case.
|
||||
*
|
||||
* @return the junit parameters
|
||||
*/
|
||||
@Parameters
|
||||
public static List<Object[]> data()
|
||||
{
|
||||
return Arrays.asList(new Object[][]
|
||||
{
|
||||
{ TestServletLengthTypeStreamWrite.class },
|
||||
{ TestServletStreamLengthTypeWrite.class },
|
||||
{ TestServletStreamTypeLengthWrite.class },
|
||||
{ TestServletTypeLengthStreamWrite.class } });
|
||||
}
|
||||
|
||||
private static final int LARGE = GzipResponseWrapper.DEFAULT_BUFFER_SIZE * 8;
|
||||
private static final int MEDIUM = GzipResponseWrapper.DEFAULT_BUFFER_SIZE;
|
||||
private static final int SMALL = GzipResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
|
||||
|
||||
@Rule
|
||||
public TestingDir testingdir = new TestingDir();
|
||||
|
||||
private Class<? extends Servlet> testServlet;
|
||||
|
||||
public GzipFilterContentLengthTest(Class<? extends Servlet> testServlet)
|
||||
{
|
||||
this.testServlet = testServlet;
|
||||
}
|
||||
|
||||
private void assertIsGzipCompressed(Class<? extends Servlet> servletClass, int filesize) throws Exception
|
||||
{
|
||||
GzipTester tester = new GzipTester(testingdir);
|
||||
|
||||
// Test content that is smaller than the buffer.
|
||||
tester.prepareServerFile("file.txt",filesize);
|
||||
|
||||
FilterHolder holder = tester.setContentServlet(servletClass);
|
||||
holder.setInitParameter("mimeTypes","text/plain");
|
||||
|
||||
try
|
||||
{
|
||||
tester.start();
|
||||
tester.assertIsResponseGzipCompressed("file.txt");
|
||||
}
|
||||
finally
|
||||
{
|
||||
tester.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void assertIsNotGzipCompressed(Class<? extends Servlet> servletClass, int filesize) throws Exception
|
||||
{
|
||||
GzipTester tester = new GzipTester(testingdir);
|
||||
|
||||
// Test content that is smaller than the buffer.
|
||||
tester.prepareServerFile("file.mp3",filesize);
|
||||
|
||||
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
|
||||
holder.setInitParameter("mimeTypes","text/plain");
|
||||
|
||||
try
|
||||
{
|
||||
tester.start();
|
||||
tester.assertIsResponseNotGzipCompressed("file.mp3",filesize);
|
||||
}
|
||||
finally
|
||||
{
|
||||
tester.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGzipCompressedTiny() throws Exception
|
||||
{
|
||||
assertIsGzipCompressed(testServlet,SMALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for Length>Type>Stream>Write problems encountered in GzipFilter
|
||||
*
|
||||
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||
*/
|
||||
@Test
|
||||
public void testIsGzipCompressedMedium() throws Exception
|
||||
{
|
||||
assertIsGzipCompressed(testServlet,MEDIUM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for Length>Type>Stream>Write problems encountered in GzipFilter
|
||||
*
|
||||
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||
*/
|
||||
@Test
|
||||
public void testIsGzipCompressedLarge() throws Exception
|
||||
{
|
||||
assertIsGzipCompressed(testServlet,LARGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests for Length>Type>Stream>Write problems encountered in GzipFilter
|
||||
*
|
||||
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||
*/
|
||||
@Test
|
||||
public void testIsNotGzipCompressed() throws Exception
|
||||
{
|
||||
assertIsNotGzipCompressed(TestServletLengthTypeStreamWrite.class,LARGE);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package org.eclipse.jetty.servlets;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlets.gzip.GzipTester;
|
||||
import org.eclipse.jetty.toolchain.test.IO;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
/**
|
||||
* Tests {@link GzipFilter} in combination with {@link DefaultServlet} for
|
||||
* ability to configure {@link GzipFilter} to ignore recompress situations
|
||||
* from upstream.
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class GzipFilterDefaultNoRecompressTest
|
||||
{
|
||||
@Parameters
|
||||
public static List<Object[]> data()
|
||||
{
|
||||
return Arrays.asList(new Object[][]
|
||||
{
|
||||
// Some already compressed files
|
||||
{ "test_quotes.gz" },
|
||||
{ "test_quotes.bz2" },
|
||||
{ "test_quotes.zip" },
|
||||
{ "test_quotes.rar" },
|
||||
// Some images (common first)
|
||||
{ "jetty_logo.png" },
|
||||
{ "jetty_logo.gif" },
|
||||
{ "jetty_logo.jpeg" },
|
||||
{ "jetty_logo.jpg" },
|
||||
// Lesser encountered images (usually found being requested from non-browser clients)
|
||||
{ "jetty_logo.bmp" },
|
||||
{ "jetty_logo.tga" },
|
||||
{ "jetty_logo.tif" },
|
||||
{ "jetty_logo.tiff" },
|
||||
{ "jetty_logo.xcf" },
|
||||
{ "jetty_logo.jp2" } });
|
||||
}
|
||||
|
||||
@Rule
|
||||
public TestingDir testingdir = new TestingDir();
|
||||
|
||||
private String alreadyCompressedFilename;
|
||||
|
||||
public GzipFilterDefaultNoRecompressTest(String testFilename) {
|
||||
this.alreadyCompressedFilename = testFilename;
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Cannot find a configuration that would allow this to pass")
|
||||
public void testNotGzipFiltered_Default_AlreadyCompressed() throws Exception
|
||||
{
|
||||
GzipTester tester = new GzipTester(testingdir);
|
||||
|
||||
copyTestFileToServer(alreadyCompressedFilename);
|
||||
|
||||
// Using DefaultServlet, with default GzipFilter setup
|
||||
FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
|
||||
// TODO: find a configuration of the GzipFilter to allow
|
||||
// each of these test cases to pass.
|
||||
|
||||
StringBuilder mimeTypes = new StringBuilder();
|
||||
mimeTypes.append("images/png");
|
||||
mimeTypes.append(",images/jpeg");
|
||||
mimeTypes.append(",images/gif");
|
||||
mimeTypes.append(",images/jp2");
|
||||
|
||||
holder.setInitParameter("mimeTypes", mimeTypes.toString());
|
||||
|
||||
try
|
||||
{
|
||||
tester.start();
|
||||
tester.assertIsResponseNotGzipFiltered(alreadyCompressedFilename,
|
||||
alreadyCompressedFilename + ".sha1");
|
||||
}
|
||||
finally
|
||||
{
|
||||
tester.stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void copyTestFileToServer(String testFilename) throws IOException
|
||||
{
|
||||
File testFile = MavenTestingUtils.getTestResourceFile(testFilename);
|
||||
File outFile = testingdir.getFile(testFilename);
|
||||
IO.copy(testFile,outFile);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package org.eclipse.jetty.servlets;
|
||||
|
||||
import org.eclipse.jetty.http.gzip.GzipResponseWrapper;
|
||||
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlets.gzip.GzipTester;
|
||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Test the GzipFilter support built into the {@link DefaultServlet}
|
||||
*/
|
||||
public class GzipFilterDefaultTest
|
||||
{
|
||||
@Rule
|
||||
public TestingDir testingdir = new TestingDir();
|
||||
|
||||
@Test
|
||||
public void testIsGzipCompressedTiny() throws Exception
|
||||
{
|
||||
GzipTester tester = new GzipTester(testingdir);
|
||||
|
||||
// Test content that is smaller than the buffer.
|
||||
int filesize = GzipResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
|
||||
tester.prepareServerFile("file.txt",filesize);
|
||||
|
||||
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
|
||||
holder.setInitParameter("mimeTypes","text/plain");
|
||||
|
||||
try
|
||||
{
|
||||
tester.start();
|
||||
tester.assertIsResponseGzipCompressed("file.txt");
|
||||
}
|
||||
finally
|
||||
{
|
||||
tester.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsGzipCompressedLarge() throws Exception
|
||||
{
|
||||
GzipTester tester = new GzipTester(testingdir);
|
||||
|
||||
// Test content that is smaller than the buffer.
|
||||
int filesize = GzipResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
|
||||
tester.prepareServerFile("file.txt",filesize);
|
||||
|
||||
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
|
||||
holder.setInitParameter("mimeTypes","text/plain");
|
||||
|
||||
try
|
||||
{
|
||||
tester.start();
|
||||
tester.assertIsResponseGzipCompressed("file.txt");
|
||||
}
|
||||
finally
|
||||
{
|
||||
tester.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsNotGzipCompressed() throws Exception
|
||||
{
|
||||
GzipTester tester = new GzipTester(testingdir);
|
||||
|
||||
// Test content that is smaller than the buffer.
|
||||
int filesize = GzipResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
|
||||
tester.prepareServerFile("file.mp3",filesize);
|
||||
|
||||
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
|
||||
holder.setInitParameter("mimeTypes","text/plain");
|
||||
|
||||
try
|
||||
{
|
||||
tester.start();
|
||||
tester.assertIsResponseNotGzipCompressed("file.mp3", filesize);
|
||||
}
|
||||
finally
|
||||
{
|
||||
tester.stop();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,115 +0,0 @@
|
|||
// ========================================================================
|
||||
// Copyright (c) 2004-2009 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.servlets;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.io.ByteArrayBuffer;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.testing.HttpTester;
|
||||
import org.eclipse.jetty.testing.ServletTester;
|
||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
public class GzipFilterTest
|
||||
{
|
||||
private static String __content =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. "+
|
||||
"Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque "+
|
||||
"habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. "+
|
||||
"Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam "+
|
||||
"at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate "+
|
||||
"velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. "+
|
||||
"Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum "+
|
||||
"eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa "+
|
||||
"sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam "+
|
||||
"consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. "+
|
||||
"Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse "+
|
||||
"et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
|
||||
|
||||
@Rule
|
||||
public TestingDir testdir = new TestingDir();
|
||||
|
||||
private ServletTester tester;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
testdir.ensureEmpty();
|
||||
|
||||
File testFile = testdir.getFile("file.txt");
|
||||
BufferedOutputStream testOut = new BufferedOutputStream(new FileOutputStream(testFile));
|
||||
ByteArrayInputStream testIn = new ByteArrayInputStream(__content.getBytes("ISO8859_1"));
|
||||
IO.copy(testIn,testOut);
|
||||
testOut.close();
|
||||
|
||||
tester=new ServletTester();
|
||||
tester.setContextPath("/context");
|
||||
tester.setResourceBase(testdir.getDir().getCanonicalPath());
|
||||
tester.addServlet(org.eclipse.jetty.servlet.DefaultServlet.class, "/");
|
||||
FilterHolder holder = tester.addFilter(GzipFilter.class,"/*",null);
|
||||
holder.setInitParameter("mimeTypes","text/plain");
|
||||
tester.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception
|
||||
{
|
||||
tester.stop();
|
||||
IO.delete(testdir.getDir());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGzipFilter() throws Exception
|
||||
{
|
||||
// generated and parsed test
|
||||
HttpTester request = new HttpTester();
|
||||
HttpTester response = new HttpTester();
|
||||
|
||||
request.setMethod("GET");
|
||||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setHeader("accept-encoding","gzip");
|
||||
request.setURI("/context/file.txt");
|
||||
|
||||
ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
|
||||
ByteArrayBuffer respBuff = tester.getResponses(reqsBuff);
|
||||
response.parse(respBuff.asArray());
|
||||
|
||||
assertTrue(response.getMethod()==null);
|
||||
assertTrue(response.getHeader("Content-Encoding").equalsIgnoreCase("gzip"));
|
||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
||||
|
||||
InputStream testIn = new GZIPInputStream(new ByteArrayInputStream(response.getContentBytes()));
|
||||
ByteArrayOutputStream testOut = new ByteArrayOutputStream();
|
||||
IO.copy(testIn,testOut);
|
||||
|
||||
assertEquals(__content, testOut.toString("ISO8859_1"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,371 @@
|
|||
package org.eclipse.jetty.servlets.gzip;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.DigestOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Enumeration;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import javax.servlet.DispatcherType;
|
||||
import javax.servlet.Servlet;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.io.ByteArrayBuffer;
|
||||
import org.eclipse.jetty.servlet.FilterHolder;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.servlets.GzipFilter;
|
||||
import org.eclipse.jetty.testing.HttpTester;
|
||||
import org.eclipse.jetty.testing.ServletTester;
|
||||
import org.eclipse.jetty.toolchain.test.IO;
|
||||
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||
import org.junit.Assert;
|
||||
|
||||
public class GzipTester
|
||||
{
|
||||
private String encoding = "ISO8859_1";
|
||||
private ServletTester servletTester;
|
||||
private TestingDir testdir;
|
||||
|
||||
public GzipTester(TestingDir testingdir)
|
||||
{
|
||||
this.testdir = testingdir;
|
||||
// Make sure we start with a clean testing directory.
|
||||
this.testdir.ensureEmpty();
|
||||
}
|
||||
|
||||
public void assertIsResponseGzipCompressed(String filename) throws Exception
|
||||
{
|
||||
assertIsResponseGzipCompressed(filename,filename);
|
||||
}
|
||||
|
||||
public void assertIsResponseGzipCompressed(String requestedFilename, String serverFilename) throws Exception
|
||||
{
|
||||
System.err.printf("[GzipTester] requesting /context/%s%n",requestedFilename);
|
||||
HttpTester request = new HttpTester();
|
||||
HttpTester response = new HttpTester();
|
||||
|
||||
request.setMethod("GET");
|
||||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding","gzip");
|
||||
request.setURI("/context/" + requestedFilename);
|
||||
|
||||
// Issue the request
|
||||
ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
|
||||
// Collect the response(s)
|
||||
ByteArrayBuffer respBuff = servletTester.getResponses(reqsBuff);
|
||||
response.parse(respBuff.asArray());
|
||||
|
||||
// Assert the response headers
|
||||
Assert.assertThat("Response.method",response.getMethod(),nullValue());
|
||||
Assert.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
|
||||
Assert.assertThat("Response.header[Content-Length]",response.getHeader("Content-Length"),notNullValue());
|
||||
Assert.assertThat("Response.header[Content-Encoding]",response.getHeader("Content-Encoding"),containsString("gzip"));
|
||||
|
||||
// Assert that the decompressed contents are what we expect.
|
||||
File serverFile = testdir.getFile(serverFilename);
|
||||
String expected = IO.readToString(serverFile);
|
||||
String actual = null;
|
||||
|
||||
ByteArrayInputStream bais = null;
|
||||
InputStream in = null;
|
||||
ByteArrayOutputStream out = null;
|
||||
try
|
||||
{
|
||||
bais = new ByteArrayInputStream(response.getContentBytes());
|
||||
in = new GZIPInputStream(bais);
|
||||
out = new ByteArrayOutputStream();
|
||||
IO.copy(in,out);
|
||||
|
||||
actual = out.toString(encoding);
|
||||
Assert.assertEquals("Uncompressed contents",expected,actual);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IO.close(out);
|
||||
IO.close(in);
|
||||
IO.close(bais);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the response contains an unfiltered file contents.
|
||||
* <p>
|
||||
* This is used to test exclusions and passthroughs in the GzipFilter.
|
||||
* <p>
|
||||
* An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be
|
||||
* compressed by the GzipFilter.
|
||||
*
|
||||
* @param requestedFilename
|
||||
* the filename used to on the GET request,.
|
||||
* @param testResourceSha1Sum
|
||||
* the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response
|
||||
* contents are what is intended.
|
||||
*/
|
||||
public void assertIsResponseNotGzipFiltered(String requestedFilename, String testResourceSha1Sum) throws Exception
|
||||
{
|
||||
System.err.printf("[GzipTester] requesting /context/%s%n",requestedFilename);
|
||||
HttpTester request = new HttpTester();
|
||||
HttpTester response = new HttpTester();
|
||||
|
||||
request.setMethod("GET");
|
||||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding","gzip");
|
||||
request.setURI("/context/" + requestedFilename);
|
||||
|
||||
// Issue the request
|
||||
ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
|
||||
// Collect the response(s)
|
||||
ByteArrayBuffer respBuff = servletTester.getResponses(reqsBuff);
|
||||
response.parse(respBuff.asArray());
|
||||
|
||||
dumpHeaders(requestedFilename + " / Response Headers",response);
|
||||
|
||||
// Assert the response headers
|
||||
Assert.assertThat(requestedFilename + " / Response.method",response.getMethod(),nullValue());
|
||||
Assert.assertThat(requestedFilename + " / Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
|
||||
Assert.assertThat(requestedFilename + " / Response.header[Content-Length]",response.getHeader("Content-Length"),notNullValue());
|
||||
Assert.assertThat(requestedFilename + " / Response.header[Content-Encoding] (should not be recompressed by GzipFilter)",
|
||||
response.getHeader("Content-Encoding"),nullValue());
|
||||
Assert.assertThat(requestedFilename + " / Response.header[Content-Type] (should have a Content-Type associated with it)",
|
||||
response.getHeader("Content-Type"),notNullValue());
|
||||
|
||||
ByteArrayInputStream bais = null;
|
||||
DigestOutputStream digester = null;
|
||||
try
|
||||
{
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA1");
|
||||
bais = new ByteArrayInputStream(response.getContentBytes());
|
||||
digester = new DigestOutputStream(new NoOpOutputStream(),digest);
|
||||
IO.copy(bais,digester);
|
||||
|
||||
String actualSha1Sum = Hex.asHex(digest.digest());
|
||||
String expectedSha1Sum = loadExpectedSha1Sum(testResourceSha1Sum);
|
||||
Assert.assertEquals(requestedFilename + " / SHA1Sum of content",expectedSha1Sum,actualSha1Sum);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IO.close(digester);
|
||||
IO.close(bais);
|
||||
}
|
||||
}
|
||||
|
||||
private void dumpHeaders(String prefix, HttpTester http)
|
||||
{
|
||||
System.out.println(prefix);
|
||||
@SuppressWarnings("unchecked")
|
||||
Enumeration<String> names = http.getHeaderNames();
|
||||
while (names.hasMoreElements())
|
||||
{
|
||||
String name = names.nextElement();
|
||||
String value = http.getHeader(name);
|
||||
System.out.printf(" [%s] = %s%n",name,value);
|
||||
}
|
||||
}
|
||||
|
||||
private String loadExpectedSha1Sum(String testResourceSha1Sum) throws IOException
|
||||
{
|
||||
File sha1File = MavenTestingUtils.getTestResourceFile(testResourceSha1Sum);
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the requested filename results in a properly structured GzipFilter response, where the content is
|
||||
* not compressed, and the content-length is returned appropriately.
|
||||
*
|
||||
* @param filename
|
||||
* the filename used for the request, and also used to compare the response to the server file, assumes
|
||||
* that the file is suitable for {@link Assert#assertEquals(Object, Object)} use. (in other words, the
|
||||
* contents of the file are text)
|
||||
* @param expectedFilesize
|
||||
* the expected filesize to be specified on the Content-Length portion of the response headers. (note:
|
||||
* passing -1 will disable the Content-Length assertion)
|
||||
* @throws Exception
|
||||
*/
|
||||
public void assertIsResponseNotGzipCompressed(String filename, int expectedFilesize) throws Exception
|
||||
{
|
||||
System.err.printf("[GzipTester] requesting /context/%s%n",filename);
|
||||
HttpTester request = new HttpTester();
|
||||
HttpTester response = new HttpTester();
|
||||
|
||||
request.setMethod("GET");
|
||||
request.setVersion("HTTP/1.0");
|
||||
request.setHeader("Host","tester");
|
||||
request.setHeader("Accept-Encoding","gzip");
|
||||
request.setURI("/context/" + filename);
|
||||
|
||||
// Issue the request
|
||||
ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
|
||||
// Collect the response(s)
|
||||
ByteArrayBuffer respBuff = servletTester.getResponses(reqsBuff);
|
||||
response.parse(respBuff.asArray());
|
||||
|
||||
// Assert the response headers
|
||||
Assert.assertThat("Response.method",response.getMethod(),nullValue());
|
||||
Assert.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
|
||||
if (expectedFilesize != (-1))
|
||||
{
|
||||
Assert.assertThat("Response.header[Content-Length]",response.getHeader("Content-Length"),notNullValue());
|
||||
int serverLength = Integer.parseInt(response.getHeader("Content-Length"));
|
||||
Assert.assertThat("Response.header[Content-Length]",serverLength,is(expectedFilesize));
|
||||
}
|
||||
Assert.assertThat("Response.header[Content-Encoding]",response.getHeader("Content-Encoding"),not(containsString("gzip")));
|
||||
|
||||
// Assert that the contents are what we expect.
|
||||
File serverFile = testdir.getFile(filename);
|
||||
String expected = IO.readToString(serverFile);
|
||||
String actual = null;
|
||||
|
||||
InputStream in = null;
|
||||
ByteArrayOutputStream out = null;
|
||||
try
|
||||
{
|
||||
in = new ByteArrayInputStream(response.getContentBytes());
|
||||
out = new ByteArrayOutputStream();
|
||||
IO.copy(in,out);
|
||||
|
||||
actual = out.toString(encoding);
|
||||
Assert.assertEquals("Server contents",expected,actual);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IO.close(out);
|
||||
IO.close(in);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate string content of arbitrary length.
|
||||
*
|
||||
* @param length
|
||||
* the length of the string to generate.
|
||||
* @return the string content.
|
||||
*/
|
||||
public String generateContent(int length)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
do
|
||||
{
|
||||
builder.append("Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc.\n");
|
||||
builder.append("Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque\n");
|
||||
builder.append("habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.\n");
|
||||
builder.append("Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam\n");
|
||||
builder.append("at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate\n");
|
||||
builder.append("velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum.\n");
|
||||
builder.append("Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum\n");
|
||||
builder.append("eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa\n");
|
||||
builder.append("sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam\n");
|
||||
builder.append("consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque.\n");
|
||||
builder.append("Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse\n");
|
||||
builder.append("et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.\n");
|
||||
}
|
||||
while (builder.length() < length);
|
||||
|
||||
// Make sure we are exactly at requested length. (truncate the extra)
|
||||
if (builder.length() > length)
|
||||
{
|
||||
builder.setLength(length);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public String getEncoding()
|
||||
{
|
||||
return encoding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file on the server resource path of a specified filename and size.
|
||||
*
|
||||
* @param filename
|
||||
* the filename to create
|
||||
* @param filesize
|
||||
* the file size to create (Note: this isn't suitable for creating large multi-megabyte files)
|
||||
*/
|
||||
public void prepareServerFile(String filename, int filesize) throws IOException
|
||||
{
|
||||
File testFile = testdir.getFile(filename);
|
||||
|
||||
FileOutputStream fos = null;
|
||||
BufferedOutputStream out = null;
|
||||
ByteArrayInputStream in = null;
|
||||
try
|
||||
{
|
||||
fos = new FileOutputStream(testFile,false);
|
||||
out = new BufferedOutputStream(fos);
|
||||
in = new ByteArrayInputStream(generateContent(filesize).getBytes(encoding));
|
||||
IO.copy(in,out);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IO.close(in);
|
||||
IO.close(out);
|
||||
IO.close(fos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the servlet that provides content for the GzipFilter in being tested.
|
||||
*
|
||||
* @param servletClass
|
||||
* the servlet that will provide content.
|
||||
* @return the FilterHolder for configuring the GzipFilter's initParameters with
|
||||
*/
|
||||
public FilterHolder setContentServlet(Class<? extends Servlet> servletClass) throws IOException
|
||||
{
|
||||
servletTester = new ServletTester();
|
||||
servletTester.setContextPath("/context");
|
||||
servletTester.setResourceBase(testdir.getDir().getCanonicalPath());
|
||||
ServletHolder servletHolder = servletTester.addServlet(servletClass,"/");
|
||||
servletHolder.setInitParameter("baseDir",testdir.getDir().getAbsolutePath());
|
||||
FilterHolder holder = servletTester.addFilter(GzipFilter.class,"/*",EnumSet.allOf(DispatcherType.class));
|
||||
return holder;
|
||||
}
|
||||
|
||||
public void setEncoding(String encoding)
|
||||
{
|
||||
this.encoding = encoding;
|
||||
}
|
||||
|
||||
public void start() throws Exception
|
||||
{
|
||||
Assert.assertThat("No servlet defined yet. Did you use #setContentServlet()?",servletTester,notNullValue());
|
||||
servletTester.dump();
|
||||
servletTester.start();
|
||||
}
|
||||
|
||||
public void stop()
|
||||
{
|
||||
// NOTE: Do not cleanup the testdir. Failures can't be diagnosed if you do that.
|
||||
// IO.delete(testdir.getDir()):
|
||||
try
|
||||
{
|
||||
servletTester.stop();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Don't toss this out into Junit as this would be the last exception
|
||||
// that junit will report as being the cause of the test failure.
|
||||
// when in reality, the earlier setup issue is the real cause.
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package org.eclipse.jetty.servlets.gzip;
|
||||
|
||||
public final class Hex
|
||||
{
|
||||
private static final char[] hexcodes = "0123456789abcdef".toCharArray();
|
||||
|
||||
public static byte[] asByteArray(String id, int size)
|
||||
{
|
||||
if ((id.length() < 0) || (id.length() > (size * 2)))
|
||||
{
|
||||
throw new IllegalArgumentException(String.format("Invalid ID length of <%d> expected range of <0> to <%d>",id.length(),(size * 2)));
|
||||
}
|
||||
|
||||
byte buf[] = new byte[size];
|
||||
byte hex;
|
||||
int len = id.length();
|
||||
|
||||
int idx = (int)Math.floor(((size * 2) - (double)len) / 2);
|
||||
int i = 0;
|
||||
if ((len % 2) != 0)
|
||||
{ // deal with odd numbered chars
|
||||
i -= 1;
|
||||
}
|
||||
|
||||
for (; i < len; i++)
|
||||
{
|
||||
hex = 0;
|
||||
if (i >= 0)
|
||||
{
|
||||
hex = (byte)(Character.digit(id.charAt(i),16) << 4);
|
||||
}
|
||||
i++;
|
||||
hex += (byte)(Character.digit(id.charAt(i),16));
|
||||
|
||||
buf[idx] = hex;
|
||||
idx++;
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
public static String asHex(byte buf[])
|
||||
{
|
||||
int len = buf.length;
|
||||
char out[] = new char[len * 2];
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
out[i * 2] = hexcodes[(buf[i] & 0xF0) >> 4];
|
||||
out[(i * 2) + 1] = hexcodes[(buf[i] & 0x0F)];
|
||||
}
|
||||
return String.valueOf(out);
|
||||
}
|
||||
|
||||
private Hex()
|
||||
{
|
||||
/* prevent instantiation */
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package org.eclipse.jetty.servlets.gzip;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Stream that does nothing. (Used by SHA1SUM routines)
|
||||
*/
|
||||
public class NoOpOutputStream extends OutputStream
|
||||
{
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
/* noop */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException
|
||||
{
|
||||
/* noop */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b) throws IOException
|
||||
{
|
||||
/* noop */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException
|
||||
{
|
||||
/* noop */
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException
|
||||
{
|
||||
/* noop */
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package org.eclipse.jetty.servlets.gzip;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
|
||||
import org.eclipse.jetty.toolchain.test.PathAssert;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class TestDirContentServlet extends HttpServlet
|
||||
{
|
||||
private File basedir;
|
||||
|
||||
@Override
|
||||
public void init(ServletConfig config) throws ServletException
|
||||
{
|
||||
basedir = new File(config.getInitParameter("baseDir"));
|
||||
}
|
||||
|
||||
public File getTestFile(String filename)
|
||||
{
|
||||
File testfile = new File(basedir,filename);
|
||||
PathAssert.assertFileExists("Content File should exist",testfile);
|
||||
return testfile;
|
||||
}
|
||||
|
||||
protected byte[] loadContentFileBytes(final String fileName) throws IOException
|
||||
{
|
||||
String relPath = fileName;
|
||||
relPath = relPath.replaceFirst("^/context/","");
|
||||
relPath = relPath.replaceFirst("^/","");
|
||||
|
||||
File contentFile = getTestFile(relPath);
|
||||
|
||||
FileInputStream in = null;
|
||||
ByteArrayOutputStream out = null;
|
||||
try
|
||||
{
|
||||
in = new FileInputStream(contentFile);
|
||||
out = new ByteArrayOutputStream();
|
||||
IO.copy(in,out);
|
||||
return out.toByteArray();
|
||||
}
|
||||
finally
|
||||
{
|
||||
IO.close(out);
|
||||
IO.close(in);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.eclipse.jetty.servlets.gzip;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.servlets.GzipFilter;
|
||||
|
||||
/**
|
||||
* A sample servlet to serve static content, using a order of construction that has caused problems for
|
||||
* {@link GzipFilter} in the past.
|
||||
*
|
||||
* Using a real-world pattern of:
|
||||
*
|
||||
* <pre>
|
||||
* 1) set content length
|
||||
* 2) set content type
|
||||
* 3) get stream
|
||||
* 4) write
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class TestServletLengthTypeStreamWrite extends TestDirContentServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
String fileName = request.getServletPath();
|
||||
byte[] dataBytes = loadContentFileBytes(fileName);
|
||||
|
||||
response.setContentLength(dataBytes.length);
|
||||
|
||||
if (fileName.endsWith("txt"))
|
||||
response.setContentType("text/plain");
|
||||
else if (fileName.endsWith("mp3"))
|
||||
response.setContentType("audio/mpeg");
|
||||
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
out.write(dataBytes);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.eclipse.jetty.servlets.gzip;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.servlets.GzipFilter;
|
||||
|
||||
/**
|
||||
* A sample servlet to serve static content, using a order of construction that has caused problems for
|
||||
* {@link GzipFilter} in the past.
|
||||
*
|
||||
* Using a real-world pattern of:
|
||||
*
|
||||
* <pre>
|
||||
* 1) get stream
|
||||
* 2) set content length
|
||||
* 3) set content type
|
||||
* 4) write
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class TestServletStreamLengthTypeWrite extends TestDirContentServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
String fileName = request.getServletPath();
|
||||
byte[] dataBytes = loadContentFileBytes(fileName);
|
||||
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
|
||||
response.setContentLength(dataBytes.length);
|
||||
|
||||
if (fileName.endsWith("txt"))
|
||||
response.setContentType("text/plain");
|
||||
else if (fileName.endsWith("mp3"))
|
||||
response.setContentType("audio/mpeg");
|
||||
|
||||
out.write(dataBytes);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.eclipse.jetty.servlets.gzip;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.servlets.GzipFilter;
|
||||
|
||||
/**
|
||||
* A sample servlet to serve static content, using a order of construction that has caused problems for
|
||||
* {@link GzipFilter} in the past.
|
||||
*
|
||||
* Using a real-world pattern of:
|
||||
*
|
||||
* <pre>
|
||||
* 1) get stream
|
||||
* 2) set content type
|
||||
* 2) set content length
|
||||
* 4) write
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class TestServletStreamTypeLengthWrite extends TestDirContentServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
String fileName = request.getServletPath();
|
||||
byte[] dataBytes = loadContentFileBytes(fileName);
|
||||
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
|
||||
if (fileName.endsWith("txt"))
|
||||
response.setContentType("text/plain");
|
||||
else if (fileName.endsWith("mp3"))
|
||||
response.setContentType("audio/mpeg");
|
||||
|
||||
response.setContentLength(dataBytes.length);
|
||||
|
||||
out.write(dataBytes);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.eclipse.jetty.servlets.gzip;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.servlets.GzipFilter;
|
||||
|
||||
/**
|
||||
* A sample servlet to serve static content, using a order of construction that has caused problems for
|
||||
* {@link GzipFilter} in the past.
|
||||
*
|
||||
* Using a real-world pattern of:
|
||||
*
|
||||
* <pre>
|
||||
* 1) set content type
|
||||
* 2) set content length
|
||||
* 3) get stream
|
||||
* 4) write
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class TestServletTypeLengthStreamWrite extends TestDirContentServlet
|
||||
{
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||
{
|
||||
String fileName = request.getServletPath();
|
||||
byte[] dataBytes = loadContentFileBytes(fileName);
|
||||
|
||||
if (fileName.endsWith("txt"))
|
||||
response.setContentType("text/plain");
|
||||
else if (fileName.endsWith("mp3"))
|
||||
response.setContentType("audio/mpeg");
|
||||
|
||||
response.setContentLength(dataBytes.length);
|
||||
|
||||
ServletOutputStream out = response.getOutputStream();
|
||||
out.write(dataBytes);
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 89 KiB |
|
@ -0,0 +1 @@
|
|||
6d51985fd71ae74564202f98cf993e0390fae3fe jetty_logo.bmp
|
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1 @@
|
|||
3bceab0485ead22e90af4f077c10684addd5dcb5 jetty_logo.gif
|
|
@ -0,0 +1 @@
|
|||
c6f62de568243be3afbb7f489ce9096dc1808859 jetty_logo.jp2
|
After Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1 @@
|
|||
c00ce14c7b266640544dc527277995e25d0c91b8 jetty_logo.jpeg
|
After Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1 @@
|
|||
c00ce14c7b266640544dc527277995e25d0c91b8 jetty_logo.jpg
|
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1 @@
|
|||
813bfd8bfa2fb8381cc4b296f3b962a24797ed8f jetty_logo.png
|
After Width: | Height: | Size: 88 KiB |
|
@ -0,0 +1 @@
|
|||
2603caf728690e1fddaf747a3eef8b5cfe20eee4 jetty_logo.tga
|
|
@ -0,0 +1 @@
|
|||
35bbf5d78d6834531d4c43c686bdc49cded4c982 jetty_logo.tif
|
|
@ -0,0 +1 @@
|
|||
3f7fa94449b96c4670b8754850ec8fbe526db3f6 jetty_logo.tiff
|
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1 @@
|
|||
3ec782dc77c0b81420317d8d445f8d8c1ec25d84 jetty_logo.xcf
|
|
@ -0,0 +1 @@
|
|||
1f8b327125e1ba9c25804734730dfe5367093216 test_quotes.bz2
|
|
@ -0,0 +1 @@
|
|||
f43ed550786662ba8a245a1769dfaa330c49fdcc test_quotes.gz
|
|
@ -0,0 +1 @@
|
|||
8071787493e74d60a273bc0787d3666968dc9eb9 test_quotes.rar
|
|
@ -0,0 +1,19 @@
|
|||
Quotes attributed to Mark Twain:
|
||||
|
||||
+ A person with a new idea is a crank until the idea succeeds.
|
||||
+ A person who won't read has no advantage over one who can't read.
|
||||
+ Action speaks louder than words but not nearly as often.
|
||||
+ Buy land, they're not making it anymore.
|
||||
+ Good friends, good books and a sleepy conscience: this is the ideal life.
|
||||
+ It's no wonder that truth is stranger than fiction. Fiction has to make sense
|
||||
+ My books are like water; those of the great geniuses are wine. (Fortunately) everybody drinks water.
|
||||
+ My mother had a great deal of trouble with me, but I think she enjoyed it.
|
||||
+ Name the greatest of all inventors. Accident.
|
||||
+ Necessity is the mother of taking chances.
|
||||
+ Never put off till tomorrow what you can do the day after tomorrow.
|
||||
+ Only one thing is impossible for God: To find any sense in any copyright law on the planet.
|
||||
+ Part of the secret of a success in life is to eat what you like and let the food fight it out inside.
|
||||
+ There are lies, damned lies and statistics.
|
||||
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
7dfe23b2baa0ad8fd1673bcbc9b981c4a564492a test_quotes.txt
|
|
@ -0,0 +1 @@
|
|||
bd1edce4dfc9e57b8e55314ee9ac29e3fbb3f671 test_quotes.zip
|
|
@ -26,6 +26,6 @@ public interface Attributes
|
|||
public void removeAttribute(String name);
|
||||
public void setAttribute(String name, Object attribute);
|
||||
public Object getAttribute(String name);
|
||||
public Enumeration getAttributeNames();
|
||||
public Enumeration<String> getAttributeNames();
|
||||
public void clearAttributes();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -109,10 +110,9 @@ public class AttributesMap implements Attributes
|
|||
{
|
||||
if (attrs instanceof AttributesMap)
|
||||
return Collections.enumeration(((AttributesMap)attrs)._map.keySet());
|
||||
ArrayList names = new ArrayList();
|
||||
Enumeration e = attrs.getAttributeNames();
|
||||
while (e.hasMoreElements())
|
||||
names.add(e.nextElement());
|
||||
|
||||
List<String> names = new ArrayList<String>();
|
||||
names.addAll(Collections.list(attrs.getAttributeNames()));
|
||||
return Collections.enumeration(names);
|
||||
}
|
||||
|
||||
|
|
|
@ -140,6 +140,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@SuppressWarnings("unchecked")
|
||||
public E peek()
|
||||
{
|
||||
if (_size.get() == 0)
|
||||
|
@ -218,6 +219,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
|||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@SuppressWarnings("unchecked")
|
||||
public E poll()
|
||||
{
|
||||
if (_size.get() == 0)
|
||||
|
@ -253,6 +255,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
|||
* @return the head of this queue
|
||||
* @throws InterruptedException if interrupted while waiting.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public E take() throws InterruptedException
|
||||
{
|
||||
E e = null;
|
||||
|
@ -301,6 +304,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
|||
* specified waiting time elapses before an element is present.
|
||||
* @throws InterruptedException if interrupted while waiting.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public E poll(long time, TimeUnit unit) throws InterruptedException
|
||||
{
|
||||
|
||||
|
@ -390,6 +394,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public E get(int index)
|
||||
{
|
||||
|
@ -434,6 +439,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
|||
int i = _head+index;
|
||||
if (i>=_capacity)
|
||||
i-=_capacity;
|
||||
@SuppressWarnings("unchecked")
|
||||
E old=(E)_elements[i];
|
||||
|
||||
if (i<_tail)
|
||||
|
@ -490,6 +496,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
|||
int i = _head+index;
|
||||
if (i>=_capacity)
|
||||
i-=_capacity;
|
||||
@SuppressWarnings("unchecked")
|
||||
E old=(E)_elements[i];
|
||||
_elements[i]=e;
|
||||
return old;
|
||||
|
|
|
@ -21,8 +21,10 @@ import java.util.Map;
|
|||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class HostMap<TYPE> extends HashMap<String, TYPE>
|
||||
{
|
||||
|
||||
/* --------------------------------------------------------------- */
|
||||
/** Construct empty HostMap.
|
||||
*/
|
||||
|
|
|
@ -36,6 +36,7 @@ import java.util.StringTokenizer;
|
|||
* a,b,... - a list of wildcard specifications
|
||||
* </pre>
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class IPAddressMap<TYPE> extends HashMap<String, TYPE>
|
||||
{
|
||||
private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>();
|
||||
|
|
|
@ -45,7 +45,7 @@ public class IntrospectionUtil
|
|||
return true;
|
||||
}
|
||||
|
||||
public static Method findMethod (Class clazz, String methodName, Class[] args, boolean checkInheritance, boolean strictArgs)
|
||||
public static Method findMethod (Class<?> clazz, String methodName, Class<?>[] args, boolean checkInheritance, boolean strictArgs)
|
||||
throws NoSuchMethodException
|
||||
{
|
||||
if (clazz == null)
|
||||
|
@ -78,7 +78,7 @@ public class IntrospectionUtil
|
|||
|
||||
|
||||
|
||||
public static Field findField (Class clazz, String targetName, Class targetType, boolean checkInheritance, boolean strictType)
|
||||
public static Field findField (Class<?> clazz, String targetName, Class<?> targetType, boolean checkInheritance, boolean strictType)
|
||||
throws NoSuchFieldException
|
||||
{
|
||||
if (clazz == null)
|
||||
|
@ -137,7 +137,7 @@ public class IntrospectionUtil
|
|||
|
||||
|
||||
|
||||
public static boolean checkParams (Class[] formalParams, Class[] actualParams, boolean strict)
|
||||
public static boolean checkParams (Class<?>[] formalParams, Class<?>[] actualParams, boolean strict)
|
||||
{
|
||||
if (formalParams==null && actualParams==null)
|
||||
return true;
|
||||
|
@ -182,8 +182,8 @@ public class IntrospectionUtil
|
|||
if (methodB==null)
|
||||
return false;
|
||||
|
||||
List parameterTypesA = Arrays.asList(methodA.getParameterTypes());
|
||||
List parameterTypesB = Arrays.asList(methodB.getParameterTypes());
|
||||
List<Class<?>> parameterTypesA = Arrays.asList(methodA.getParameterTypes());
|
||||
List<Class<?>> parameterTypesB = Arrays.asList(methodB.getParameterTypes());
|
||||
|
||||
if (methodA.getName().equals(methodB.getName())
|
||||
&&
|
||||
|
@ -193,7 +193,7 @@ public class IntrospectionUtil
|
|||
return false;
|
||||
}
|
||||
|
||||
public static boolean isTypeCompatible (Class formalType, Class actualType, boolean strict)
|
||||
public static boolean isTypeCompatible (Class<?> formalType, Class<?> actualType, boolean strict)
|
||||
{
|
||||
if (formalType==null && actualType != null)
|
||||
return false;
|
||||
|
@ -211,7 +211,7 @@ public class IntrospectionUtil
|
|||
|
||||
|
||||
|
||||
public static boolean containsSameMethodSignature (Method method, Class c, boolean checkPackage)
|
||||
public static boolean containsSameMethodSignature (Method method, Class<?> c, boolean checkPackage)
|
||||
{
|
||||
if (checkPackage)
|
||||
{
|
||||
|
@ -230,7 +230,7 @@ public class IntrospectionUtil
|
|||
}
|
||||
|
||||
|
||||
public static boolean containsSameFieldName(Field field, Class c, boolean checkPackage)
|
||||
public static boolean containsSameFieldName(Field field, Class<?> c, boolean checkPackage)
|
||||
{
|
||||
if (checkPackage)
|
||||
{
|
||||
|
@ -250,7 +250,7 @@ public class IntrospectionUtil
|
|||
|
||||
|
||||
|
||||
protected static Method findInheritedMethod (Package pack, Class clazz, String methodName, Class[] args, boolean strictArgs)
|
||||
protected static Method findInheritedMethod (Package pack, Class<?> clazz, String methodName, Class<?>[] args, boolean strictArgs)
|
||||
throws NoSuchMethodException
|
||||
{
|
||||
if (clazz==null)
|
||||
|
@ -275,7 +275,7 @@ public class IntrospectionUtil
|
|||
return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
|
||||
}
|
||||
|
||||
protected static Field findInheritedField (Package pack, Class clazz, String fieldName, Class fieldType, boolean strictType)
|
||||
protected static Field findInheritedField (Package pack, Class<?> clazz, String fieldName, Class<?> fieldType, boolean strictType)
|
||||
throws NoSuchFieldException
|
||||
{
|
||||
if (clazz==null)
|
||||
|
|
|
@ -28,13 +28,13 @@ import java.util.ListIterator;
|
|||
* creation. If a method needs to create a List to return, but it is
|
||||
* expected that this will either be empty or frequently contain a
|
||||
* single item, then using LazyList will avoid additional object
|
||||
* creations by using Collections.EMPTY_LIST or
|
||||
* Collections.singletonList where possible.
|
||||
* creations by using {@link Collections#EMPTY_LIST} or
|
||||
* {@link Collections#singletonList(Object)} where possible.
|
||||
* <p>
|
||||
* LazyList works by passing an opaque representation of the list in
|
||||
* and out of all the LazyList methods. This opaque object is either
|
||||
* null for an empty list, an Object for a list with a single entry
|
||||
* or an ArrayList<Object> for a list of items.
|
||||
* or an {@link ArrayList} for a list of items.
|
||||
*
|
||||
* <p><h4>Usage</h4>
|
||||
* <pre>
|
||||
|
@ -155,7 +155,7 @@ public class LazyList
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Ensure the capcity of the underlying list.
|
||||
/** Ensure the capacity of the underlying list.
|
||||
*
|
||||
*/
|
||||
public static Object ensureSize(Object list, int initialSize)
|
||||
|
@ -284,7 +284,6 @@ public class LazyList
|
|||
* @param clazz The class of the array, which may be a primitive type
|
||||
* @return array of the lazylist entries passed in
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Object toArray(Object list,Class<?> clazz)
|
||||
{
|
||||
if (list==null)
|
||||
|
@ -429,21 +428,23 @@ public class LazyList
|
|||
* @param type The type of the array (in case of null array)
|
||||
* @return new array with contents of array plus item
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Object[] addToArray(Object[] array, Object item, Class<?> type)
|
||||
public static<T> T[] addToArray(T[] array, T item, Class<?> type)
|
||||
{
|
||||
if (array==null)
|
||||
{
|
||||
if (type==null && item!=null)
|
||||
type= item.getClass();
|
||||
Object[] na = (Object[])Array.newInstance(type, 1);
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] na = (T[])Array.newInstance(type, 1);
|
||||
na[0]=item;
|
||||
return na;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Replace with Arrays.copyOf(T[] original, int newLength) from Java 1.6+
|
||||
Class<?> c = array.getClass().getComponentType();
|
||||
Object[] na = (Object[])Array.newInstance(c, Array.getLength(array)+1);
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] na = (T[])Array.newInstance(c, Array.getLength(array)+1);
|
||||
System.arraycopy(array, 0, na, 0, array.length);
|
||||
na[array.length]=item;
|
||||
return na;
|
||||
|
@ -451,8 +452,7 @@ public class LazyList
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Object removeFromArray(Object[] array, Object item)
|
||||
public static<T> T[] removeFromArray(T[] array, Object item)
|
||||
{
|
||||
if (item==null || array==null)
|
||||
return array;
|
||||
|
@ -461,7 +461,8 @@ public class LazyList
|
|||
if (item.equals(array[i]))
|
||||
{
|
||||
Class<?> c = array==null?item.getClass():array.getClass().getComponentType();
|
||||
Object[] na = (Object[])Array.newInstance(c, Array.getLength(array)-1);
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] na = (T[])Array.newInstance(c, Array.getLength(array)-1);
|
||||
if (i>0)
|
||||
System.arraycopy(array, 0, na, 0, i);
|
||||
if (i+1<array.length)
|
||||
|
|
|
@ -37,7 +37,7 @@ import java.util.ResourceBundle;
|
|||
public class Loader
|
||||
{
|
||||
/* ------------------------------------------------------------ */
|
||||
public static URL getResource(Class loadClass,String name, boolean checkParents)
|
||||
public static URL getResource(Class<?> loadClass,String name, boolean checkParents)
|
||||
throws ClassNotFoundException
|
||||
{
|
||||
URL url =null;
|
||||
|
@ -64,6 +64,7 @@ public class Loader
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class loadClass(Class loadClass,String name)
|
||||
throws ClassNotFoundException
|
||||
{
|
||||
|
@ -79,11 +80,12 @@ public class Loader
|
|||
* @return Class
|
||||
* @throws ClassNotFoundException
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class loadClass(Class loadClass,String name,boolean checkParents)
|
||||
throws ClassNotFoundException
|
||||
{
|
||||
ClassNotFoundException ex=null;
|
||||
Class c =null;
|
||||
Class<?> c =null;
|
||||
ClassLoader loader=Thread.currentThread().getContextClassLoader();
|
||||
while (c==null && loader!=null )
|
||||
{
|
||||
|
@ -111,7 +113,7 @@ public class Loader
|
|||
throw ex;
|
||||
}
|
||||
|
||||
public static ResourceBundle getResourceBundle(Class loadClass,String name,boolean checkParents, Locale locale)
|
||||
public static ResourceBundle getResourceBundle(Class<?> loadClass,String name,boolean checkParents, Locale locale)
|
||||
throws MissingResourceException
|
||||
{
|
||||
MissingResourceException ex=null;
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.List;
|
|||
*
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class MultiException extends Exception
|
||||
{
|
||||
private Object nested;
|
||||
|
@ -54,7 +55,7 @@ public class MultiException extends Exception
|
|||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public List getThrowables()
|
||||
public List<Throwable> getThrowables()
|
||||
{
|
||||
return LazyList.getList(nested);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@ import java.io.Serializable;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -46,7 +45,7 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
|||
_map=new HashMap<K, Object>();
|
||||
}
|
||||
|
||||
public MultiMap(Map map)
|
||||
public MultiMap(Map<K,Object> map)
|
||||
{
|
||||
if (map instanceof ConcurrentMap)
|
||||
_map=_cmap=new ConcurrentHashMap<K, Object>(map);
|
||||
|
@ -173,7 +172,7 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
|||
* @param values The List of multiple values.
|
||||
* @return The previous value or null.
|
||||
*/
|
||||
public Object putValues(K name, List values)
|
||||
public Object putValues(K name, List<? extends Object> values)
|
||||
{
|
||||
return _map.put(name,values);
|
||||
}
|
||||
|
@ -184,12 +183,12 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
|||
* @param values The String array of multiple values.
|
||||
* @return The previous value or null.
|
||||
*/
|
||||
public Object putValues(K name, String[] values)
|
||||
public Object putValues(K name, String... values)
|
||||
{
|
||||
Object list=null;
|
||||
for (int i=0;i<values.length;i++)
|
||||
list=LazyList.add(list,values[i]);
|
||||
return put(name,list);
|
||||
return _map.put(name,list);
|
||||
}
|
||||
|
||||
|
||||
|
@ -215,7 +214,7 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
|||
* @param name The entry key.
|
||||
* @param values The List of multiple values.
|
||||
*/
|
||||
public void addValues(K name, List values)
|
||||
public void addValues(K name, List<? extends Object> values)
|
||||
{
|
||||
Object lo = _map.get(name);
|
||||
Object ln = LazyList.addCollection(lo,values);
|
||||
|
@ -260,21 +259,25 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
|||
return LazyList.size(ln)!=s;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Put all contents of map.
|
||||
* @param m Map
|
||||
*/
|
||||
public void putAll(Map m)
|
||||
public void putAll(Map<? extends K, ? extends Object> m)
|
||||
{
|
||||
Iterator i = m.entrySet().iterator();
|
||||
boolean multi=m instanceof MultiMap;
|
||||
while(i.hasNext())
|
||||
boolean multi = (m instanceof MultiMap);
|
||||
|
||||
if (multi)
|
||||
{
|
||||
Map.Entry entry = (Map.Entry)i.next();
|
||||
if (multi)
|
||||
_map.put((K)(entry.getKey()),LazyList.clone(entry.getValue()));
|
||||
else
|
||||
put((K)(entry.getKey()),entry.getValue());
|
||||
for (Map.Entry<? extends K, ? extends Object> entry : m.entrySet())
|
||||
{
|
||||
_map.put(entry.getKey(),LazyList.clone(entry.getValue()));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_map.putAll(m);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -282,19 +285,13 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
|||
/**
|
||||
* @return Map of String arrays
|
||||
*/
|
||||
public Map toStringArrayMap()
|
||||
public Map<K,String[]> toStringArrayMap()
|
||||
{
|
||||
HashMap map = new HashMap(_map.size()*3/2);
|
||||
HashMap<K,String[]> map = new HashMap<K,String[]>(_map.size()*3/2);
|
||||
|
||||
Iterator i = _map.entrySet().iterator();
|
||||
while(i.hasNext())
|
||||
for(Map.Entry<K,Object> entry: _map.entrySet())
|
||||
{
|
||||
Map.Entry entry = (Map.Entry)i.next();
|
||||
Object l = entry.getValue();
|
||||
String[] a = LazyList.toStringArray(l);
|
||||
// for (int j=a.length;j-->0;)
|
||||
// if (a[j]==null)
|
||||
// a[j]="";
|
||||
String[] a = LazyList.toStringArray(entry.getValue());
|
||||
map.put(entry.getKey(),a);
|
||||
}
|
||||
return map;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
package org.eclipse.jetty.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
|
@ -130,7 +131,7 @@ public class QuotedStringTokenizer
|
|||
_hasToken=true;
|
||||
state=1;
|
||||
}
|
||||
continue;
|
||||
break;
|
||||
|
||||
case 1: // Token
|
||||
_hasToken=true;
|
||||
|
@ -153,9 +154,10 @@ public class QuotedStringTokenizer
|
|||
state=3;
|
||||
}
|
||||
else
|
||||
{
|
||||
_token.append(c);
|
||||
continue;
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: // Single Quote
|
||||
_hasToken=true;
|
||||
|
@ -177,9 +179,10 @@ public class QuotedStringTokenizer
|
|||
escape=true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_token.append(c);
|
||||
continue;
|
||||
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // Double Quote
|
||||
_hasToken=true;
|
||||
|
@ -201,8 +204,10 @@ public class QuotedStringTokenizer
|
|||
escape=true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_token.append(c);
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -262,9 +267,10 @@ public class QuotedStringTokenizer
|
|||
/* ------------------------------------------------------------ */
|
||||
/** Quote a string.
|
||||
* The string is quoted only if quoting is required due to
|
||||
* embeded delimiters, quote characters or the
|
||||
* embedded delimiters, quote characters or the
|
||||
* empty string.
|
||||
* @param s The string to quote.
|
||||
* @param delim the delimiter to use to quote the string
|
||||
* @return quoted string
|
||||
*/
|
||||
public static String quoteIfNeeded(String s, String delim)
|
||||
|
@ -310,67 +316,59 @@ public class QuotedStringTokenizer
|
|||
|
||||
}
|
||||
|
||||
private static final char[] escapes = new char[32];
|
||||
static
|
||||
{
|
||||
Arrays.fill(escapes, (char)0xFFFF);
|
||||
escapes['\b'] = 'b';
|
||||
escapes['\t'] = 't';
|
||||
escapes['\n'] = 'n';
|
||||
escapes['\f'] = 'f';
|
||||
escapes['\r'] = 'r';
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Quote a string into an Appendable.
|
||||
* The characters ", \, \n, \r, \t, \f and \b are escaped
|
||||
* @param buf The Appendable
|
||||
* @param s The String to quote.
|
||||
* @param buffer The Appendable
|
||||
* @param input The String to quote.
|
||||
*/
|
||||
public static void quote(Appendable buf, String s)
|
||||
public static void quote(Appendable buffer, String input)
|
||||
{
|
||||
try
|
||||
{
|
||||
buf.append('"');
|
||||
|
||||
for (int i=0;i<s.length();i++)
|
||||
buffer.append('"');
|
||||
for (int i = 0; i < input.length(); ++i)
|
||||
{
|
||||
char c = s.charAt(i);
|
||||
switch(c)
|
||||
char c = input.charAt(i);
|
||||
if (c >= 32)
|
||||
{
|
||||
case '"':
|
||||
buf.append("\\\"");
|
||||
continue;
|
||||
case '\\':
|
||||
buf.append("\\\\");
|
||||
continue;
|
||||
case '\n':
|
||||
buf.append("\\n");
|
||||
continue;
|
||||
case '\r':
|
||||
buf.append("\\r");
|
||||
continue;
|
||||
case '\t':
|
||||
buf.append("\\t");
|
||||
continue;
|
||||
case '\f':
|
||||
buf.append("\\f");
|
||||
continue;
|
||||
case '\b':
|
||||
buf.append("\\b");
|
||||
continue;
|
||||
|
||||
default:
|
||||
if (c<0x10)
|
||||
{
|
||||
buf.append("\\u000");
|
||||
buf.append(Integer.toString(c,16));
|
||||
}
|
||||
else if (c<=0x1f)
|
||||
{
|
||||
buf.append("\\u00");
|
||||
buf.append(Integer.toString(c,16));
|
||||
}
|
||||
else
|
||||
buf.append(c);
|
||||
continue;
|
||||
if (c == '"' || c == '\\')
|
||||
buffer.append('\\');
|
||||
buffer.append(c);
|
||||
}
|
||||
else
|
||||
{
|
||||
char escape = escapes[c];
|
||||
if (escape == 0xFFFF)
|
||||
{
|
||||
// Unicode escape
|
||||
buffer.append('\\').append('u').append('0').append('0');
|
||||
if (c < 0x10)
|
||||
buffer.append('0');
|
||||
buffer.append(Integer.toString(c, 16));
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.append('\\').append(escape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf.append('"');
|
||||
buffer.append('"');
|
||||
}
|
||||
catch(IOException e)
|
||||
catch (IOException x)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
throw new RuntimeException(x);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -423,67 +421,65 @@ public class QuotedStringTokenizer
|
|||
if (first!=last || (first!='"' && first!='\''))
|
||||
return s;
|
||||
|
||||
StringBuffer b=new StringBuffer(s.length()-2);
|
||||
synchronized(b)
|
||||
StringBuilder b = new StringBuilder(s.length() - 2);
|
||||
boolean escape=false;
|
||||
for (int i=1;i<s.length()-1;i++)
|
||||
{
|
||||
boolean escape=false;
|
||||
for (int i=1;i<s.length()-1;i++)
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (escape)
|
||||
{
|
||||
char c = s.charAt(i);
|
||||
|
||||
if (escape)
|
||||
escape=false;
|
||||
switch (c)
|
||||
{
|
||||
escape=false;
|
||||
switch (c)
|
||||
{
|
||||
case 'n':
|
||||
b.append('\n');
|
||||
break;
|
||||
case 'r':
|
||||
b.append('\r');
|
||||
break;
|
||||
case 't':
|
||||
b.append('\t');
|
||||
break;
|
||||
case 'f':
|
||||
b.append('\f');
|
||||
break;
|
||||
case 'b':
|
||||
b.append('\b');
|
||||
break;
|
||||
case '\\':
|
||||
b.append('\\');
|
||||
break;
|
||||
case '/':
|
||||
b.append('/');
|
||||
break;
|
||||
case '"':
|
||||
b.append('"');
|
||||
break;
|
||||
case 'u':
|
||||
b.append((char)(
|
||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
|
||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
|
||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
|
||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++)))
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
b.append(c);
|
||||
}
|
||||
case 'n':
|
||||
b.append('\n');
|
||||
break;
|
||||
case 'r':
|
||||
b.append('\r');
|
||||
break;
|
||||
case 't':
|
||||
b.append('\t');
|
||||
break;
|
||||
case 'f':
|
||||
b.append('\f');
|
||||
break;
|
||||
case 'b':
|
||||
b.append('\b');
|
||||
break;
|
||||
case '\\':
|
||||
b.append('\\');
|
||||
break;
|
||||
case '/':
|
||||
b.append('/');
|
||||
break;
|
||||
case '"':
|
||||
b.append('"');
|
||||
break;
|
||||
case 'u':
|
||||
b.append((char)(
|
||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
|
||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
|
||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
|
||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++)))
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
b.append(c);
|
||||
}
|
||||
else if (c=='\\')
|
||||
{
|
||||
escape=true;
|
||||
continue;
|
||||
}
|
||||
else
|
||||
b.append(c);
|
||||
}
|
||||
|
||||
return b.toString();
|
||||
else if (c=='\\')
|
||||
{
|
||||
escape=true;
|
||||
}
|
||||
else
|
||||
{
|
||||
b.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -522,15 +518,3 @@ public class QuotedStringTokenizer
|
|||
_single=single;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -32,8 +32,6 @@ import java.util.Set;
|
|||
* objects from being created just to look up in the map.
|
||||
*
|
||||
* This map is NOT synchronized.
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class StringMap extends AbstractMap implements Externalizable
|
||||
{
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
package org.eclipse.jetty.util.log;
|
||||
|
||||
import java.security.AccessControlException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.eclipse.jetty.util.DateCache;
|
||||
|
||||
|
@ -43,6 +45,8 @@ public class StdErrLog implements Logger
|
|||
System.getProperty("org.eclipse.jetty.util.log.SOURCE",
|
||||
System.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE", "false")));
|
||||
|
||||
private final static ConcurrentMap<String,StdErrLog> __loggers = new ConcurrentHashMap<String, StdErrLog>();
|
||||
|
||||
static
|
||||
{
|
||||
try
|
||||
|
@ -315,9 +319,21 @@ public class StdErrLog implements Logger
|
|||
|
||||
public Logger getLogger(String name)
|
||||
{
|
||||
if ((name == null && this._name == null) || (name != null && name.equals(this._name)))
|
||||
String fullname=_name == null || _name.length() == 0?name:_name + "." + name;
|
||||
|
||||
if ((name == null && this._name == null) || fullname.equals(_name))
|
||||
return this;
|
||||
return new StdErrLog(_name == null || _name.length() == 0?name:_name + "." + name);
|
||||
|
||||
StdErrLog logger = __loggers.get(name);
|
||||
if (logger==null)
|
||||
{
|
||||
StdErrLog sel=new StdErrLog(fullname);
|
||||
logger=__loggers.putIfAbsent(fullname,sel);
|
||||
if (logger==null)
|
||||
logger=sel;
|
||||
}
|
||||
|
||||
return logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,540 @@
|
|||
package org.eclipse.jetty.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
public class MultiMapTest
|
||||
{
|
||||
/**
|
||||
* Tests {@link MultiMap#put(Object, Object)}
|
||||
*/
|
||||
@Test
|
||||
public void testPut()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
mm.put(key,"gzip");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#put(Object, Object)}
|
||||
*/
|
||||
@Test
|
||||
public void testPut_Null()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
mm.put(key,null);
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,new Object[]
|
||||
{ null });
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#put(Object, Object)}
|
||||
*/
|
||||
@Test
|
||||
public void testPut_Replace()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
Object ret;
|
||||
|
||||
ret = mm.put(key,"gzip");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip");
|
||||
Assert.assertNull("Should not have replaced anything", ret);
|
||||
Object orig = mm.get(key);
|
||||
|
||||
// Now replace it
|
||||
ret = mm.put(key,"jar");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"jar");
|
||||
Assert.assertEquals("Should have replaced original", orig, ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#putValues(Object, List)}
|
||||
*/
|
||||
@Test
|
||||
public void testPutValues_List()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
List<String> input = new ArrayList<String>();
|
||||
input.add("gzip");
|
||||
input.add("jar");
|
||||
input.add("pack200");
|
||||
|
||||
mm.putValues(key,input);
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","jar","pack200");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#putValues(Object, String...)}
|
||||
*/
|
||||
@Test
|
||||
public void testPutValues_StringArray()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
String input[] = { "gzip", "jar", "pack200" };
|
||||
mm.putValues(key,input);
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","jar","pack200");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#putValues(Object, String...)}
|
||||
*/
|
||||
@Test
|
||||
public void testPutValues_VarArgs()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
mm.putValues(key,"gzip", "jar", "pack200");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","jar","pack200");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#add(Object, Object)}
|
||||
*/
|
||||
@Test
|
||||
public void testAdd()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
// Setup the key
|
||||
mm.put(key,"gzip");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip");
|
||||
|
||||
// Add to the key
|
||||
mm.add(key,"jar");
|
||||
mm.add(key,"pack200");
|
||||
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","jar","pack200");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#addValues(Object, List)}
|
||||
*/
|
||||
@Test
|
||||
public void testAddValues_List()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
// Setup the key
|
||||
mm.put(key,"gzip");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip");
|
||||
|
||||
// Add to the key
|
||||
List<String> extras = new ArrayList<String>();
|
||||
extras.add("jar");
|
||||
extras.add("pack200");
|
||||
extras.add("zip");
|
||||
mm.addValues(key,extras);
|
||||
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","jar","pack200","zip");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#addValues(Object, List)}
|
||||
*/
|
||||
@Test
|
||||
public void testAddValues_List_Empty()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
// Setup the key
|
||||
mm.put(key,"gzip");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip");
|
||||
|
||||
// Add to the key
|
||||
List<String> extras = new ArrayList<String>();
|
||||
mm.addValues(key,extras);
|
||||
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#addValues(Object, String[])}
|
||||
*/
|
||||
@Test
|
||||
public void testAddValues_StringArray()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
// Setup the key
|
||||
mm.put(key,"gzip");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip");
|
||||
|
||||
// Add to the key
|
||||
String extras[] = { "jar", "pack200", "zip" };
|
||||
mm.addValues(key,extras);
|
||||
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","jar","pack200","zip");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#addValues(Object, String[])}
|
||||
*/
|
||||
@Test
|
||||
public void testAddValues_StringArray_Empty()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
// Setup the key
|
||||
mm.put(key,"gzip");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip");
|
||||
|
||||
// Add to the key
|
||||
String extras[] = new String[0];
|
||||
mm.addValues(key,extras);
|
||||
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#removeValue(Object, Object)}
|
||||
*/
|
||||
@Test
|
||||
public void testRemoveValue()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
// Setup the key
|
||||
mm.putValues(key,"gzip","jar","pack200");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","jar","pack200");
|
||||
|
||||
// Remove a value
|
||||
mm.removeValue(key,"jar");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","pack200");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#removeValue(Object, Object)}
|
||||
*/
|
||||
@Test
|
||||
public void testRemoveValue_InvalidItem()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
// Setup the key
|
||||
mm.putValues(key,"gzip","jar","pack200");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","jar","pack200");
|
||||
|
||||
// Remove a value that isn't there
|
||||
mm.removeValue(key,"msi");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","jar","pack200");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#removeValue(Object, Object)}
|
||||
*/
|
||||
@Test
|
||||
public void testRemoveValue_AllItems()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
// Setup the key
|
||||
mm.putValues(key,"gzip","jar","pack200");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","jar","pack200");
|
||||
|
||||
// Remove a value
|
||||
mm.removeValue(key,"jar");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"gzip","pack200");
|
||||
|
||||
// Remove another value
|
||||
mm.removeValue(key,"gzip");
|
||||
assertMapSize(mm,1);
|
||||
assertValues(mm,key,"pack200");
|
||||
|
||||
// Remove last value
|
||||
mm.removeValue(key,"pack200");
|
||||
assertMapSize(mm,0); // should be empty now
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#removeValue(Object, Object)}
|
||||
*/
|
||||
@Test
|
||||
public void testRemoveValue_FromEmpty()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
String key = "formats";
|
||||
|
||||
// Setup the key
|
||||
mm.putValues(key,new String[0]);
|
||||
assertMapSize(mm,1);
|
||||
assertEmptyValues(mm,key);
|
||||
|
||||
// Remove a value that isn't in the underlying values
|
||||
mm.removeValue(key,"jar");
|
||||
assertMapSize(mm,1);
|
||||
assertEmptyValues(mm,key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#putAll(java.util.Map)}
|
||||
*/
|
||||
@Test
|
||||
public void testPutAll_Map()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
assertMapSize(mm,0); // Shouldn't have anything yet.
|
||||
|
||||
Map<String,String> input = new HashMap<String,String>();
|
||||
input.put("food","apple");
|
||||
input.put("color","red");
|
||||
input.put("amount","bushel");
|
||||
|
||||
mm.putAll(input);
|
||||
|
||||
assertMapSize(mm,3);
|
||||
assertValues(mm,"food","apple");
|
||||
assertValues(mm,"color","red");
|
||||
assertValues(mm,"amount","bushel");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#putAll(java.util.Map)}
|
||||
*/
|
||||
@Test
|
||||
public void testPutAll_MultiMap_Simple()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
assertMapSize(mm,0); // Shouldn't have anything yet.
|
||||
|
||||
MultiMap<String> input = new MultiMap<String>();
|
||||
input.put("food","apple");
|
||||
input.put("color","red");
|
||||
input.put("amount","bushel");
|
||||
|
||||
mm.putAll(input);
|
||||
|
||||
assertMapSize(mm,3);
|
||||
assertValues(mm,"food","apple");
|
||||
assertValues(mm,"color","red");
|
||||
assertValues(mm,"amount","bushel");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#putAll(java.util.Map)}
|
||||
*/
|
||||
@Test
|
||||
public void testPutAll_MultiMapComplex()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
|
||||
assertMapSize(mm,0); // Shouldn't have anything yet.
|
||||
|
||||
MultiMap<String> input = new MultiMap<String>();
|
||||
input.putValues("food","apple","cherry","raspberry");
|
||||
input.put("color","red");
|
||||
input.putValues("amount","bushel","pint");
|
||||
|
||||
mm.putAll(input);
|
||||
|
||||
assertMapSize(mm,3);
|
||||
assertValues(mm,"food","apple","cherry","raspberry");
|
||||
assertValues(mm,"color","red");
|
||||
assertValues(mm,"amount","bushel","pint");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#toStringArrayMap()}
|
||||
*/
|
||||
@Test
|
||||
public void testToStringArrayMap()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
mm.putValues("food","apple","cherry","raspberry");
|
||||
mm.put("color","red");
|
||||
mm.putValues("amount","bushel","pint");
|
||||
|
||||
assertMapSize(mm,3);
|
||||
|
||||
Map<String,String[]> sam = mm.toStringArrayMap();
|
||||
Assert.assertEquals("String Array Map.size",3,sam.size());
|
||||
|
||||
assertArray("toStringArrayMap(food)", sam.get("food"), "apple","cherry","raspberry");
|
||||
assertArray("toStringArrayMap(color)", sam.get("color"), "red");
|
||||
assertArray("toStringArrayMap(amount)", sam.get("amount"), "bushel","pint");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#toString()}
|
||||
*/
|
||||
@Test
|
||||
public void testToString()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
mm.put("color","red");
|
||||
|
||||
Assert.assertEquals("{color=red}", mm.toString());
|
||||
|
||||
mm.putValues("food","apple","cherry","raspberry");
|
||||
|
||||
Assert.assertEquals("{color=red, food=[apple, cherry, raspberry]}", mm.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#clear()}
|
||||
*/
|
||||
@Test
|
||||
public void testClear()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
mm.putValues("food","apple","cherry","raspberry");
|
||||
mm.put("color","red");
|
||||
mm.putValues("amount","bushel","pint");
|
||||
|
||||
assertMapSize(mm,3);
|
||||
|
||||
mm.clear();
|
||||
|
||||
assertMapSize(mm,0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#containsKey(Object)}
|
||||
*/
|
||||
@Test
|
||||
public void testContainsKey()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
mm.putValues("food","apple","cherry","raspberry");
|
||||
mm.put("color","red");
|
||||
mm.putValues("amount","bushel","pint");
|
||||
|
||||
Assert.assertTrue("Contains Key [color]", mm.containsKey("color"));
|
||||
Assert.assertFalse("Contains Key [nutrition]", mm.containsKey("nutrition"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#containsValue(Object)}
|
||||
*/
|
||||
@Test
|
||||
public void testContainsValue()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
mm.putValues("food","apple","cherry","raspberry");
|
||||
mm.put("color","red");
|
||||
mm.putValues("amount","bushel","pint");
|
||||
|
||||
Assert.assertTrue("Contains Value [red]", mm.containsValue("red"));
|
||||
Assert.assertFalse("Contains Value [nutrition]", mm.containsValue("nutrition"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link MultiMap#containsValue(Object)}
|
||||
*/
|
||||
@Test
|
||||
public void testContainsValue_LazyList()
|
||||
{
|
||||
MultiMap<String> mm = new MultiMap<String>();
|
||||
mm.putValues("food","apple","cherry","raspberry");
|
||||
mm.put("color","red");
|
||||
mm.putValues("amount","bushel","pint");
|
||||
|
||||
Object list = LazyList.add(null, "bushel");
|
||||
list = LazyList.add(list, "pint");
|
||||
|
||||
Assert.assertTrue("Contains Value [" + list + "]", mm.containsValue(list));
|
||||
}
|
||||
|
||||
private void assertArray(String prefix, Object[] actualValues, Object ...expectedValues)
|
||||
{
|
||||
Assert.assertEquals(prefix + ".size",expectedValues.length,actualValues.length);
|
||||
int len = actualValues.length;
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
Assert.assertEquals(prefix + "[" + i + "]",expectedValues[i],actualValues[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertValues(MultiMap<String> mm, String key, Object... expectedValues)
|
||||
{
|
||||
List<Object> values = mm.getValues(key);
|
||||
|
||||
String prefix = "MultiMap.getValues(" + key + ")";
|
||||
|
||||
Assert.assertNotNull(prefix,values);
|
||||
Assert.assertEquals(prefix + ".size",expectedValues.length,values.size());
|
||||
int len = values.size();
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
Assert.assertEquals(prefix + "[" + i + "]",expectedValues[i],values.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void assertEmptyValues(MultiMap<String> mm, String key)
|
||||
{
|
||||
List<Object> values = mm.getValues(key);
|
||||
|
||||
String prefix = "MultiMap.getValues(" + key + ")";
|
||||
|
||||
Assert.assertEquals(prefix + ".size",0,LazyList.size(values));
|
||||
}
|
||||
|
||||
private void assertMapSize(MultiMap<String> mm, int expectedSize)
|
||||
{
|
||||
Assert.assertEquals("MultiMap.size",expectedSize,mm.size());
|
||||
}
|
||||
}
|
|
@ -18,6 +18,8 @@ import static org.junit.Assert.assertTrue;
|
|||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
@ -123,8 +125,12 @@ public class LogTest
|
|||
public void testStdErrLogName()
|
||||
{
|
||||
StdErrLog log = new StdErrLog("test");
|
||||
Assert.assertEquals("test",log.getName());
|
||||
|
||||
Logger next=log.getLogger("next");
|
||||
|
||||
Assert.assertEquals("test.next",next.getName());
|
||||
|
||||
next.info("testing {} {}","next","info");
|
||||
logContains(":test.next:testing next info");
|
||||
|
||||
|
|
|
@ -17,16 +17,16 @@
|
|||
<artifactId>jetty-server</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>${servlet.spec.groupId}</groupId>
|
||||
<artifactId>${servlet.spec.artifactId}</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||
<artifactId>jetty-test-helper</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
|
|
@ -6,7 +6,6 @@ import java.util.Map;
|
|||
|
||||
import org.eclipse.jetty.io.Buffer;
|
||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.websocket.WebSocketParser.FrameHandler;
|
||||
|
||||
public class AbstractExtension implements Extension
|
||||
|
|
|
@ -8,7 +8,6 @@ import java.util.zip.Inflater;
|
|||
|
||||
import org.eclipse.jetty.io.Buffer;
|
||||
import org.eclipse.jetty.io.ByteArrayBuffer;
|
||||
import org.eclipse.jetty.util.ByteArrayOutputStream2;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
||||
public class DeflateFrameExtension extends AbstractExtension
|
||||
|
|
|
@ -1,32 +1,18 @@
|
|||
package org.eclipse.jetty.websocket;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.CyclicBarrier;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.eclipse.jetty.io.Buffer;
|
||||
import org.eclipse.jetty.io.bio.SocketEndPoint;
|
||||
import org.eclipse.jetty.util.B64Code;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.websocket.WebSocket.Connection;
|
||||
import org.eclipse.jetty.websocket.WebSocket.FrameConnection;
|
||||
|
||||
/**
|
||||
* @version $Revision$ $Date$
|
||||
|
@ -68,14 +54,6 @@ public class TestClient implements WebSocket.OnFrame
|
|||
{
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
System.err.println("onError: "+message);
|
||||
if (ex!=null)
|
||||
ex.printStackTrace();
|
||||
_handshook.countDown();
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
_handshook.countDown();
|
||||
|
@ -141,8 +119,10 @@ public class TestClient implements WebSocket.OnFrame
|
|||
|
||||
private void open() throws Exception
|
||||
{
|
||||
__client.open(new URI("ws://"+_host+":"+_port+"/"),this,_protocol,_timeout);
|
||||
_handshook.await(10,TimeUnit.SECONDS);
|
||||
WebSocketClient client = new WebSocketClient(__client);
|
||||
client.setProtocol(_protocol);
|
||||
client.setMaxIdleTime(_timeout);
|
||||
client.open(new URI("ws://"+_host+":"+_port+"/"),this).get(10,TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void ping(byte opcode,byte[] data,int fragment) throws Exception
|
||||
|
|
|
@ -107,12 +107,6 @@ public class TestServer extends Server
|
|||
System.err.printf("%s#onOpen %s\n",this.getClass().getSimpleName(),connection);
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
if (_verbose)
|
||||
System.err.printf("%s#onOpen %s\n",this.getClass().getSimpleName(),message);
|
||||
}
|
||||
|
||||
public void onHandshake(FrameConnection connection)
|
||||
{
|
||||
if (_verbose)
|
||||
|
|
|
@ -29,13 +29,6 @@ public interface WebSocket
|
|||
*/
|
||||
void onOpen(Connection connection);
|
||||
|
||||
/**
|
||||
* Called when a new websocket connection cannot be created
|
||||
* @param message The error message
|
||||
* @param ex The exception or null
|
||||
*/
|
||||
void onError(String message, Throwable ex);
|
||||
|
||||
/**
|
||||
* Called when an established websocket connection closes
|
||||
* @param closeCode
|
||||
|
@ -155,7 +148,10 @@ public interface WebSocket
|
|||
byte textOpcode();
|
||||
byte continuationOpcode();
|
||||
byte finMask();
|
||||
String getProtocol();
|
||||
void setFakeFragments(boolean fake);
|
||||
|
||||
boolean isFakeFragments();
|
||||
boolean isControl(byte opcode);
|
||||
boolean isText(byte opcode);
|
||||
boolean isBinary(byte opcode);
|
||||
|
|
|
@ -15,11 +15,8 @@ package org.eclipse.jetty.websocket;
|
|||
|
||||
import org.eclipse.jetty.io.Buffer;
|
||||
import org.eclipse.jetty.io.Buffers;
|
||||
import org.eclipse.jetty.io.BuffersFactory;
|
||||
import org.eclipse.jetty.io.Buffers.Type;
|
||||
import org.eclipse.jetty.io.ThreadLocalBuffers;
|
||||
import org.eclipse.jetty.io.nio.DirectNIOBuffer;
|
||||
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
|
||||
import org.eclipse.jetty.io.BuffersFactory;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -3,14 +3,21 @@ package org.eclipse.jetty.websocket;
|
|||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ProtocolException;
|
||||
import java.net.URI;
|
||||
import java.nio.channels.ByteChannel;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.SocketChannel;
|
||||
import java.nio.channels.UnsupportedAddressTypeException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jetty.http.HttpFields;
|
||||
import org.eclipse.jetty.http.HttpParser;
|
||||
|
@ -29,150 +36,227 @@ import org.eclipse.jetty.util.component.AggregateLifeCycle;
|
|||
import org.eclipse.jetty.util.log.Logger;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ThreadPool;
|
||||
import org.eclipse.jetty.util.thread.Timeout;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** WebSocket Client
|
||||
* <p>This WebSocket Client class can create multiple websocket connections to multiple destinations.
|
||||
* It uses the same {@link WebSocket} endpoint API as the server.
|
||||
* Simple usage is as follows: <pre>
|
||||
* WebSocketClient client = new WebSocketClient();
|
||||
* client.setMaxIdleTime(500);
|
||||
* client.start();
|
||||
*
|
||||
* WebSocket.Connection connection = client.open(new URI("ws://127.0.0.1:8080/"),new WebSocket.OnTextMessage()
|
||||
* {
|
||||
* public void onOpen(Connection connection)
|
||||
* {
|
||||
* // open notification
|
||||
* }
|
||||
*
|
||||
* public void onClose(int closeCode, String message)
|
||||
* {
|
||||
* // close notification
|
||||
* }
|
||||
*
|
||||
* public void onMessage(String data)
|
||||
* {
|
||||
* // handle incoming message
|
||||
* }
|
||||
* }).get(5,TimeUnit.SECONDS);
|
||||
*
|
||||
* connection.sendMessage("Hello World");
|
||||
* </pre>
|
||||
*/
|
||||
public class WebSocketClient extends AggregateLifeCycle
|
||||
{
|
||||
private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClient.class.getCanonicalName());
|
||||
private final static Random __random = new Random();
|
||||
private final static ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept");
|
||||
|
||||
private final WebSocketClient _root;
|
||||
private final WebSocketClient _parent;
|
||||
private final ThreadPool _threadPool;
|
||||
private final Selector _selector=new Selector();
|
||||
private final Timeout _connectQ=new Timeout();
|
||||
private int _connectTimeout=30000;
|
||||
private final WebSocketClientSelector _selector;
|
||||
|
||||
private final Map<String,String> _cookies=new ConcurrentHashMap<String, String>();
|
||||
private final List<String> _extensions=new CopyOnWriteArrayList<String>();
|
||||
|
||||
private int _bufferSize=64*1024;
|
||||
private boolean _blockingConnect=false;
|
||||
private String _protocol;
|
||||
private int _maxIdleTime=-1;
|
||||
|
||||
private WebSocketBuffers _buffers;
|
||||
|
||||
public WebSocketClient(ThreadPool threadpool)
|
||||
{
|
||||
_threadPool=threadpool;
|
||||
addBean(_selector);
|
||||
addBean(_threadPool);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Create a WebSocket Client with default configuration.
|
||||
*/
|
||||
public WebSocketClient()
|
||||
{
|
||||
this(new QueuedThreadPool());
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Create a WebSocket Client with shared threadpool.
|
||||
* @param threadpool
|
||||
*/
|
||||
public WebSocketClient(ThreadPool threadpool)
|
||||
{
|
||||
_root=this;
|
||||
_parent=null;
|
||||
_threadPool=threadpool;
|
||||
_selector=new WebSocketClientSelector();
|
||||
addBean(_selector);
|
||||
addBean(_threadPool);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Create a WebSocket Client from another.
|
||||
* <p>If multiple clients are required so that connections created may have different
|
||||
* configurations, then it is more efficient to create a client based on another, so
|
||||
* that the thread pool and IO infrastructure may be shared.
|
||||
*/
|
||||
public WebSocketClient(WebSocketClient parent)
|
||||
{
|
||||
_root=parent._root;
|
||||
_parent=parent;
|
||||
_threadPool=parent._threadPool;
|
||||
_selector=parent._selector;
|
||||
_parent.addBean(this);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* Get the selectorManager. Used to configure the manager.
|
||||
* @return The {@link SelectorManager} instance.
|
||||
*/
|
||||
public SelectorManager getSelectorManager()
|
||||
{
|
||||
return _selector;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the ThreadPool.
|
||||
* <p>Used to set/query the thread pool configuration.
|
||||
* @return The {@link ThreadPool}
|
||||
*/
|
||||
public ThreadPool getThreadPool()
|
||||
{
|
||||
return _threadPool;
|
||||
}
|
||||
|
||||
public int getConnectTimeout()
|
||||
{
|
||||
return _connectTimeout;
|
||||
}
|
||||
|
||||
public void setConnectTimeout(int connectTimeout)
|
||||
{
|
||||
if (isRunning())
|
||||
throw new IllegalStateException(getState());
|
||||
_connectTimeout = connectTimeout;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the maxIdleTime for connections opened by this client.
|
||||
* @return The maxIdleTime in ms, or -1 if the default from {@link #getSelectorManager()} is used.
|
||||
*/
|
||||
public int getMaxIdleTime()
|
||||
{
|
||||
return (int)_selector.getMaxIdleTime();
|
||||
return _maxIdleTime;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Set the maxIdleTime for connections opened by this client.
|
||||
* @param maxIdleTime max idle time in ms
|
||||
*/
|
||||
public void setMaxIdleTime(int maxIdleTime)
|
||||
{
|
||||
_selector.setMaxIdleTime(maxIdleTime);
|
||||
_maxIdleTime=maxIdleTime;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the WebSocket Buffer size for connections opened by this client.
|
||||
* @return the buffer size in bytes.
|
||||
*/
|
||||
public int getBufferSize()
|
||||
{
|
||||
return _bufferSize;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Set the WebSocket Buffer size for connections opened by this client.
|
||||
* @param bufferSize the buffer size in bytes.
|
||||
*/
|
||||
public void setBufferSize(int bufferSize)
|
||||
{
|
||||
if (isRunning())
|
||||
throw new IllegalStateException(getState());
|
||||
_bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
public boolean isBlockingConnect()
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Get the subprotocol string for connections opened by this client.
|
||||
* @return The subprotocol
|
||||
*/
|
||||
public String getProtocol()
|
||||
{
|
||||
return _blockingConnect;
|
||||
return _protocol;
|
||||
}
|
||||
|
||||
public void setBlockingConnect(boolean blockingConnect)
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Set the subprotocol string for connections opened by this client.
|
||||
* @param protocol The subprotocol
|
||||
*/
|
||||
public void setProtocol(String protocol)
|
||||
{
|
||||
_blockingConnect = blockingConnect;
|
||||
_protocol = protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
/* ------------------------------------------------------------ */
|
||||
public Map<String,String> getCookies()
|
||||
{
|
||||
_buffers = new WebSocketBuffers(_bufferSize);
|
||||
return _cookies;
|
||||
}
|
||||
|
||||
super.doStart();
|
||||
for (int i=0;i<_selector.getSelectSets();i++)
|
||||
/* ------------------------------------------------------------ */
|
||||
public List<String> getExtensions()
|
||||
{
|
||||
return _extensions;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Open a WebSocket connection.
|
||||
* Open a websocket connection to the URI and block until the connection is accepted or there is an error.
|
||||
* @param uri The URI to connect to.
|
||||
* @param websocket The {@link WebSocket} instance to handle incoming events.
|
||||
* @param maxConnectTime The interval to wait for a successful connection
|
||||
* @param units the units of the maxConnectTime
|
||||
* @return A {@link WebSocket.Connection}
|
||||
* @throws IOException
|
||||
* @throws InterruptedException
|
||||
* @throws TimeoutException
|
||||
*/
|
||||
public WebSocket.Connection open(URI uri, WebSocket websocket,long maxConnectTime,TimeUnit units) throws IOException, InterruptedException, TimeoutException
|
||||
{
|
||||
try
|
||||
{
|
||||
final int id=i;
|
||||
_threadPool.dispatch(new Runnable(){
|
||||
public void run()
|
||||
{
|
||||
while(isRunning())
|
||||
{
|
||||
try
|
||||
{
|
||||
_selector.doSelect(id);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
__log.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return open(uri,websocket).get(maxConnectTime,units);
|
||||
}
|
||||
catch (ExecutionException e)
|
||||
{
|
||||
Throwable cause = e.getCause();
|
||||
if (cause instanceof IOException)
|
||||
throw (IOException)cause;
|
||||
if (cause instanceof Error)
|
||||
throw (Error)cause;
|
||||
if (cause instanceof RuntimeException)
|
||||
throw (RuntimeException)cause;
|
||||
throw new RuntimeException(cause);
|
||||
}
|
||||
|
||||
_connectQ.setDuration(_connectTimeout);
|
||||
_threadPool.dispatch(new Runnable(){
|
||||
public void run()
|
||||
{
|
||||
while(isRunning())
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread.sleep(200); // TODO configure?
|
||||
_connectQ.tick(System.currentTimeMillis());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
__log.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void open(URI uri, WebSocket websocket) throws IOException
|
||||
{
|
||||
open(uri,websocket,null,(int)_selector.getMaxIdleTime(),null,null);
|
||||
}
|
||||
|
||||
public void open(URI uri, WebSocket websocket, String protocol,int maxIdleTime) throws IOException
|
||||
{
|
||||
open(uri,websocket,protocol,(int)_selector.getMaxIdleTime(),null,null);
|
||||
}
|
||||
|
||||
public void open(URI uri, WebSocket websocket, String protocol,int maxIdleTime,Map<String,String> cookies) throws IOException
|
||||
{
|
||||
open(uri,websocket,protocol,(int)_selector.getMaxIdleTime(),cookies,null);
|
||||
}
|
||||
|
||||
public void open(URI uri, WebSocket websocket, String protocol,int maxIdleTime,Map<String,String> cookies,List<String> extensions) throws IOException
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Asynchronously open a websocket connection.
|
||||
* Open a websocket connection and return a {@link Future} to obtain the connection.
|
||||
* The caller must call {@link Future#get(long, TimeUnit)} if they wish to impose a connect timeout on the open.
|
||||
*
|
||||
* @param uri The URI to connect to.
|
||||
* @param websocket The {@link WebSocket} instance to handle incoming events.
|
||||
* @return A {@link Future} to the {@link WebSocket.Connection}
|
||||
* @throws IOException
|
||||
*/
|
||||
public Future<WebSocket.Connection> open(URI uri, WebSocket websocket) throws IOException
|
||||
{
|
||||
if (!isStarted())
|
||||
throw new IllegalStateException("!started");
|
||||
|
@ -184,40 +268,66 @@ public class WebSocketClient extends AggregateLifeCycle
|
|||
|
||||
SocketChannel channel = SocketChannel.open();
|
||||
channel.socket().setTcpNoDelay(true);
|
||||
channel.socket().setSoTimeout(getMaxIdleTime());
|
||||
int maxIdleTime = getMaxIdleTime();
|
||||
if (maxIdleTime<0)
|
||||
maxIdleTime=(int)_selector.getMaxIdleTime();
|
||||
if (maxIdleTime>0)
|
||||
channel.socket().setSoTimeout(maxIdleTime);
|
||||
|
||||
InetSocketAddress address=new InetSocketAddress(uri.getHost(),uri.getPort());
|
||||
|
||||
WebSocketHolder holder=new WebSocketHolder(websocket,uri,protocol,maxIdleTime,cookies,extensions,channel);
|
||||
final WebSocketFuture holder=new WebSocketFuture(websocket,uri,_protocol,maxIdleTime,_cookies,_extensions,channel);
|
||||
|
||||
channel.configureBlocking(false);
|
||||
channel.connect(address);
|
||||
_selector.register( channel, holder);
|
||||
|
||||
return holder;
|
||||
}
|
||||
|
||||
|
||||
_connectQ.schedule(holder);
|
||||
boolean thrown=true;
|
||||
try
|
||||
@Override
|
||||
protected void doStart() throws Exception
|
||||
{
|
||||
if (_parent!=null && !_parent.isRunning())
|
||||
throw new IllegalStateException("parent:"+getState());
|
||||
|
||||
_buffers = new WebSocketBuffers(_bufferSize);
|
||||
|
||||
super.doStart();
|
||||
|
||||
// Start a selector and timer if this is the root client
|
||||
if (_parent==null)
|
||||
{
|
||||
if (isBlockingConnect())
|
||||
for (int i=0;i<_selector.getSelectSets();i++)
|
||||
{
|
||||
channel.socket().connect(address,0);
|
||||
channel.configureBlocking(false);
|
||||
final int id=i;
|
||||
_threadPool.dispatch(new Runnable()
|
||||
{
|
||||
public void run()
|
||||
{
|
||||
while(isRunning())
|
||||
{
|
||||
try
|
||||
{
|
||||
_selector.doSelect(id);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
__log.warn(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
channel.configureBlocking(false);
|
||||
channel.connect(address);
|
||||
}
|
||||
|
||||
_selector.register( channel, holder);
|
||||
thrown=false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (thrown)
|
||||
holder.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Selector extends SelectorManager
|
||||
/* ------------------------------------------------------------ */
|
||||
/** WebSocket Client Selector Manager
|
||||
*/
|
||||
class WebSocketClientSelector extends SelectorManager
|
||||
{
|
||||
@Override
|
||||
public boolean dispatch(Runnable task)
|
||||
|
@ -234,18 +344,20 @@ public class WebSocketClient extends AggregateLifeCycle
|
|||
@Override
|
||||
protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint)
|
||||
{
|
||||
WebSocketHolder holder = (WebSocketHolder) endpoint.getSelectionKey().attachment();
|
||||
WebSocketFuture holder = (WebSocketFuture) endpoint.getSelectionKey().attachment();
|
||||
return new HandshakeConnection(endpoint,holder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void endPointOpened(SelectChannelEndPoint endpoint)
|
||||
{
|
||||
// TODO expose on outer class
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
|
||||
{
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -257,29 +369,34 @@ public class WebSocketClient extends AggregateLifeCycle
|
|||
@Override
|
||||
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
|
||||
{
|
||||
if (!(attachment instanceof WebSocketHolder))
|
||||
if (!(attachment instanceof WebSocketFuture))
|
||||
super.connectionFailed(channel,ex,attachment);
|
||||
else
|
||||
{
|
||||
__log.debug(ex);
|
||||
WebSocketHolder holder = (WebSocketHolder)attachment;
|
||||
holder.cancel();
|
||||
holder.getWebSocket().onError(ex.toString(),ex);
|
||||
WebSocketFuture holder = (WebSocketFuture)attachment;
|
||||
|
||||
holder.handshakeFailed(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/** Handshake Connection.
|
||||
* Handles the connection until the handshake succeeds or fails.
|
||||
*/
|
||||
class HandshakeConnection extends AbstractConnection
|
||||
{
|
||||
private final SelectChannelEndPoint _endp;
|
||||
private final WebSocketHolder _holder;
|
||||
private final WebSocketFuture _holder;
|
||||
private final String _key;
|
||||
private final HttpParser _parser;
|
||||
private String _accept;
|
||||
private String _error;
|
||||
|
||||
|
||||
public HandshakeConnection(SelectChannelEndPoint endpoint, WebSocketHolder holder)
|
||||
public HandshakeConnection(SelectChannelEndPoint endpoint, WebSocketFuture holder)
|
||||
{
|
||||
super(endpoint,System.currentTimeMillis());
|
||||
_endp=endpoint;
|
||||
|
@ -328,15 +445,18 @@ public class WebSocketClient extends AggregateLifeCycle
|
|||
}
|
||||
});
|
||||
|
||||
String path=_holder.getURI().getPath();
|
||||
if (path==null || path.length()==0)
|
||||
path="/";
|
||||
|
||||
String request=
|
||||
"GET "+_holder.getURI().getPath()+" HTTP/1.1\r\n"+
|
||||
"GET "+path+" HTTP/1.1\r\n"+
|
||||
"Host: "+holder.getURI().getHost()+":"+_holder.getURI().getPort()+"\r\n"+
|
||||
"Upgrade: websocket\r\n"+
|
||||
"Connection: Upgrade\r\n"+
|
||||
"Sec-WebSocket-Key: "+_key+"\r\n"+
|
||||
"Sec-WebSocket-Origin: http://example.com\r\n"+
|
||||
"Sec-WebSocket-Version: 8\r\n";
|
||||
"Sec-WebSocket-Version: "+WebSocketConnectionD10.VERSION+"\r\n";
|
||||
|
||||
if (holder.getProtocol()!=null)
|
||||
request+="Sec-WebSocket-Protocol: "+holder.getProtocol()+"\r\n";
|
||||
|
@ -356,16 +476,16 @@ public class WebSocketClient extends AggregateLifeCycle
|
|||
|
||||
try
|
||||
{
|
||||
ByteArrayBuffer handshake = new ByteArrayBuffer(request);
|
||||
Buffer handshake = new ByteArrayBuffer(request,false);
|
||||
int len=handshake.length();
|
||||
if (len!=_endp.flush(handshake))
|
||||
throw new IOException("incomplete");
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
__log.debug(e);
|
||||
_holder.getWebSocket().onError("Handshake failed",e);
|
||||
holder.handshakeFailed(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Connection handle() throws IOException
|
||||
|
@ -375,8 +495,7 @@ public class WebSocketClient extends AggregateLifeCycle
|
|||
switch (_parser.parseAvailable())
|
||||
{
|
||||
case -1:
|
||||
_holder.cancel();
|
||||
_holder.getWebSocket().onError("EOF",new EOFException());
|
||||
_holder.handshakeFailed(new IOException("Incomplete handshake response"));
|
||||
return this;
|
||||
case 0:
|
||||
return this;
|
||||
|
@ -384,25 +503,25 @@ public class WebSocketClient extends AggregateLifeCycle
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_error==null && _accept==null)
|
||||
_error="No Sec-WebSocket-Accept";
|
||||
else if (_error==null && !WebSocketConnectionD10.hashKey(_key).equals(_accept))
|
||||
_error="Bad Sec-WebSocket-Accept";
|
||||
else
|
||||
if (_error==null)
|
||||
{
|
||||
Buffer header=_parser.getHeaderBuffer();
|
||||
WebSocketConnectionD10 connection = new WebSocketConnectionD10(_holder.getWebSocket(),_endp,_buffers,System.currentTimeMillis(),_holder.getMaxIdleTime(),_holder.getProtocol(),null,10, new WebSocketGeneratorD10.RandomMaskGen());
|
||||
if (_accept==null)
|
||||
_error="No Sec-WebSocket-Accept";
|
||||
else if (!WebSocketConnectionD10.hashKey(_key).equals(_accept))
|
||||
_error="Bad Sec-WebSocket-Accept";
|
||||
else
|
||||
{
|
||||
Buffer header=_parser.getHeaderBuffer();
|
||||
WebSocketConnectionD10 connection = new WebSocketConnectionD10(_holder.getWebSocket(),_endp,_buffers,System.currentTimeMillis(),_holder.getMaxIdleTime(),_holder.getProtocol(),null,10, new WebSocketGeneratorD10.RandomMaskGen());
|
||||
|
||||
if (header.hasContent())
|
||||
connection.fillBuffersFrom(header);
|
||||
_buffers.returnBuffer(header);
|
||||
if (header.hasContent())
|
||||
connection.fillBuffersFrom(header);
|
||||
_buffers.returnBuffer(header);
|
||||
|
||||
if (_holder.getWebSocket() instanceof WebSocket.OnFrame)
|
||||
((WebSocket.OnFrame)_holder.getWebSocket()).onHandshake((WebSocket.FrameConnection)connection.getConnection());
|
||||
_holder.cancel();
|
||||
_holder.getWebSocket().onOpen(connection.getConnection());
|
||||
return connection;
|
||||
_holder.onConnection(connection);
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
|
||||
_endp.close();
|
||||
|
@ -421,13 +540,18 @@ public class WebSocketClient extends AggregateLifeCycle
|
|||
|
||||
public void closed()
|
||||
{
|
||||
_holder.cancel();
|
||||
_holder.getWebSocket().onError(_error==null?"EOF":_error,null);
|
||||
if (_error!=null)
|
||||
_holder.handshakeFailed(new ProtocolException(_error));
|
||||
else
|
||||
_holder.handshakeFailed(new EOFException());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class WebSocketHolder extends Timeout.Task
|
||||
/* ------------------------------------------------------------ */
|
||||
/** The Future Websocket Connection.
|
||||
*/
|
||||
class WebSocketFuture implements Future<WebSocket.Connection>
|
||||
{
|
||||
final WebSocket _websocket;;
|
||||
final URI _uri;
|
||||
|
@ -435,9 +559,13 @@ public class WebSocketClient extends AggregateLifeCycle
|
|||
final int _maxIdleTime;
|
||||
final Map<String,String> _cookies;
|
||||
final List<String> _extensions;
|
||||
final ByteChannel _channel;
|
||||
final CountDownLatch _done = new CountDownLatch(1);
|
||||
|
||||
public WebSocketHolder(WebSocket websocket, URI uri, String protocol, int maxIdleTime, Map<String,String> cookies,List<String> extensions, ByteChannel channel)
|
||||
ByteChannel _channel;
|
||||
WebSocketConnection _connection;
|
||||
Throwable _exception;
|
||||
|
||||
public WebSocketFuture(WebSocket websocket, URI uri, String protocol, int maxIdleTime, Map<String,String> cookies,List<String> extensions, ByteChannel channel)
|
||||
{
|
||||
_websocket=websocket;
|
||||
_uri=uri;
|
||||
|
@ -448,6 +576,60 @@ public class WebSocketClient extends AggregateLifeCycle
|
|||
_channel=channel;
|
||||
}
|
||||
|
||||
public void onConnection(WebSocketConnection connection)
|
||||
{
|
||||
try
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
if (_channel!=null)
|
||||
_connection=connection;
|
||||
}
|
||||
|
||||
if (_connection!=null)
|
||||
{
|
||||
if (_websocket instanceof WebSocket.OnFrame)
|
||||
((WebSocket.OnFrame)_websocket).onHandshake((WebSocket.FrameConnection)connection.getConnection());
|
||||
|
||||
_websocket.onOpen(connection.getConnection());
|
||||
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_done.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
public void handshakeFailed(Throwable ex)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteChannel channel=null;
|
||||
synchronized (this)
|
||||
{
|
||||
if (_channel!=null)
|
||||
{
|
||||
channel=_channel;
|
||||
_channel=null;
|
||||
_exception=ex;
|
||||
}
|
||||
}
|
||||
|
||||
if (channel!=null)
|
||||
{
|
||||
if (ex instanceof ProtocolException)
|
||||
closeChannel(channel,WebSocketConnectionD10.CLOSE_PROTOCOL,ex.getMessage());
|
||||
else
|
||||
closeChannel(channel,WebSocketConnectionD10.CLOSE_NOCLOSE,ex.getMessage());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_done.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String,String> getCookies()
|
||||
{
|
||||
return _cookies;
|
||||
|
@ -473,25 +655,115 @@ public class WebSocketClient extends AggregateLifeCycle
|
|||
return _maxIdleTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expired()
|
||||
{
|
||||
try
|
||||
{
|
||||
__log.debug("expired "+this);
|
||||
getWebSocket().onError("expired",null);
|
||||
_channel.close();
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
__log.ignore(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
return "[" + _uri + ","+_websocket+"]@"+hashCode();
|
||||
}
|
||||
|
||||
public boolean cancel(boolean mayInterruptIfRunning)
|
||||
{
|
||||
try
|
||||
{
|
||||
ByteChannel channel=null;
|
||||
synchronized (this)
|
||||
{
|
||||
if (_connection==null && _exception==null && _channel!=null)
|
||||
{
|
||||
channel=_channel;
|
||||
_channel=null;
|
||||
}
|
||||
}
|
||||
|
||||
if (channel!=null)
|
||||
{
|
||||
closeChannel(channel,WebSocketConnectionD10.CLOSE_NOCLOSE,"cancelled");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_done.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCancelled()
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
return _channel==null && _connection==null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isDone()
|
||||
{
|
||||
synchronized (this)
|
||||
{
|
||||
return _connection!=null && _exception==null;
|
||||
}
|
||||
}
|
||||
|
||||
public org.eclipse.jetty.websocket.WebSocket.Connection get() throws InterruptedException, ExecutionException
|
||||
{
|
||||
try
|
||||
{
|
||||
return get(Long.MAX_VALUE,TimeUnit.SECONDS);
|
||||
}
|
||||
catch(TimeoutException e)
|
||||
{
|
||||
throw new IllegalStateException("The universe has ended",e);
|
||||
}
|
||||
}
|
||||
|
||||
public org.eclipse.jetty.websocket.WebSocket.Connection get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
|
||||
TimeoutException
|
||||
{
|
||||
_done.await(timeout,unit);
|
||||
ByteChannel channel=null;
|
||||
org.eclipse.jetty.websocket.WebSocket.Connection connection=null;
|
||||
Throwable exception=null;
|
||||
synchronized (this)
|
||||
{
|
||||
exception=_exception;
|
||||
if (_connection==null)
|
||||
{
|
||||
exception=_exception;
|
||||
channel=_channel;
|
||||
_channel=null;
|
||||
}
|
||||
else
|
||||
connection=_connection.getConnection();
|
||||
}
|
||||
|
||||
if (channel!=null)
|
||||
closeChannel(channel,WebSocketConnectionD10.CLOSE_NOCLOSE,"timeout");
|
||||
if (exception!=null)
|
||||
throw new ExecutionException(exception);
|
||||
if (connection!=null)
|
||||
return connection;
|
||||
throw new TimeoutException();
|
||||
}
|
||||
|
||||
private void closeChannel(ByteChannel channel,int code, String message)
|
||||
{
|
||||
try
|
||||
{
|
||||
_websocket.onClose(code,message);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
__log.warn(e);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
channel.close();
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
__log.debug(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,4 +17,6 @@ public interface WebSocketConnection extends Connection
|
|||
void handshake(HttpServletRequest request, HttpServletResponse response, String origin, String subprotocol) throws IOException;
|
||||
|
||||
List<Extension> getExtensions();
|
||||
|
||||
WebSocket.Connection getConnection();
|
||||
}
|
|
@ -87,6 +87,13 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc
|
|||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public org.eclipse.jetty.websocket.WebSocket.Connection getConnection()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void setHixieKeys(String key1,String key2)
|
||||
{
|
||||
|
@ -523,4 +530,16 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc
|
|||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void setFakeFragments(boolean fake)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public boolean isFakeFragments()
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,10 +33,10 @@ import org.eclipse.jetty.util.B64Code;
|
|||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnControl;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
|
||||
|
||||
public class WebSocketConnectionD06 extends AbstractConnection implements WebSocketConnection
|
||||
{
|
||||
|
@ -491,6 +491,15 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc
|
|||
{
|
||||
return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort();
|
||||
}
|
||||
|
||||
public void setFakeFragments(boolean fake)
|
||||
{
|
||||
}
|
||||
|
||||
public boolean isFakeFragments()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -33,10 +33,10 @@ import org.eclipse.jetty.util.B64Code;
|
|||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnControl;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
|
||||
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
|
||||
import org.eclipse.jetty.websocket.WebSocketGeneratorD10.MaskGen;
|
||||
|
||||
public class WebSocketConnectionD10 extends AbstractConnection implements WebSocketConnection
|
||||
|
@ -46,6 +46,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
final static byte OP_BINARY = 0x02;
|
||||
final static byte OP_EXT_DATA = 0x03;
|
||||
|
||||
final static byte OP_CONTROL = 0x08;
|
||||
final static byte OP_CLOSE = 0x08;
|
||||
final static byte OP_PING = 0x09;
|
||||
final static byte OP_PONG = 0x0A;
|
||||
|
@ -60,14 +61,18 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
final static int CLOSE_NOCLOSE=1006;
|
||||
final static int CLOSE_NOTUTF8=1007;
|
||||
|
||||
final static int FLAG_FIN=0x8;
|
||||
|
||||
final static int VERSION=8;
|
||||
|
||||
static boolean isLastFrame(byte flags)
|
||||
{
|
||||
return (flags&0x8)!=0;
|
||||
return (flags&FLAG_FIN)!=0;
|
||||
}
|
||||
|
||||
static boolean isControlFrame(byte opcode)
|
||||
{
|
||||
return (opcode&0x8)!=0;
|
||||
return (opcode&OP_CONTROL)!=0;
|
||||
}
|
||||
|
||||
private final static byte[] MAGIC;
|
||||
|
@ -84,9 +89,10 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
private final OnControl _onControl;
|
||||
private final String _protocol;
|
||||
private final int _draft;
|
||||
private final ClassLoader _context;
|
||||
private int _close;
|
||||
private boolean _closedIn;
|
||||
private boolean _closedOut;
|
||||
private volatile boolean _closedIn;
|
||||
private volatile boolean _closedOut;
|
||||
private int _maxTextMessageSize;
|
||||
private int _maxBinaryMessageSize=-1;
|
||||
|
||||
|
@ -103,12 +109,12 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
}
|
||||
}
|
||||
|
||||
private final WebSocketParser.FrameHandler _frameHandler= new FrameHandlerD07();
|
||||
private final WebSocketParser.FrameHandler _frameHandler= new WSFrameHandler();
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
private final WebSocket.FrameConnection _connection = new FrameConnectionD10();
|
||||
private final WebSocket.FrameConnection _connection = new WSFrameConnection();
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -124,6 +130,8 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
{
|
||||
super(endpoint,timestamp);
|
||||
|
||||
_context=Thread.currentThread().getContextClassLoader();
|
||||
|
||||
// TODO - can we use the endpoint idle mechanism?
|
||||
if (endpoint instanceof AsyncEndPoint)
|
||||
((AsyncEndPoint)endpoint).cancelIdle();
|
||||
|
@ -204,6 +212,9 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
/* ------------------------------------------------------------ */
|
||||
public Connection handle() throws IOException
|
||||
{
|
||||
Thread current = Thread.currentThread();
|
||||
ClassLoader oldcontext = current.getContextClassLoader();
|
||||
current.setContextClassLoader(_context);
|
||||
try
|
||||
{
|
||||
// handle the framing protocol
|
||||
|
@ -237,6 +248,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
}
|
||||
finally
|
||||
{
|
||||
current.setContextClassLoader(oldcontext);
|
||||
if (_endp.isOpen())
|
||||
{
|
||||
_generator.idle();
|
||||
|
@ -339,7 +351,6 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
_close=code;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (closed)
|
||||
|
@ -358,7 +369,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
|
||||
bytes[0]=(byte)(code/0x100);
|
||||
bytes[1]=(byte)(code%0x100);
|
||||
_outbound.addFrame((byte)0x8,WebSocketConnectionD10.OP_CLOSE,bytes,0,bytes.length);
|
||||
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD10.OP_CLOSE,bytes,0,bytes.length);
|
||||
}
|
||||
_outbound.flush();
|
||||
|
||||
|
@ -388,29 +399,29 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
private class FrameConnectionD10 implements WebSocket.FrameConnection
|
||||
private class WSFrameConnection implements WebSocket.FrameConnection
|
||||
{
|
||||
volatile boolean _disconnecting;
|
||||
int _maxTextMessage=WebSocketConnectionD10.this._maxTextMessageSize;
|
||||
int _maxBinaryMessage=WebSocketConnectionD10.this._maxBinaryMessageSize;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public synchronized void sendMessage(String content) throws IOException
|
||||
public void sendMessage(String content) throws IOException
|
||||
{
|
||||
if (_closedOut)
|
||||
throw new IOException("closing");
|
||||
byte[] data = content.getBytes(StringUtil.__UTF8);
|
||||
_outbound.addFrame((byte)0x8,WebSocketConnectionD10.OP_TEXT,data,0,data.length);
|
||||
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD10.OP_TEXT,data,0,data.length);
|
||||
checkWriteable();
|
||||
_idle.access(_endp);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public synchronized void sendMessage(byte[] content, int offset, int length) throws IOException
|
||||
public void sendMessage(byte[] content, int offset, int length) throws IOException
|
||||
{
|
||||
if (_closedOut)
|
||||
throw new IOException("closing");
|
||||
_outbound.addFrame((byte)0x8,WebSocketConnectionD10.OP_BINARY,content,offset,length);
|
||||
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD10.OP_BINARY,content,offset,length);
|
||||
checkWriteable();
|
||||
_idle.access(_endp);
|
||||
}
|
||||
|
@ -430,7 +441,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
{
|
||||
if (_closedOut)
|
||||
throw new IOException("closing");
|
||||
_outbound.addFrame((byte)0x8,ctrl,data,offset,length);
|
||||
_outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
|
||||
checkWriteable();
|
||||
_idle.access(_endp);
|
||||
}
|
||||
|
@ -507,7 +518,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
/* ------------------------------------------------------------ */
|
||||
public byte finMask()
|
||||
{
|
||||
return 0x8;
|
||||
return FLAG_FIN;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
@ -558,6 +569,18 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
close(CLOSE_NORMAL,null);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public void setFakeFragments(boolean fake)
|
||||
{
|
||||
_parser.setFakeFragments(fake);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public boolean isFakeFragments()
|
||||
{
|
||||
return _parser.isFakeFragments();
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public String toString()
|
||||
{
|
||||
|
@ -568,13 +591,13 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
/* ------------------------------------------------------------ */
|
||||
private class FrameHandlerD07 implements WebSocketParser.FrameHandler
|
||||
private class WSFrameHandler implements WebSocketParser.FrameHandler
|
||||
{
|
||||
private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
|
||||
private ByteArrayBuffer _aggregate;
|
||||
private byte _opcode=-1;
|
||||
|
||||
public void onFrame(byte flags, byte opcode, Buffer buffer)
|
||||
public void onFrame(final byte flags, final byte opcode, final Buffer buffer)
|
||||
{
|
||||
boolean lastFrame = isLastFrame(flags);
|
||||
|
||||
|
@ -583,176 +606,176 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
|||
// Ignore incoming after a close
|
||||
if (_closedIn)
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
byte[] array=buffer.array();
|
||||
|
||||
try
|
||||
// Deliver frame if websocket is a FrameWebSocket
|
||||
if (_onFrame!=null)
|
||||
{
|
||||
byte[] array=buffer.array();
|
||||
if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
|
||||
return;
|
||||
}
|
||||
|
||||
// Deliver frame if websocket is a FrameWebSocket
|
||||
if (_onFrame!=null)
|
||||
{
|
||||
if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
|
||||
return;
|
||||
}
|
||||
if (_onControl!=null && isControlFrame(opcode))
|
||||
{
|
||||
if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
|
||||
return;
|
||||
}
|
||||
|
||||
if (_onControl!=null && isControlFrame(opcode))
|
||||
switch(opcode)
|
||||
{
|
||||
case WebSocketConnectionD10.OP_CONTINUATION:
|
||||
{
|
||||
if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
|
||||
return;
|
||||
}
|
||||
|
||||
switch(opcode)
|
||||
{
|
||||
case WebSocketConnectionD10.OP_CONTINUATION:
|
||||
// If text, append to the message buffer
|
||||
if (_onTextMessage!=null && _opcode==WebSocketConnectionD10.OP_TEXT)
|
||||
{
|
||||
// If text, append to the message buffer
|
||||
if (_opcode==WebSocketConnectionD10.OP_TEXT && _connection.getMaxTextMessageSize()>=0)
|
||||
if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
|
||||
{
|
||||
if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
|
||||
// If this is the last fragment, deliver the text buffer
|
||||
if (lastFrame)
|
||||
{
|
||||
// If this is the last fragment, deliver the text buffer
|
||||
if (lastFrame && _onTextMessage!=null)
|
||||
_opcode=-1;
|
||||
String msg =_utf8.toString();
|
||||
_utf8.reset();
|
||||
_onTextMessage.onMessage(msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
textMessageTooLarge();
|
||||
}
|
||||
|
||||
if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
|
||||
{
|
||||
if (checkBinaryMessageSize(_aggregate.length(),buffer.length()))
|
||||
{
|
||||
_aggregate.put(buffer);
|
||||
|
||||
// If this is the last fragment, deliver
|
||||
if (lastFrame && _onBinaryMessage!=null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
|
||||
}
|
||||
finally
|
||||
{
|
||||
_opcode=-1;
|
||||
String msg =_utf8.toString();
|
||||
_utf8.reset();
|
||||
_onTextMessage.onMessage(msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
|
||||
_utf8.reset();
|
||||
_opcode=-1;
|
||||
}
|
||||
}
|
||||
else if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
|
||||
{
|
||||
if (_aggregate.space()<_aggregate.length())
|
||||
{
|
||||
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
|
||||
_aggregate.clear();
|
||||
_opcode=-1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_aggregate.put(buffer);
|
||||
|
||||
// If this is the last fragment, deliver
|
||||
if (lastFrame && _onBinaryMessage!=null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
|
||||
}
|
||||
finally
|
||||
{
|
||||
_opcode=-1;
|
||||
_aggregate.clear();
|
||||
}
|
||||
_aggregate.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WebSocketConnectionD10.OP_PING:
|
||||
{
|
||||
Log.debug("PING {}",this);
|
||||
if (!_closedOut)
|
||||
_connection.sendControl(WebSocketConnectionD10.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WebSocketConnectionD10.OP_PING:
|
||||
{
|
||||
Log.debug("PING {}",this);
|
||||
if (!_closedOut)
|
||||
_connection.sendControl(WebSocketConnectionD10.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketConnectionD10.OP_PONG:
|
||||
{
|
||||
Log.debug("PONG {}",this);
|
||||
break;
|
||||
}
|
||||
case WebSocketConnectionD10.OP_PONG:
|
||||
{
|
||||
Log.debug("PONG {}",this);
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketConnectionD10.OP_CLOSE:
|
||||
case WebSocketConnectionD10.OP_CLOSE:
|
||||
{
|
||||
int code=WebSocketConnectionD10.CLOSE_NOCODE;
|
||||
String message=null;
|
||||
if (buffer.length()>=2)
|
||||
{
|
||||
int code=WebSocketConnectionD10.CLOSE_NOCODE;
|
||||
String message=null;
|
||||
if (buffer.length()>=2)
|
||||
{
|
||||
code=buffer.array()[buffer.getIndex()]*0x100+buffer.array()[buffer.getIndex()+1];
|
||||
if (buffer.length()>2)
|
||||
message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
|
||||
}
|
||||
closeIn(code,message);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
case WebSocketConnectionD10.OP_TEXT:
|
||||
{
|
||||
if(_onTextMessage!=null)
|
||||
code=buffer.array()[buffer.getIndex()]*0x100+buffer.array()[buffer.getIndex()+1];
|
||||
if (buffer.length()>2)
|
||||
message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
|
||||
}
|
||||
closeIn(code,message);
|
||||
break;
|
||||
}
|
||||
|
||||
case WebSocketConnectionD10.OP_TEXT:
|
||||
{
|
||||
if(_onTextMessage!=null)
|
||||
{
|
||||
if (_connection.getMaxTextMessageSize()<=0)
|
||||
{
|
||||
// No size limit, so handle only final frames
|
||||
if (lastFrame)
|
||||
{
|
||||
// Deliver the message
|
||||
_onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_connection.getMaxTextMessageSize()>=0)
|
||||
{
|
||||
// If this is a text fragment, append to buffer
|
||||
if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
|
||||
_opcode=WebSocketConnectionD10.OP_TEXT;
|
||||
else
|
||||
{
|
||||
_utf8.reset();
|
||||
_opcode=-1;
|
||||
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
if (_onBinaryMessage!=null)
|
||||
else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
|
||||
{
|
||||
if (lastFrame)
|
||||
{
|
||||
_onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
|
||||
String msg =_utf8.toString();
|
||||
_utf8.reset();
|
||||
_onTextMessage.onMessage(msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_connection.getMaxBinaryMessageSize()>=0)
|
||||
{
|
||||
if (buffer.length()>_connection.getMaxBinaryMessageSize())
|
||||
{
|
||||
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
|
||||
if (_aggregate!=null)
|
||||
_aggregate.clear();
|
||||
_opcode=-1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_opcode=opcode;
|
||||
if (_aggregate==null)
|
||||
_aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
|
||||
_aggregate.put(buffer);
|
||||
}
|
||||
}
|
||||
_opcode=WebSocketConnectionD10.OP_TEXT;
|
||||
}
|
||||
}
|
||||
else
|
||||
textMessageTooLarge();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
if (_onBinaryMessage!=null && checkBinaryMessageSize(0,buffer.length()))
|
||||
{
|
||||
if (lastFrame)
|
||||
{
|
||||
_onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
|
||||
}
|
||||
else if (_connection.getMaxBinaryMessageSize()>=0)
|
||||
{
|
||||
_opcode=opcode;
|
||||
// TODO use a growing buffer rather than a fixed one.
|
||||
if (_aggregate==null)
|
||||
_aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
|
||||
_aggregate.put(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(ThreadDeath th)
|
||||
{
|
||||
throw th;
|
||||
}
|
||||
catch(Throwable th)
|
||||
{
|
||||
Log.warn(th);
|
||||
}
|
||||
}
|
||||
catch(ThreadDeath th)
|
||||
{
|
||||
throw th;
|
||||
}
|
||||
catch(Throwable th)
|
||||
{
|
||||
Log.warn(th);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkBinaryMessageSize(int bufferLen, int length)
|
||||
{
|
||||
int max = _connection.getMaxBinaryMessageSize();
|
||||
if (max>0 && (bufferLen+length)>max)
|
||||
{
|
||||
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
|
||||
_opcode=-1;
|
||||
if (_aggregate!=null)
|
||||
_aggregate.clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void textMessageTooLarge()
|
||||
{
|
||||
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
|
||||
|
||||
_opcode=-1;
|
||||
_utf8.reset();
|
||||
}
|
||||
|
||||
public void close(int code,String message)
|
||||
|
|
|
@ -20,7 +20,6 @@ import java.util.Random;
|
|||
import org.eclipse.jetty.io.Buffer;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.EofException;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -14,13 +14,11 @@
|
|||
package org.eclipse.jetty.websocket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Random;
|
||||
|
||||
import org.eclipse.jetty.io.Buffer;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.io.EofException;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
|
|
|
@ -18,8 +18,6 @@ import java.io.IOException;
|
|||
import org.eclipse.jetty.io.Buffer;
|
||||
import org.eclipse.jetty.io.Buffers;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
||||
|
||||
|
|
|
@ -18,8 +18,6 @@ import java.io.IOException;
|
|||
import org.eclipse.jetty.io.Buffer;
|
||||
import org.eclipse.jetty.io.Buffers;
|
||||
import org.eclipse.jetty.io.EndPoint;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
||||
|
||||
|
@ -48,7 +46,6 @@ public class WebSocketParserD10 implements WebSocketParser
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
private final WebSocketBuffers _buffers;
|
||||
private final EndPoint _endp;
|
||||
private final FrameHandler _handler;
|
||||
|
@ -63,6 +60,7 @@ public class WebSocketParserD10 implements WebSocketParser
|
|||
private final byte[] _mask = new byte[4];
|
||||
private int _m;
|
||||
private boolean _skip;
|
||||
private boolean _fakeFragments=true;
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
|
@ -81,6 +79,24 @@ public class WebSocketParserD10 implements WebSocketParser
|
|||
_state=State.START;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @return True if fake fragments should be created for frames larger than the buffer.
|
||||
*/
|
||||
public boolean isFakeFragments()
|
||||
{
|
||||
return _fakeFragments;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
/**
|
||||
* @param fakeFragments True if fake fragments should be created for frames larger than the buffer.
|
||||
*/
|
||||
public void setFakeFragments(boolean fakeFragments)
|
||||
{
|
||||
_fakeFragments = fakeFragments;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------ */
|
||||
public boolean isBufferEmpty()
|
||||
{
|
||||
|
@ -122,7 +138,33 @@ public class WebSocketParserD10 implements WebSocketParser
|
|||
|
||||
// if no space, then the data is too big for buffer
|
||||
if (_buffer.space() == 0)
|
||||
{
|
||||
// Can we send a fake frame?
|
||||
if (_fakeFragments && _state==State.DATA)
|
||||
{
|
||||
Buffer data =_buffer.get(4*(available/4));
|
||||
_buffer.compact();
|
||||
if (_masked)
|
||||
{
|
||||
if (data.array()==null)
|
||||
data=_buffer.asMutableBuffer();
|
||||
byte[] array = data.array();
|
||||
final int end=data.putIndex();
|
||||
for (int i=data.getIndex();i<end;i++)
|
||||
array[i]^=_mask[_m++%4];
|
||||
}
|
||||
|
||||
// System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
|
||||
events++;
|
||||
_bytesNeeded-=data.length();
|
||||
_handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionD10.FLAG_FIN)), _opcode, data);
|
||||
|
||||
_opcode=WebSocketConnectionD10.OP_CONTINUATION;
|
||||
}
|
||||
|
||||
if (_buffer.space() == 0)
|
||||
throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
|
||||
}
|
||||
|
||||
// catch IOExceptions (probably EOF) and try to parse what we have
|
||||
try
|
||||
|
@ -202,7 +244,7 @@ public class WebSocketParserD10 implements WebSocketParser
|
|||
_length = _length*0x100 + (0xff&b);
|
||||
if (--_bytesNeeded==0)
|
||||
{
|
||||
if (_length>_buffer.capacity())
|
||||
if (_length>_buffer.capacity() && !_fakeFragments)
|
||||
{
|
||||
events++;
|
||||
_handler.close(WebSocketConnectionD10.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());
|
||||
|
|
|
@ -2,26 +2,47 @@ package org.eclipse.jetty.websocket;
|
|||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.net.ConnectException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Exchanger;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||
import org.eclipse.jetty.websocket.WebSocket.Connection;
|
||||
import org.eclipse.jetty.util.IO;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
public class WebSocketClientTest
|
||||
{
|
||||
private ServerSocket _server;
|
||||
private int _serverPort;
|
||||
|
||||
@Before
|
||||
public void startServer() throws IOException {
|
||||
_server = new ServerSocket();
|
||||
_server.bind(null);
|
||||
_serverPort = _server.getLocalPort();
|
||||
}
|
||||
|
||||
@After
|
||||
public void stopServer() throws IOException {
|
||||
if(_server != null) {
|
||||
_server.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadURL() throws Exception
|
||||
|
@ -40,10 +61,6 @@ public class WebSocketClientTest
|
|||
open.set(true);
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{}
|
||||
});
|
||||
|
@ -59,289 +76,124 @@ public class WebSocketClientTest
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testBlockingConnectionRefused() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.start();
|
||||
client.setBlockingConnect(true);
|
||||
|
||||
boolean bad=false;
|
||||
final AtomicBoolean open = new AtomicBoolean();
|
||||
try
|
||||
{
|
||||
client.open(new URI("ws://127.0.0.1:1"),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{}
|
||||
});
|
||||
|
||||
Assert.fail();
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
bad=true;
|
||||
}
|
||||
Assert.assertTrue(bad);
|
||||
Assert.assertFalse(open.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncConnectionRefused() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.setConnectTimeout(1000);
|
||||
client.start();
|
||||
client.setBlockingConnect(false);
|
||||
|
||||
boolean bad=false;
|
||||
final AtomicBoolean open = new AtomicBoolean();
|
||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
||||
final AtomicInteger close = new AtomicInteger();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:1"),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
}
|
||||
});
|
||||
|
||||
Throwable error=null;
|
||||
try
|
||||
{
|
||||
client.open(new URI("ws://127.0.0.1:1"),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
error.set(message);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
future.get(1,TimeUnit.SECONDS);
|
||||
Assert.fail();
|
||||
}
|
||||
catch(IOException e)
|
||||
catch(ExecutionException e)
|
||||
{
|
||||
bad=true;
|
||||
error=e.getCause();
|
||||
}
|
||||
|
||||
Assert.assertFalse(bad);
|
||||
Assert.assertFalse(open.get());
|
||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
||||
Assert.assertNotNull(error.get());
|
||||
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
|
||||
Assert.assertTrue(error instanceof ConnectException);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockingConnectionNotAccepted() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.setConnectTimeout(500);
|
||||
client.setBlockingConnect(true);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(null);
|
||||
int port = server.getLocalPort();
|
||||
|
||||
|
||||
boolean bad=false;
|
||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
try
|
||||
{
|
||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
error.set(message);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
bad=true;
|
||||
}
|
||||
|
||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
||||
Assert.assertTrue(bad||error.get()!=null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncConnectionNotAccepted() throws Exception
|
||||
public void testConnectionNotAccepted() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.setBlockingConnect(true);
|
||||
client.setConnectTimeout(300);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(null);
|
||||
int port = server.getLocalPort();
|
||||
|
||||
|
||||
boolean bad=false;
|
||||
final AtomicBoolean open = new AtomicBoolean();
|
||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
||||
final AtomicInteger close = new AtomicInteger();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Throwable error=null;
|
||||
try
|
||||
{
|
||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
error.set(message);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
future.get(250,TimeUnit.MILLISECONDS);
|
||||
Assert.fail();
|
||||
}
|
||||
catch(IOException e)
|
||||
catch(TimeoutException e)
|
||||
{
|
||||
bad=true;
|
||||
error=e;
|
||||
}
|
||||
|
||||
Assert.assertFalse(bad);
|
||||
Assert.assertFalse(open.get());
|
||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
||||
Assert.assertNotNull(error.get());
|
||||
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
|
||||
Assert.assertTrue(error instanceof TimeoutException);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockingConnectionTimeout() throws Exception
|
||||
public void testConnectionTimeout() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.setConnectTimeout(500);
|
||||
client.setBlockingConnect(true);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(null);
|
||||
int port = server.getLocalPort();
|
||||
|
||||
|
||||
boolean bad=false;
|
||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
try
|
||||
{
|
||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
error.set(message);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
bad=true;
|
||||
}
|
||||
|
||||
Assert.assertNotNull(server.accept());
|
||||
|
||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
||||
Assert.assertTrue(bad||error.get()!=null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncConnectionTimeout() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.setBlockingConnect(true);
|
||||
client.setConnectTimeout(300);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(null);
|
||||
int port = server.getLocalPort();
|
||||
|
||||
|
||||
boolean bad=false;
|
||||
final AtomicBoolean open = new AtomicBoolean();
|
||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
||||
final AtomicInteger close = new AtomicInteger();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
}
|
||||
});
|
||||
|
||||
Assert.assertNotNull(_server.accept());
|
||||
|
||||
Throwable error=null;
|
||||
try
|
||||
{
|
||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
error.set(message);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
future.get(250,TimeUnit.MILLISECONDS);
|
||||
Assert.fail();
|
||||
}
|
||||
catch(IOException e)
|
||||
catch(TimeoutException e)
|
||||
{
|
||||
bad=true;
|
||||
error=e;
|
||||
}
|
||||
Assert.assertNotNull(server.accept());
|
||||
|
||||
Assert.assertFalse(bad);
|
||||
Assert.assertFalse(open.get());
|
||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
||||
Assert.assertNotNull(error.get());
|
||||
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
|
||||
Assert.assertTrue(error instanceof TimeoutException);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -349,261 +201,160 @@ public class WebSocketClientTest
|
|||
public void testBadHandshake() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.setBlockingConnect(true);
|
||||
client.setConnectTimeout(300);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(null);
|
||||
int port = server.getLocalPort();
|
||||
|
||||
|
||||
boolean bad=false;
|
||||
final AtomicBoolean open = new AtomicBoolean();
|
||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
||||
final AtomicInteger close = new AtomicInteger();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
}
|
||||
});
|
||||
|
||||
Socket connection = _server.accept();
|
||||
respondToClient(connection, "HTTP/1.1 404 NOT FOUND\r\n\r\n");
|
||||
|
||||
Throwable error=null;
|
||||
try
|
||||
{
|
||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
error.set(message);
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
future.get(250,TimeUnit.MILLISECONDS);
|
||||
Assert.fail();
|
||||
}
|
||||
catch(IOException e)
|
||||
catch(ExecutionException e)
|
||||
{
|
||||
bad=true;
|
||||
error=e.getCause();
|
||||
}
|
||||
|
||||
Socket connection = server.accept();
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
for (String line=in.readLine();line!=null;line=in.readLine())
|
||||
{
|
||||
// System.err.println(line);
|
||||
if (line.length()==0)
|
||||
break;
|
||||
}
|
||||
|
||||
connection.getOutputStream().write("HTTP/1.1 404 NOT FOUND\r\n\r\n".getBytes());
|
||||
|
||||
Assert.assertFalse(bad);
|
||||
Assert.assertFalse(open.get());
|
||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
||||
Assert.assertNotNull(error.get());
|
||||
Assert.assertEquals(WebSocketConnectionD10.CLOSE_PROTOCOL,close.get());
|
||||
Assert.assertTrue(error instanceof IOException);
|
||||
Assert.assertTrue(error.getMessage().indexOf("404 NOT FOUND")>0);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadUpgrade() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.setBlockingConnect(true);
|
||||
client.setConnectTimeout(10000);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(null);
|
||||
int port = server.getLocalPort();
|
||||
|
||||
|
||||
boolean bad=false;
|
||||
final AtomicBoolean open = new AtomicBoolean();
|
||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
||||
final AtomicInteger close = new AtomicInteger();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
try
|
||||
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
|
||||
{
|
||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
latch.countDown();
|
||||
}
|
||||
open.set(true);
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
error.set(message);
|
||||
latch.countDown();
|
||||
}
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
}
|
||||
});
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
bad=true;
|
||||
}
|
||||
|
||||
Socket connection = server.accept();
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
for (String line=in.readLine();line!=null;line=in.readLine())
|
||||
{
|
||||
// System.err.println(line);
|
||||
if (line.length()==0)
|
||||
break;
|
||||
}
|
||||
|
||||
connection.getOutputStream().write((
|
||||
Socket connection = _server.accept();
|
||||
respondToClient(connection,
|
||||
"HTTP/1.1 101 Upgrade\r\n" +
|
||||
"Sec-WebSocket-Accept: rubbish\r\n" +
|
||||
"\r\n").getBytes());
|
||||
"\r\n" );
|
||||
|
||||
Assert.assertFalse(bad);
|
||||
Assert.assertFalse(open.get());
|
||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
||||
Assert.assertNotNull(error.get());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUpgrade() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.setBlockingConnect(true);
|
||||
client.setConnectTimeout(10000);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(null);
|
||||
int port = server.getLocalPort();
|
||||
|
||||
|
||||
boolean bad=false;
|
||||
final AtomicBoolean open = new AtomicBoolean();
|
||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
||||
final AtomicInteger close = new AtomicInteger();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
Throwable error=null;
|
||||
try
|
||||
{
|
||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
||||
future.get(250,TimeUnit.MILLISECONDS);
|
||||
Assert.fail();
|
||||
}
|
||||
catch(ExecutionException e)
|
||||
{
|
||||
error=e.getCause();
|
||||
}
|
||||
Assert.assertFalse(open.get());
|
||||
Assert.assertEquals(WebSocketConnectionD10.CLOSE_PROTOCOL,close.get());
|
||||
Assert.assertTrue(error instanceof IOException);
|
||||
Assert.assertTrue(error.getMessage().indexOf("Bad Sec-WebSocket-Accept")>=0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpgradeThenTCPClose() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.start();
|
||||
|
||||
final AtomicBoolean open = new AtomicBoolean();
|
||||
final AtomicInteger close = new AtomicInteger();
|
||||
final CountDownLatch _latch = new CountDownLatch(1);
|
||||
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
latch.countDown();
|
||||
}
|
||||
open.set(true);
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
error.set(message);
|
||||
latch.countDown();
|
||||
}
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
_latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
bad=true;
|
||||
}
|
||||
Assert.assertFalse(bad);
|
||||
Socket socket = _server.accept();
|
||||
accept(socket);
|
||||
|
||||
String key="not sent";
|
||||
Socket connection = server.accept();
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
for (String line=in.readLine();line!=null;line=in.readLine())
|
||||
{
|
||||
if (line.length()==0)
|
||||
break;
|
||||
if (line.startsWith("Sec-WebSocket-Key:"))
|
||||
key=line.substring(18).trim();
|
||||
}
|
||||
connection.getOutputStream().write((
|
||||
"HTTP/1.1 101 Upgrade\r\n" +
|
||||
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
|
||||
"\r\n").getBytes());
|
||||
|
||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
||||
Assert.assertNull(error.get());
|
||||
WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
|
||||
Assert.assertNotNull(connection);
|
||||
Assert.assertTrue(open.get());
|
||||
Assert.assertEquals(0,close.get());
|
||||
|
||||
socket.close();
|
||||
_latch.await(10,TimeUnit.SECONDS);
|
||||
|
||||
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdle() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.setBlockingConnect(true);
|
||||
client.setConnectTimeout(10000);
|
||||
client.setMaxIdleTime(500);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(null);
|
||||
int port = server.getLocalPort();
|
||||
|
||||
boolean bad=false;
|
||||
final AtomicBoolean open = new AtomicBoolean();
|
||||
final AtomicInteger close = new AtomicInteger();
|
||||
final CountDownLatch latch = new CountDownLatch(2);
|
||||
try
|
||||
final CountDownLatch _latch = new CountDownLatch(1);
|
||||
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
|
||||
{
|
||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
open.set(true);
|
||||
latch.countDown();
|
||||
}
|
||||
open.set(true);
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
_latch.countDown();
|
||||
}
|
||||
});
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
bad=true;
|
||||
}
|
||||
Assert.assertFalse(bad);
|
||||
Socket socket = _server.accept();
|
||||
accept(socket);
|
||||
|
||||
String key="not sent";
|
||||
Socket connection = server.accept();
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
for (String line=in.readLine();line!=null;line=in.readLine())
|
||||
{
|
||||
if (line.length()==0)
|
||||
break;
|
||||
if (line.startsWith("Sec-WebSocket-Key:"))
|
||||
key=line.substring(18).trim();
|
||||
}
|
||||
connection.getOutputStream().write((
|
||||
"HTTP/1.1 101 Upgrade\r\n" +
|
||||
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
|
||||
"\r\n").getBytes());
|
||||
|
||||
Assert.assertTrue(latch.await(10,TimeUnit.SECONDS));
|
||||
WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
|
||||
Assert.assertNotNull(connection);
|
||||
Assert.assertTrue(open.get());
|
||||
Assert.assertEquals(0,close.get());
|
||||
|
||||
long start=System.currentTimeMillis();
|
||||
_latch.await(10,TimeUnit.SECONDS);
|
||||
Assert.assertTrue(System.currentTimeMillis()-start<5000);
|
||||
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NORMAL,close.get());
|
||||
}
|
||||
|
||||
|
@ -612,77 +363,41 @@ public class WebSocketClientTest
|
|||
public void testNotIdle() throws Exception
|
||||
{
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
client.setBlockingConnect(true);
|
||||
client.setConnectTimeout(10000);
|
||||
client.setMaxIdleTime(500);
|
||||
client.start();
|
||||
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(null);
|
||||
int port = server.getLocalPort();
|
||||
|
||||
boolean bad=false;
|
||||
final AtomicBoolean open = new AtomicBoolean();
|
||||
final Exchanger<Integer> close = new Exchanger<Integer>();
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final AtomicReference<WebSocket.Connection> connection = new AtomicReference<WebSocket.Connection>();
|
||||
final AtomicInteger close = new AtomicInteger();
|
||||
final CountDownLatch _latch = new CountDownLatch(1);
|
||||
final BlockingQueue<String> queue = new BlockingArrayQueue<String>();
|
||||
try
|
||||
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage()
|
||||
{
|
||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket.OnTextMessage()
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
public void onOpen(Connection c)
|
||||
{
|
||||
open.set(true);
|
||||
connection.set(c);
|
||||
latch.countDown();
|
||||
}
|
||||
open.set(true);
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable ex)
|
||||
{
|
||||
latch.countDown();
|
||||
}
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
close.set(closeCode);
|
||||
_latch.countDown();
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
try
|
||||
{
|
||||
close.exchange(closeCode);
|
||||
}
|
||||
catch(InterruptedException ex)
|
||||
{}
|
||||
latch.countDown();
|
||||
}
|
||||
public void onMessage(String data)
|
||||
{
|
||||
queue.add(data);
|
||||
}
|
||||
});
|
||||
|
||||
public void onMessage(String data)
|
||||
{
|
||||
queue.add(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch(IOException e)
|
||||
{
|
||||
bad=true;
|
||||
}
|
||||
Assert.assertFalse(bad);
|
||||
Socket socket = _server.accept();
|
||||
accept(socket);
|
||||
|
||||
String key="not sent";
|
||||
Socket socket = server.accept();
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
for (String line=in.readLine();line!=null;line=in.readLine())
|
||||
{
|
||||
if (line.length()==0)
|
||||
break;
|
||||
if (line.startsWith("Sec-WebSocket-Key:"))
|
||||
key=line.substring(18).trim();
|
||||
}
|
||||
socket.getOutputStream().write((
|
||||
"HTTP/1.1 101 Upgrade\r\n" +
|
||||
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
|
||||
"\r\n").getBytes());
|
||||
|
||||
Assert.assertTrue(latch.await(10,TimeUnit.SECONDS));
|
||||
WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
|
||||
Assert.assertNotNull(connection);
|
||||
Assert.assertTrue(open.get());
|
||||
Assert.assertEquals(0,close.get());
|
||||
|
||||
|
||||
|
||||
// Send some messages client to server
|
||||
byte[] recv = new byte[1024];
|
||||
|
@ -690,7 +405,7 @@ public class WebSocketClientTest
|
|||
for (int i=0;i<10;i++)
|
||||
{
|
||||
Thread.sleep(250);
|
||||
connection.get().sendMessage("Hello");
|
||||
connection.sendMessage("Hello");
|
||||
len=socket.getInputStream().read(recv,0,recv.length);
|
||||
Assert.assertTrue(len>0);
|
||||
}
|
||||
|
@ -706,10 +421,68 @@ public class WebSocketClientTest
|
|||
Assert.assertEquals("Hi",queue.poll(1,TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
// Close with code
|
||||
long start=System.currentTimeMillis();
|
||||
socket.getOutputStream().write(new byte[]{(byte)0x88, (byte) 0x02, (byte)4, (byte)87 },0,4);
|
||||
socket.getOutputStream().flush();
|
||||
|
||||
Assert.assertEquals(new Integer(1111),close.exchange(null,1,TimeUnit.SECONDS));
|
||||
_latch.await(10,TimeUnit.SECONDS);
|
||||
Assert.assertTrue(System.currentTimeMillis()-start<5000);
|
||||
Assert.assertEquals(1111,close.get());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void respondToClient(Socket connection, String serverResponse) throws IOException
|
||||
{
|
||||
InputStream in = null;
|
||||
InputStreamReader isr = null;
|
||||
BufferedReader buf = null;
|
||||
OutputStream out = null;
|
||||
try {
|
||||
in = connection.getInputStream();
|
||||
isr = new InputStreamReader(in);
|
||||
buf = new BufferedReader(isr);
|
||||
String line;
|
||||
while((line = buf.readLine())!=null)
|
||||
{
|
||||
// System.err.println(line);
|
||||
if(line.length() == 0)
|
||||
{
|
||||
// Got the "\r\n" line.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// System.out.println("[Server-Out] " + serverResponse);
|
||||
out = connection.getOutputStream();
|
||||
out.write(serverResponse.getBytes());
|
||||
out.flush();
|
||||
}
|
||||
finally
|
||||
{
|
||||
IO.close(buf);
|
||||
IO.close(isr);
|
||||
IO.close(in);
|
||||
IO.close(out);
|
||||
}
|
||||
}
|
||||
|
||||
private void accept(Socket connection) throws IOException
|
||||
{
|
||||
String key="not sent";
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
for (String line=in.readLine();line!=null;line=in.readLine())
|
||||
{
|
||||
if (line.length()==0)
|
||||
break;
|
||||
if (line.startsWith("Sec-WebSocket-Key:"))
|
||||
key=line.substring(18).trim();
|
||||
}
|
||||
connection.getOutputStream().write((
|
||||
"HTTP/1.1 101 Upgrade\r\n" +
|
||||
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
|
||||
"\r\n").getBytes());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
@ -117,10 +116,6 @@ public class WebSocketLoadD10Test
|
|||
this.outbound = outbound;
|
||||
}
|
||||
|
||||
public void onError(String message,Throwable ex)
|
||||
{
|
||||
}
|
||||
|
||||
public void onMessage(String data)
|
||||
{
|
||||
try
|
||||
|
|
|
@ -250,10 +250,6 @@ public class WebSocketMessageD00Test
|
|||
return latch.await(time, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void onError(String message,Throwable ex)
|
||||
{
|
||||
}
|
||||
|
||||
public void onClose(int code,String message)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||
import org.eclipse.jetty.util.StringUtil;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
|
@ -757,10 +756,6 @@ public class WebSocketMessageD06Test
|
|||
this.connection = connection;
|
||||
}
|
||||
|
||||
public void onError(String message,Throwable ex)
|
||||
{
|
||||
}
|
||||
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
if (onConnect)
|
||||
|
|