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,11 +19,17 @@ jetty-7.5.0-SNAPSHOT
+ 352421 HttpURI paths beginning with '.'
+ 352684 Implemented spinning thread analyzer
+ 352786 GzipFilter fails to pass parameters to GzipResponseWrapper
+ 352999 ExpireTest running too long
+ 353073 WebSocketClient
+ 353095 maven-jetty-plugin: PermGen leak due to javax.el.BeanELResolver
+ 353165 addJars can follow symbolic link jar files
+ 353210 Bundle-Version in o.e.j.o.boot.logback fix
+ 353465 JAASLoginService ignores callbackHandlerClass
+ 353563 HttpDestinationQueueTest too slow
+ 353862 Improve performance of QuotedStringTokenizer.quote()
+ 354014 Content-Length is passed to wrapped response in GZipFilter
+ 354204 Charset encodings property file not used
+ 354466 Typo in example config of jetty-plus.xml
jetty-7.4.4.v20110707 July 7th 2011
+ 308851 Converted all jetty-client module tests to JUnit 4

View File

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

View File

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

View File

@ -103,14 +103,6 @@ public class WebSocketUpgradeTest
_results.add("clientWS.onMessage");
_results.add(data);
}
/* ------------------------------------------------------------ */
public void onError(String message, Throwable ex)
{
_results.add("clientWS.onError");
_results.add(message);
_results.add(ex);
}
};
@ -253,18 +245,10 @@ public class WebSocketUpgradeTest
_results.add(data);
}
/* ------------------------------------------------------------ */
public void onError(String message, Throwable ex)
{
_results.add("serverWS.onError");
_results.add(message);
_results.add(ex);
}
/* ------------------------------------------------------------ */
public void onClose(int code, String message)
{
_results.add("onDisconnect");
_results.add("onClose");
_webSockets.remove(this);
}

View File

@ -142,6 +142,8 @@ public class ScanningAppProviderRuntimeUpdatesTest
// This test will not work on Windows as second war file would
// not be written over the first one because of a file lock
Assume.assumeTrue(!OS.IS_WINDOWS);
Assume.assumeTrue(!OS.IS_OSX); // build server has issues with finding itself apparently
jetty.copyWebapp("foo-webapp-1.war","foo.war");
jetty.copyContext("foo.xml","foo.xml");

View File

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

View File

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

View File

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

View File

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

View File

@ -77,6 +77,19 @@ public class ByteArrayBuffer extends AbstractBuffer
_string = value;
}
public ByteArrayBuffer(String value,boolean immutable)
{
super(READWRITE,NON_VOLATILE);
_bytes = StringUtil.getBytes(value);
setGetIndex(0);
setPutIndex(_bytes.length);
if (immutable)
{
_access=IMMUTABLE;
_string = value;
}
}
public ByteArrayBuffer(String value,String encoding) throws UnsupportedEncodingException
{
super(READWRITE,NON_VOLATILE);

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
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="busyThreshold">90</Set>
<Set name="stackDepth">3</Set>
<Set name="trailLength">2</Set>
<!-- To enable logging CPU utilization for threads above specified threshold, -->
<!-- uncomment the following lines, changing log interval (in milliseconds) -->
<!-- and log threshold (in percent) as desired. -->
<!--
<Set name="logInterval">10000</Arg>
<Set name="logThreshold">1</Arg>
-->
<!-- To enable detail dump of the server whenever a thread is detected as spinning, -->
<!-- uncomment the following lines. -->
<!--
<Set name="dumpable"><Ref id="Server"/></Set>
-->
</New>
</Arg>
</Call>

View File

@ -15,15 +15,16 @@
package org.eclipse.jetty.monitor;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
@ -32,17 +33,20 @@ import org.eclipse.jetty.util.log.Logger;
public class ThreadMonitor extends AbstractLifeCycle implements Runnable
{
private int _scanInterval;
private int _logInterval;
private int _busyThreshold;
private int _logThreshold;
private int _stackDepth;
private int _trailLength;
private ThreadMXBean _threadBean;
private Method findDeadlockedThreadsMethod;
private Thread _runner;
private Logger _logger;
private volatile boolean _done = true;
private Dumpable _dumpable;
private Map<Long,ExtThreadInfo> _extInfo;
private Map<Long,ThreadMonitorInfo> _monitorInfo;
/* ------------------------------------------------------------ */
/**
@ -52,7 +56,32 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
*/
public ThreadMonitor() throws Exception
{
this(5000, 95, 3);
this(5000);
}
/* ------------------------------------------------------------ */
/**
* Instantiates a new thread monitor.
*
* @param intervalMs scan interval
* @throws Exception
*/
public ThreadMonitor(int intervalMs) throws Exception
{
this(intervalMs, 95);
}
/* ------------------------------------------------------------ */
/**
* Instantiates a new thread monitor.
*
* @param intervalMs scan interval
* @param threshold busy threshold
* @throws Exception
*/
public ThreadMonitor(int intervalMs, int threshold) throws Exception
{
this(intervalMs, threshold, 3);
}
/* ------------------------------------------------------------ */
@ -65,53 +94,196 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
* @throws Exception
*/
public ThreadMonitor(int intervalMs, int threshold, int depth) throws Exception
{
this(intervalMs, threshold, depth, 3);
}
/* ------------------------------------------------------------ */
/**
* Instantiates a new thread monitor.
*
* @param intervalMs scan interval
* @param threshold busy threshold
* @param depth stack compare depth
* @param trail length of stack trail
* @throws Exception
*/
public ThreadMonitor(int intervalMs, int threshold, int depth, int trail) throws Exception
{
_scanInterval = intervalMs;
_busyThreshold = threshold;
_stackDepth = depth;
_trailLength = trail;
_logger = Log.getLogger(getClass().getName());
_extInfo = new HashMap<Long, ExtThreadInfo>();
_logger = Log.getLogger(ThreadMonitor.class.getName());
_monitorInfo = new HashMap<Long, ThreadMonitorInfo>();
init();
}
/* ------------------------------------------------------------ */
/**
* Gets the scan interval.
*
* @return the scan interval
*/
public int getScanInterval()
{
return _scanInterval;
}
/* ------------------------------------------------------------ */
/**
* Sets the scan interval.
*
* @param ms the new scan interval
*/
public void setScanInterval(int ms)
{
_scanInterval = ms;
}
/* ------------------------------------------------------------ */
public int getBusyThreshold()
/**
* Gets the log interval.
*
* @return the log interval
*/
public int getLogInterval()
{
return _logInterval;
}
/* ------------------------------------------------------------ */
/**
* Sets the log interval.
*
* @param ms the new log interval
*/
public void setLogInterval(int ms)
{
_logInterval = ms;
}
/* ------------------------------------------------------------ */
/**
* Gets the busy threshold.
*
* @return the busy threshold
*/
public int getBusyThreshold()
{
return _busyThreshold;
}
/* ------------------------------------------------------------ */
/**
* Sets the busy threshold.
*
* @param percent the new busy threshold
*/
public void setBusyThreshold(int percent)
{
_busyThreshold = percent;
}
/* ------------------------------------------------------------ */
/**
* Gets the log threshold.
*
* @return the log threshold
*/
public int getLogThreshold()
{
return _logThreshold;
}
/* ------------------------------------------------------------ */
/**
* Sets the log threshold.
*
* @param percent the new log threshold
*/
public void setLogThreshold(int percent)
{
_logThreshold = percent;
}
/* ------------------------------------------------------------ */
/**
* Gets the stack depth.
*
* @return the stack depth
*/
public int getStackDepth()
{
return _stackDepth;
}
/* ------------------------------------------------------------ */
/**
* Sets the stack depth.
*
* @param stackDepth the new stack depth
*/
public void setStackDepth(int stackDepth)
{
_stackDepth = stackDepth;
}
/* ------------------------------------------------------------ */
/**
* Sets the stack trace trail length.
*
* @param trailLength the new trail length
*/
public void setTrailLength(int trailLength)
{
_trailLength = trailLength;
}
/* ------------------------------------------------------------ */
/**
* Gets the stack trace trail length.
*
* @return the trail length
*/
public int getTrailLength()
{
return _trailLength;
}
/* ------------------------------------------------------------ */
/**
* Enable logging of CPU usage.
*
* @param frequencyMs the logging frequency
* @param thresholdPercent the logging threshold
*/
public void logCpuUsage(int frequencyMs, int thresholdPercent)
{
setLogInterval(frequencyMs);
setLogThreshold(thresholdPercent);
}
/* ------------------------------------------------------------ */
/**
* @return A {@link Dumpable} that is dumped whenever spinning threads are detected
*/
public Dumpable getDumpable()
{
return _dumpable;
}
/* ------------------------------------------------------------ */
/**
* @param dumpable A {@link Dumpable} that is dumped whenever spinning threads are detected
*/
public void setDumpable(Dumpable dumpable)
{
_dumpable = dumpable;
}
/* ------------------------------------------------------------ */
/**
* @see org.eclipse.jetty.util.component.AbstractLifeCycle#doStart()
@ -121,6 +293,7 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
_done = false;
_runner = new Thread(this);
_runner.setDaemon(true);
_runner.start();
Log.info("Thread Monitor started successfully");
@ -143,49 +316,6 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
}
}
/* ------------------------------------------------------------ */
/**
* Initialize JMX objects.
*/
protected void init()
{
_threadBean = ManagementFactory.getThreadMXBean();
if (_threadBean.isThreadCpuTimeSupported())
{
_threadBean.setThreadCpuTimeEnabled(true);
}
String versionStr = System.getProperty("java.version");
float version = Float.valueOf(versionStr.substring(0,versionStr.lastIndexOf('.')));
try
{
if (version < 1.6)
{
findDeadlockedThreadsMethod = ThreadMXBean.class.getMethod("findMonitorDeadlockedThreads");
}
else
{
findDeadlockedThreadsMethod = ThreadMXBean.class.getMethod("findDeadlockedThreads");
}
}
catch (Exception ex)
{
Log.debug(ex);
}
}
/* ------------------------------------------------------------ */
/**
* Find deadlocked threads.
*
* @return array of the deadlocked thread ids
* @throws Exception the exception
*/
protected long[] findDeadlockedThreads() throws Exception
{
return (long[]) findDeadlockedThreadsMethod.invoke(_threadBean,(Object[])null);
}
/* ------------------------------------------------------------ */
/**
* Retrieve all avaliable thread ids
@ -211,42 +341,14 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
/* ------------------------------------------------------------ */
/**
* Retrieve thread info.
*
* @param id thread id
* @param maxDepth maximum stack depth
* @return thread info
* Initialize JMX objects.
*/
protected ThreadInfo getThreadInfo(long id, int maxDepth)
protected void init()
{
return _threadBean.getThreadInfo(id,maxDepth);
}
/* ------------------------------------------------------------ */
/**
* Output thread info to log.
*
* @param threads thread info list
*/
protected void dump(final List<ThreadInfo> threads)
{
if (threads != null && threads.size() > 0)
_threadBean = ManagementFactory.getThreadMXBean();
if (_threadBean.isThreadCpuTimeSupported())
{
for (ThreadInfo info : threads)
{
StringBuffer msg = new StringBuffer();
if (info.getLockOwnerId() < 0)
{
msg.append(String.format("Thread %s[%d] is spinning", info.getThreadName(), info.getThreadId()));
}
else
{
msg.append(String.format("Thread %s[%d] is %s", info.getThreadName(), info.getThreadId(), info.getThreadState()));
msg.append(String.format(" on %s owned by %s[%d]", info.getLockName(), info.getLockOwnerName(), info.getLockOwnerId()));
}
_logger.warn(new ThreadMonitorException(msg.toString(), info.getStackTrace()));
}
_threadBean.setThreadCpuTimeEnabled(true);
}
}
@ -256,34 +358,45 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
*/
public void run()
{
long currTime;
long lastTime = 0;
// Initialize repeat flag
boolean repeat = false;
boolean scanNow, logNow;
// Set next scan time and log time
long nextScanTime = System.currentTimeMillis();
long nextLogTime = nextScanTime + _logInterval;
while (!_done)
{
currTime = System.currentTimeMillis();
if (currTime < lastTime + _scanInterval)
long currTime = System.currentTimeMillis();
scanNow = (currTime > nextScanTime);
logNow = (_logInterval > 0 && currTime > nextLogTime);
if (repeat || scanNow || logNow)
{
repeat = collectThreadInfo();
logThreadInfo(logNow);
if (scanNow)
{
nextScanTime = System.currentTimeMillis() + _scanInterval;
}
if (logNow)
{
nextLogTime = System.currentTimeMillis() + _logInterval;
}
}
// Sleep only if not going to repeat scanning immediately
if (!repeat)
{
try
{
Thread.sleep(50);
Thread.sleep(100);
}
catch (InterruptedException ex)
{
Log.ignore(ex);
}
continue;
}
List<ThreadInfo> threadInfo = new ArrayList<ThreadInfo>();
findSpinningThreads(threadInfo);
findDeadlockedThreads(threadInfo);
lastTime = System.currentTimeMillis();
if (threadInfo.size() > 0)
{
dump(threadInfo);
}
}
@ -291,104 +404,158 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
/* ------------------------------------------------------------ */
/**
* Find spinning threads.
*
* @param threadInfo thread info list to add the results
* @return thread info list
* Collect thread info.
*/
private List<ThreadInfo> findSpinningThreads(final List<ThreadInfo> threadInfo)
private boolean collectThreadInfo()
{
if (threadInfo != null)
boolean repeat = false;
try
{
try
// Retrieve stack traces for all threads at once as it
// was proven to be an order of magnitude faster when
// retrieving a single thread stack trace.
Map<Thread,StackTraceElement[]> all = Thread.getAllStackTraces();
for (Map.Entry<Thread,StackTraceElement[]> entry : all.entrySet())
{
long[] allThreadId = getAllThreadIds();
for (int idx=0; idx < allThreadId.length; idx++)
Thread thread = entry.getKey();
long threadId = thread.getId();
// Skip our own runner thread
if (threadId == _runner.getId())
{
long currId = allThreadId[idx];
continue;
}
if (currId == _runner.getId())
ThreadMonitorInfo currMonitorInfo = _monitorInfo.get(Long.valueOf(threadId));
if (currMonitorInfo == null)
{
// Create thread info object for a new thread
currMonitorInfo = new ThreadMonitorInfo(thread);
currMonitorInfo.setStackTrace(entry.getValue());
currMonitorInfo.setCpuTime(getThreadCpuTime(threadId));
currMonitorInfo.setSampleTime(System.nanoTime());
_monitorInfo.put(Long.valueOf(threadId), currMonitorInfo);
}
else
{
// Update the existing thread info object
currMonitorInfo.setStackTrace(entry.getValue());
currMonitorInfo.setCpuTime(getThreadCpuTime(threadId));
currMonitorInfo.setSampleTime(System.nanoTime());
// Stack trace count holds logging state
int count = currMonitorInfo.getTraceCount();
if (count >= 0 && currMonitorInfo.isSpinning())
{
continue;
}
long currCpuTime = getThreadCpuTime(currId);
long currNanoTime = System.nanoTime();
ExtThreadInfo currExtInfo = _extInfo.get(Long.valueOf(currId));
if (currExtInfo != null)
{
long elapsedCpuTime = currCpuTime - currExtInfo.getLastCpuTime();
long elapsedNanoTime = currNanoTime - currExtInfo.getLastSampleTime();
if (((elapsedCpuTime * 100.0) / elapsedNanoTime) > _busyThreshold)
// Thread was spinning and was logged before
if (count < _trailLength)
{
ThreadInfo currInfo = getThreadInfo(currId, Integer.MAX_VALUE);
if (currInfo != null)
{
StackTraceElement[] lastStackTrace = currExtInfo.getStackTrace();
currExtInfo.setStackTrace(currInfo.getStackTrace());
// Log another stack trace
currMonitorInfo.setTraceCount(count+1);
repeat = true;
continue;
}
if (lastStackTrace != null
&& matchStackTraces(lastStackTrace, currInfo.getStackTrace())) {
threadInfo.add(currInfo);
}
// Reset spin flag and trace count
currMonitorInfo.setSpinning(false);
currMonitorInfo.setTraceCount(-1);
}
if (currMonitorInfo.getCpuUtilization() > _busyThreshold)
{
// Thread is busy
StackTraceElement[] lastStackTrace = currMonitorInfo.getStackTrace();
if (lastStackTrace != null
&& matchStackTraces(lastStackTrace, entry.getValue()))
{
// Thread is spinning
currMonitorInfo.setSpinning(true);
if (count < 0)
{
// Enable logging of spin status and stack traces
// only if the incoming trace count is negative
// that indicates a new scan for this thread
currMonitorInfo.setTraceCount(0);
repeat = (_trailLength > 0);
}
}
}
else
{
currExtInfo = new ExtThreadInfo(currId);
_extInfo.put(Long.valueOf(currId), currExtInfo);
}
currExtInfo.setLastCpuTime(currCpuTime);
currExtInfo.setLastSampleTime(currNanoTime);
}
}
catch (Exception ex)
{
Log.debug(ex);
}
}
return threadInfo;
catch (Exception ex)
{
Log.debug(ex);
}
return repeat;
}
/* ------------------------------------------------------------ */
/**
* Find deadlocked threads.
*
* @param threadInfo thread info list to add the results
* @return thread info list
*/
private List<ThreadInfo> findDeadlockedThreads(final List<ThreadInfo> threadInfo)
protected void logThreadInfo(boolean logAll)
{
if (threadInfo != null)
if (_monitorInfo.size() > 0)
{
try
// Select thread objects for all live threads
long[] running = getAllThreadIds();
List<ThreadMonitorInfo> all = new ArrayList<ThreadMonitorInfo>();
for (int idx=0; idx<running.length; idx++)
{
long[] threads = findDeadlockedThreads();
if (threads != null && threads.length > 0)
ThreadMonitorInfo info = _monitorInfo.get(running[idx]);
if (info != null)
{
ThreadInfo currInfo;
for (int idx=0; idx < threads.length; idx++)
{
currInfo = getThreadInfo(threads[idx], Integer.MAX_VALUE);
if (currInfo != null)
{
threadInfo.add(currInfo);
}
}
all.add(info);
}
}
catch (Exception ex)
// Sort selected thread objects by their CPU utilization
Collections.sort(all, new Comparator<ThreadMonitorInfo>()
{
Log.debug(ex);
/* ------------------------------------------------------------ */
public int compare(ThreadMonitorInfo info1, ThreadMonitorInfo info2)
{
return (int)Math.signum(info2.getCpuUtilization()-info1.getCpuUtilization());
}
});
String format = "Thread '%2$s'[%3$s,id:%1$d,cpu:%4$.2f%%]%5$s";
// Log thread information for threads that exceed logging threshold
// or log spinning threads if their trace count is zero
boolean spinning=false;
for (ThreadMonitorInfo info : all)
{
if (logAll && info.getCpuUtilization() > _logThreshold
|| info.isSpinning() && info.getTraceCount() == 0)
{
String message = String.format(format,
info.getThreadId(), info.getThreadName(),
info.getThreadState(), info.getCpuUtilization(),
info.isSpinning() ? " SPINNING" : "");
_logger.info(message);
spinning=true;
}
}
// Dump info
if (spinning && _dumpable!=null)
{
System.err.println(_dumpable.dump());
}
// Log stack traces for spinning threads with positive trace count
for (ThreadMonitorInfo info : all)
{
if (info.isSpinning() && info.getTraceCount() >= 0)
{
String message = String.format(format,
info.getThreadId(), info.getThreadName(),
info.getThreadState(), info.getCpuUtilization(),
" STACK TRACE");
_logger.warn(new ThreadMonitorException(message, info.getStackTrace()));
}
}
}
return threadInfo;
}
/* ------------------------------------------------------------ */
@ -414,91 +581,4 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
}
return match;
}
/* ------------------------------------------------------------ */
private class ExtThreadInfo
{
private long _threadId;
private long _lastCpuTime;
private long _lastSampleTime;
private StackTraceElement[] _stackTrace;
/* ------------------------------------------------------------ */
public ExtThreadInfo(long threadId)
{
_threadId = threadId;
}
/* ------------------------------------------------------------ */
/**
* @return thread id associated with the instance
*/
public long getThreadId()
{
return _threadId;
}
/* ------------------------------------------------------------ */
/**
* @return the last CPU time of the thread
*/
public long getLastCpuTime()
{
return _lastCpuTime;
}
/* ------------------------------------------------------------ */
/**
* Set the last CPU time.
*
* @param lastCpuTime new last CPU time
*/
public void setLastCpuTime(long lastCpuTime)
{
this._lastCpuTime = lastCpuTime;
}
/* ------------------------------------------------------------ */
/**
* @return the time of last sample
*/
public long getLastSampleTime()
{
return _lastSampleTime;
}
/* ------------------------------------------------------------ */
/**
* Sets the last sample time.
*
* @param lastSampleTime the time of last sample
*/
public void setLastSampleTime(long lastSampleTime)
{
_lastSampleTime = lastSampleTime;
}
/* ------------------------------------------------------------ */
/**
* Gets the stack trace.
*
* @return the stack trace
*/
public StackTraceElement[] getStackTrace()
{
return _stackTrace;
}
/* ------------------------------------------------------------ */
/**
* Sets the stack trace.
*
* @param stackTrace the new stack trace
*/
public void setStackTrace(StackTraceElement[] stackTrace)
{
_stackTrace = stackTrace;
}
}
}

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 java.lang.management.ThreadInfo;
import java.util.List;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StdErrLog;
import org.junit.Test;
@ -28,24 +31,55 @@ import org.junit.Test;
*/
public class ThreadMonitorTest
{
public final static int DURATION=9000;
private AtomicInteger count=new AtomicInteger(0);
public final static int DURATION=4000;
@Test
public void monitorTest() throws Exception
{
((StdErrLog)Log.getLogger(ThreadMonitor.class.getName())).setHideStacks(true);
((StdErrLog)Log.getLogger(ThreadMonitor.class.getName())).setSource(false);
ThreadMonitor monitor = new ThreadMonitor(1000,50,2)
final AtomicInteger countLogs=new AtomicInteger(0);
final AtomicInteger countSpin=new AtomicInteger(0);
ThreadMonitor monitor = new ThreadMonitor(1000,50,1,1)
{
@Override
protected void dump(List<ThreadInfo> threads)
protected void logThreadInfo(boolean logAll)
{
count.incrementAndGet();
super.dump(threads);
if (logAll)
countLogs.incrementAndGet();
else
countSpin.incrementAndGet();
super.logThreadInfo(logAll);
}
};
monitor.setDumpable(new Dumpable()
{
public void dump(Appendable out, String indent) throws IOException
{
out.append(dump());
}
public String dump()
{
return "Dump Spinning";
}
});
monitor.logCpuUsage(2000,0);
monitor.start();
Random rnd = new Random();
for (long cnt=0; cnt<100; cnt++)
{
long value = rnd.nextLong() % 50 + 50;
Sleeper sleeper = new Sleeper(value);
Thread runner = new Thread(sleeper);
runner.setDaemon(true);
runner.start();
}
Spinner spinner = new Spinner();
Thread runner = new Thread(spinner);
runner.start();
@ -55,7 +89,8 @@ public class ThreadMonitorTest
spinner.setDone();
monitor.stop();
assertTrue(count.get() >= 2);
assertTrue(countLogs.get() >= 1);
assertTrue(countSpin.get() >= 2);
}
@ -64,8 +99,6 @@ public class ThreadMonitorTest
private volatile boolean done = false;
/* ------------------------------------------------------------ */
/**
*/
public void setDone()
{
done = true;
@ -91,6 +124,36 @@ public class ThreadMonitorTest
if (result==42)
System.err.println("Bingo!");
}
}
private class Sleeper implements Runnable
{
private long _value;
/* ------------------------------------------------------------ */
public Sleeper(long value)
{
_value = value;
}
/* ------------------------------------------------------------ */
public void run()
{
try
{
fn(_value);
}
catch (InterruptedException e) {}
}
/* ------------------------------------------------------------ */
public long fn(long value) throws InterruptedException
{
long result = value > 1 ? fn(value-1) : 1;
Thread.sleep(50);
return result;
}
}
}

View File

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

View File

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

View File

@ -48,7 +48,7 @@ import org.eclipse.jetty.util.log.Log;
*
* <p>
* Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and CPU cycles. If this handler is used for static content,
* then use of efficient direct NIO may be prevented, thus use of the gzip mechanism of the {@link org.eclipse.jetty.servlet.DefaultServlet} is advised instead.
* then use of efficient direct NIO may be prevented, thus use of the gzip mechanism of the <code>org.eclipse.jetty.servlet.DefaultServlet</code> is advised instead.
* </p>
*/
public class GzipHandler extends HandlerWrapper

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
{

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.
*
* @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSessionManager.AbstractSession)
* @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
*/
@Override
protected void addSession(AbstractSession session)

View File

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

View File

@ -116,11 +116,20 @@ public class ResponseTest
assertEquals("foo2/bar2;charset=ISO-8859-1",response.getContentType());
response.recycle();
response.setContentType("text/xml;charset=ISO-8859-7");
response.getWriter();
response.setContentType("text/html;charset=UTF-8");
assertEquals("text/html;charset=ISO-8859-7",response.getContentType());
response.recycle();
response.setContentType("text/html;charset=US-ASCII");
response.getWriter();
assertEquals("text/html;charset=US-ASCII",response.getContentType());
response.recycle();
response.setContentType("text/json");
response.getWriter();
assertEquals("text/json;charset=UTF-8", response.getContentType());
}
@Test

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 setAttribute(String name, Object attribute);
public Object getAttribute(String name);
public Enumeration getAttributeNames();
public Enumeration<String> getAttributeNames();
public void clearAttributes();
}

View File

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

View File

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

View File

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

View File

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

View File

@ -45,7 +45,7 @@ public class IntrospectionUtil
return true;
}
public static Method findMethod (Class clazz, String methodName, Class[] args, boolean checkInheritance, boolean strictArgs)
public static Method findMethod (Class<?> clazz, String methodName, Class<?>[] args, boolean checkInheritance, boolean strictArgs)
throws NoSuchMethodException
{
if (clazz == null)
@ -78,7 +78,7 @@ public class IntrospectionUtil
public static Field findField (Class clazz, String targetName, Class targetType, boolean checkInheritance, boolean strictType)
public static Field findField (Class<?> clazz, String targetName, Class<?> targetType, boolean checkInheritance, boolean strictType)
throws NoSuchFieldException
{
if (clazz == null)
@ -137,7 +137,7 @@ public class IntrospectionUtil
public static boolean checkParams (Class[] formalParams, Class[] actualParams, boolean strict)
public static boolean checkParams (Class<?>[] formalParams, Class<?>[] actualParams, boolean strict)
{
if (formalParams==null && actualParams==null)
return true;
@ -182,8 +182,8 @@ public class IntrospectionUtil
if (methodB==null)
return false;
List parameterTypesA = Arrays.asList(methodA.getParameterTypes());
List parameterTypesB = Arrays.asList(methodB.getParameterTypes());
List<Class<?>> parameterTypesA = Arrays.asList(methodA.getParameterTypes());
List<Class<?>> parameterTypesB = Arrays.asList(methodB.getParameterTypes());
if (methodA.getName().equals(methodB.getName())
&&
@ -193,7 +193,7 @@ public class IntrospectionUtil
return false;
}
public static boolean isTypeCompatible (Class formalType, Class actualType, boolean strict)
public static boolean isTypeCompatible (Class<?> formalType, Class<?> actualType, boolean strict)
{
if (formalType==null && actualType != null)
return false;
@ -211,7 +211,7 @@ public class IntrospectionUtil
public static boolean containsSameMethodSignature (Method method, Class c, boolean checkPackage)
public static boolean containsSameMethodSignature (Method method, Class<?> c, boolean checkPackage)
{
if (checkPackage)
{
@ -230,7 +230,7 @@ public class IntrospectionUtil
}
public static boolean containsSameFieldName(Field field, Class c, boolean checkPackage)
public static boolean containsSameFieldName(Field field, Class<?> c, boolean checkPackage)
{
if (checkPackage)
{
@ -250,7 +250,7 @@ public class IntrospectionUtil
protected static Method findInheritedMethod (Package pack, Class clazz, String methodName, Class[] args, boolean strictArgs)
protected static Method findInheritedMethod (Package pack, Class<?> clazz, String methodName, Class<?>[] args, boolean strictArgs)
throws NoSuchMethodException
{
if (clazz==null)
@ -275,7 +275,7 @@ public class IntrospectionUtil
return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
}
protected static Field findInheritedField (Package pack, Class clazz, String fieldName, Class fieldType, boolean strictType)
protected static Field findInheritedField (Package pack, Class<?> clazz, String fieldName, Class<?> fieldType, boolean strictType)
throws NoSuchFieldException
{
if (clazz==null)

View File

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

View File

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

View File

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

View File

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

View File

@ -14,6 +14,7 @@
package org.eclipse.jetty.util;
import java.io.IOException;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
@ -130,7 +131,7 @@ public class QuotedStringTokenizer
_hasToken=true;
state=1;
}
continue;
break;
case 1: // Token
_hasToken=true;
@ -153,9 +154,10 @@ public class QuotedStringTokenizer
state=3;
}
else
{
_token.append(c);
continue;
}
break;
case 2: // Single Quote
_hasToken=true;
@ -177,9 +179,10 @@ public class QuotedStringTokenizer
escape=true;
}
else
{
_token.append(c);
continue;
}
break;
case 3: // Double Quote
_hasToken=true;
@ -201,8 +204,10 @@ public class QuotedStringTokenizer
escape=true;
}
else
{
_token.append(c);
continue;
}
break;
}
}
@ -262,9 +267,10 @@ public class QuotedStringTokenizer
/* ------------------------------------------------------------ */
/** Quote a string.
* The string is quoted only if quoting is required due to
* embeded delimiters, quote characters or the
* embedded delimiters, quote characters or the
* empty string.
* @param s The string to quote.
* @param delim the delimiter to use to quote the string
* @return quoted string
*/
public static String quoteIfNeeded(String s, String delim)
@ -310,67 +316,59 @@ public class QuotedStringTokenizer
}
private static final char[] escapes = new char[32];
static
{
Arrays.fill(escapes, (char)0xFFFF);
escapes['\b'] = 'b';
escapes['\t'] = 't';
escapes['\n'] = 'n';
escapes['\f'] = 'f';
escapes['\r'] = 'r';
}
/* ------------------------------------------------------------ */
/** Quote a string into an Appendable.
* The characters ", \, \n, \r, \t, \f and \b are escaped
* @param buf The Appendable
* @param s The String to quote.
* @param buffer The Appendable
* @param input The String to quote.
*/
public static void quote(Appendable buf, String s)
public static void quote(Appendable buffer, String input)
{
try
{
buf.append('"');
for (int i=0;i<s.length();i++)
buffer.append('"');
for (int i = 0; i < input.length(); ++i)
{
char c = s.charAt(i);
switch(c)
char c = input.charAt(i);
if (c >= 32)
{
case '"':
buf.append("\\\"");
continue;
case '\\':
buf.append("\\\\");
continue;
case '\n':
buf.append("\\n");
continue;
case '\r':
buf.append("\\r");
continue;
case '\t':
buf.append("\\t");
continue;
case '\f':
buf.append("\\f");
continue;
case '\b':
buf.append("\\b");
continue;
default:
if (c<0x10)
{
buf.append("\\u000");
buf.append(Integer.toString(c,16));
}
else if (c<=0x1f)
{
buf.append("\\u00");
buf.append(Integer.toString(c,16));
}
else
buf.append(c);
continue;
if (c == '"' || c == '\\')
buffer.append('\\');
buffer.append(c);
}
else
{
char escape = escapes[c];
if (escape == 0xFFFF)
{
// Unicode escape
buffer.append('\\').append('u').append('0').append('0');
if (c < 0x10)
buffer.append('0');
buffer.append(Integer.toString(c, 16));
}
else
{
buffer.append('\\').append(escape);
}
}
}
buf.append('"');
buffer.append('"');
}
catch(IOException e)
catch (IOException x)
{
throw new RuntimeException(e);
throw new RuntimeException(x);
}
}
@ -423,67 +421,65 @@ public class QuotedStringTokenizer
if (first!=last || (first!='"' && first!='\''))
return s;
StringBuffer b=new StringBuffer(s.length()-2);
synchronized(b)
StringBuilder b = new StringBuilder(s.length() - 2);
boolean escape=false;
for (int i=1;i<s.length()-1;i++)
{
boolean escape=false;
for (int i=1;i<s.length()-1;i++)
char c = s.charAt(i);
if (escape)
{
char c = s.charAt(i);
if (escape)
escape=false;
switch (c)
{
escape=false;
switch (c)
{
case 'n':
b.append('\n');
break;
case 'r':
b.append('\r');
break;
case 't':
b.append('\t');
break;
case 'f':
b.append('\f');
break;
case 'b':
b.append('\b');
break;
case '\\':
b.append('\\');
break;
case '/':
b.append('/');
break;
case '"':
b.append('"');
break;
case 'u':
b.append((char)(
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
(TypeUtil.convertHexDigit((byte)s.charAt(i++)))
)
);
break;
default:
b.append(c);
}
case 'n':
b.append('\n');
break;
case 'r':
b.append('\r');
break;
case 't':
b.append('\t');
break;
case 'f':
b.append('\f');
break;
case 'b':
b.append('\b');
break;
case '\\':
b.append('\\');
break;
case '/':
b.append('/');
break;
case '"':
b.append('"');
break;
case 'u':
b.append((char)(
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
(TypeUtil.convertHexDigit((byte)s.charAt(i++)))
)
);
break;
default:
b.append(c);
}
else if (c=='\\')
{
escape=true;
continue;
}
else
b.append(c);
}
return b.toString();
else if (c=='\\')
{
escape=true;
}
else
{
b.append(c);
}
}
return b.toString();
}
/* ------------------------------------------------------------ */
@ -522,15 +518,3 @@ public class QuotedStringTokenizer
_single=single;
}
}

View File

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

View File

@ -14,6 +14,8 @@
package org.eclipse.jetty.util.log;
import java.security.AccessControlException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.eclipse.jetty.util.DateCache;
@ -43,6 +45,8 @@ public class StdErrLog implements Logger
System.getProperty("org.eclipse.jetty.util.log.SOURCE",
System.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE", "false")));
private final static ConcurrentMap<String,StdErrLog> __loggers = new ConcurrentHashMap<String, StdErrLog>();
static
{
try
@ -315,9 +319,21 @@ public class StdErrLog implements Logger
public Logger getLogger(String name)
{
if ((name == null && this._name == null) || (name != null && name.equals(this._name)))
String fullname=_name == null || _name.length() == 0?name:_name + "." + name;
if ((name == null && this._name == null) || fullname.equals(_name))
return this;
return new StdErrLog(_name == null || _name.length() == 0?name:_name + "." + name);
StdErrLog logger = __loggers.get(name);
if (logger==null)
{
StdErrLog sel=new StdErrLog(fullname);
logger=__loggers.putIfAbsent(fullname,sel);
if (logger==null)
logger=sel;
}
return logger;
}
@Override

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.PrintStream;
import junit.framework.Assert;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
@ -123,8 +125,12 @@ public class LogTest
public void testStdErrLogName()
{
StdErrLog log = new StdErrLog("test");
Assert.assertEquals("test",log.getName());
Logger next=log.getLogger("next");
Assert.assertEquals("test.next",next.getName());
next.info("testing {} {}","next","info");
logContains(":test.next:testing next info");

View File

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

View File

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

View File

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

View File

@ -1,32 +1,18 @@
package org.eclipse.jetty.websocket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URI;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.bio.SocketEndPoint;
import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.websocket.WebSocket.Connection;
import org.eclipse.jetty.websocket.WebSocket.FrameConnection;
/**
* @version $Revision$ $Date$
@ -68,14 +54,6 @@ public class TestClient implements WebSocket.OnFrame
{
}
public void onError(String message, Throwable ex)
{
System.err.println("onError: "+message);
if (ex!=null)
ex.printStackTrace();
_handshook.countDown();
}
public void onClose(int closeCode, String message)
{
_handshook.countDown();
@ -141,8 +119,10 @@ public class TestClient implements WebSocket.OnFrame
private void open() throws Exception
{
__client.open(new URI("ws://"+_host+":"+_port+"/"),this,_protocol,_timeout);
_handshook.await(10,TimeUnit.SECONDS);
WebSocketClient client = new WebSocketClient(__client);
client.setProtocol(_protocol);
client.setMaxIdleTime(_timeout);
client.open(new URI("ws://"+_host+":"+_port+"/"),this).get(10,TimeUnit.SECONDS);
}
public void ping(byte opcode,byte[] data,int fragment) throws Exception

View File

@ -107,12 +107,6 @@ public class TestServer extends Server
System.err.printf("%s#onOpen %s\n",this.getClass().getSimpleName(),connection);
}
public void onError(String message, Throwable ex)
{
if (_verbose)
System.err.printf("%s#onOpen %s\n",this.getClass().getSimpleName(),message);
}
public void onHandshake(FrameConnection connection)
{
if (_verbose)

View File

@ -29,13 +29,6 @@ public interface WebSocket
*/
void onOpen(Connection connection);
/**
* Called when a new websocket connection cannot be created
* @param message The error message
* @param ex The exception or null
*/
void onError(String message, Throwable ex);
/**
* Called when an established websocket connection closes
* @param closeCode
@ -155,7 +148,10 @@ public interface WebSocket
byte textOpcode();
byte continuationOpcode();
byte finMask();
String getProtocol();
void setFakeFragments(boolean fake);
boolean isFakeFragments();
boolean isControl(byte opcode);
boolean isText(byte opcode);
boolean isBinary(byte opcode);

View File

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

View File

@ -3,14 +3,21 @@ package org.eclipse.jetty.websocket;
import java.io.EOFException;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.URI;
import java.nio.channels.ByteChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.nio.channels.UnsupportedAddressTypeException;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.eclipse.jetty.http.HttpFields;
import org.eclipse.jetty.http.HttpParser;
@ -29,150 +36,227 @@ import org.eclipse.jetty.util.component.AggregateLifeCycle;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.eclipse.jetty.util.thread.Timeout;
/* ------------------------------------------------------------ */
/** WebSocket Client
* <p>This WebSocket Client class can create multiple websocket connections to multiple destinations.
* It uses the same {@link WebSocket} endpoint API as the server.
* Simple usage is as follows: <pre>
* WebSocketClient client = new WebSocketClient();
* client.setMaxIdleTime(500);
* client.start();
*
* WebSocket.Connection connection = client.open(new URI("ws://127.0.0.1:8080/"),new WebSocket.OnTextMessage()
* {
* public void onOpen(Connection connection)
* {
* // open notification
* }
*
* public void onClose(int closeCode, String message)
* {
* // close notification
* }
*
* public void onMessage(String data)
* {
* // handle incoming message
* }
* }).get(5,TimeUnit.SECONDS);
*
* connection.sendMessage("Hello World");
* </pre>
*/
public class WebSocketClient extends AggregateLifeCycle
{
private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClient.class.getCanonicalName());
private final static Random __random = new Random();
private final static ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept");
private final WebSocketClient _root;
private final WebSocketClient _parent;
private final ThreadPool _threadPool;
private final Selector _selector=new Selector();
private final Timeout _connectQ=new Timeout();
private int _connectTimeout=30000;
private final WebSocketClientSelector _selector;
private final Map<String,String> _cookies=new ConcurrentHashMap<String, String>();
private final List<String> _extensions=new CopyOnWriteArrayList<String>();
private int _bufferSize=64*1024;
private boolean _blockingConnect=false;
private String _protocol;
private int _maxIdleTime=-1;
private WebSocketBuffers _buffers;
public WebSocketClient(ThreadPool threadpool)
{
_threadPool=threadpool;
addBean(_selector);
addBean(_threadPool);
}
/* ------------------------------------------------------------ */
/** Create a WebSocket Client with default configuration.
*/
public WebSocketClient()
{
this(new QueuedThreadPool());
}
/* ------------------------------------------------------------ */
/** Create a WebSocket Client with shared threadpool.
* @param threadpool
*/
public WebSocketClient(ThreadPool threadpool)
{
_root=this;
_parent=null;
_threadPool=threadpool;
_selector=new WebSocketClientSelector();
addBean(_selector);
addBean(_threadPool);
}
/* ------------------------------------------------------------ */
/** Create a WebSocket Client from another.
* <p>If multiple clients are required so that connections created may have different
* configurations, then it is more efficient to create a client based on another, so
* that the thread pool and IO infrastructure may be shared.
*/
public WebSocketClient(WebSocketClient parent)
{
_root=parent._root;
_parent=parent;
_threadPool=parent._threadPool;
_selector=parent._selector;
_parent.addBean(this);
}
/* ------------------------------------------------------------ */
/**
* Get the selectorManager. Used to configure the manager.
* @return The {@link SelectorManager} instance.
*/
public SelectorManager getSelectorManager()
{
return _selector;
}
/* ------------------------------------------------------------ */
/** Get the ThreadPool.
* <p>Used to set/query the thread pool configuration.
* @return The {@link ThreadPool}
*/
public ThreadPool getThreadPool()
{
return _threadPool;
}
public int getConnectTimeout()
{
return _connectTimeout;
}
public void setConnectTimeout(int connectTimeout)
{
if (isRunning())
throw new IllegalStateException(getState());
_connectTimeout = connectTimeout;
}
/* ------------------------------------------------------------ */
/** Get the maxIdleTime for connections opened by this client.
* @return The maxIdleTime in ms, or -1 if the default from {@link #getSelectorManager()} is used.
*/
public int getMaxIdleTime()
{
return (int)_selector.getMaxIdleTime();
return _maxIdleTime;
}
/* ------------------------------------------------------------ */
/** Set the maxIdleTime for connections opened by this client.
* @param maxIdleTime max idle time in ms
*/
public void setMaxIdleTime(int maxIdleTime)
{
_selector.setMaxIdleTime(maxIdleTime);
_maxIdleTime=maxIdleTime;
}
/* ------------------------------------------------------------ */
/** Get the WebSocket Buffer size for connections opened by this client.
* @return the buffer size in bytes.
*/
public int getBufferSize()
{
return _bufferSize;
}
/* ------------------------------------------------------------ */
/** Set the WebSocket Buffer size for connections opened by this client.
* @param bufferSize the buffer size in bytes.
*/
public void setBufferSize(int bufferSize)
{
if (isRunning())
throw new IllegalStateException(getState());
_bufferSize = bufferSize;
}
public boolean isBlockingConnect()
/* ------------------------------------------------------------ */
/** Get the subprotocol string for connections opened by this client.
* @return The subprotocol
*/
public String getProtocol()
{
return _blockingConnect;
return _protocol;
}
public void setBlockingConnect(boolean blockingConnect)
/* ------------------------------------------------------------ */
/** Set the subprotocol string for connections opened by this client.
* @param protocol The subprotocol
*/
public void setProtocol(String protocol)
{
_blockingConnect = blockingConnect;
_protocol = protocol;
}
@Override
protected void doStart() throws Exception
/* ------------------------------------------------------------ */
public Map<String,String> getCookies()
{
_buffers = new WebSocketBuffers(_bufferSize);
return _cookies;
}
super.doStart();
for (int i=0;i<_selector.getSelectSets();i++)
/* ------------------------------------------------------------ */
public List<String> getExtensions()
{
return _extensions;
}
/* ------------------------------------------------------------ */
/** Open a WebSocket connection.
* Open a websocket connection to the URI and block until the connection is accepted or there is an error.
* @param uri The URI to connect to.
* @param websocket The {@link WebSocket} instance to handle incoming events.
* @param maxConnectTime The interval to wait for a successful connection
* @param units the units of the maxConnectTime
* @return A {@link WebSocket.Connection}
* @throws IOException
* @throws InterruptedException
* @throws TimeoutException
*/
public WebSocket.Connection open(URI uri, WebSocket websocket,long maxConnectTime,TimeUnit units) throws IOException, InterruptedException, TimeoutException
{
try
{
final int id=i;
_threadPool.dispatch(new Runnable(){
public void run()
{
while(isRunning())
{
try
{
_selector.doSelect(id);
}
catch (IOException e)
{
__log.warn(e);
}
}
}
});
return open(uri,websocket).get(maxConnectTime,units);
}
catch (ExecutionException e)
{
Throwable cause = e.getCause();
if (cause instanceof IOException)
throw (IOException)cause;
if (cause instanceof Error)
throw (Error)cause;
if (cause instanceof RuntimeException)
throw (RuntimeException)cause;
throw new RuntimeException(cause);
}
_connectQ.setDuration(_connectTimeout);
_threadPool.dispatch(new Runnable(){
public void run()
{
while(isRunning())
{
try
{
Thread.sleep(200); // TODO configure?
_connectQ.tick(System.currentTimeMillis());
}
catch(Exception e)
{
__log.warn(e);
}
}
}
});
}
public void open(URI uri, WebSocket websocket) throws IOException
{
open(uri,websocket,null,(int)_selector.getMaxIdleTime(),null,null);
}
public void open(URI uri, WebSocket websocket, String protocol,int maxIdleTime) throws IOException
{
open(uri,websocket,protocol,(int)_selector.getMaxIdleTime(),null,null);
}
public void open(URI uri, WebSocket websocket, String protocol,int maxIdleTime,Map<String,String> cookies) throws IOException
{
open(uri,websocket,protocol,(int)_selector.getMaxIdleTime(),cookies,null);
}
public void open(URI uri, WebSocket websocket, String protocol,int maxIdleTime,Map<String,String> cookies,List<String> extensions) throws IOException
/* ------------------------------------------------------------ */
/** Asynchronously open a websocket connection.
* Open a websocket connection and return a {@link Future} to obtain the connection.
* The caller must call {@link Future#get(long, TimeUnit)} if they wish to impose a connect timeout on the open.
*
* @param uri The URI to connect to.
* @param websocket The {@link WebSocket} instance to handle incoming events.
* @return A {@link Future} to the {@link WebSocket.Connection}
* @throws IOException
*/
public Future<WebSocket.Connection> open(URI uri, WebSocket websocket) throws IOException
{
if (!isStarted())
throw new IllegalStateException("!started");
@ -184,40 +268,66 @@ public class WebSocketClient extends AggregateLifeCycle
SocketChannel channel = SocketChannel.open();
channel.socket().setTcpNoDelay(true);
channel.socket().setSoTimeout(getMaxIdleTime());
int maxIdleTime = getMaxIdleTime();
if (maxIdleTime<0)
maxIdleTime=(int)_selector.getMaxIdleTime();
if (maxIdleTime>0)
channel.socket().setSoTimeout(maxIdleTime);
InetSocketAddress address=new InetSocketAddress(uri.getHost(),uri.getPort());
WebSocketHolder holder=new WebSocketHolder(websocket,uri,protocol,maxIdleTime,cookies,extensions,channel);
final WebSocketFuture holder=new WebSocketFuture(websocket,uri,_protocol,maxIdleTime,_cookies,_extensions,channel);
channel.configureBlocking(false);
channel.connect(address);
_selector.register( channel, holder);
return holder;
}
_connectQ.schedule(holder);
boolean thrown=true;
try
@Override
protected void doStart() throws Exception
{
if (_parent!=null && !_parent.isRunning())
throw new IllegalStateException("parent:"+getState());
_buffers = new WebSocketBuffers(_bufferSize);
super.doStart();
// Start a selector and timer if this is the root client
if (_parent==null)
{
if (isBlockingConnect())
for (int i=0;i<_selector.getSelectSets();i++)
{
channel.socket().connect(address,0);
channel.configureBlocking(false);
final int id=i;
_threadPool.dispatch(new Runnable()
{
public void run()
{
while(isRunning())
{
try
{
_selector.doSelect(id);
}
catch (IOException e)
{
__log.warn(e);
}
}
}
});
}
else
{
channel.configureBlocking(false);
channel.connect(address);
}
_selector.register( channel, holder);
thrown=false;
}
finally
{
if (thrown)
holder.cancel();
}
}
class Selector extends SelectorManager
/* ------------------------------------------------------------ */
/** WebSocket Client Selector Manager
*/
class WebSocketClientSelector extends SelectorManager
{
@Override
public boolean dispatch(Runnable task)
@ -234,18 +344,20 @@ public class WebSocketClient extends AggregateLifeCycle
@Override
protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint)
{
WebSocketHolder holder = (WebSocketHolder) endpoint.getSelectionKey().attachment();
WebSocketFuture holder = (WebSocketFuture) endpoint.getSelectionKey().attachment();
return new HandshakeConnection(endpoint,holder);
}
@Override
protected void endPointOpened(SelectChannelEndPoint endpoint)
{
// TODO expose on outer class
}
@Override
protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
{
throw new IllegalStateException();
}
@Override
@ -257,29 +369,34 @@ public class WebSocketClient extends AggregateLifeCycle
@Override
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
{
if (!(attachment instanceof WebSocketHolder))
if (!(attachment instanceof WebSocketFuture))
super.connectionFailed(channel,ex,attachment);
else
{
__log.debug(ex);
WebSocketHolder holder = (WebSocketHolder)attachment;
holder.cancel();
holder.getWebSocket().onError(ex.toString(),ex);
WebSocketFuture holder = (WebSocketFuture)attachment;
holder.handshakeFailed(ex);
}
}
}
/* ------------------------------------------------------------ */
/** Handshake Connection.
* Handles the connection until the handshake succeeds or fails.
*/
class HandshakeConnection extends AbstractConnection
{
private final SelectChannelEndPoint _endp;
private final WebSocketHolder _holder;
private final WebSocketFuture _holder;
private final String _key;
private final HttpParser _parser;
private String _accept;
private String _error;
public HandshakeConnection(SelectChannelEndPoint endpoint, WebSocketHolder holder)
public HandshakeConnection(SelectChannelEndPoint endpoint, WebSocketFuture holder)
{
super(endpoint,System.currentTimeMillis());
_endp=endpoint;
@ -328,15 +445,18 @@ public class WebSocketClient extends AggregateLifeCycle
}
});
String path=_holder.getURI().getPath();
if (path==null || path.length()==0)
path="/";
String request=
"GET "+_holder.getURI().getPath()+" HTTP/1.1\r\n"+
"GET "+path+" HTTP/1.1\r\n"+
"Host: "+holder.getURI().getHost()+":"+_holder.getURI().getPort()+"\r\n"+
"Upgrade: websocket\r\n"+
"Connection: Upgrade\r\n"+
"Sec-WebSocket-Key: "+_key+"\r\n"+
"Sec-WebSocket-Origin: http://example.com\r\n"+
"Sec-WebSocket-Version: 8\r\n";
"Sec-WebSocket-Version: "+WebSocketConnectionD10.VERSION+"\r\n";
if (holder.getProtocol()!=null)
request+="Sec-WebSocket-Protocol: "+holder.getProtocol()+"\r\n";
@ -356,16 +476,16 @@ public class WebSocketClient extends AggregateLifeCycle
try
{
ByteArrayBuffer handshake = new ByteArrayBuffer(request);
Buffer handshake = new ByteArrayBuffer(request,false);
int len=handshake.length();
if (len!=_endp.flush(handshake))
throw new IOException("incomplete");
}
catch(IOException e)
{
__log.debug(e);
_holder.getWebSocket().onError("Handshake failed",e);
holder.handshakeFailed(e);
}
}
public Connection handle() throws IOException
@ -375,8 +495,7 @@ public class WebSocketClient extends AggregateLifeCycle
switch (_parser.parseAvailable())
{
case -1:
_holder.cancel();
_holder.getWebSocket().onError("EOF",new EOFException());
_holder.handshakeFailed(new IOException("Incomplete handshake response"));
return this;
case 0:
return this;
@ -384,25 +503,25 @@ public class WebSocketClient extends AggregateLifeCycle
break;
}
}
if (_error==null && _accept==null)
_error="No Sec-WebSocket-Accept";
else if (_error==null && !WebSocketConnectionD10.hashKey(_key).equals(_accept))
_error="Bad Sec-WebSocket-Accept";
else
if (_error==null)
{
Buffer header=_parser.getHeaderBuffer();
WebSocketConnectionD10 connection = new WebSocketConnectionD10(_holder.getWebSocket(),_endp,_buffers,System.currentTimeMillis(),_holder.getMaxIdleTime(),_holder.getProtocol(),null,10, new WebSocketGeneratorD10.RandomMaskGen());
if (_accept==null)
_error="No Sec-WebSocket-Accept";
else if (!WebSocketConnectionD10.hashKey(_key).equals(_accept))
_error="Bad Sec-WebSocket-Accept";
else
{
Buffer header=_parser.getHeaderBuffer();
WebSocketConnectionD10 connection = new WebSocketConnectionD10(_holder.getWebSocket(),_endp,_buffers,System.currentTimeMillis(),_holder.getMaxIdleTime(),_holder.getProtocol(),null,10, new WebSocketGeneratorD10.RandomMaskGen());
if (header.hasContent())
connection.fillBuffersFrom(header);
_buffers.returnBuffer(header);
if (header.hasContent())
connection.fillBuffersFrom(header);
_buffers.returnBuffer(header);
if (_holder.getWebSocket() instanceof WebSocket.OnFrame)
((WebSocket.OnFrame)_holder.getWebSocket()).onHandshake((WebSocket.FrameConnection)connection.getConnection());
_holder.cancel();
_holder.getWebSocket().onOpen(connection.getConnection());
return connection;
_holder.onConnection(connection);
return connection;
}
}
_endp.close();
@ -421,13 +540,18 @@ public class WebSocketClient extends AggregateLifeCycle
public void closed()
{
_holder.cancel();
_holder.getWebSocket().onError(_error==null?"EOF":_error,null);
if (_error!=null)
_holder.handshakeFailed(new ProtocolException(_error));
else
_holder.handshakeFailed(new EOFException());
}
}
class WebSocketHolder extends Timeout.Task
/* ------------------------------------------------------------ */
/** The Future Websocket Connection.
*/
class WebSocketFuture implements Future<WebSocket.Connection>
{
final WebSocket _websocket;;
final URI _uri;
@ -435,9 +559,13 @@ public class WebSocketClient extends AggregateLifeCycle
final int _maxIdleTime;
final Map<String,String> _cookies;
final List<String> _extensions;
final ByteChannel _channel;
final CountDownLatch _done = new CountDownLatch(1);
public WebSocketHolder(WebSocket websocket, URI uri, String protocol, int maxIdleTime, Map<String,String> cookies,List<String> extensions, ByteChannel channel)
ByteChannel _channel;
WebSocketConnection _connection;
Throwable _exception;
public WebSocketFuture(WebSocket websocket, URI uri, String protocol, int maxIdleTime, Map<String,String> cookies,List<String> extensions, ByteChannel channel)
{
_websocket=websocket;
_uri=uri;
@ -448,6 +576,60 @@ public class WebSocketClient extends AggregateLifeCycle
_channel=channel;
}
public void onConnection(WebSocketConnection connection)
{
try
{
synchronized (this)
{
if (_channel!=null)
_connection=connection;
}
if (_connection!=null)
{
if (_websocket instanceof WebSocket.OnFrame)
((WebSocket.OnFrame)_websocket).onHandshake((WebSocket.FrameConnection)connection.getConnection());
_websocket.onOpen(connection.getConnection());
}
}
finally
{
_done.countDown();
}
}
public void handshakeFailed(Throwable ex)
{
try
{
ByteChannel channel=null;
synchronized (this)
{
if (_channel!=null)
{
channel=_channel;
_channel=null;
_exception=ex;
}
}
if (channel!=null)
{
if (ex instanceof ProtocolException)
closeChannel(channel,WebSocketConnectionD10.CLOSE_PROTOCOL,ex.getMessage());
else
closeChannel(channel,WebSocketConnectionD10.CLOSE_NOCLOSE,ex.getMessage());
}
}
finally
{
_done.countDown();
}
}
public Map<String,String> getCookies()
{
return _cookies;
@ -473,25 +655,115 @@ public class WebSocketClient extends AggregateLifeCycle
return _maxIdleTime;
}
@Override
public void expired()
{
try
{
__log.debug("expired "+this);
getWebSocket().onError("expired",null);
_channel.close();
}
catch(IOException e)
{
__log.ignore(e);
}
}
public String toString()
{
return "[" + _uri + ","+_websocket+"]@"+hashCode();
}
public boolean cancel(boolean mayInterruptIfRunning)
{
try
{
ByteChannel channel=null;
synchronized (this)
{
if (_connection==null && _exception==null && _channel!=null)
{
channel=_channel;
_channel=null;
}
}
if (channel!=null)
{
closeChannel(channel,WebSocketConnectionD10.CLOSE_NOCLOSE,"cancelled");
return true;
}
return false;
}
finally
{
_done.countDown();
}
}
public boolean isCancelled()
{
synchronized (this)
{
return _channel==null && _connection==null;
}
}
public boolean isDone()
{
synchronized (this)
{
return _connection!=null && _exception==null;
}
}
public org.eclipse.jetty.websocket.WebSocket.Connection get() throws InterruptedException, ExecutionException
{
try
{
return get(Long.MAX_VALUE,TimeUnit.SECONDS);
}
catch(TimeoutException e)
{
throw new IllegalStateException("The universe has ended",e);
}
}
public org.eclipse.jetty.websocket.WebSocket.Connection get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
TimeoutException
{
_done.await(timeout,unit);
ByteChannel channel=null;
org.eclipse.jetty.websocket.WebSocket.Connection connection=null;
Throwable exception=null;
synchronized (this)
{
exception=_exception;
if (_connection==null)
{
exception=_exception;
channel=_channel;
_channel=null;
}
else
connection=_connection.getConnection();
}
if (channel!=null)
closeChannel(channel,WebSocketConnectionD10.CLOSE_NOCLOSE,"timeout");
if (exception!=null)
throw new ExecutionException(exception);
if (connection!=null)
return connection;
throw new TimeoutException();
}
private void closeChannel(ByteChannel channel,int code, String message)
{
try
{
_websocket.onClose(code,message);
}
catch(Exception e)
{
__log.warn(e);
}
try
{
channel.close();
}
catch(IOException e)
{
__log.debug(e);
}
}
}
}

View File

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

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

View File

@ -33,10 +33,10 @@ import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
import org.eclipse.jetty.websocket.WebSocket.OnControl;
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
public class WebSocketConnectionD06 extends AbstractConnection implements WebSocketConnection
{
@ -491,6 +491,15 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc
{
return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort();
}
public void setFakeFragments(boolean fake)
{
}
public boolean isFakeFragments()
{
return false;
}
}
/* ------------------------------------------------------------ */

View File

@ -33,10 +33,10 @@ import org.eclipse.jetty.util.B64Code;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
import org.eclipse.jetty.websocket.WebSocket.OnControl;
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
import org.eclipse.jetty.websocket.WebSocketGeneratorD10.MaskGen;
public class WebSocketConnectionD10 extends AbstractConnection implements WebSocketConnection
@ -46,6 +46,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
final static byte OP_BINARY = 0x02;
final static byte OP_EXT_DATA = 0x03;
final static byte OP_CONTROL = 0x08;
final static byte OP_CLOSE = 0x08;
final static byte OP_PING = 0x09;
final static byte OP_PONG = 0x0A;
@ -60,14 +61,18 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
final static int CLOSE_NOCLOSE=1006;
final static int CLOSE_NOTUTF8=1007;
final static int FLAG_FIN=0x8;
final static int VERSION=8;
static boolean isLastFrame(byte flags)
{
return (flags&0x8)!=0;
return (flags&FLAG_FIN)!=0;
}
static boolean isControlFrame(byte opcode)
{
return (opcode&0x8)!=0;
return (opcode&OP_CONTROL)!=0;
}
private final static byte[] MAGIC;
@ -84,9 +89,10 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
private final OnControl _onControl;
private final String _protocol;
private final int _draft;
private final ClassLoader _context;
private int _close;
private boolean _closedIn;
private boolean _closedOut;
private volatile boolean _closedIn;
private volatile boolean _closedOut;
private int _maxTextMessageSize;
private int _maxBinaryMessageSize=-1;
@ -103,12 +109,12 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
}
}
private final WebSocketParser.FrameHandler _frameHandler= new FrameHandlerD07();
private final WebSocketParser.FrameHandler _frameHandler= new WSFrameHandler();
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private final WebSocket.FrameConnection _connection = new FrameConnectionD10();
private final WebSocket.FrameConnection _connection = new WSFrameConnection();
/* ------------------------------------------------------------ */
@ -124,6 +130,8 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
{
super(endpoint,timestamp);
_context=Thread.currentThread().getContextClassLoader();
// TODO - can we use the endpoint idle mechanism?
if (endpoint instanceof AsyncEndPoint)
((AsyncEndPoint)endpoint).cancelIdle();
@ -204,6 +212,9 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
/* ------------------------------------------------------------ */
public Connection handle() throws IOException
{
Thread current = Thread.currentThread();
ClassLoader oldcontext = current.getContextClassLoader();
current.setContextClassLoader(_context);
try
{
// handle the framing protocol
@ -237,6 +248,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
}
finally
{
current.setContextClassLoader(oldcontext);
if (_endp.isOpen())
{
_generator.idle();
@ -339,7 +351,6 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
_close=code;
}
try
{
if (closed)
@ -358,7 +369,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
bytes[0]=(byte)(code/0x100);
bytes[1]=(byte)(code%0x100);
_outbound.addFrame((byte)0x8,WebSocketConnectionD10.OP_CLOSE,bytes,0,bytes.length);
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD10.OP_CLOSE,bytes,0,bytes.length);
}
_outbound.flush();
@ -388,29 +399,29 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class FrameConnectionD10 implements WebSocket.FrameConnection
private class WSFrameConnection implements WebSocket.FrameConnection
{
volatile boolean _disconnecting;
int _maxTextMessage=WebSocketConnectionD10.this._maxTextMessageSize;
int _maxBinaryMessage=WebSocketConnectionD10.this._maxBinaryMessageSize;
/* ------------------------------------------------------------ */
public synchronized void sendMessage(String content) throws IOException
public void sendMessage(String content) throws IOException
{
if (_closedOut)
throw new IOException("closing");
byte[] data = content.getBytes(StringUtil.__UTF8);
_outbound.addFrame((byte)0x8,WebSocketConnectionD10.OP_TEXT,data,0,data.length);
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD10.OP_TEXT,data,0,data.length);
checkWriteable();
_idle.access(_endp);
}
/* ------------------------------------------------------------ */
public synchronized void sendMessage(byte[] content, int offset, int length) throws IOException
public void sendMessage(byte[] content, int offset, int length) throws IOException
{
if (_closedOut)
throw new IOException("closing");
_outbound.addFrame((byte)0x8,WebSocketConnectionD10.OP_BINARY,content,offset,length);
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD10.OP_BINARY,content,offset,length);
checkWriteable();
_idle.access(_endp);
}
@ -430,7 +441,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
{
if (_closedOut)
throw new IOException("closing");
_outbound.addFrame((byte)0x8,ctrl,data,offset,length);
_outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
checkWriteable();
_idle.access(_endp);
}
@ -507,7 +518,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
/* ------------------------------------------------------------ */
public byte finMask()
{
return 0x8;
return FLAG_FIN;
}
/* ------------------------------------------------------------ */
@ -558,6 +569,18 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
close(CLOSE_NORMAL,null);
}
/* ------------------------------------------------------------ */
public void setFakeFragments(boolean fake)
{
_parser.setFakeFragments(fake);
}
/* ------------------------------------------------------------ */
public boolean isFakeFragments()
{
return _parser.isFakeFragments();
}
/* ------------------------------------------------------------ */
public String toString()
{
@ -568,13 +591,13 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class FrameHandlerD07 implements WebSocketParser.FrameHandler
private class WSFrameHandler implements WebSocketParser.FrameHandler
{
private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
private ByteArrayBuffer _aggregate;
private byte _opcode=-1;
public void onFrame(byte flags, byte opcode, Buffer buffer)
public void onFrame(final byte flags, final byte opcode, final Buffer buffer)
{
boolean lastFrame = isLastFrame(flags);
@ -583,176 +606,176 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
// Ignore incoming after a close
if (_closedIn)
return;
}
try
{
byte[] array=buffer.array();
try
// Deliver frame if websocket is a FrameWebSocket
if (_onFrame!=null)
{
byte[] array=buffer.array();
if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
return;
}
// Deliver frame if websocket is a FrameWebSocket
if (_onFrame!=null)
{
if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
return;
}
if (_onControl!=null && isControlFrame(opcode))
{
if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
return;
}
if (_onControl!=null && isControlFrame(opcode))
switch(opcode)
{
case WebSocketConnectionD10.OP_CONTINUATION:
{
if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
return;
}
switch(opcode)
{
case WebSocketConnectionD10.OP_CONTINUATION:
// If text, append to the message buffer
if (_onTextMessage!=null && _opcode==WebSocketConnectionD10.OP_TEXT)
{
// If text, append to the message buffer
if (_opcode==WebSocketConnectionD10.OP_TEXT && _connection.getMaxTextMessageSize()>=0)
if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
{
if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
// If this is the last fragment, deliver the text buffer
if (lastFrame)
{
// If this is the last fragment, deliver the text buffer
if (lastFrame && _onTextMessage!=null)
_opcode=-1;
String msg =_utf8.toString();
_utf8.reset();
_onTextMessage.onMessage(msg);
}
}
else
textMessageTooLarge();
}
if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
{
if (checkBinaryMessageSize(_aggregate.length(),buffer.length()))
{
_aggregate.put(buffer);
// If this is the last fragment, deliver
if (lastFrame && _onBinaryMessage!=null)
{
try
{
_onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
}
finally
{
_opcode=-1;
String msg =_utf8.toString();
_utf8.reset();
_onTextMessage.onMessage(msg);
}
}
else
{
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
_utf8.reset();
_opcode=-1;
}
}
else if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
{
if (_aggregate.space()<_aggregate.length())
{
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
_aggregate.clear();
_opcode=-1;
}
else
{
_aggregate.put(buffer);
// If this is the last fragment, deliver
if (lastFrame && _onBinaryMessage!=null)
{
try
{
_onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
}
finally
{
_opcode=-1;
_aggregate.clear();
}
_aggregate.clear();
}
}
}
break;
}
case WebSocketConnectionD10.OP_PING:
{
Log.debug("PING {}",this);
if (!_closedOut)
_connection.sendControl(WebSocketConnectionD10.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
break;
}
break;
}
case WebSocketConnectionD10.OP_PING:
{
Log.debug("PING {}",this);
if (!_closedOut)
_connection.sendControl(WebSocketConnectionD10.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
break;
}
case WebSocketConnectionD10.OP_PONG:
{
Log.debug("PONG {}",this);
break;
}
case WebSocketConnectionD10.OP_PONG:
{
Log.debug("PONG {}",this);
break;
}
case WebSocketConnectionD10.OP_CLOSE:
case WebSocketConnectionD10.OP_CLOSE:
{
int code=WebSocketConnectionD10.CLOSE_NOCODE;
String message=null;
if (buffer.length()>=2)
{
int code=WebSocketConnectionD10.CLOSE_NOCODE;
String message=null;
if (buffer.length()>=2)
{
code=buffer.array()[buffer.getIndex()]*0x100+buffer.array()[buffer.getIndex()+1];
if (buffer.length()>2)
message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
}
closeIn(code,message);
break;
}
case WebSocketConnectionD10.OP_TEXT:
{
if(_onTextMessage!=null)
code=buffer.array()[buffer.getIndex()]*0x100+buffer.array()[buffer.getIndex()+1];
if (buffer.length()>2)
message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
}
closeIn(code,message);
break;
}
case WebSocketConnectionD10.OP_TEXT:
{
if(_onTextMessage!=null)
{
if (_connection.getMaxTextMessageSize()<=0)
{
// No size limit, so handle only final frames
if (lastFrame)
{
// Deliver the message
_onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
}
else
{
if (_connection.getMaxTextMessageSize()>=0)
{
// If this is a text fragment, append to buffer
if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
_opcode=WebSocketConnectionD10.OP_TEXT;
else
{
_utf8.reset();
_opcode=-1;
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
}
}
}
}
break;
}
default:
{
if (_onBinaryMessage!=null)
else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
{
if (lastFrame)
{
_onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
String msg =_utf8.toString();
_utf8.reset();
_onTextMessage.onMessage(msg);
}
else
{
if (_connection.getMaxBinaryMessageSize()>=0)
{
if (buffer.length()>_connection.getMaxBinaryMessageSize())
{
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
if (_aggregate!=null)
_aggregate.clear();
_opcode=-1;
}
else
{
_opcode=opcode;
if (_aggregate==null)
_aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
_aggregate.put(buffer);
}
}
_opcode=WebSocketConnectionD10.OP_TEXT;
}
}
else
textMessageTooLarge();
}
break;
}
default:
{
if (_onBinaryMessage!=null && checkBinaryMessageSize(0,buffer.length()))
{
if (lastFrame)
{
_onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
}
else if (_connection.getMaxBinaryMessageSize()>=0)
{
_opcode=opcode;
// TODO use a growing buffer rather than a fixed one.
if (_aggregate==null)
_aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
_aggregate.put(buffer);
}
}
}
}
catch(ThreadDeath th)
{
throw th;
}
catch(Throwable th)
{
Log.warn(th);
}
}
catch(ThreadDeath th)
{
throw th;
}
catch(Throwable th)
{
Log.warn(th);
}
}
private boolean checkBinaryMessageSize(int bufferLen, int length)
{
int max = _connection.getMaxBinaryMessageSize();
if (max>0 && (bufferLen+length)>max)
{
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
_opcode=-1;
if (_aggregate!=null)
_aggregate.clear();
return false;
}
return true;
}
private void textMessageTooLarge()
{
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
_opcode=-1;
_utf8.reset();
}
public void close(int code,String message)

View File

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

View File

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

View File

@ -18,8 +18,6 @@ import java.io.IOException;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;

View File

@ -18,8 +18,6 @@ import java.io.IOException;
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.Buffers;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.eclipse.jetty.util.log.Log;
@ -48,7 +46,6 @@ public class WebSocketParserD10 implements WebSocketParser
}
};
private final WebSocketBuffers _buffers;
private final EndPoint _endp;
private final FrameHandler _handler;
@ -63,6 +60,7 @@ public class WebSocketParserD10 implements WebSocketParser
private final byte[] _mask = new byte[4];
private int _m;
private boolean _skip;
private boolean _fakeFragments=true;
/* ------------------------------------------------------------ */
/**
@ -81,6 +79,24 @@ public class WebSocketParserD10 implements WebSocketParser
_state=State.START;
}
/* ------------------------------------------------------------ */
/**
* @return True if fake fragments should be created for frames larger than the buffer.
*/
public boolean isFakeFragments()
{
return _fakeFragments;
}
/* ------------------------------------------------------------ */
/**
* @param fakeFragments True if fake fragments should be created for frames larger than the buffer.
*/
public void setFakeFragments(boolean fakeFragments)
{
_fakeFragments = fakeFragments;
}
/* ------------------------------------------------------------ */
public boolean isBufferEmpty()
{
@ -122,7 +138,33 @@ public class WebSocketParserD10 implements WebSocketParser
// if no space, then the data is too big for buffer
if (_buffer.space() == 0)
{
// Can we send a fake frame?
if (_fakeFragments && _state==State.DATA)
{
Buffer data =_buffer.get(4*(available/4));
_buffer.compact();
if (_masked)
{
if (data.array()==null)
data=_buffer.asMutableBuffer();
byte[] array = data.array();
final int end=data.putIndex();
for (int i=data.getIndex();i<end;i++)
array[i]^=_mask[_m++%4];
}
// System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
events++;
_bytesNeeded-=data.length();
_handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionD10.FLAG_FIN)), _opcode, data);
_opcode=WebSocketConnectionD10.OP_CONTINUATION;
}
if (_buffer.space() == 0)
throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
}
// catch IOExceptions (probably EOF) and try to parse what we have
try
@ -202,7 +244,7 @@ public class WebSocketParserD10 implements WebSocketParser
_length = _length*0x100 + (0xff&b);
if (--_bytesNeeded==0)
{
if (_length>_buffer.capacity())
if (_length>_buffer.capacity() && !_fakeFragments)
{
events++;
_handler.close(WebSocketConnectionD10.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());

View File

@ -2,26 +2,47 @@ package org.eclipse.jetty.websocket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URI;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jetty.util.BlockingArrayQueue;
import org.eclipse.jetty.websocket.WebSocket.Connection;
import org.eclipse.jetty.util.IO;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class WebSocketClientTest
{
private ServerSocket _server;
private int _serverPort;
@Before
public void startServer() throws IOException {
_server = new ServerSocket();
_server.bind(null);
_serverPort = _server.getLocalPort();
}
@After
public void stopServer() throws IOException {
if(_server != null) {
_server.close();
}
}
@Test
public void testBadURL() throws Exception
@ -40,10 +61,6 @@ public class WebSocketClientTest
open.set(true);
}
public void onError(String message, Throwable ex)
{
}
public void onClose(int closeCode, String message)
{}
});
@ -59,289 +76,124 @@ public class WebSocketClientTest
}
@Test
public void testBlockingConnectionRefused() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.start();
client.setBlockingConnect(true);
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean();
try
{
client.open(new URI("ws://127.0.0.1:1"),new WebSocket()
{
public void onOpen(Connection connection)
{
open.set(true);
}
public void onError(String message, Throwable ex)
{
}
public void onClose(int closeCode, String message)
{}
});
Assert.fail();
}
catch(IOException e)
{
bad=true;
}
Assert.assertTrue(bad);
Assert.assertFalse(open.get());
}
@Test
public void testAsyncConnectionRefused() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.setConnectTimeout(1000);
client.start();
client.setBlockingConnect(false);
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean();
final AtomicReference<String> error = new AtomicReference<String>(null);
final AtomicInteger close = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(1);
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:1"),new WebSocket()
{
public void onOpen(Connection connection)
{
open.set(true);
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
}
});
Throwable error=null;
try
{
client.open(new URI("ws://127.0.0.1:1"),new WebSocket()
{
public void onOpen(Connection connection)
{
open.set(true);
latch.countDown();
}
public void onError(String message, Throwable ex)
{
error.set(message);
latch.countDown();
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
latch.countDown();
}
});
future.get(1,TimeUnit.SECONDS);
Assert.fail();
}
catch(IOException e)
catch(ExecutionException e)
{
bad=true;
error=e.getCause();
}
Assert.assertFalse(bad);
Assert.assertFalse(open.get());
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
Assert.assertNotNull(error.get());
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
Assert.assertTrue(error instanceof ConnectException);
}
@Test
public void testBlockingConnectionNotAccepted() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.setConnectTimeout(500);
client.setBlockingConnect(true);
client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicReference<String> error = new AtomicReference<String>(null);
final CountDownLatch latch = new CountDownLatch(1);
try
{
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
{
public void onOpen(Connection connection)
{
latch.countDown();
}
public void onError(String message, Throwable ex)
{
error.set(message);
latch.countDown();
}
public void onClose(int closeCode, String message)
{
latch.countDown();
}
});
}
catch(IOException e)
{
e.printStackTrace();
bad=true;
}
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
Assert.assertTrue(bad||error.get()!=null);
}
@Test
public void testAsyncConnectionNotAccepted() throws Exception
public void testConnectionNotAccepted() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(300);
client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean();
final AtomicReference<String> error = new AtomicReference<String>(null);
final AtomicInteger close = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(1);
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort),new WebSocket()
{
public void onOpen(Connection connection)
{
open.set(true);
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
}
});
Throwable error=null;
try
{
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
{
public void onOpen(Connection connection)
{
open.set(true);
latch.countDown();
}
public void onError(String message, Throwable ex)
{
error.set(message);
latch.countDown();
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
latch.countDown();
}
});
future.get(250,TimeUnit.MILLISECONDS);
Assert.fail();
}
catch(IOException e)
catch(TimeoutException e)
{
bad=true;
error=e;
}
Assert.assertFalse(bad);
Assert.assertFalse(open.get());
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
Assert.assertNotNull(error.get());
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
Assert.assertTrue(error instanceof TimeoutException);
}
@Test
public void testBlockingConnectionTimeout() throws Exception
public void testConnectionTimeout() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.setConnectTimeout(500);
client.setBlockingConnect(true);
client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicReference<String> error = new AtomicReference<String>(null);
final CountDownLatch latch = new CountDownLatch(1);
try
{
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
{
public void onOpen(Connection connection)
{
latch.countDown();
}
public void onError(String message, Throwable ex)
{
error.set(message);
latch.countDown();
}
public void onClose(int closeCode, String message)
{
latch.countDown();
}
});
}
catch(IOException e)
{
e.printStackTrace();
bad=true;
}
Assert.assertNotNull(server.accept());
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
Assert.assertTrue(bad||error.get()!=null);
}
@Test
public void testAsyncConnectionTimeout() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(300);
client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean();
final AtomicReference<String> error = new AtomicReference<String>(null);
final AtomicInteger close = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(1);
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort),new WebSocket()
{
public void onOpen(Connection connection)
{
open.set(true);
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
}
});
Assert.assertNotNull(_server.accept());
Throwable error=null;
try
{
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
{
public void onOpen(Connection connection)
{
open.set(true);
latch.countDown();
}
public void onError(String message, Throwable ex)
{
error.set(message);
latch.countDown();
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
latch.countDown();
}
});
future.get(250,TimeUnit.MILLISECONDS);
Assert.fail();
}
catch(IOException e)
catch(TimeoutException e)
{
bad=true;
error=e;
}
Assert.assertNotNull(server.accept());
Assert.assertFalse(bad);
Assert.assertFalse(open.get());
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
Assert.assertNotNull(error.get());
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
Assert.assertTrue(error instanceof TimeoutException);
}
@ -349,261 +201,160 @@ public class WebSocketClientTest
public void testBadHandshake() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(300);
client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean();
final AtomicReference<String> error = new AtomicReference<String>(null);
final AtomicInteger close = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(1);
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
{
public void onOpen(Connection connection)
{
open.set(true);
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
}
});
Socket connection = _server.accept();
respondToClient(connection, "HTTP/1.1 404 NOT FOUND\r\n\r\n");
Throwable error=null;
try
{
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
{
public void onOpen(Connection connection)
{
open.set(true);
latch.countDown();
}
public void onError(String message, Throwable ex)
{
error.set(message);
latch.countDown();
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
latch.countDown();
}
});
future.get(250,TimeUnit.MILLISECONDS);
Assert.fail();
}
catch(IOException e)
catch(ExecutionException e)
{
bad=true;
error=e.getCause();
}
Socket connection = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
for (String line=in.readLine();line!=null;line=in.readLine())
{
// System.err.println(line);
if (line.length()==0)
break;
}
connection.getOutputStream().write("HTTP/1.1 404 NOT FOUND\r\n\r\n".getBytes());
Assert.assertFalse(bad);
Assert.assertFalse(open.get());
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
Assert.assertNotNull(error.get());
Assert.assertEquals(WebSocketConnectionD10.CLOSE_PROTOCOL,close.get());
Assert.assertTrue(error instanceof IOException);
Assert.assertTrue(error.getMessage().indexOf("404 NOT FOUND")>0);
}
@Test
public void testBadUpgrade() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(10000);
client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean();
final AtomicReference<String> error = new AtomicReference<String>(null);
final AtomicInteger close = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(1);
try
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
{
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
public void onOpen(Connection connection)
{
public void onOpen(Connection connection)
{
open.set(true);
latch.countDown();
}
open.set(true);
}
public void onError(String message, Throwable ex)
{
error.set(message);
latch.countDown();
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
}
});
public void onClose(int closeCode, String message)
{
close.set(closeCode);
latch.countDown();
}
});
}
catch(IOException e)
{
bad=true;
}
Socket connection = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
for (String line=in.readLine();line!=null;line=in.readLine())
{
// System.err.println(line);
if (line.length()==0)
break;
}
connection.getOutputStream().write((
Socket connection = _server.accept();
respondToClient(connection,
"HTTP/1.1 101 Upgrade\r\n" +
"Sec-WebSocket-Accept: rubbish\r\n" +
"\r\n").getBytes());
"\r\n" );
Assert.assertFalse(bad);
Assert.assertFalse(open.get());
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
Assert.assertNotNull(error.get());
}
@Test
public void testUpgrade() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(10000);
client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean();
final AtomicReference<String> error = new AtomicReference<String>(null);
final AtomicInteger close = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(1);
Throwable error=null;
try
{
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
future.get(250,TimeUnit.MILLISECONDS);
Assert.fail();
}
catch(ExecutionException e)
{
error=e.getCause();
}
Assert.assertFalse(open.get());
Assert.assertEquals(WebSocketConnectionD10.CLOSE_PROTOCOL,close.get());
Assert.assertTrue(error instanceof IOException);
Assert.assertTrue(error.getMessage().indexOf("Bad Sec-WebSocket-Accept")>=0);
}
@Test
public void testUpgradeThenTCPClose() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.start();
final AtomicBoolean open = new AtomicBoolean();
final AtomicInteger close = new AtomicInteger();
final CountDownLatch _latch = new CountDownLatch(1);
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
{
public void onOpen(Connection connection)
{
public void onOpen(Connection connection)
{
open.set(true);
latch.countDown();
}
open.set(true);
}
public void onError(String message, Throwable ex)
{
error.set(message);
latch.countDown();
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
_latch.countDown();
}
});
public void onClose(int closeCode, String message)
{
close.set(closeCode);
latch.countDown();
}
});
}
catch(IOException e)
{
bad=true;
}
Assert.assertFalse(bad);
Socket socket = _server.accept();
accept(socket);
String key="not sent";
Socket connection = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
for (String line=in.readLine();line!=null;line=in.readLine())
{
if (line.length()==0)
break;
if (line.startsWith("Sec-WebSocket-Key:"))
key=line.substring(18).trim();
}
connection.getOutputStream().write((
"HTTP/1.1 101 Upgrade\r\n" +
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
"\r\n").getBytes());
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
Assert.assertNull(error.get());
WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
Assert.assertNotNull(connection);
Assert.assertTrue(open.get());
Assert.assertEquals(0,close.get());
socket.close();
_latch.await(10,TimeUnit.SECONDS);
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
}
@Test
public void testIdle() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(10000);
client.setMaxIdleTime(500);
client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean();
final AtomicInteger close = new AtomicInteger();
final CountDownLatch latch = new CountDownLatch(2);
try
final CountDownLatch _latch = new CountDownLatch(1);
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
{
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
public void onOpen(Connection connection)
{
public void onOpen(Connection connection)
{
open.set(true);
latch.countDown();
}
open.set(true);
}
public void onError(String message, Throwable ex)
{
latch.countDown();
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
_latch.countDown();
}
});
public void onClose(int closeCode, String message)
{
close.set(closeCode);
latch.countDown();
}
});
}
catch(IOException e)
{
bad=true;
}
Assert.assertFalse(bad);
Socket socket = _server.accept();
accept(socket);
String key="not sent";
Socket connection = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
for (String line=in.readLine();line!=null;line=in.readLine())
{
if (line.length()==0)
break;
if (line.startsWith("Sec-WebSocket-Key:"))
key=line.substring(18).trim();
}
connection.getOutputStream().write((
"HTTP/1.1 101 Upgrade\r\n" +
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
"\r\n").getBytes());
Assert.assertTrue(latch.await(10,TimeUnit.SECONDS));
WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
Assert.assertNotNull(connection);
Assert.assertTrue(open.get());
Assert.assertEquals(0,close.get());
long start=System.currentTimeMillis();
_latch.await(10,TimeUnit.SECONDS);
Assert.assertTrue(System.currentTimeMillis()-start<5000);
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NORMAL,close.get());
}
@ -612,77 +363,41 @@ public class WebSocketClientTest
public void testNotIdle() throws Exception
{
WebSocketClient client = new WebSocketClient();
client.setBlockingConnect(true);
client.setConnectTimeout(10000);
client.setMaxIdleTime(500);
client.start();
ServerSocket server = new ServerSocket();
server.bind(null);
int port = server.getLocalPort();
boolean bad=false;
final AtomicBoolean open = new AtomicBoolean();
final Exchanger<Integer> close = new Exchanger<Integer>();
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<WebSocket.Connection> connection = new AtomicReference<WebSocket.Connection>();
final AtomicInteger close = new AtomicInteger();
final CountDownLatch _latch = new CountDownLatch(1);
final BlockingQueue<String> queue = new BlockingArrayQueue<String>();
try
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage()
{
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket.OnTextMessage()
public void onOpen(Connection connection)
{
public void onOpen(Connection c)
{
open.set(true);
connection.set(c);
latch.countDown();
}
open.set(true);
}
public void onError(String message, Throwable ex)
{
latch.countDown();
}
public void onClose(int closeCode, String message)
{
close.set(closeCode);
_latch.countDown();
}
public void onClose(int closeCode, String message)
{
try
{
close.exchange(closeCode);
}
catch(InterruptedException ex)
{}
latch.countDown();
}
public void onMessage(String data)
{
queue.add(data);
}
});
public void onMessage(String data)
{
queue.add(data);
}
});
}
catch(IOException e)
{
bad=true;
}
Assert.assertFalse(bad);
Socket socket = _server.accept();
accept(socket);
String key="not sent";
Socket socket = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
for (String line=in.readLine();line!=null;line=in.readLine())
{
if (line.length()==0)
break;
if (line.startsWith("Sec-WebSocket-Key:"))
key=line.substring(18).trim();
}
socket.getOutputStream().write((
"HTTP/1.1 101 Upgrade\r\n" +
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
"\r\n").getBytes());
Assert.assertTrue(latch.await(10,TimeUnit.SECONDS));
WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
Assert.assertNotNull(connection);
Assert.assertTrue(open.get());
Assert.assertEquals(0,close.get());
// Send some messages client to server
byte[] recv = new byte[1024];
@ -690,7 +405,7 @@ public class WebSocketClientTest
for (int i=0;i<10;i++)
{
Thread.sleep(250);
connection.get().sendMessage("Hello");
connection.sendMessage("Hello");
len=socket.getInputStream().read(recv,0,recv.length);
Assert.assertTrue(len>0);
}
@ -706,10 +421,68 @@ public class WebSocketClientTest
Assert.assertEquals("Hi",queue.poll(1,TimeUnit.SECONDS));
}
// Close with code
long start=System.currentTimeMillis();
socket.getOutputStream().write(new byte[]{(byte)0x88, (byte) 0x02, (byte)4, (byte)87 },0,4);
socket.getOutputStream().flush();
Assert.assertEquals(new Integer(1111),close.exchange(null,1,TimeUnit.SECONDS));
_latch.await(10,TimeUnit.SECONDS);
Assert.assertTrue(System.currentTimeMillis()-start<5000);
Assert.assertEquals(1111,close.get());
}
private void respondToClient(Socket connection, String serverResponse) throws IOException
{
InputStream in = null;
InputStreamReader isr = null;
BufferedReader buf = null;
OutputStream out = null;
try {
in = connection.getInputStream();
isr = new InputStreamReader(in);
buf = new BufferedReader(isr);
String line;
while((line = buf.readLine())!=null)
{
// System.err.println(line);
if(line.length() == 0)
{
// Got the "\r\n" line.
break;
}
}
// System.out.println("[Server-Out] " + serverResponse);
out = connection.getOutputStream();
out.write(serverResponse.getBytes());
out.flush();
}
finally
{
IO.close(buf);
IO.close(isr);
IO.close(in);
IO.close(out);
}
}
private void accept(Socket connection) throws IOException
{
String key="not sent";
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
for (String line=in.readLine();line!=null;line=in.readLine())
{
if (line.length()==0)
break;
if (line.startsWith("Sec-WebSocket-Key:"))
key=line.substring(18).trim();
}
connection.getOutputStream().write((
"HTTP/1.1 101 Upgrade\r\n" +
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
"\r\n").getBytes());
}
}

View File

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

View File

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

View File

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

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