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
This commit is contained in:
Jan Bartel 2011-08-12 18:43:15 +10:00
commit 050cb83b35
105 changed files with 5585 additions and 1854 deletions

View File

@ -19,12 +19,18 @@ jetty-7.5.0-SNAPSHOT
+ 352421 HttpURI paths beginning with '.' + 352421 HttpURI paths beginning with '.'
+ 352684 Implemented spinning thread analyzer + 352684 Implemented spinning thread analyzer
+ 352786 GzipFilter fails to pass parameters to GzipResponseWrapper + 352786 GzipFilter fails to pass parameters to GzipResponseWrapper
+ 352999 ExpireTest running too long
+ 353073 WebSocketClient + 353073 WebSocketClient
+ 353095 maven-jetty-plugin: PermGen leak due to javax.el.BeanELResolver + 353095 maven-jetty-plugin: PermGen leak due to javax.el.BeanELResolver
+ 353165 addJars can follow symbolic link jar files + 353165 addJars can follow symbolic link jar files
+ 353210 Bundle-Version in o.e.j.o.boot.logback fix + 353210 Bundle-Version in o.e.j.o.boot.logback fix
+ 353465 JAASLoginService ignores callbackHandlerClass + 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 jetty-7.4.4.v20110707 July 7th 2011
+ 308851 Converted all jetty-client module tests to JUnit 4 + 308851 Converted all jetty-client module tests to JUnit 4
+ 345268 JDBCSessionManager does not work with maxInactiveInterval = -1 + 345268 JDBCSessionManager does not work with maxInactiveInterval = -1

View File

@ -16,7 +16,6 @@ package org.eclipse.jetty.client;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -105,7 +104,6 @@ public class ExpireTest
exchange.setURL(baseUrl); exchange.setURL(baseUrl);
client.send(exchange); client.send(exchange);
Thread.sleep(50);
} }
// Wait to be sure that all exchanges have expired // Wait to be sure that all exchanges have expired

View File

@ -5,25 +5,34 @@ import java.net.Socket;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import org.junit.Assert; import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
public class HttpDestinationQueueTest 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 @Test
public void testDestinationMaxQueueSize() throws Exception public void testDestinationMaxQueueSize() throws Exception
{ {
HttpClient client = new HttpClient();
client.setMaxConnectionsPerAddress(1);
client.setMaxQueueSizePerAddress(1);
client.start();
ServerSocket server = new ServerSocket(0); ServerSocket server = new ServerSocket(0);
// This will keep the connection busy // This will keep the connection busy
HttpExchange exchange1 = new HttpExchange(); HttpExchange exchange1 = new HttpExchange();
exchange1.setMethod("GET"); exchange1.setMethod("GET");
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1"); 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 // Read request so we are sure that this exchange is out of the queue
Socket socket = server.accept(); Socket socket = server.accept();
@ -32,7 +41,7 @@ public class HttpDestinationQueueTest
while (true) while (true)
{ {
int read = socket.getInputStream().read(buffer); 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")) if (request.toString().endsWith("\r\n\r\n"))
break; break;
} }
@ -42,7 +51,7 @@ public class HttpDestinationQueueTest
HttpExchange exchange2 = new HttpExchange(); HttpExchange exchange2 = new HttpExchange();
exchange2.setMethod("GET"); exchange2.setMethod("GET");
exchange2.setURL("http://localhost:" + server.getLocalPort() + "/exchange2"); 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 // This will be rejected, since the connection is busy and the queue is full
HttpExchange exchange3 = new HttpExchange(); HttpExchange exchange3 = new HttpExchange();
@ -50,7 +59,7 @@ public class HttpDestinationQueueTest
exchange3.setURL("http://localhost:" + server.getLocalPort() + "/exchange3"); exchange3.setURL("http://localhost:" + server.getLocalPort() + "/exchange3");
try try
{ {
client.send(exchange3); _httpClient.send(exchange3);
Assert.fail(); Assert.fail();
} }
catch (RejectedExecutionException x) catch (RejectedExecutionException x)
@ -60,14 +69,14 @@ public class HttpDestinationQueueTest
// Send the response to avoid exceptions in the console // 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")); 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 // Be sure that the second exchange can be sent
request.setLength(0); request.setLength(0);
while (true) while (true)
{ {
int read = socket.getInputStream().read(buffer); 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")) if (request.toString().endsWith("\r\n\r\n"))
break; 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.getOutputStream().write("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n".getBytes("UTF-8"));
socket.close(); socket.close();
Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange2.waitForDone()); Assert.assertEquals(HttpExchange.STATUS_COMPLETED,exchange2.waitForDone());
server.close(); server.close();
client.stop();
} }
@Test @Test
public void testDefaultTimeoutIncludesQueuingExchangeExpiresInQueue() throws Exception 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); ServerSocket server = new ServerSocket(0);
// This will keep the connection busy // This will keep the connection busy
HttpExchange exchange1 = new HttpExchange(); 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.setMethod("GET");
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1"); 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 // Read request so we are sure that this exchange is out of the queue
Socket socket = server.accept(); Socket socket = server.accept();
@ -108,7 +109,7 @@ public class HttpDestinationQueueTest
while (true) while (true)
{ {
int read = socket.getInputStream().read(buffer); 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")) if (request.toString().endsWith("\r\n\r\n"))
break; break;
} }
@ -118,39 +119,30 @@ public class HttpDestinationQueueTest
HttpExchange exchange2 = new HttpExchange(); HttpExchange exchange2 = new HttpExchange();
exchange2.setMethod("GET"); exchange2.setMethod("GET");
exchange2.setURL("http://localhost:" + server.getLocalPort() + "/exchange2"); exchange2.setURL("http://localhost:" + server.getLocalPort() + "/exchange2");
client.send(exchange2); _httpClient.send(exchange2);
// Wait until the queued exchange times out in the queue // 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 // 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")); 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(); socket.close();
server.close(); server.close();
client.stop();
} }
@Test @Test
public void testDefaultTimeoutIncludesQueuingExchangeExpiresDuringRequest() throws Exception 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); ServerSocket server = new ServerSocket(0);
HttpExchange exchange1 = new HttpExchange(); HttpExchange exchange1 = new HttpExchange();
exchange1.setMethod("GET"); exchange1.setMethod("GET");
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1"); 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 // Read request so we are sure that this exchange is out of the queue
Socket socket = server.accept(); Socket socket = server.accept();
@ -159,32 +151,25 @@ public class HttpDestinationQueueTest
while (true) while (true)
{ {
int read = socket.getInputStream().read(buffer); 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")) if (request.toString().endsWith("\r\n\r\n"))
break; break;
} }
Assert.assertTrue(request.toString().contains("exchange1")); Assert.assertTrue(request.toString().contains("exchange1"));
// Wait until the exchange times out during the request // 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(); socket.close();
server.close(); server.close();
client.stop();
} }
@Test @Test
public void testExchangeTimeoutIncludesQueuingExchangeExpiresDuringResponse() throws Exception public void testExchangeTimeoutIncludesQueuingExchangeExpiresDuringResponse() throws Exception
{ {
HttpClient client = new HttpClient();
client.setMaxConnectionsPerAddress(1);
client.setMaxQueueSizePerAddress(1);
client.start();
ServerSocket server = new ServerSocket(0); ServerSocket server = new ServerSocket(0);
long timeout = 1000; long timeout = 1000;
@ -192,7 +177,7 @@ public class HttpDestinationQueueTest
exchange1.setTimeout(timeout); exchange1.setTimeout(timeout);
exchange1.setMethod("GET"); exchange1.setMethod("GET");
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1"); 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 // Read request so we are sure that this exchange is out of the queue
Socket socket = server.accept(); Socket socket = server.accept();
@ -201,7 +186,7 @@ public class HttpDestinationQueueTest
while (true) while (true)
{ {
int read = socket.getInputStream().read(buffer); 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")) if (request.toString().endsWith("\r\n\r\n"))
break; break;
} }
@ -213,12 +198,10 @@ public class HttpDestinationQueueTest
// Wait until the exchange times out during the response // Wait until the exchange times out during the response
Thread.sleep(timeout * 2); Thread.sleep(timeout * 2);
Assert.assertEquals(HttpExchange.STATUS_EXPIRED, exchange1.getStatus()); Assert.assertEquals(HttpExchange.STATUS_EXPIRED,exchange1.getStatus());
socket.close(); socket.close();
server.close(); server.close();
client.stop();
} }
} }

View File

@ -103,14 +103,6 @@ public class WebSocketUpgradeTest
_results.add("clientWS.onMessage"); _results.add("clientWS.onMessage");
_results.add(data); _results.add(data);
} }
/* ------------------------------------------------------------ */
public void onError(String message, Throwable ex)
{
_results.add("clientWS.onError");
_results.add(message);
_results.add(ex);
}
}; };
@ -252,19 +244,11 @@ public class WebSocketUpgradeTest
_results.add("serverWS.onMessage"); _results.add("serverWS.onMessage");
_results.add(data); _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) public void onClose(int code, String message)
{ {
_results.add("onDisconnect"); _results.add("onClose");
_webSockets.remove(this); _webSockets.remove(this);
} }

View File

@ -142,6 +142,8 @@ public class ScanningAppProviderRuntimeUpdatesTest
// This test will not work on Windows as second war file would // This test will not work on Windows as second war file would
// not be written over the first one because of a file lock // not be written over the first one because of a file lock
Assume.assumeTrue(!OS.IS_WINDOWS); 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.copyWebapp("foo-webapp-1.war","foo.war");
jetty.copyContext("foo.xml","foo.xml"); jetty.copyContext("foo.xml","foo.xml");

View File

@ -293,7 +293,6 @@ public class MimeTypes
case TEXT_XML_8859_1_ORDINAL: case TEXT_XML_8859_1_ORDINAL:
return StringUtil.__ISO_8859_1; return StringUtil.__ISO_8859_1;
case TEXT_JSON_ORDINAL:
case TEXT_HTML_UTF_8_ORDINAL: case TEXT_HTML_UTF_8_ORDINAL:
case TEXT_PLAIN_UTF_8_ORDINAL: case TEXT_PLAIN_UTF_8_ORDINAL:
case TEXT_XML_UTF_8_ORDINAL: case TEXT_XML_UTF_8_ORDINAL:
@ -363,6 +362,7 @@ public class MimeTypes
if (state==10) if (state==10)
return CACHE.lookup(value.peek(start,i-start)).toString(); return CACHE.lookup(value.peek(start,i-start)).toString();
return null;
return (String)__encodings.get(value);
} }
} }

View File

@ -22,6 +22,7 @@ import java.io.UnsupportedEncodingException;
import java.util.Set; import java.util.Set;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper; import javax.servlet.http.HttpServletResponseWrapper;
@ -34,10 +35,13 @@ import org.eclipse.jetty.util.StringUtil;
*/ */
public class GzipResponseWrapper extends HttpServletResponseWrapper public class GzipResponseWrapper extends HttpServletResponseWrapper
{ {
public static int DEFAULT_BUFFER_SIZE = 8192;
public static int DEFAULT_MIN_GZIP_SIZE = 256;
private HttpServletRequest _request; private HttpServletRequest _request;
private Set<String> _mimeTypes; private Set<String> _mimeTypes;
private int _bufferSize=8192; private int _bufferSize=DEFAULT_BUFFER_SIZE;
private int _minGzipSize=256; private int _minGzipSize=DEFAULT_MIN_GZIP_SIZE;
private PrintWriter _writer; private PrintWriter _writer;
private GzipStream _gzStream; private GzipStream _gzStream;
@ -137,12 +141,30 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
* @see javax.servlet.ServletResponseWrapper#setContentLength(int) * @see javax.servlet.ServletResponseWrapper#setContentLength(int)
*/ */
public void setContentLength(int length) public void setContentLength(int length)
{
setContentLength((long)length);
}
/* ------------------------------------------------------------ */
protected void setContentLength(long length)
{ {
_contentLength=length; _contentLength=length;
if (_gzStream!=null) if (_gzStream!=null)
_gzStream.setContentLength(length); _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));
}
}
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @see javax.servlet.http.HttpServletResponseWrapper#addHeader(java.lang.String, java.lang.String) * @see javax.servlet.http.HttpServletResponseWrapper#addHeader(java.lang.String, java.lang.String)
@ -179,9 +201,7 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
{ {
if ("content-length".equalsIgnoreCase(name)) if ("content-length".equalsIgnoreCase(name))
{ {
_contentLength=Long.parseLong(value); setContentLength(Long.parseLong(value));
if (_gzStream!=null)
_gzStream.setContentLength(_contentLength);
} }
else if ("content-type".equalsIgnoreCase(name)) else if ("content-type".equalsIgnoreCase(name))
{ {
@ -296,7 +316,10 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
if (_gzStream==null) if (_gzStream==null)
{ {
if (getResponse().isCommitted() || _noGzip) if (getResponse().isCommitted() || _noGzip)
{
setContentLength(_contentLength);
return getResponse().getOutputStream(); return getResponse().getOutputStream();
}
_gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize); _gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
} }
@ -318,7 +341,10 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
throw new IllegalStateException("getOutputStream() called"); throw new IllegalStateException("getOutputStream() called");
if (getResponse().isCommitted() || _noGzip) if (getResponse().isCommitted() || _noGzip)
{
setContentLength(_contentLength);
return getResponse().getWriter(); return getResponse().getWriter();
}
_gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize); _gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
_writer=newWriter(_gzStream,getCharacterEncoding()); _writer=newWriter(_gzStream,getCharacterEncoding());

View File

@ -42,6 +42,7 @@ public class GzipStream extends ServletOutputStream
protected int _bufferSize; protected int _bufferSize;
protected int _minGzipSize; protected int _minGzipSize;
protected long _contentLength; protected long _contentLength;
protected boolean _doNotGzip;
/** /**
* Instantiates a new gzip stream. * Instantiates a new gzip stream.
@ -77,6 +78,7 @@ public class GzipStream extends ServletOutputStream
if (_gzOut!=null) if (_gzOut!=null)
_response.setHeader("Content-Encoding",null); _response.setHeader("Content-Encoding",null);
_gzOut=null; _gzOut=null;
_doNotGzip=false;
} }
/** /**
@ -87,6 +89,13 @@ public class GzipStream extends ServletOutputStream
public void setContentLength(long length) public void setContentLength(long length)
{ {
_contentLength=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(); throw new IllegalStateException();
if (_out==null || _bOut!=null ) if (_out==null || _bOut!=null )
{ {
_doNotGzip = true;
_out=_response.getOutputStream(); _out=_response.getOutputStream();
if (_contentLength>=0) setContentLength(_contentLength);
{
if(_contentLength<Integer.MAX_VALUE)
_response.setContentLength((int)_contentLength);
else
_response.setHeader("Content-Length",Long.toString(_contentLength));
}
if (_bOut!=null) if (_bOut!=null)
_out.write(_bOut.getBuf(),0,_bOut.getCount()); _out.write(_bOut.getBuf(),0,_bOut.getCount());

View File

@ -1,3 +1,4 @@
text/html = ISO-8859-1 text/html = ISO-8859-1
text/plain = US-ASCII text/plain = ISO-8859-1
text/xml = UTF-8 text/xml = UTF-8
text/json = UTF-8

View File

@ -76,6 +76,19 @@ public class ByteArrayBuffer extends AbstractBuffer
_access=IMMUTABLE; _access=IMMUTABLE;
_string = value; _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 public ByteArrayBuffer(String value,String encoding) throws UnsupportedEncodingException
{ {

View File

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

View File

@ -9,6 +9,20 @@
<Set name="scanInterval">2000</Set> <Set name="scanInterval">2000</Set>
<Set name="busyThreshold">90</Set> <Set name="busyThreshold">90</Set>
<Set name="stackDepth">3</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> </New>
</Arg> </Arg>
</Call> </Call>

View File

@ -15,15 +15,16 @@
package org.eclipse.jetty.monitor; package org.eclipse.jetty.monitor;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean; import java.lang.management.ThreadMXBean;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import org.eclipse.jetty.util.component.AbstractLifeCycle; 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.Log;
import org.eclipse.jetty.util.log.Logger; 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 public class ThreadMonitor extends AbstractLifeCycle implements Runnable
{ {
private int _scanInterval; private int _scanInterval;
private int _logInterval;
private int _busyThreshold; private int _busyThreshold;
private int _logThreshold;
private int _stackDepth; private int _stackDepth;
private int _trailLength;
private ThreadMXBean _threadBean; private ThreadMXBean _threadBean;
private Method findDeadlockedThreadsMethod;
private Thread _runner; private Thread _runner;
private Logger _logger; private Logger _logger;
private volatile boolean _done = true; 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 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,52 +94,195 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
* @throws Exception * @throws Exception
*/ */
public ThreadMonitor(int intervalMs, int threshold, int depth) 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; _scanInterval = intervalMs;
_busyThreshold = threshold; _busyThreshold = threshold;
_stackDepth = depth; _stackDepth = depth;
_trailLength = trail;
_logger = Log.getLogger(getClass().getName()); _logger = Log.getLogger(ThreadMonitor.class.getName());
_extInfo = new HashMap<Long, ExtThreadInfo>(); _monitorInfo = new HashMap<Long, ThreadMonitorInfo>();
init(); init();
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/**
* Gets the scan interval.
*
* @return the scan interval
*/
public int getScanInterval() public int getScanInterval()
{ {
return _scanInterval; return _scanInterval;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/**
* Sets the scan interval.
*
* @param ms the new scan interval
*/
public void setScanInterval(int ms) public void setScanInterval(int ms)
{ {
_scanInterval = 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; return _busyThreshold;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/**
* Sets the busy threshold.
*
* @param percent the new busy threshold
*/
public void setBusyThreshold(int percent) public void setBusyThreshold(int percent)
{ {
_busyThreshold = 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() public int getStackDepth()
{ {
return _stackDepth; return _stackDepth;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/**
* Sets the stack depth.
*
* @param stackDepth the new stack depth
*/
public void setStackDepth(int stackDepth) public void setStackDepth(int stackDepth)
{ {
_stackDepth = 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;
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
@ -121,6 +293,7 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
_done = false; _done = false;
_runner = new Thread(this); _runner = new Thread(this);
_runner.setDaemon(true);
_runner.start(); _runner.start();
Log.info("Thread Monitor started successfully"); 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 * Retrieve all avaliable thread ids
@ -196,7 +326,7 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
{ {
return _threadBean.getAllThreadIds(); return _threadBean.getAllThreadIds();
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Retrieve the cpu time for specified thread. * Retrieve the cpu time for specified thread.
@ -211,79 +341,62 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Retrieve thread info. * Initialize JMX objects.
*
* @param id thread id
* @param maxDepth maximum stack depth
* @return thread info
*/ */
protected ThreadInfo getThreadInfo(long id, int maxDepth) protected void init()
{ {
return _threadBean.getThreadInfo(id,maxDepth); _threadBean = ManagementFactory.getThreadMXBean();
} if (_threadBean.isThreadCpuTimeSupported())
/* ------------------------------------------------------------ */
/**
* Output thread info to log.
*
* @param threads thread info list
*/
protected void dump(final List<ThreadInfo> threads)
{
if (threads != null && threads.size() > 0)
{ {
for (ThreadInfo info : threads) _threadBean.setThreadCpuTimeEnabled(true);
{
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()));
}
} }
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* @see java.lang.Runnable#run() * @see java.lang.Runnable#run()
*/ */
public void run() public void run()
{ {
long currTime; // Initialize repeat flag
long lastTime = 0; boolean repeat = false;
boolean scanNow, logNow;
// Set next scan time and log time
long nextScanTime = System.currentTimeMillis();
long nextLogTime = nextScanTime + _logInterval;
while (!_done) while (!_done)
{ {
currTime = System.currentTimeMillis(); long currTime = System.currentTimeMillis();
if (currTime < lastTime + _scanInterval) 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 try
{ {
Thread.sleep(50); Thread.sleep(100);
} }
catch (InterruptedException ex) catch (InterruptedException ex)
{ {
Log.ignore(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. * Collect thread info.
*
* @param threadInfo thread info list to add the results
* @return thread info list
*/ */
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(); Thread thread = entry.getKey();
for (int idx=0; idx < allThreadId.length; idx++) long threadId = thread.getId();
// Skip our own runner thread
if (threadId == _runner.getId())
{ {
long currId = allThreadId[idx]; continue;
}
if (currId == _runner.getId())
{
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)
{
ThreadInfo currInfo = getThreadInfo(currId, Integer.MAX_VALUE);
if (currInfo != null)
{
StackTraceElement[] lastStackTrace = currExtInfo.getStackTrace();
currExtInfo.setStackTrace(currInfo.getStackTrace());
if (lastStackTrace != null ThreadMonitorInfo currMonitorInfo = _monitorInfo.get(Long.valueOf(threadId));
&& matchStackTraces(lastStackTrace, currInfo.getStackTrace())) { if (currMonitorInfo == null)
threadInfo.add(currInfo); {
} // 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())
{
// Thread was spinning and was logged before
if (count < _trailLength)
{
// Log another stack trace
currMonitorInfo.setTraceCount(count+1);
repeat = true;
continue;
}
// 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);
}
} }
catch (Exception ex)
return threadInfo; {
Log.debug(ex);
}
return repeat;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** protected void logThreadInfo(boolean logAll)
* Find deadlocked threads.
*
* @param threadInfo thread info list to add the results
* @return thread info list
*/
private List<ThreadInfo> findDeadlockedThreads(final List<ThreadInfo> threadInfo)
{ {
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(); ThreadMonitorInfo info = _monitorInfo.get(running[idx]);
if (threads != null && threads.length > 0) if (info != null)
{ {
ThreadInfo currInfo; all.add(info);
for (int idx=0; idx < threads.length; idx++)
{
currInfo = getThreadInfo(threads[idx], Integer.MAX_VALUE);
if (currInfo != null)
{
threadInfo.add(currInfo);
}
}
} }
} }
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; 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;
}
}
} }

View File

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

View File

@ -16,10 +16,13 @@ package org.eclipse.jetty.monitor;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.lang.management.ThreadInfo; import java.io.IOException;
import java.util.List; import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger; 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; import org.junit.Test;
@ -28,24 +31,55 @@ import org.junit.Test;
*/ */
public class ThreadMonitorTest public class ThreadMonitorTest
{ {
public final static int DURATION=9000; public final static int DURATION=4000;
private AtomicInteger count=new AtomicInteger(0);
@Test @Test
public void monitorTest() throws Exception 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 @Override
protected void dump(List<ThreadInfo> threads) protected void logThreadInfo(boolean logAll)
{ {
count.incrementAndGet(); if (logAll)
super.dump(threads); 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(); 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(); Spinner spinner = new Spinner();
Thread runner = new Thread(spinner); Thread runner = new Thread(spinner);
runner.start(); runner.start();
@ -55,7 +89,8 @@ public class ThreadMonitorTest
spinner.setDone(); spinner.setDone();
monitor.stop(); 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; private volatile boolean done = false;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/**
*/
public void setDone() public void setDone()
{ {
done = true; done = true;
@ -91,6 +124,36 @@ public class ThreadMonitorTest
if (result==42) if (result==42)
System.err.println("Bingo!"); 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;
}
} }
} }

View File

@ -26,7 +26,7 @@
<!-- the web.xml file. --> <!-- the web.xml file. -->
<!-- =========================================================== --> <!-- =========================================================== -->
<!-- <!--
<Call name="addLoginService"> <Call name="addBean">
<Arg> <Arg>
<New class="org.eclipse.jetty.plus.jaas.JAASLoginService"> <New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
<Set name="name">xyzrealm</Set> <Set name="name">xyzrealm</Set>

View File

@ -667,8 +667,8 @@ public class Response implements HttpServletResponse
if (encoding==null) if (encoding==null)
{ {
/* implementation of educated defaults */ /* implementation of educated defaults */
if(_mimeType!=null) if(_cachedMimeType != null)
encoding = null; // TODO getHttpContext().getEncodingByMimeType(_mimeType); encoding = MimeTypes.getCharsetFromContentType(_cachedMimeType);
if (encoding==null) if (encoding==null)
encoding = StringUtil.__ISO_8859_1; encoding = StringUtil.__ISO_8859_1;

View File

@ -48,7 +48,7 @@ import org.eclipse.jetty.util.log.Log;
* *
* <p> * <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, * 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> * </p>
*/ */
public class GzipHandler extends HandlerWrapper public class GzipHandler extends HandlerWrapper

View File

@ -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 protected void checkValid() throws IllegalStateException
{ {

View File

@ -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. * 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 @Override
protected void addSession(AbstractSession session) protected void addSession(AbstractSession session)

View File

@ -65,7 +65,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
private static final String REQUEST2_HEADER= private static final String REQUEST2_HEADER=
"POST / HTTP/1.0\n"+ "POST / HTTP/1.0\n"+
"Host: localhost\n"+ "Host: localhost\n"+
"Content-Type: text/xml\n"+ "Content-Type: text/xml;charset=ISO-8859-1\n"+
"Content-Length: "; "Content-Length: ";
private static final String REQUEST2_CONTENT= private static final String REQUEST2_CONTENT=
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+ "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+

View File

@ -116,11 +116,20 @@ public class ResponseTest
assertEquals("foo2/bar2;charset=ISO-8859-1",response.getContentType()); assertEquals("foo2/bar2;charset=ISO-8859-1",response.getContentType());
response.recycle(); response.recycle();
response.setContentType("text/xml;charset=ISO-8859-7"); response.setContentType("text/xml;charset=ISO-8859-7");
response.getWriter(); response.getWriter();
response.setContentType("text/html;charset=UTF-8"); response.setContentType("text/html;charset=UTF-8");
assertEquals("text/html;charset=ISO-8859-7",response.getContentType()); 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 @Test

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 */
}
}

View File

@ -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 */
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -0,0 +1 @@
6d51985fd71ae74564202f98cf993e0390fae3fe jetty_logo.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1 @@
3bceab0485ead22e90af4f077c10684addd5dcb5 jetty_logo.gif

Binary file not shown.

View File

@ -0,0 +1 @@
c6f62de568243be3afbb7f489ce9096dc1808859 jetty_logo.jp2

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1 @@
c00ce14c7b266640544dc527277995e25d0c91b8 jetty_logo.jpeg

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -0,0 +1 @@
c00ce14c7b266640544dc527277995e25d0c91b8 jetty_logo.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1 @@
813bfd8bfa2fb8381cc4b296f3b962a24797ed8f jetty_logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@ -0,0 +1 @@
2603caf728690e1fddaf747a3eef8b5cfe20eee4 jetty_logo.tga

Binary file not shown.

View File

@ -0,0 +1 @@
35bbf5d78d6834531d4c43c686bdc49cded4c982 jetty_logo.tif

Binary file not shown.

View File

@ -0,0 +1 @@
3f7fa94449b96c4670b8754850ec8fbe526db3f6 jetty_logo.tiff

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1 @@
3ec782dc77c0b81420317d8d445f8d8c1ec25d84 jetty_logo.xcf

Binary file not shown.

View File

@ -0,0 +1 @@
1f8b327125e1ba9c25804734730dfe5367093216 test_quotes.bz2

Binary file not shown.

View File

@ -0,0 +1 @@
f43ed550786662ba8a245a1769dfaa330c49fdcc test_quotes.gz

Binary file not shown.

View File

@ -0,0 +1 @@
8071787493e74d60a273bc0787d3666968dc9eb9 test_quotes.rar

View File

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

View File

@ -0,0 +1 @@
7dfe23b2baa0ad8fd1673bcbc9b981c4a564492a test_quotes.txt

Binary file not shown.

View File

@ -0,0 +1 @@
bd1edce4dfc9e57b8e55314ee9ac29e3fbb3f671 test_quotes.zip

View File

@ -26,6 +26,6 @@ public interface Attributes
public void removeAttribute(String name); public void removeAttribute(String name);
public void setAttribute(String name, Object attribute); public void setAttribute(String name, Object attribute);
public Object getAttribute(String name); public Object getAttribute(String name);
public Enumeration getAttributeNames(); public Enumeration<String> getAttributeNames();
public void clearAttributes(); public void clearAttributes();
} }

View File

@ -17,6 +17,7 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -109,10 +110,9 @@ public class AttributesMap implements Attributes
{ {
if (attrs instanceof AttributesMap) if (attrs instanceof AttributesMap)
return Collections.enumeration(((AttributesMap)attrs)._map.keySet()); return Collections.enumeration(((AttributesMap)attrs)._map.keySet());
ArrayList names = new ArrayList();
Enumeration e = attrs.getAttributeNames(); List<String> names = new ArrayList<String>();
while (e.hasMoreElements()) names.addAll(Collections.list(attrs.getAttributeNames()));
names.add(e.nextElement());
return Collections.enumeration(names); return Collections.enumeration(names);
} }

View File

@ -140,6 +140,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@SuppressWarnings("unchecked")
public E peek() public E peek()
{ {
if (_size.get() == 0) if (_size.get() == 0)
@ -218,6 +219,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@SuppressWarnings("unchecked")
public E poll() public E poll()
{ {
if (_size.get() == 0) if (_size.get() == 0)
@ -253,6 +255,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
* @return the head of this queue * @return the head of this queue
* @throws InterruptedException if interrupted while waiting. * @throws InterruptedException if interrupted while waiting.
*/ */
@SuppressWarnings("unchecked")
public E take() throws InterruptedException public E take() throws InterruptedException
{ {
E e = null; 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. * specified waiting time elapses before an element is present.
* @throws InterruptedException if interrupted while waiting. * @throws InterruptedException if interrupted while waiting.
*/ */
@SuppressWarnings("unchecked")
public E poll(long time, TimeUnit unit) throws InterruptedException 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 @Override
public E get(int index) public E get(int index)
{ {
@ -434,6 +439,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
int i = _head+index; int i = _head+index;
if (i>=_capacity) if (i>=_capacity)
i-=_capacity; i-=_capacity;
@SuppressWarnings("unchecked")
E old=(E)_elements[i]; E old=(E)_elements[i];
if (i<_tail) if (i<_tail)
@ -490,6 +496,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
int i = _head+index; int i = _head+index;
if (i>=_capacity) if (i>=_capacity)
i-=_capacity; i-=_capacity;
@SuppressWarnings("unchecked")
E old=(E)_elements[i]; E old=(E)_elements[i];
_elements[i]=e; _elements[i]=e;
return old; return old;

View File

@ -21,8 +21,10 @@ import java.util.Map;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
*/ */
@SuppressWarnings("serial")
public class HostMap<TYPE> extends HashMap<String, TYPE> public class HostMap<TYPE> extends HashMap<String, TYPE>
{ {
/* --------------------------------------------------------------- */ /* --------------------------------------------------------------- */
/** Construct empty HostMap. /** Construct empty HostMap.
*/ */

View File

@ -36,6 +36,7 @@ import java.util.StringTokenizer;
* a,b,... - a list of wildcard specifications * a,b,... - a list of wildcard specifications
* </pre> * </pre>
*/ */
@SuppressWarnings("serial")
public class IPAddressMap<TYPE> extends HashMap<String, TYPE> public class IPAddressMap<TYPE> extends HashMap<String, TYPE>
{ {
private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>(); private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>();

View File

@ -45,7 +45,7 @@ public class IntrospectionUtil
return true; 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 throws NoSuchMethodException
{ {
if (clazz == null) 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 throws NoSuchFieldException
{ {
if (clazz == null) 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) if (formalParams==null && actualParams==null)
return true; return true;
@ -182,8 +182,8 @@ public class IntrospectionUtil
if (methodB==null) if (methodB==null)
return false; return false;
List parameterTypesA = Arrays.asList(methodA.getParameterTypes()); List<Class<?>> parameterTypesA = Arrays.asList(methodA.getParameterTypes());
List parameterTypesB = Arrays.asList(methodB.getParameterTypes()); List<Class<?>> parameterTypesB = Arrays.asList(methodB.getParameterTypes());
if (methodA.getName().equals(methodB.getName()) if (methodA.getName().equals(methodB.getName())
&& &&
@ -193,7 +193,7 @@ public class IntrospectionUtil
return false; 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) if (formalType==null && actualType != null)
return false; 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) 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) 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 throws NoSuchMethodException
{ {
if (clazz==null) if (clazz==null)
@ -275,7 +275,7 @@ public class IntrospectionUtil
return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs); 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 throws NoSuchFieldException
{ {
if (clazz==null) if (clazz==null)

View File

@ -28,13 +28,13 @@ import java.util.ListIterator;
* creation. If a method needs to create a List to return, but it is * 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 * expected that this will either be empty or frequently contain a
* single item, then using LazyList will avoid additional object * single item, then using LazyList will avoid additional object
* creations by using Collections.EMPTY_LIST or * creations by using {@link Collections#EMPTY_LIST} or
* Collections.singletonList where possible. * {@link Collections#singletonList(Object)} where possible.
* <p> * <p>
* LazyList works by passing an opaque representation of the list in * LazyList works by passing an opaque representation of the list in
* and out of all the LazyList methods. This opaque object is either * 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 * 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> * <p><h4>Usage</h4>
* <pre> * <pre>
@ -124,7 +124,7 @@ public class LazyList
List<Object> l=new ArrayList<Object>(); List<Object> l=new ArrayList<Object>();
l.add(list); l.add(list);
l.add(index,item); l.add(index,item);
return l; return l;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -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) 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 * @param clazz The class of the array, which may be a primitive type
* @return array of the lazylist entries passed in * @return array of the lazylist entries passed in
*/ */
@SuppressWarnings("unchecked")
public static Object toArray(Object list,Class<?> clazz) public static Object toArray(Object list,Class<?> clazz)
{ {
if (list==null) if (list==null)
@ -429,21 +428,23 @@ public class LazyList
* @param type The type of the array (in case of null array) * @param type The type of the array (in case of null array)
* @return new array with contents of array plus item * @return new array with contents of array plus item
*/ */
@SuppressWarnings("unchecked") public static<T> T[] addToArray(T[] array, T item, Class<?> type)
public static Object[] addToArray(Object[] array, Object item, Class<?> type)
{ {
if (array==null) if (array==null)
{ {
if (type==null && item!=null) if (type==null && item!=null)
type= item.getClass(); type= item.getClass();
Object[] na = (Object[])Array.newInstance(type, 1); @SuppressWarnings("unchecked")
T[] na = (T[])Array.newInstance(type, 1);
na[0]=item; na[0]=item;
return na; return na;
} }
else else
{ {
// TODO: Replace with Arrays.copyOf(T[] original, int newLength) from Java 1.6+
Class<?> c = array.getClass().getComponentType(); 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); System.arraycopy(array, 0, na, 0, array.length);
na[array.length]=item; na[array.length]=item;
return na; return na;
@ -451,8 +452,7 @@ public class LazyList
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@SuppressWarnings("unchecked") public static<T> T[] removeFromArray(T[] array, Object item)
public static Object removeFromArray(Object[] array, Object item)
{ {
if (item==null || array==null) if (item==null || array==null)
return array; return array;
@ -461,7 +461,8 @@ public class LazyList
if (item.equals(array[i])) if (item.equals(array[i]))
{ {
Class<?> c = array==null?item.getClass():array.getClass().getComponentType(); 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) if (i>0)
System.arraycopy(array, 0, na, 0, i); System.arraycopy(array, 0, na, 0, i);
if (i+1<array.length) if (i+1<array.length)

View File

@ -37,7 +37,7 @@ import java.util.ResourceBundle;
public class Loader 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 throws ClassNotFoundException
{ {
URL url =null; URL url =null;
@ -64,6 +64,7 @@ public class Loader
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@SuppressWarnings("rawtypes")
public static Class loadClass(Class loadClass,String name) public static Class loadClass(Class loadClass,String name)
throws ClassNotFoundException throws ClassNotFoundException
{ {
@ -79,11 +80,12 @@ public class Loader
* @return Class * @return Class
* @throws ClassNotFoundException * @throws ClassNotFoundException
*/ */
@SuppressWarnings("rawtypes")
public static Class loadClass(Class loadClass,String name,boolean checkParents) public static Class loadClass(Class loadClass,String name,boolean checkParents)
throws ClassNotFoundException throws ClassNotFoundException
{ {
ClassNotFoundException ex=null; ClassNotFoundException ex=null;
Class c =null; Class<?> c =null;
ClassLoader loader=Thread.currentThread().getContextClassLoader(); ClassLoader loader=Thread.currentThread().getContextClassLoader();
while (c==null && loader!=null ) while (c==null && loader!=null )
{ {
@ -111,7 +113,7 @@ public class Loader
throw ex; 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 throws MissingResourceException
{ {
MissingResourceException ex=null; MissingResourceException ex=null;

View File

@ -24,6 +24,7 @@ import java.util.List;
* *
* *
*/ */
@SuppressWarnings("serial")
public class MultiException extends Exception public class MultiException extends Exception
{ {
private Object nested; private Object nested;
@ -54,7 +55,7 @@ public class MultiException extends Exception
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public List getThrowables() public List<Throwable> getThrowables()
{ {
return LazyList.getList(nested); return LazyList.getList(nested);
} }

View File

@ -17,7 +17,6 @@ import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -46,7 +45,7 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
_map=new HashMap<K, Object>(); _map=new HashMap<K, Object>();
} }
public MultiMap(Map map) public MultiMap(Map<K,Object> map)
{ {
if (map instanceof ConcurrentMap) if (map instanceof ConcurrentMap)
_map=_cmap=new ConcurrentHashMap<K, Object>(map); _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. * @param values The List of multiple values.
* @return The previous value or null. * @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); 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. * @param values The String array of multiple values.
* @return The previous value or null. * @return The previous value or null.
*/ */
public Object putValues(K name, String[] values) public Object putValues(K name, String... values)
{ {
Object list=null; Object list=null;
for (int i=0;i<values.length;i++) for (int i=0;i<values.length;i++)
list=LazyList.add(list,values[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 name The entry key.
* @param values The List of multiple values. * @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 lo = _map.get(name);
Object ln = LazyList.addCollection(lo,values); Object ln = LazyList.addCollection(lo,values);
@ -260,21 +259,25 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
return LazyList.size(ln)!=s; return LazyList.size(ln)!=s;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Put all contents of map. /** Put all contents of map.
* @param m 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);
boolean multi=m instanceof MultiMap;
while(i.hasNext()) if (multi)
{ {
Map.Entry entry = (Map.Entry)i.next(); for (Map.Entry<? extends K, ? extends Object> entry : m.entrySet())
if (multi) {
_map.put((K)(entry.getKey()),LazyList.clone(entry.getValue())); _map.put(entry.getKey(),LazyList.clone(entry.getValue()));
else }
put((K)(entry.getKey()),entry.getValue()); }
else
{
_map.putAll(m);
} }
} }
@ -282,19 +285,13 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
/** /**
* @return Map of String arrays * @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(); for(Map.Entry<K,Object> entry: _map.entrySet())
while(i.hasNext())
{ {
Map.Entry entry = (Map.Entry)i.next(); String[] a = LazyList.toStringArray(entry.getValue());
Object l = entry.getValue();
String[] a = LazyList.toStringArray(l);
// for (int j=a.length;j-->0;)
// if (a[j]==null)
// a[j]="";
map.put(entry.getKey(),a); map.put(entry.getKey(),a);
} }
return map; return map;

View File

@ -4,16 +4,17 @@
// All rights reserved. This program and the accompanying materials // All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0 // are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution. // and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at // The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html // http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at // The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php // http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses. // You may elect to redistribute this code under either of these licenses.
// ======================================================================== // ========================================================================
package org.eclipse.jetty.util; package org.eclipse.jetty.util;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.StringTokenizer; import java.util.StringTokenizer;
@ -27,7 +28,7 @@ import java.util.StringTokenizer;
* Quotes can be escaped with '\'. * Quotes can be escaped with '\'.
* *
* @see java.util.StringTokenizer * @see java.util.StringTokenizer
* *
*/ */
public class QuotedStringTokenizer public class QuotedStringTokenizer
extends StringTokenizer extends StringTokenizer
@ -43,7 +44,7 @@ public class QuotedStringTokenizer
private int _lastStart=0; private int _lastStart=0;
private boolean _double=true; private boolean _double=true;
private boolean _single=true; private boolean _single=true;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public QuotedStringTokenizer(String str, public QuotedStringTokenizer(String str,
String delim, String delim,
@ -56,11 +57,11 @@ public class QuotedStringTokenizer
_delim=delim; _delim=delim;
_returnDelimiters=returnDelimiters; _returnDelimiters=returnDelimiters;
_returnQuotes=returnQuotes; _returnQuotes=returnQuotes;
if (_delim.indexOf('\'')>=0 || if (_delim.indexOf('\'')>=0 ||
_delim.indexOf('"')>=0) _delim.indexOf('"')>=0)
throw new Error("Can't use quotes as delimiters: "+_delim); throw new Error("Can't use quotes as delimiters: "+_delim);
_token=new StringBuffer(_string.length()>1024?512:_string.length()/2); _token=new StringBuffer(_string.length()>1024?512:_string.length()/2);
} }
@ -71,7 +72,7 @@ public class QuotedStringTokenizer
{ {
this(str,delim,returnDelimiters,false); this(str,delim,returnDelimiters,false);
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public QuotedStringTokenizer(String str, public QuotedStringTokenizer(String str,
String delim) String delim)
@ -92,15 +93,15 @@ public class QuotedStringTokenizer
// Already found a token // Already found a token
if (_hasToken) if (_hasToken)
return true; return true;
_lastStart=_i; _lastStart=_i;
int state=0; int state=0;
boolean escape=false; boolean escape=false;
while (_i<_string.length()) while (_i<_string.length())
{ {
char c=_string.charAt(_i++); char c=_string.charAt(_i++);
switch (state) switch (state)
{ {
case 0: // Start case 0: // Start
@ -130,8 +131,8 @@ public class QuotedStringTokenizer
_hasToken=true; _hasToken=true;
state=1; state=1;
} }
continue; break;
case 1: // Token case 1: // Token
_hasToken=true; _hasToken=true;
if(_delim.indexOf(c)>=0) if(_delim.indexOf(c)>=0)
@ -153,10 +154,11 @@ public class QuotedStringTokenizer
state=3; state=3;
} }
else else
{
_token.append(c); _token.append(c);
continue; }
break;
case 2: // Single Quote case 2: // Single Quote
_hasToken=true; _hasToken=true;
if (escape) if (escape)
@ -177,10 +179,11 @@ public class QuotedStringTokenizer
escape=true; escape=true;
} }
else else
{
_token.append(c); _token.append(c);
continue; }
break;
case 3: // Double Quote case 3: // Double Quote
_hasToken=true; _hasToken=true;
if (escape) if (escape)
@ -201,8 +204,10 @@ public class QuotedStringTokenizer
escape=true; escape=true;
} }
else else
{
_token.append(c); _token.append(c);
continue; }
break;
} }
} }
@ -212,7 +217,7 @@ public class QuotedStringTokenizer
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override @Override
public String nextToken() public String nextToken()
throws NoSuchElementException throws NoSuchElementException
{ {
if (!hasMoreTokens() || _token==null) if (!hasMoreTokens() || _token==null)
throw new NoSuchElementException(); throw new NoSuchElementException();
@ -225,7 +230,7 @@ public class QuotedStringTokenizer
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override @Override
public String nextToken(String delim) public String nextToken(String delim)
throws NoSuchElementException throws NoSuchElementException
{ {
_delim=delim; _delim=delim;
_i=_lastStart; _i=_lastStart;
@ -244,7 +249,7 @@ public class QuotedStringTokenizer
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@Override @Override
public Object nextElement() public Object nextElement()
throws NoSuchElementException throws NoSuchElementException
{ {
return nextToken(); return nextToken();
} }
@ -258,13 +263,14 @@ public class QuotedStringTokenizer
return -1; return -1;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Quote a string. /** Quote a string.
* The string is quoted only if quoting is required due to * 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. * empty string.
* @param s The string to quote. * @param s The string to quote.
* @param delim the delimiter to use to quote the string
* @return quoted string * @return quoted string
*/ */
public static String quoteIfNeeded(String s, String delim) public static String quoteIfNeeded(String s, String delim)
@ -274,7 +280,7 @@ public class QuotedStringTokenizer
if (s.length()==0) if (s.length()==0)
return "\"\""; return "\"\"";
for (int i=0;i<s.length();i++) for (int i=0;i<s.length();i++)
{ {
char c = s.charAt(i); char c = s.charAt(i);
@ -285,7 +291,7 @@ public class QuotedStringTokenizer
return b.toString(); return b.toString();
} }
} }
return s; return s;
} }
@ -303,81 +309,73 @@ public class QuotedStringTokenizer
return null; return null;
if (s.length()==0) if (s.length()==0)
return "\"\""; return "\"\"";
StringBuffer b=new StringBuffer(s.length()+8); StringBuffer b=new StringBuffer(s.length()+8);
quote(b,s); quote(b,s);
return b.toString(); return b.toString();
}
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. /** Quote a string into an Appendable.
* The characters ", \, \n, \r, \t, \f and \b are escaped * The characters ", \, \n, \r, \t, \f and \b are escaped
* @param buf The Appendable * @param buffer The Appendable
* @param s The String to quote. * @param input The String to quote.
*/ */
public static void quote(Appendable buf, String s) public static void quote(Appendable buffer, String input)
{ {
try try
{ {
buf.append('"'); buffer.append('"');
for (int i = 0; i < input.length(); ++i)
for (int i=0;i<s.length();i++)
{ {
char c = s.charAt(i); char c = input.charAt(i);
switch(c) if (c >= 32)
{ {
case '"': if (c == '"' || c == '\\')
buf.append("\\\""); buffer.append('\\');
continue; buffer.append(c);
case '\\': }
buf.append("\\\\"); else
continue; {
case '\n': char escape = escapes[c];
buf.append("\\n"); if (escape == 0xFFFF)
continue; {
case '\r': // Unicode escape
buf.append("\\r"); buffer.append('\\').append('u').append('0').append('0');
continue; if (c < 0x10)
case '\t': buffer.append('0');
buf.append("\\t"); buffer.append(Integer.toString(c, 16));
continue; }
case '\f': else
buf.append("\\f"); {
continue; buffer.append('\\').append(escape);
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;
} }
} }
buffer.append('"');
buf.append('"'); }
} catch (IOException x)
catch(IOException e)
{ {
throw new RuntimeException(e); throw new RuntimeException(x);
} }
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Quote a string into a StringBuffer only if needed. /** Quote a string into a StringBuffer only if needed.
* Quotes are forced if any delim characters are present. * Quotes are forced if any delim characters are present.
* *
* @param buf The StringBuffer * @param buf The StringBuffer
* @param s The String to quote. * @param s The String to quote.
* @param delim String of characters that must be quoted. * @param delim String of characters that must be quoted.
@ -394,7 +392,7 @@ public class QuotedStringTokenizer
return true; return true;
} }
} }
try try
{ {
buf.append(s); buf.append(s);
@ -405,7 +403,7 @@ public class QuotedStringTokenizer
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Unquote a string. /** Unquote a string.
* @param s The string to unquote. * @param s The string to unquote.
@ -422,68 +420,66 @@ public class QuotedStringTokenizer
char last=s.charAt(s.length()-1); char last=s.charAt(s.length()-1);
if (first!=last || (first!='"' && first!='\'')) if (first!=last || (first!='"' && first!='\''))
return s; return s;
StringBuffer b=new StringBuffer(s.length()-2);
synchronized(b)
{
boolean escape=false;
for (int i=1;i<s.length()-1;i++)
{
char c = s.charAt(i);
if (escape) StringBuilder b = new StringBuilder(s.length() - 2);
boolean escape=false;
for (int i=1;i<s.length()-1;i++)
{
char c = s.charAt(i);
if (escape)
{
escape=false;
switch (c)
{ {
escape=false; case 'n':
switch (c) b.append('\n');
{ break;
case 'n': case 'r':
b.append('\n'); b.append('\r');
break; break;
case 'r': case 't':
b.append('\r'); b.append('\t');
break; break;
case 't': case 'f':
b.append('\t'); b.append('\f');
break; break;
case 'f': case 'b':
b.append('\f'); b.append('\b');
break; break;
case 'b': case '\\':
b.append('\b'); b.append('\\');
break; break;
case '\\': case '/':
b.append('\\'); b.append('/');
break; break;
case '/': case '"':
b.append('/'); b.append('"');
break; break;
case '"': case 'u':
b.append('"'); b.append((char)(
break; (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
case 'u': (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
b.append((char)( (TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+ (TypeUtil.convertHexDigit((byte)s.charAt(i++)))
(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);
break;
default:
b.append(c);
}
} }
else if (c=='\\')
{
escape=true;
continue;
}
else
b.append(c);
} }
else if (c=='\\')
return b.toString(); {
escape=true;
}
else
{
b.append(c);
}
} }
return b.toString();
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -522,15 +518,3 @@ public class QuotedStringTokenizer
_single=single; _single=single;
} }
} }

View File

@ -32,8 +32,6 @@ import java.util.Set;
* objects from being created just to look up in the map. * objects from being created just to look up in the map.
* *
* This map is NOT synchronized. * This map is NOT synchronized.
*
*
*/ */
public class StringMap extends AbstractMap implements Externalizable public class StringMap extends AbstractMap implements Externalizable
{ {

View File

@ -14,6 +14,8 @@
package org.eclipse.jetty.util.log; package org.eclipse.jetty.util.log;
import java.security.AccessControlException; import java.security.AccessControlException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.eclipse.jetty.util.DateCache; 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.SOURCE",
System.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE", "false"))); System.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE", "false")));
private final static ConcurrentMap<String,StdErrLog> __loggers = new ConcurrentHashMap<String, StdErrLog>();
static static
{ {
try try
@ -315,9 +319,21 @@ public class StdErrLog implements Logger
public Logger getLogger(String name) 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 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 @Override

View File

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

View File

@ -18,6 +18,8 @@ import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.PrintStream; import java.io.PrintStream;
import junit.framework.Assert;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
@ -123,8 +125,12 @@ public class LogTest
public void testStdErrLogName() public void testStdErrLogName()
{ {
StdErrLog log = new StdErrLog("test"); StdErrLog log = new StdErrLog("test");
Assert.assertEquals("test",log.getName());
Logger next=log.getLogger("next"); Logger next=log.getLogger("next");
Assert.assertEquals("test.next",next.getName());
next.info("testing {} {}","next","info"); next.info("testing {} {}","next","info");
logContains(":test.next:testing next info"); logContains(":test.next:testing next info");

View File

@ -17,16 +17,16 @@
<artifactId>jetty-server</artifactId> <artifactId>jetty-server</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>${servlet.spec.groupId}</groupId> <groupId>${servlet.spec.groupId}</groupId>
<artifactId>${servlet.spec.artifactId}</artifactId> <artifactId>${servlet.spec.artifactId}</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>org.eclipse.jetty.toolchain</groupId>
<artifactId>jetty-test-helper</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>

View File

@ -6,7 +6,6 @@ import java.util.Map;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.QuotedStringTokenizer;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.websocket.WebSocketParser.FrameHandler; import org.eclipse.jetty.websocket.WebSocketParser.FrameHandler;
public class AbstractExtension implements Extension public class AbstractExtension implements Extension

View File

@ -8,7 +8,6 @@ import java.util.zip.Inflater;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.ByteArrayBuffer;
import org.eclipse.jetty.util.ByteArrayOutputStream2;
import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Log;
public class DeflateFrameExtension extends AbstractExtension public class DeflateFrameExtension extends AbstractExtension

View File

@ -1,32 +1,18 @@
package org.eclipse.jetty.websocket; 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.InetSocketAddress;
import java.net.Socket;
import java.net.URI; import java.net.URI;
import java.security.SecureRandom;
import java.util.Arrays; import java.util.Arrays;
import java.util.Random; import java.util.Random;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong; 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.StringUtil;
import org.eclipse.jetty.util.TypeUtil; 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$ * @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) public void onClose(int closeCode, String message)
{ {
_handshook.countDown(); _handshook.countDown();
@ -141,8 +119,10 @@ public class TestClient implements WebSocket.OnFrame
private void open() throws Exception private void open() throws Exception
{ {
__client.open(new URI("ws://"+_host+":"+_port+"/"),this,_protocol,_timeout); WebSocketClient client = new WebSocketClient(__client);
_handshook.await(10,TimeUnit.SECONDS); 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 public void ping(byte opcode,byte[] data,int fragment) throws Exception

View File

@ -107,12 +107,6 @@ public class TestServer extends Server
System.err.printf("%s#onOpen %s\n",this.getClass().getSimpleName(),connection); 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) public void onHandshake(FrameConnection connection)
{ {
if (_verbose) if (_verbose)

View File

@ -29,13 +29,6 @@ public interface WebSocket
*/ */
void onOpen(Connection connection); 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 * Called when an established websocket connection closes
* @param closeCode * @param closeCode
@ -155,7 +148,10 @@ public interface WebSocket
byte textOpcode(); byte textOpcode();
byte continuationOpcode(); byte continuationOpcode();
byte finMask(); byte finMask();
String getProtocol();
void setFakeFragments(boolean fake);
boolean isFakeFragments();
boolean isControl(byte opcode); boolean isControl(byte opcode);
boolean isText(byte opcode); boolean isText(byte opcode);
boolean isBinary(byte opcode); boolean isBinary(byte opcode);

View File

@ -15,11 +15,8 @@ package org.eclipse.jetty.websocket;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.BuffersFactory;
import org.eclipse.jetty.io.Buffers.Type; import org.eclipse.jetty.io.Buffers.Type;
import org.eclipse.jetty.io.ThreadLocalBuffers; import org.eclipse.jetty.io.BuffersFactory;
import org.eclipse.jetty.io.nio.DirectNIOBuffer;
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */

View File

@ -3,14 +3,21 @@ package org.eclipse.jetty.websocket;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.URI; import java.net.URI;
import java.nio.channels.ByteChannel; import java.nio.channels.ByteChannel;
import java.nio.channels.SelectionKey; import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel; import java.nio.channels.SocketChannel;
import java.nio.channels.UnsupportedAddressTypeException;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random; 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.HttpFields;
import org.eclipse.jetty.http.HttpParser; 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.log.Logger;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool; 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 public class WebSocketClient extends AggregateLifeCycle
{ {
private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClient.class.getCanonicalName()); 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 Random __random = new Random();
private final static ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept"); 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 ThreadPool _threadPool;
private final Selector _selector=new Selector(); private final WebSocketClientSelector _selector;
private final Timeout _connectQ=new Timeout();
private int _connectTimeout=30000; private final Map<String,String> _cookies=new ConcurrentHashMap<String, String>();
private final List<String> _extensions=new CopyOnWriteArrayList<String>();
private int _bufferSize=64*1024; private int _bufferSize=64*1024;
private boolean _blockingConnect=false; private String _protocol;
private int _maxIdleTime=-1;
private WebSocketBuffers _buffers; private WebSocketBuffers _buffers;
public WebSocketClient(ThreadPool threadpool)
{ /* ------------------------------------------------------------ */
_threadPool=threadpool; /** Create a WebSocket Client with default configuration.
addBean(_selector); */
addBean(_threadPool);
}
public WebSocketClient() public WebSocketClient()
{ {
this(new QueuedThreadPool()); 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() public SelectorManager getSelectorManager()
{ {
return _selector; return _selector;
} }
/* ------------------------------------------------------------ */
/** Get the ThreadPool.
* <p>Used to set/query the thread pool configuration.
* @return The {@link ThreadPool}
*/
public ThreadPool getThreadPool() public ThreadPool getThreadPool()
{ {
return _threadPool; return _threadPool;
} }
public int getConnectTimeout() /* ------------------------------------------------------------ */
{ /** Get the maxIdleTime for connections opened by this client.
return _connectTimeout; * @return The maxIdleTime in ms, or -1 if the default from {@link #getSelectorManager()} is used.
} */
public void setConnectTimeout(int connectTimeout)
{
if (isRunning())
throw new IllegalStateException(getState());
_connectTimeout = connectTimeout;
}
public int getMaxIdleTime() 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) 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() public int getBufferSize()
{ {
return _bufferSize; 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) public void setBufferSize(int bufferSize)
{ {
if (isRunning())
throw new IllegalStateException(getState());
_bufferSize = bufferSize; _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; return open(uri,websocket).get(maxConnectTime,units);
_threadPool.dispatch(new Runnable(){ }
public void run() catch (ExecutionException e)
{ {
while(isRunning()) Throwable cause = e.getCause();
{ if (cause instanceof IOException)
try throw (IOException)cause;
{ if (cause instanceof Error)
_selector.doSelect(id); throw (Error)cause;
} if (cause instanceof RuntimeException)
catch (IOException e) throw (RuntimeException)cause;
{ throw new RuntimeException(cause);
__log.warn(e);
}
}
}
});
} }
_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 /* ------------------------------------------------------------ */
{ /** Asynchronously open a websocket connection.
open(uri,websocket,protocol,(int)_selector.getMaxIdleTime(),null,null); * 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.
*
public void open(URI uri, WebSocket websocket, String protocol,int maxIdleTime,Map<String,String> cookies) throws IOException * @param uri The URI to connect to.
{ * @param websocket The {@link WebSocket} instance to handle incoming events.
open(uri,websocket,protocol,(int)_selector.getMaxIdleTime(),cookies,null); * @return A {@link Future} to the {@link WebSocket.Connection}
} * @throws IOException
*/
public void open(URI uri, WebSocket websocket, String protocol,int maxIdleTime,Map<String,String> cookies,List<String> extensions) throws IOException public Future<WebSocket.Connection> open(URI uri, WebSocket websocket) throws IOException
{ {
if (!isStarted()) if (!isStarted())
throw new IllegalStateException("!started"); throw new IllegalStateException("!started");
@ -184,40 +268,66 @@ public class WebSocketClient extends AggregateLifeCycle
SocketChannel channel = SocketChannel.open(); SocketChannel channel = SocketChannel.open();
channel.socket().setTcpNoDelay(true); 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()); 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);
_connectQ.schedule(holder); channel.configureBlocking(false);
boolean thrown=true; channel.connect(address);
try _selector.register( channel, holder);
{
if (isBlockingConnect())
{
channel.socket().connect(address,0);
channel.configureBlocking(false);
}
else
{
channel.configureBlocking(false);
channel.connect(address);
}
_selector.register( channel, holder); return holder;
thrown=false;
}
finally
{
if (thrown)
holder.cancel();
}
} }
@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)
{
for (int i=0;i<_selector.getSelectSets();i++)
{
final int id=i;
_threadPool.dispatch(new Runnable()
{
public void run()
{
while(isRunning())
{
try
{
_selector.doSelect(id);
}
catch (IOException e)
{
__log.warn(e);
}
}
}
});
}
}
}
class Selector extends SelectorManager /* ------------------------------------------------------------ */
/** WebSocket Client Selector Manager
*/
class WebSocketClientSelector extends SelectorManager
{ {
@Override @Override
public boolean dispatch(Runnable task) public boolean dispatch(Runnable task)
@ -234,18 +344,20 @@ public class WebSocketClient extends AggregateLifeCycle
@Override @Override
protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint) protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint)
{ {
WebSocketHolder holder = (WebSocketHolder) endpoint.getSelectionKey().attachment(); WebSocketFuture holder = (WebSocketFuture) endpoint.getSelectionKey().attachment();
return new HandshakeConnection(endpoint,holder); return new HandshakeConnection(endpoint,holder);
} }
@Override @Override
protected void endPointOpened(SelectChannelEndPoint endpoint) protected void endPointOpened(SelectChannelEndPoint endpoint)
{ {
// TODO expose on outer class
} }
@Override @Override
protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection) protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
{ {
throw new IllegalStateException();
} }
@Override @Override
@ -257,29 +369,34 @@ public class WebSocketClient extends AggregateLifeCycle
@Override @Override
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment) protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
{ {
if (!(attachment instanceof WebSocketHolder)) if (!(attachment instanceof WebSocketFuture))
super.connectionFailed(channel,ex,attachment); super.connectionFailed(channel,ex,attachment);
else else
{ {
__log.debug(ex); __log.debug(ex);
WebSocketHolder holder = (WebSocketHolder)attachment; WebSocketFuture holder = (WebSocketFuture)attachment;
holder.cancel();
holder.getWebSocket().onError(ex.toString(),ex); holder.handshakeFailed(ex);
} }
} }
} }
/* ------------------------------------------------------------ */
/** Handshake Connection.
* Handles the connection until the handshake succeeds or fails.
*/
class HandshakeConnection extends AbstractConnection class HandshakeConnection extends AbstractConnection
{ {
private final SelectChannelEndPoint _endp; private final SelectChannelEndPoint _endp;
private final WebSocketHolder _holder; private final WebSocketFuture _holder;
private final String _key; private final String _key;
private final HttpParser _parser; private final HttpParser _parser;
private String _accept; private String _accept;
private String _error; private String _error;
public HandshakeConnection(SelectChannelEndPoint endpoint, WebSocketHolder holder) public HandshakeConnection(SelectChannelEndPoint endpoint, WebSocketFuture holder)
{ {
super(endpoint,System.currentTimeMillis()); super(endpoint,System.currentTimeMillis());
_endp=endpoint; _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= 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"+ "Host: "+holder.getURI().getHost()+":"+_holder.getURI().getPort()+"\r\n"+
"Upgrade: websocket\r\n"+ "Upgrade: websocket\r\n"+
"Connection: Upgrade\r\n"+ "Connection: Upgrade\r\n"+
"Sec-WebSocket-Key: "+_key+"\r\n"+ "Sec-WebSocket-Key: "+_key+"\r\n"+
"Sec-WebSocket-Origin: http://example.com\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) if (holder.getProtocol()!=null)
request+="Sec-WebSocket-Protocol: "+holder.getProtocol()+"\r\n"; request+="Sec-WebSocket-Protocol: "+holder.getProtocol()+"\r\n";
@ -356,16 +476,16 @@ public class WebSocketClient extends AggregateLifeCycle
try try
{ {
ByteArrayBuffer handshake = new ByteArrayBuffer(request); Buffer handshake = new ByteArrayBuffer(request,false);
int len=handshake.length(); int len=handshake.length();
if (len!=_endp.flush(handshake)) if (len!=_endp.flush(handshake))
throw new IOException("incomplete"); throw new IOException("incomplete");
} }
catch(IOException e) catch(IOException e)
{ {
__log.debug(e); holder.handshakeFailed(e);
_holder.getWebSocket().onError("Handshake failed",e);
} }
} }
public Connection handle() throws IOException public Connection handle() throws IOException
@ -375,8 +495,7 @@ public class WebSocketClient extends AggregateLifeCycle
switch (_parser.parseAvailable()) switch (_parser.parseAvailable())
{ {
case -1: case -1:
_holder.cancel(); _holder.handshakeFailed(new IOException("Incomplete handshake response"));
_holder.getWebSocket().onError("EOF",new EOFException());
return this; return this;
case 0: case 0:
return this; return this;
@ -384,25 +503,25 @@ public class WebSocketClient extends AggregateLifeCycle
break; break;
} }
} }
if (_error==null)
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
{ {
Buffer header=_parser.getHeaderBuffer(); if (_accept==null)
WebSocketConnectionD10 connection = new WebSocketConnectionD10(_holder.getWebSocket(),_endp,_buffers,System.currentTimeMillis(),_holder.getMaxIdleTime(),_holder.getProtocol(),null,10, new WebSocketGeneratorD10.RandomMaskGen()); _error="No Sec-WebSocket-Accept";
else if (!WebSocketConnectionD10.hashKey(_key).equals(_accept))
if (header.hasContent()) _error="Bad Sec-WebSocket-Accept";
connection.fillBuffersFrom(header); else
_buffers.returnBuffer(header); {
Buffer header=_parser.getHeaderBuffer();
WebSocketConnectionD10 connection = new WebSocketConnectionD10(_holder.getWebSocket(),_endp,_buffers,System.currentTimeMillis(),_holder.getMaxIdleTime(),_holder.getProtocol(),null,10, new WebSocketGeneratorD10.RandomMaskGen());
if (_holder.getWebSocket() instanceof WebSocket.OnFrame) if (header.hasContent())
((WebSocket.OnFrame)_holder.getWebSocket()).onHandshake((WebSocket.FrameConnection)connection.getConnection()); connection.fillBuffersFrom(header);
_holder.cancel(); _buffers.returnBuffer(header);
_holder.getWebSocket().onOpen(connection.getConnection());
return connection; _holder.onConnection(connection);
return connection;
}
} }
_endp.close(); _endp.close();
@ -421,13 +540,18 @@ public class WebSocketClient extends AggregateLifeCycle
public void closed() public void closed()
{ {
_holder.cancel(); if (_error!=null)
_holder.getWebSocket().onError(_error==null?"EOF":_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 WebSocket _websocket;;
final URI _uri; final URI _uri;
@ -435,9 +559,13 @@ public class WebSocketClient extends AggregateLifeCycle
final int _maxIdleTime; final int _maxIdleTime;
final Map<String,String> _cookies; final Map<String,String> _cookies;
final List<String> _extensions; final List<String> _extensions;
final ByteChannel _channel; final CountDownLatch _done = new CountDownLatch(1);
ByteChannel _channel;
WebSocketConnection _connection;
Throwable _exception;
public WebSocketHolder(WebSocket websocket, URI uri, String protocol, int maxIdleTime, Map<String,String> cookies,List<String> extensions, ByteChannel channel) public WebSocketFuture(WebSocket websocket, URI uri, String protocol, int maxIdleTime, Map<String,String> cookies,List<String> extensions, ByteChannel channel)
{ {
_websocket=websocket; _websocket=websocket;
_uri=uri; _uri=uri;
@ -448,6 +576,60 @@ public class WebSocketClient extends AggregateLifeCycle
_channel=channel; _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() public Map<String,String> getCookies()
{ {
return _cookies; return _cookies;
@ -472,26 +654,116 @@ public class WebSocketClient extends AggregateLifeCycle
{ {
return _maxIdleTime; 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() public String toString()
{ {
return "[" + _uri + ","+_websocket+"]@"+hashCode(); 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);
}
}
} }
} }

View File

@ -17,4 +17,6 @@ public interface WebSocketConnection extends Connection
void handshake(HttpServletRequest request, HttpServletResponse response, String origin, String subprotocol) throws IOException; void handshake(HttpServletRequest request, HttpServletResponse response, String origin, String subprotocol) throws IOException;
List<Extension> getExtensions(); List<Extension> getExtensions();
WebSocket.Connection getConnection();
} }

View File

@ -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) public void setHixieKeys(String key1,String key2)
{ {
@ -523,4 +530,16 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc
{ {
return 0; return 0;
} }
public void setFakeFragments(boolean fake)
{
// TODO Auto-generated method stub
}
public boolean isFakeFragments()
{
// TODO Auto-generated method stub
return false;
}
} }

View File

@ -33,10 +33,10 @@ import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log; 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.OnBinaryMessage;
import org.eclipse.jetty.websocket.WebSocket.OnControl; 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 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(); return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort();
} }
public void setFakeFragments(boolean fake)
{
}
public boolean isFakeFragments()
{
return false;
}
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */

View File

@ -33,10 +33,10 @@ import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log; 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.OnBinaryMessage;
import org.eclipse.jetty.websocket.WebSocket.OnControl; 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; import org.eclipse.jetty.websocket.WebSocketGeneratorD10.MaskGen;
public class WebSocketConnectionD10 extends AbstractConnection implements WebSocketConnection public class WebSocketConnectionD10 extends AbstractConnection implements WebSocketConnection
@ -45,7 +45,8 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
final static byte OP_TEXT = 0x01; final static byte OP_TEXT = 0x01;
final static byte OP_BINARY = 0x02; final static byte OP_BINARY = 0x02;
final static byte OP_EXT_DATA = 0x03; final static byte OP_EXT_DATA = 0x03;
final static byte OP_CONTROL = 0x08;
final static byte OP_CLOSE = 0x08; final static byte OP_CLOSE = 0x08;
final static byte OP_PING = 0x09; final static byte OP_PING = 0x09;
final static byte OP_PONG = 0x0A; 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_NOCLOSE=1006;
final static int CLOSE_NOTUTF8=1007; final static int CLOSE_NOTUTF8=1007;
final static int FLAG_FIN=0x8;
final static int VERSION=8;
static boolean isLastFrame(byte flags) static boolean isLastFrame(byte flags)
{ {
return (flags&0x8)!=0; return (flags&FLAG_FIN)!=0;
} }
static boolean isControlFrame(byte opcode) static boolean isControlFrame(byte opcode)
{ {
return (opcode&0x8)!=0; return (opcode&OP_CONTROL)!=0;
} }
private final static byte[] MAGIC; private final static byte[] MAGIC;
@ -84,9 +89,10 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
private final OnControl _onControl; private final OnControl _onControl;
private final String _protocol; private final String _protocol;
private final int _draft; private final int _draft;
private final ClassLoader _context;
private int _close; private int _close;
private boolean _closedIn; private volatile boolean _closedIn;
private boolean _closedOut; private volatile boolean _closedOut;
private int _maxTextMessageSize; private int _maxTextMessageSize;
private int _maxBinaryMessageSize=-1; 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); super(endpoint,timestamp);
_context=Thread.currentThread().getContextClassLoader();
// TODO - can we use the endpoint idle mechanism? // TODO - can we use the endpoint idle mechanism?
if (endpoint instanceof AsyncEndPoint) if (endpoint instanceof AsyncEndPoint)
((AsyncEndPoint)endpoint).cancelIdle(); ((AsyncEndPoint)endpoint).cancelIdle();
@ -204,6 +212,9 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public Connection handle() throws IOException public Connection handle() throws IOException
{ {
Thread current = Thread.currentThread();
ClassLoader oldcontext = current.getContextClassLoader();
current.setContextClassLoader(_context);
try try
{ {
// handle the framing protocol // handle the framing protocol
@ -237,6 +248,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
} }
finally finally
{ {
current.setContextClassLoader(oldcontext);
if (_endp.isOpen()) if (_endp.isOpen())
{ {
_generator.idle(); _generator.idle();
@ -339,7 +351,6 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
_close=code; _close=code;
} }
try try
{ {
if (closed) if (closed)
@ -358,7 +369,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1); byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
bytes[0]=(byte)(code/0x100); bytes[0]=(byte)(code/0x100);
bytes[1]=(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(); _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; volatile boolean _disconnecting;
int _maxTextMessage=WebSocketConnectionD10.this._maxTextMessageSize; int _maxTextMessage=WebSocketConnectionD10.this._maxTextMessageSize;
int _maxBinaryMessage=WebSocketConnectionD10.this._maxBinaryMessageSize; int _maxBinaryMessage=WebSocketConnectionD10.this._maxBinaryMessageSize;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public synchronized void sendMessage(String content) throws IOException public void sendMessage(String content) throws IOException
{ {
if (_closedOut) if (_closedOut)
throw new IOException("closing"); throw new IOException("closing");
byte[] data = content.getBytes(StringUtil.__UTF8); 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(); checkWriteable();
_idle.access(_endp); _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) if (_closedOut)
throw new IOException("closing"); 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(); checkWriteable();
_idle.access(_endp); _idle.access(_endp);
} }
@ -430,7 +441,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
{ {
if (_closedOut) if (_closedOut)
throw new IOException("closing"); throw new IOException("closing");
_outbound.addFrame((byte)0x8,ctrl,data,offset,length); _outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
checkWriteable(); checkWriteable();
_idle.access(_endp); _idle.access(_endp);
} }
@ -507,7 +518,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public byte finMask() public byte finMask()
{ {
return 0x8; return FLAG_FIN;
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -557,6 +568,18 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
{ {
close(CLOSE_NORMAL,null); close(CLOSE_NORMAL,null);
} }
/* ------------------------------------------------------------ */
public void setFakeFragments(boolean fake)
{
_parser.setFakeFragments(fake);
}
/* ------------------------------------------------------------ */
public boolean isFakeFragments()
{
return _parser.isFakeFragments();
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
public String toString() 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 final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
private ByteArrayBuffer _aggregate; private ByteArrayBuffer _aggregate;
private byte _opcode=-1; 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); boolean lastFrame = isLastFrame(flags);
@ -583,176 +606,176 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
// Ignore incoming after a close // Ignore incoming after a close
if (_closedIn) if (_closedIn)
return; return;
}
try try
{ {
byte[] array=buffer.array(); byte[] array=buffer.array();
// Deliver frame if websocket is a FrameWebSocket // Deliver frame if websocket is a FrameWebSocket
if (_onFrame!=null) 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;
}
switch(opcode)
{
case WebSocketConnectionD10.OP_CONTINUATION:
{ {
if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length())) // If text, append to the message buffer
return; if (_onTextMessage!=null && _opcode==WebSocketConnectionD10.OP_TEXT)
}
if (_onControl!=null && isControlFrame(opcode))
{
if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
return;
}
switch(opcode)
{
case WebSocketConnectionD10.OP_CONTINUATION:
{ {
// If text, append to the message buffer if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
if (_opcode==WebSocketConnectionD10.OP_TEXT && _connection.getMaxTextMessageSize()>=0)
{ {
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 _opcode=-1;
if (lastFrame && _onTextMessage!=null) 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; _opcode=-1;
String msg =_utf8.toString(); _aggregate.clear();
_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();
}
} }
} }
} }
break;
} }
case WebSocketConnectionD10.OP_PING: break;
{
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_CLOSE:
{
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)
{
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)
{
if (lastFrame)
{
_onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
}
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);
}
}
}
}
}
} }
} case WebSocketConnectionD10.OP_PING:
catch(ThreadDeath th) {
{ Log.debug("PING {}",this);
throw th; if (!_closedOut)
} _connection.sendControl(WebSocketConnectionD10.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
catch(Throwable th) break;
{ }
Log.warn(th);
case WebSocketConnectionD10.OP_PONG:
{
Log.debug("PONG {}",this);
break;
}
case WebSocketConnectionD10.OP_CLOSE:
{
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)
{
if (_connection.getMaxTextMessageSize()<=0)
{
// No size limit, so handle only final frames
if (lastFrame)
_onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
}
else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
{
if (lastFrame)
{
String msg =_utf8.toString();
_utf8.reset();
_onTextMessage.onMessage(msg);
}
else
{
_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);
}
}
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) public void close(int code,String message)

View File

@ -20,7 +20,6 @@ import java.util.Random;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.TypeUtil;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */

View File

@ -14,13 +14,11 @@
package org.eclipse.jetty.websocket; package org.eclipse.jetty.websocket;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom;
import java.util.Random; import java.util.Random;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.util.TypeUtil;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */

View File

@ -18,8 +18,6 @@ import java.io.IOException;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.EndPoint; 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; import org.eclipse.jetty.util.log.Log;

View File

@ -18,8 +18,6 @@ import java.io.IOException;
import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers; import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.EndPoint; 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; import org.eclipse.jetty.util.log.Log;
@ -48,7 +46,6 @@ public class WebSocketParserD10 implements WebSocketParser
} }
}; };
private final WebSocketBuffers _buffers; private final WebSocketBuffers _buffers;
private final EndPoint _endp; private final EndPoint _endp;
private final FrameHandler _handler; private final FrameHandler _handler;
@ -63,6 +60,7 @@ public class WebSocketParserD10 implements WebSocketParser
private final byte[] _mask = new byte[4]; private final byte[] _mask = new byte[4];
private int _m; private int _m;
private boolean _skip; private boolean _skip;
private boolean _fakeFragments=true;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
@ -81,6 +79,24 @@ public class WebSocketParserD10 implements WebSocketParser
_state=State.START; _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() public boolean isBufferEmpty()
{ {
@ -122,7 +138,33 @@ public class WebSocketParserD10 implements WebSocketParser
// if no space, then the data is too big for buffer // if no space, then the data is too big for buffer
if (_buffer.space() == 0) 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()); throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
}
// catch IOExceptions (probably EOF) and try to parse what we have // catch IOExceptions (probably EOF) and try to parse what we have
try try
@ -202,7 +244,7 @@ public class WebSocketParserD10 implements WebSocketParser
_length = _length*0x100 + (0xff&b); _length = _length*0x100 + (0xff&b);
if (--_bytesNeeded==0) if (--_bytesNeeded==0)
{ {
if (_length>_buffer.capacity()) if (_length>_buffer.capacity() && !_fakeFragments)
{ {
events++; events++;
_handler.close(WebSocketConnectionD10.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity()); _handler.close(WebSocketConnectionD10.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());

View File

@ -2,27 +2,48 @@ package org.eclipse.jetty.websocket;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.URI; import java.net.URI;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch; 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.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.BlockingArrayQueue; 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.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
public class WebSocketClientTest 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 @Test
public void testBadURL() throws Exception public void testBadURL() throws Exception
{ {
@ -39,10 +60,6 @@ public class WebSocketClientTest
{ {
open.set(true); open.set(true);
} }
public void onError(String message, Throwable ex)
{
}
public void onClose(int closeCode, String message) public void onClose(int closeCode, String message)
{} {}
@ -57,553 +74,287 @@ public class WebSocketClientTest
Assert.assertTrue(bad); Assert.assertTrue(bad);
Assert.assertFalse(open.get()); Assert.assertFalse(open.get());
} }
@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 @Test
public void testAsyncConnectionRefused() throws Exception public void testAsyncConnectionRefused() throws Exception
{ {
WebSocketClient client = new WebSocketClient(); WebSocketClient client = new WebSocketClient();
client.setConnectTimeout(1000);
client.start(); client.start();
client.setBlockingConnect(false);
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean(); final AtomicBoolean open = new AtomicBoolean();
final AtomicReference<String> error = new AtomicReference<String>(null);
final AtomicInteger close = new AtomicInteger(); 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 try
{ {
client.open(new URI("ws://127.0.0.1:1"),new WebSocket() future.get(1,TimeUnit.SECONDS);
{ Assert.fail();
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();
}
});
} }
catch(IOException e) catch(ExecutionException e)
{ {
bad=true; error=e.getCause();
} }
Assert.assertFalse(bad);
Assert.assertFalse(open.get()); Assert.assertFalse(open.get());
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS)); Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
Assert.assertNotNull(error.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 @Test
public void testAsyncConnectionNotAccepted() throws Exception public void testConnectionNotAccepted() throws Exception
{ {
WebSocketClient client = new WebSocketClient(); WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(300);
client.start(); client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean(); final AtomicBoolean open = new AtomicBoolean();
final AtomicReference<String> error = new AtomicReference<String>(null);
final AtomicInteger close = new AtomicInteger(); 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 try
{ {
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket() future.get(250,TimeUnit.MILLISECONDS);
{ Assert.fail();
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();
}
});
} }
catch(IOException e) catch(TimeoutException e)
{ {
bad=true; error=e;
} }
Assert.assertFalse(bad);
Assert.assertFalse(open.get()); Assert.assertFalse(open.get());
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS)); Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
Assert.assertNotNull(error.get()); Assert.assertTrue(error instanceof TimeoutException);
} }
@Test @Test
public void testBlockingConnectionTimeout() throws Exception public void testConnectionTimeout() throws Exception
{ {
WebSocketClient client = new WebSocketClient(); WebSocketClient client = new WebSocketClient();
client.setConnectTimeout(500);
client.setBlockingConnect(true);
client.start(); 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 AtomicBoolean open = new AtomicBoolean();
final AtomicReference<String> error = new AtomicReference<String>(null);
final AtomicInteger close = new AtomicInteger(); 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 try
{ {
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket() future.get(250,TimeUnit.MILLISECONDS);
{ Assert.fail();
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();
}
});
} }
catch(IOException e) catch(TimeoutException e)
{ {
bad=true; error=e;
} }
Assert.assertNotNull(server.accept());
Assert.assertFalse(bad);
Assert.assertFalse(open.get()); Assert.assertFalse(open.get());
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS)); Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
Assert.assertNotNull(error.get()); Assert.assertTrue(error instanceof TimeoutException);
} }
@Test @Test
public void testBadHandshake() throws Exception public void testBadHandshake() throws Exception
{ {
WebSocketClient client = new WebSocketClient(); WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(300);
client.start(); client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean(); final AtomicBoolean open = new AtomicBoolean();
final AtomicReference<String> error = new AtomicReference<String>(null);
final AtomicInteger close = new AtomicInteger(); 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 try
{ {
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket() future.get(250,TimeUnit.MILLISECONDS);
{ Assert.fail();
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();
}
});
} }
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.assertFalse(open.get());
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS)); Assert.assertEquals(WebSocketConnectionD10.CLOSE_PROTOCOL,close.get());
Assert.assertNotNull(error.get()); Assert.assertTrue(error instanceof IOException);
Assert.assertTrue(error.getMessage().indexOf("404 NOT FOUND")>0);
} }
@Test @Test
public void testBadUpgrade() throws Exception public void testBadUpgrade() throws Exception
{ {
WebSocketClient client = new WebSocketClient(); WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(10000);
client.start(); client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean(); final AtomicBoolean open = new AtomicBoolean();
final AtomicReference<String> error = new AtomicReference<String>(null);
final AtomicInteger close = new AtomicInteger(); 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()
try
{ {
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);
{ }
open.set(true);
latch.countDown();
}
public void onError(String message, Throwable ex) public void onClose(int closeCode, String message)
{ {
error.set(message); close.set(closeCode);
latch.countDown(); }
} });
public void onClose(int closeCode, String message) Socket connection = _server.accept();
{ respondToClient(connection,
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((
"HTTP/1.1 101 Upgrade\r\n" + "HTTP/1.1 101 Upgrade\r\n" +
"Sec-WebSocket-Accept: rubbish\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());
}
Throwable error=null;
@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);
try 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);
{ }
open.set(true);
latch.countDown();
}
public void onError(String message, Throwable ex) public void onClose(int closeCode, String message)
{ {
error.set(message); close.set(closeCode);
latch.countDown(); _latch.countDown();
} }
});
public void onClose(int closeCode, String message)
{
close.set(closeCode);
latch.countDown();
}
});
}
catch(IOException e)
{
bad=true;
}
Assert.assertFalse(bad);
String key="not sent"; Socket socket = _server.accept();
Socket connection = server.accept(); accept(socket);
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)); WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
Assert.assertNull(error.get()); Assert.assertNotNull(connection);
Assert.assertTrue(open.get()); Assert.assertTrue(open.get());
Assert.assertEquals(0,close.get());
socket.close();
_latch.await(10,TimeUnit.SECONDS);
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
} }
@Test @Test
public void testIdle() throws Exception public void testIdle() throws Exception
{ {
WebSocketClient client = new WebSocketClient(); WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(10000);
client.setMaxIdleTime(500); client.setMaxIdleTime(500);
client.start(); client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean(); final AtomicBoolean open = new AtomicBoolean();
final AtomicInteger close = new AtomicInteger(); final AtomicInteger close = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(2); 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);
{ }
open.set(true);
latch.countDown();
}
public void onError(String message, Throwable ex) public void onClose(int closeCode, String message)
{ {
latch.countDown(); 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);
String key="not sent"; Socket socket = _server.accept();
Socket connection = server.accept(); accept(socket);
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.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()); Assert.assertEquals(WebSocketConnectionD10.CLOSE_NORMAL,close.get());
} }
@ -612,77 +363,41 @@ public class WebSocketClientTest
public void testNotIdle() throws Exception public void testNotIdle() throws Exception
{ {
WebSocketClient client = new WebSocketClient(); WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(10000);
client.setMaxIdleTime(500); client.setMaxIdleTime(500);
client.start(); client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean(); final AtomicBoolean open = new AtomicBoolean();
final Exchanger<Integer> close = new Exchanger<Integer>(); final AtomicInteger close = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch _latch = new CountDownLatch(1);
final AtomicReference<WebSocket.Connection> connection = new AtomicReference<WebSocket.Connection>();
final BlockingQueue<String> queue = new BlockingArrayQueue<String>(); 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);
{ }
open.set(true);
connection.set(c);
latch.countDown();
}
public void onError(String message, Throwable ex) public void onClose(int closeCode, String message)
{ {
latch.countDown(); close.set(closeCode);
} _latch.countDown();
}
public void onClose(int closeCode, String message)
{ public void onMessage(String data)
try {
{ queue.add(data);
close.exchange(closeCode); }
} });
catch(InterruptedException ex)
{}
latch.countDown();
}
public void onMessage(String data)
{
queue.add(data);
}
});
}
catch(IOException e)
{
bad=true;
}
Assert.assertFalse(bad);
String key="not sent"; Socket socket = _server.accept();
Socket socket = server.accept(); accept(socket);
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.assertTrue(open.get());
Assert.assertEquals(0,close.get());
// Send some messages client to server // Send some messages client to server
byte[] recv = new byte[1024]; byte[] recv = new byte[1024];
@ -690,7 +405,7 @@ public class WebSocketClientTest
for (int i=0;i<10;i++) for (int i=0;i<10;i++)
{ {
Thread.sleep(250); Thread.sleep(250);
connection.get().sendMessage("Hello"); connection.sendMessage("Hello");
len=socket.getInputStream().read(recv,0,recv.length); len=socket.getInputStream().read(recv,0,recv.length);
Assert.assertTrue(len>0); Assert.assertTrue(len>0);
} }
@ -706,10 +421,68 @@ public class WebSocketClientTest
Assert.assertEquals("Hi",queue.poll(1,TimeUnit.SECONDS)); 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().write(new byte[]{(byte)0x88, (byte) 0x02, (byte)4, (byte)87 },0,4);
socket.getOutputStream().flush(); socket.getOutputStream().flush();
_latch.await(10,TimeUnit.SECONDS);
Assert.assertTrue(System.currentTimeMillis()-start<5000);
Assert.assertEquals(1111,close.get());
Assert.assertEquals(new Integer(1111),close.exchange(null,1,TimeUnit.SECONDS));
} }
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());
}
} }

View File

@ -25,7 +25,6 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -116,10 +115,6 @@ public class WebSocketLoadD10Test
{ {
this.outbound = outbound; this.outbound = outbound;
} }
public void onError(String message,Throwable ex)
{
}
public void onMessage(String data) public void onMessage(String data)
{ {

View File

@ -249,10 +249,6 @@ public class WebSocketMessageD00Test
{ {
return latch.await(time, TimeUnit.MILLISECONDS); return latch.await(time, TimeUnit.MILLISECONDS);
} }
public void onError(String message,Throwable ex)
{
}
public void onClose(int code,String message) public void onClose(int code,String message)
{ {

View File

@ -22,7 +22,6 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.Utf8StringBuilder; import org.eclipse.jetty.util.Utf8StringBuilder;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
@ -756,10 +755,6 @@ public class WebSocketMessageD06Test
{ {
this.connection = connection; this.connection = connection;
} }
public void onError(String message,Throwable ex)
{
}
public void onOpen(Connection connection) public void onOpen(Connection connection)
{ {

Some files were not shown because too many files have changed in this diff Show More