Merge remote-tracking branch 'origin/master' into jetty-8
Conflicts: jetty-osgi/jetty-osgi-equinoxtools/src/main/java/org/eclipse/jetty/osgi/equinoxtools/console/EquinoxConsoleWebSocketServlet.java jetty-servlets/src/test/java/org/eclipse/jetty/servlets/GzipFilterTest.java jetty-websocket/pom.xml
|
@ -19,12 +19,18 @@ jetty-7.5.0-SNAPSHOT
|
||||||
+ 352421 HttpURI paths beginning with '.'
|
+ 352421 HttpURI paths beginning with '.'
|
||||||
+ 352684 Implemented spinning thread analyzer
|
+ 352684 Implemented spinning thread analyzer
|
||||||
+ 352786 GzipFilter fails to pass parameters to GzipResponseWrapper
|
+ 352786 GzipFilter fails to pass parameters to GzipResponseWrapper
|
||||||
|
+ 352999 ExpireTest running too long
|
||||||
+ 353073 WebSocketClient
|
+ 353073 WebSocketClient
|
||||||
+ 353095 maven-jetty-plugin: PermGen leak due to javax.el.BeanELResolver
|
+ 353095 maven-jetty-plugin: PermGen leak due to javax.el.BeanELResolver
|
||||||
+ 353165 addJars can follow symbolic link jar files
|
+ 353165 addJars can follow symbolic link jar files
|
||||||
+ 353210 Bundle-Version in o.e.j.o.boot.logback fix
|
+ 353210 Bundle-Version in o.e.j.o.boot.logback fix
|
||||||
+ 353465 JAASLoginService ignores callbackHandlerClass
|
+ 353465 JAASLoginService ignores callbackHandlerClass
|
||||||
|
+ 353563 HttpDestinationQueueTest too slow
|
||||||
|
+ 353862 Improve performance of QuotedStringTokenizer.quote()
|
||||||
|
+ 354014 Content-Length is passed to wrapped response in GZipFilter
|
||||||
|
+ 354204 Charset encodings property file not used
|
||||||
|
+ 354466 Typo in example config of jetty-plus.xml
|
||||||
|
|
||||||
jetty-7.4.4.v20110707 July 7th 2011
|
jetty-7.4.4.v20110707 July 7th 2011
|
||||||
+ 308851 Converted all jetty-client module tests to JUnit 4
|
+ 308851 Converted all jetty-client module tests to JUnit 4
|
||||||
+ 345268 JDBCSessionManager does not work with maxInactiveInterval = -1
|
+ 345268 JDBCSessionManager does not work with maxInactiveInterval = -1
|
||||||
|
|
|
@ -16,7 +16,6 @@ package org.eclipse.jetty.client;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InterruptedIOException;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -105,7 +104,6 @@ public class ExpireTest
|
||||||
exchange.setURL(baseUrl);
|
exchange.setURL(baseUrl);
|
||||||
|
|
||||||
client.send(exchange);
|
client.send(exchange);
|
||||||
Thread.sleep(50);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait to be sure that all exchanges have expired
|
// Wait to be sure that all exchanges have expired
|
||||||
|
|
|
@ -5,25 +5,34 @@ import java.net.Socket;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class HttpDestinationQueueTest
|
public class HttpDestinationQueueTest
|
||||||
{
|
{
|
||||||
|
private static HttpClient _httpClient;
|
||||||
|
private static long _timeout = 200;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeOnce() throws Exception
|
||||||
|
{
|
||||||
|
_httpClient = new HttpClient();
|
||||||
|
_httpClient.setMaxConnectionsPerAddress(1);
|
||||||
|
_httpClient.setMaxQueueSizePerAddress(1);
|
||||||
|
_httpClient.setTimeout(_timeout);
|
||||||
|
_httpClient.start();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDestinationMaxQueueSize() throws Exception
|
public void testDestinationMaxQueueSize() throws Exception
|
||||||
{
|
{
|
||||||
HttpClient client = new HttpClient();
|
|
||||||
client.setMaxConnectionsPerAddress(1);
|
|
||||||
client.setMaxQueueSizePerAddress(1);
|
|
||||||
client.start();
|
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket(0);
|
ServerSocket server = new ServerSocket(0);
|
||||||
|
|
||||||
// This will keep the connection busy
|
// This will keep the connection busy
|
||||||
HttpExchange exchange1 = new HttpExchange();
|
HttpExchange exchange1 = new HttpExchange();
|
||||||
exchange1.setMethod("GET");
|
exchange1.setMethod("GET");
|
||||||
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
||||||
client.send(exchange1);
|
_httpClient.send(exchange1);
|
||||||
|
|
||||||
// Read request so we are sure that this exchange is out of the queue
|
// Read request so we are sure that this exchange is out of the queue
|
||||||
Socket socket = server.accept();
|
Socket socket = server.accept();
|
||||||
|
@ -32,7 +41,7 @@ public class HttpDestinationQueueTest
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int read = socket.getInputStream().read(buffer);
|
int read = socket.getInputStream().read(buffer);
|
||||||
request.append(new String(buffer, 0, read, "UTF-8"));
|
request.append(new String(buffer,0,read,"UTF-8"));
|
||||||
if (request.toString().endsWith("\r\n\r\n"))
|
if (request.toString().endsWith("\r\n\r\n"))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +51,7 @@ public class HttpDestinationQueueTest
|
||||||
HttpExchange exchange2 = new HttpExchange();
|
HttpExchange exchange2 = new HttpExchange();
|
||||||
exchange2.setMethod("GET");
|
exchange2.setMethod("GET");
|
||||||
exchange2.setURL("http://localhost:" + server.getLocalPort() + "/exchange2");
|
exchange2.setURL("http://localhost:" + server.getLocalPort() + "/exchange2");
|
||||||
client.send(exchange2);
|
_httpClient.send(exchange2);
|
||||||
|
|
||||||
// This will be rejected, since the connection is busy and the queue is full
|
// This will be rejected, since the connection is busy and the queue is full
|
||||||
HttpExchange exchange3 = new HttpExchange();
|
HttpExchange exchange3 = new HttpExchange();
|
||||||
|
@ -50,7 +59,7 @@ public class HttpDestinationQueueTest
|
||||||
exchange3.setURL("http://localhost:" + server.getLocalPort() + "/exchange3");
|
exchange3.setURL("http://localhost:" + server.getLocalPort() + "/exchange3");
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.send(exchange3);
|
_httpClient.send(exchange3);
|
||||||
Assert.fail();
|
Assert.fail();
|
||||||
}
|
}
|
||||||
catch (RejectedExecutionException x)
|
catch (RejectedExecutionException x)
|
||||||
|
@ -60,14 +69,14 @@ public class HttpDestinationQueueTest
|
||||||
|
|
||||||
// Send the response to avoid exceptions in the console
|
// Send the response to avoid exceptions in the console
|
||||||
socket.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes("UTF-8"));
|
socket.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes("UTF-8"));
|
||||||
Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange1.waitForDone());
|
Assert.assertEquals(HttpExchange.STATUS_COMPLETED,exchange1.waitForDone());
|
||||||
|
|
||||||
// Be sure that the second exchange can be sent
|
// Be sure that the second exchange can be sent
|
||||||
request.setLength(0);
|
request.setLength(0);
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int read = socket.getInputStream().read(buffer);
|
int read = socket.getInputStream().read(buffer);
|
||||||
request.append(new String(buffer, 0, read, "UTF-8"));
|
request.append(new String(buffer,0,read,"UTF-8"));
|
||||||
if (request.toString().endsWith("\r\n\r\n"))
|
if (request.toString().endsWith("\r\n\r\n"))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -75,31 +84,23 @@ public class HttpDestinationQueueTest
|
||||||
|
|
||||||
socket.getOutputStream().write("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n".getBytes("UTF-8"));
|
socket.getOutputStream().write("HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n".getBytes("UTF-8"));
|
||||||
socket.close();
|
socket.close();
|
||||||
Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange2.waitForDone());
|
Assert.assertEquals(HttpExchange.STATUS_COMPLETED,exchange2.waitForDone());
|
||||||
|
|
||||||
server.close();
|
server.close();
|
||||||
|
|
||||||
client.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDefaultTimeoutIncludesQueuingExchangeExpiresInQueue() throws Exception
|
public void testDefaultTimeoutIncludesQueuingExchangeExpiresInQueue() throws Exception
|
||||||
{
|
{
|
||||||
HttpClient client = new HttpClient();
|
|
||||||
client.setMaxConnectionsPerAddress(1);
|
|
||||||
client.setMaxQueueSizePerAddress(1);
|
|
||||||
long timeout = 1000;
|
|
||||||
client.setTimeout(timeout);
|
|
||||||
client.start();
|
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket(0);
|
ServerSocket server = new ServerSocket(0);
|
||||||
|
|
||||||
// This will keep the connection busy
|
// This will keep the connection busy
|
||||||
HttpExchange exchange1 = new HttpExchange();
|
HttpExchange exchange1 = new HttpExchange();
|
||||||
exchange1.setTimeout(timeout * 3); // Be sure it does not expire
|
exchange1.setTimeout(_timeout * 3); // Be sure it does not expire
|
||||||
exchange1.setMethod("GET");
|
exchange1.setMethod("GET");
|
||||||
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
||||||
client.send(exchange1);
|
_httpClient.send(exchange1);
|
||||||
|
|
||||||
// Read request so we are sure that this exchange is out of the queue
|
// Read request so we are sure that this exchange is out of the queue
|
||||||
Socket socket = server.accept();
|
Socket socket = server.accept();
|
||||||
|
@ -108,7 +109,7 @@ public class HttpDestinationQueueTest
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int read = socket.getInputStream().read(buffer);
|
int read = socket.getInputStream().read(buffer);
|
||||||
request.append(new String(buffer, 0, read, "UTF-8"));
|
request.append(new String(buffer,0,read,"UTF-8"));
|
||||||
if (request.toString().endsWith("\r\n\r\n"))
|
if (request.toString().endsWith("\r\n\r\n"))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -118,39 +119,30 @@ public class HttpDestinationQueueTest
|
||||||
HttpExchange exchange2 = new HttpExchange();
|
HttpExchange exchange2 = new HttpExchange();
|
||||||
exchange2.setMethod("GET");
|
exchange2.setMethod("GET");
|
||||||
exchange2.setURL("http://localhost:" + server.getLocalPort() + "/exchange2");
|
exchange2.setURL("http://localhost:" + server.getLocalPort() + "/exchange2");
|
||||||
client.send(exchange2);
|
_httpClient.send(exchange2);
|
||||||
|
|
||||||
// Wait until the queued exchange times out in the queue
|
// Wait until the queued exchange times out in the queue
|
||||||
Thread.sleep(timeout * 2);
|
Thread.sleep(_timeout * 2);
|
||||||
|
|
||||||
Assert.assertEquals(HttpExchange.STATUS_EXPIRED, exchange2.getStatus());
|
Assert.assertEquals(HttpExchange.STATUS_EXPIRED,exchange2.getStatus());
|
||||||
|
|
||||||
// Send the response to the first exchange to avoid exceptions in the console
|
// Send the response to the first exchange to avoid exceptions in the console
|
||||||
socket.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes("UTF-8"));
|
socket.getOutputStream().write("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n".getBytes("UTF-8"));
|
||||||
Assert.assertEquals(HttpExchange.STATUS_COMPLETED, exchange1.waitForDone());
|
Assert.assertEquals(HttpExchange.STATUS_COMPLETED,exchange1.waitForDone());
|
||||||
socket.close();
|
socket.close();
|
||||||
|
|
||||||
server.close();
|
server.close();
|
||||||
|
|
||||||
client.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDefaultTimeoutIncludesQueuingExchangeExpiresDuringRequest() throws Exception
|
public void testDefaultTimeoutIncludesQueuingExchangeExpiresDuringRequest() throws Exception
|
||||||
{
|
{
|
||||||
HttpClient client = new HttpClient();
|
|
||||||
client.setMaxConnectionsPerAddress(1);
|
|
||||||
client.setMaxQueueSizePerAddress(1);
|
|
||||||
long timeout = 1000;
|
|
||||||
client.setTimeout(timeout);
|
|
||||||
client.start();
|
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket(0);
|
ServerSocket server = new ServerSocket(0);
|
||||||
|
|
||||||
HttpExchange exchange1 = new HttpExchange();
|
HttpExchange exchange1 = new HttpExchange();
|
||||||
exchange1.setMethod("GET");
|
exchange1.setMethod("GET");
|
||||||
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
||||||
client.send(exchange1);
|
_httpClient.send(exchange1);
|
||||||
|
|
||||||
// Read request so we are sure that this exchange is out of the queue
|
// Read request so we are sure that this exchange is out of the queue
|
||||||
Socket socket = server.accept();
|
Socket socket = server.accept();
|
||||||
|
@ -159,32 +151,25 @@ public class HttpDestinationQueueTest
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int read = socket.getInputStream().read(buffer);
|
int read = socket.getInputStream().read(buffer);
|
||||||
request.append(new String(buffer, 0, read, "UTF-8"));
|
request.append(new String(buffer,0,read,"UTF-8"));
|
||||||
if (request.toString().endsWith("\r\n\r\n"))
|
if (request.toString().endsWith("\r\n\r\n"))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Assert.assertTrue(request.toString().contains("exchange1"));
|
Assert.assertTrue(request.toString().contains("exchange1"));
|
||||||
|
|
||||||
// Wait until the exchange times out during the request
|
// Wait until the exchange times out during the request
|
||||||
Thread.sleep(timeout * 2);
|
Thread.sleep(_timeout * 2);
|
||||||
|
|
||||||
Assert.assertEquals(HttpExchange.STATUS_EXPIRED, exchange1.getStatus());
|
Assert.assertEquals(HttpExchange.STATUS_EXPIRED,exchange1.getStatus());
|
||||||
|
|
||||||
socket.close();
|
socket.close();
|
||||||
|
|
||||||
server.close();
|
server.close();
|
||||||
|
|
||||||
client.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testExchangeTimeoutIncludesQueuingExchangeExpiresDuringResponse() throws Exception
|
public void testExchangeTimeoutIncludesQueuingExchangeExpiresDuringResponse() throws Exception
|
||||||
{
|
{
|
||||||
HttpClient client = new HttpClient();
|
|
||||||
client.setMaxConnectionsPerAddress(1);
|
|
||||||
client.setMaxQueueSizePerAddress(1);
|
|
||||||
client.start();
|
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket(0);
|
ServerSocket server = new ServerSocket(0);
|
||||||
|
|
||||||
long timeout = 1000;
|
long timeout = 1000;
|
||||||
|
@ -192,7 +177,7 @@ public class HttpDestinationQueueTest
|
||||||
exchange1.setTimeout(timeout);
|
exchange1.setTimeout(timeout);
|
||||||
exchange1.setMethod("GET");
|
exchange1.setMethod("GET");
|
||||||
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
exchange1.setURL("http://localhost:" + server.getLocalPort() + "/exchange1");
|
||||||
client.send(exchange1);
|
_httpClient.send(exchange1);
|
||||||
|
|
||||||
// Read request so we are sure that this exchange is out of the queue
|
// Read request so we are sure that this exchange is out of the queue
|
||||||
Socket socket = server.accept();
|
Socket socket = server.accept();
|
||||||
|
@ -201,7 +186,7 @@ public class HttpDestinationQueueTest
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int read = socket.getInputStream().read(buffer);
|
int read = socket.getInputStream().read(buffer);
|
||||||
request.append(new String(buffer, 0, read, "UTF-8"));
|
request.append(new String(buffer,0,read,"UTF-8"));
|
||||||
if (request.toString().endsWith("\r\n\r\n"))
|
if (request.toString().endsWith("\r\n\r\n"))
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -213,12 +198,10 @@ public class HttpDestinationQueueTest
|
||||||
// Wait until the exchange times out during the response
|
// Wait until the exchange times out during the response
|
||||||
Thread.sleep(timeout * 2);
|
Thread.sleep(timeout * 2);
|
||||||
|
|
||||||
Assert.assertEquals(HttpExchange.STATUS_EXPIRED, exchange1.getStatus());
|
Assert.assertEquals(HttpExchange.STATUS_EXPIRED,exchange1.getStatus());
|
||||||
|
|
||||||
socket.close();
|
socket.close();
|
||||||
|
|
||||||
server.close();
|
server.close();
|
||||||
|
|
||||||
client.stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -103,14 +103,6 @@ public class WebSocketUpgradeTest
|
||||||
_results.add("clientWS.onMessage");
|
_results.add("clientWS.onMessage");
|
||||||
_results.add(data);
|
_results.add(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
_results.add("clientWS.onError");
|
|
||||||
_results.add(message);
|
|
||||||
_results.add(ex);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -252,19 +244,11 @@ public class WebSocketUpgradeTest
|
||||||
_results.add("serverWS.onMessage");
|
_results.add("serverWS.onMessage");
|
||||||
_results.add(data);
|
_results.add(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
_results.add("serverWS.onError");
|
|
||||||
_results.add(message);
|
|
||||||
_results.add(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public void onClose(int code, String message)
|
public void onClose(int code, String message)
|
||||||
{
|
{
|
||||||
_results.add("onDisconnect");
|
_results.add("onClose");
|
||||||
_webSockets.remove(this);
|
_webSockets.remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,8 @@ public class ScanningAppProviderRuntimeUpdatesTest
|
||||||
// This test will not work on Windows as second war file would
|
// This test will not work on Windows as second war file would
|
||||||
// not be written over the first one because of a file lock
|
// not be written over the first one because of a file lock
|
||||||
Assume.assumeTrue(!OS.IS_WINDOWS);
|
Assume.assumeTrue(!OS.IS_WINDOWS);
|
||||||
|
Assume.assumeTrue(!OS.IS_OSX); // build server has issues with finding itself apparently
|
||||||
|
|
||||||
|
|
||||||
jetty.copyWebapp("foo-webapp-1.war","foo.war");
|
jetty.copyWebapp("foo-webapp-1.war","foo.war");
|
||||||
jetty.copyContext("foo.xml","foo.xml");
|
jetty.copyContext("foo.xml","foo.xml");
|
||||||
|
|
|
@ -293,7 +293,6 @@ public class MimeTypes
|
||||||
case TEXT_XML_8859_1_ORDINAL:
|
case TEXT_XML_8859_1_ORDINAL:
|
||||||
return StringUtil.__ISO_8859_1;
|
return StringUtil.__ISO_8859_1;
|
||||||
|
|
||||||
case TEXT_JSON_ORDINAL:
|
|
||||||
case TEXT_HTML_UTF_8_ORDINAL:
|
case TEXT_HTML_UTF_8_ORDINAL:
|
||||||
case TEXT_PLAIN_UTF_8_ORDINAL:
|
case TEXT_PLAIN_UTF_8_ORDINAL:
|
||||||
case TEXT_XML_UTF_8_ORDINAL:
|
case TEXT_XML_UTF_8_ORDINAL:
|
||||||
|
@ -363,6 +362,7 @@ public class MimeTypes
|
||||||
|
|
||||||
if (state==10)
|
if (state==10)
|
||||||
return CACHE.lookup(value.peek(start,i-start)).toString();
|
return CACHE.lookup(value.peek(start,i-start)).toString();
|
||||||
return null;
|
|
||||||
|
return (String)__encodings.get(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.io.UnsupportedEncodingException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpServletResponseWrapper;
|
import javax.servlet.http.HttpServletResponseWrapper;
|
||||||
|
@ -34,10 +35,13 @@ import org.eclipse.jetty.util.StringUtil;
|
||||||
*/
|
*/
|
||||||
public class GzipResponseWrapper extends HttpServletResponseWrapper
|
public class GzipResponseWrapper extends HttpServletResponseWrapper
|
||||||
{
|
{
|
||||||
|
public static int DEFAULT_BUFFER_SIZE = 8192;
|
||||||
|
public static int DEFAULT_MIN_GZIP_SIZE = 256;
|
||||||
|
|
||||||
private HttpServletRequest _request;
|
private HttpServletRequest _request;
|
||||||
private Set<String> _mimeTypes;
|
private Set<String> _mimeTypes;
|
||||||
private int _bufferSize=8192;
|
private int _bufferSize=DEFAULT_BUFFER_SIZE;
|
||||||
private int _minGzipSize=256;
|
private int _minGzipSize=DEFAULT_MIN_GZIP_SIZE;
|
||||||
|
|
||||||
private PrintWriter _writer;
|
private PrintWriter _writer;
|
||||||
private GzipStream _gzStream;
|
private GzipStream _gzStream;
|
||||||
|
@ -137,12 +141,30 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
|
||||||
* @see javax.servlet.ServletResponseWrapper#setContentLength(int)
|
* @see javax.servlet.ServletResponseWrapper#setContentLength(int)
|
||||||
*/
|
*/
|
||||||
public void setContentLength(int length)
|
public void setContentLength(int length)
|
||||||
|
{
|
||||||
|
setContentLength((long)length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
protected void setContentLength(long length)
|
||||||
{
|
{
|
||||||
_contentLength=length;
|
_contentLength=length;
|
||||||
if (_gzStream!=null)
|
if (_gzStream!=null)
|
||||||
_gzStream.setContentLength(length);
|
_gzStream.setContentLength(length);
|
||||||
|
else if (_noGzip && _contentLength>=0)
|
||||||
|
{
|
||||||
|
HttpServletResponse response = (HttpServletResponse)getResponse();
|
||||||
|
if(_contentLength<Integer.MAX_VALUE)
|
||||||
|
{
|
||||||
|
response.setContentLength((int)_contentLength);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
response.setHeader("Content-Length", Long.toString(_contentLength));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* @see javax.servlet.http.HttpServletResponseWrapper#addHeader(java.lang.String, java.lang.String)
|
* @see javax.servlet.http.HttpServletResponseWrapper#addHeader(java.lang.String, java.lang.String)
|
||||||
|
@ -179,9 +201,7 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
|
||||||
{
|
{
|
||||||
if ("content-length".equalsIgnoreCase(name))
|
if ("content-length".equalsIgnoreCase(name))
|
||||||
{
|
{
|
||||||
_contentLength=Long.parseLong(value);
|
setContentLength(Long.parseLong(value));
|
||||||
if (_gzStream!=null)
|
|
||||||
_gzStream.setContentLength(_contentLength);
|
|
||||||
}
|
}
|
||||||
else if ("content-type".equalsIgnoreCase(name))
|
else if ("content-type".equalsIgnoreCase(name))
|
||||||
{
|
{
|
||||||
|
@ -296,7 +316,10 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
|
||||||
if (_gzStream==null)
|
if (_gzStream==null)
|
||||||
{
|
{
|
||||||
if (getResponse().isCommitted() || _noGzip)
|
if (getResponse().isCommitted() || _noGzip)
|
||||||
|
{
|
||||||
|
setContentLength(_contentLength);
|
||||||
return getResponse().getOutputStream();
|
return getResponse().getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
_gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
|
_gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
|
||||||
}
|
}
|
||||||
|
@ -318,7 +341,10 @@ public class GzipResponseWrapper extends HttpServletResponseWrapper
|
||||||
throw new IllegalStateException("getOutputStream() called");
|
throw new IllegalStateException("getOutputStream() called");
|
||||||
|
|
||||||
if (getResponse().isCommitted() || _noGzip)
|
if (getResponse().isCommitted() || _noGzip)
|
||||||
|
{
|
||||||
|
setContentLength(_contentLength);
|
||||||
return getResponse().getWriter();
|
return getResponse().getWriter();
|
||||||
|
}
|
||||||
|
|
||||||
_gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
|
_gzStream=newGzipStream(_request,(HttpServletResponse)getResponse(),_contentLength,_bufferSize,_minGzipSize);
|
||||||
_writer=newWriter(_gzStream,getCharacterEncoding());
|
_writer=newWriter(_gzStream,getCharacterEncoding());
|
||||||
|
|
|
@ -42,6 +42,7 @@ public class GzipStream extends ServletOutputStream
|
||||||
protected int _bufferSize;
|
protected int _bufferSize;
|
||||||
protected int _minGzipSize;
|
protected int _minGzipSize;
|
||||||
protected long _contentLength;
|
protected long _contentLength;
|
||||||
|
protected boolean _doNotGzip;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new gzip stream.
|
* Instantiates a new gzip stream.
|
||||||
|
@ -77,6 +78,7 @@ public class GzipStream extends ServletOutputStream
|
||||||
if (_gzOut!=null)
|
if (_gzOut!=null)
|
||||||
_response.setHeader("Content-Encoding",null);
|
_response.setHeader("Content-Encoding",null);
|
||||||
_gzOut=null;
|
_gzOut=null;
|
||||||
|
_doNotGzip=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -87,6 +89,13 @@ public class GzipStream extends ServletOutputStream
|
||||||
public void setContentLength(long length)
|
public void setContentLength(long length)
|
||||||
{
|
{
|
||||||
_contentLength=length;
|
_contentLength=length;
|
||||||
|
if (_doNotGzip && length>=0)
|
||||||
|
{
|
||||||
|
if(_contentLength<Integer.MAX_VALUE)
|
||||||
|
_response.setContentLength((int)_contentLength);
|
||||||
|
else
|
||||||
|
_response.setHeader("Content-Length",Long.toString(_contentLength));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -245,14 +254,10 @@ public class GzipStream extends ServletOutputStream
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
if (_out==null || _bOut!=null )
|
if (_out==null || _bOut!=null )
|
||||||
{
|
{
|
||||||
|
_doNotGzip = true;
|
||||||
|
|
||||||
_out=_response.getOutputStream();
|
_out=_response.getOutputStream();
|
||||||
if (_contentLength>=0)
|
setContentLength(_contentLength);
|
||||||
{
|
|
||||||
if(_contentLength<Integer.MAX_VALUE)
|
|
||||||
_response.setContentLength((int)_contentLength);
|
|
||||||
else
|
|
||||||
_response.setHeader("Content-Length",Long.toString(_contentLength));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_bOut!=null)
|
if (_bOut!=null)
|
||||||
_out.write(_bOut.getBuf(),0,_bOut.getCount());
|
_out.write(_bOut.getBuf(),0,_bOut.getCount());
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
text/html = ISO-8859-1
|
text/html = ISO-8859-1
|
||||||
text/plain = US-ASCII
|
text/plain = ISO-8859-1
|
||||||
text/xml = UTF-8
|
text/xml = UTF-8
|
||||||
|
text/json = UTF-8
|
||||||
|
|
|
@ -76,6 +76,19 @@ public class ByteArrayBuffer extends AbstractBuffer
|
||||||
_access=IMMUTABLE;
|
_access=IMMUTABLE;
|
||||||
_string = value;
|
_string = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ByteArrayBuffer(String value,boolean immutable)
|
||||||
|
{
|
||||||
|
super(READWRITE,NON_VOLATILE);
|
||||||
|
_bytes = StringUtil.getBytes(value);
|
||||||
|
setGetIndex(0);
|
||||||
|
setPutIndex(_bytes.length);
|
||||||
|
if (immutable)
|
||||||
|
{
|
||||||
|
_access=IMMUTABLE;
|
||||||
|
_string = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ByteArrayBuffer(String value,String encoding) throws UnsupportedEncodingException
|
public ByteArrayBuffer(String value,String encoding) throws UnsupportedEncodingException
|
||||||
{
|
{
|
||||||
|
|
|
@ -9,3 +9,5 @@ To run ThreadMonitor on a Jetty installation that doesn't include jetty-monitor
|
||||||
java -jar start.jar etc/jetty-monitor.xml
|
java -jar start.jar etc/jetty-monitor.xml
|
||||||
|
|
||||||
If running Jetty on Java VM version 1.5, the -Dcom.sun.management.jmxremote option should be added to the command lines above in order to enable the JMX agent.
|
If running Jetty on Java VM version 1.5, the -Dcom.sun.management.jmxremote option should be added to the command lines above in order to enable the JMX agent.
|
||||||
|
|
||||||
|
In order to log CPU utilization for threads that are above specified threshold, you need to follow instructions inside jetty-monitor.xml configuration file.
|
|
@ -9,6 +9,20 @@
|
||||||
<Set name="scanInterval">2000</Set>
|
<Set name="scanInterval">2000</Set>
|
||||||
<Set name="busyThreshold">90</Set>
|
<Set name="busyThreshold">90</Set>
|
||||||
<Set name="stackDepth">3</Set>
|
<Set name="stackDepth">3</Set>
|
||||||
|
<Set name="trailLength">2</Set>
|
||||||
|
<!-- To enable logging CPU utilization for threads above specified threshold, -->
|
||||||
|
<!-- uncomment the following lines, changing log interval (in milliseconds) -->
|
||||||
|
<!-- and log threshold (in percent) as desired. -->
|
||||||
|
<!--
|
||||||
|
<Set name="logInterval">10000</Arg>
|
||||||
|
<Set name="logThreshold">1</Arg>
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- To enable detail dump of the server whenever a thread is detected as spinning, -->
|
||||||
|
<!-- uncomment the following lines. -->
|
||||||
|
<!--
|
||||||
|
<Set name="dumpable"><Ref id="Server"/></Set>
|
||||||
|
-->
|
||||||
</New>
|
</New>
|
||||||
</Arg>
|
</Arg>
|
||||||
</Call>
|
</Call>
|
||||||
|
|
|
@ -15,15 +15,16 @@
|
||||||
package org.eclipse.jetty.monitor;
|
package org.eclipse.jetty.monitor;
|
||||||
|
|
||||||
import java.lang.management.ManagementFactory;
|
import java.lang.management.ManagementFactory;
|
||||||
import java.lang.management.ThreadInfo;
|
|
||||||
import java.lang.management.ThreadMXBean;
|
import java.lang.management.ThreadMXBean;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||||
|
import org.eclipse.jetty.util.component.Dumpable;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
|
@ -32,17 +33,20 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
||||||
{
|
{
|
||||||
private int _scanInterval;
|
private int _scanInterval;
|
||||||
|
private int _logInterval;
|
||||||
private int _busyThreshold;
|
private int _busyThreshold;
|
||||||
|
private int _logThreshold;
|
||||||
private int _stackDepth;
|
private int _stackDepth;
|
||||||
|
private int _trailLength;
|
||||||
|
|
||||||
private ThreadMXBean _threadBean;
|
private ThreadMXBean _threadBean;
|
||||||
private Method findDeadlockedThreadsMethod;
|
|
||||||
|
|
||||||
private Thread _runner;
|
private Thread _runner;
|
||||||
private Logger _logger;
|
private Logger _logger;
|
||||||
private volatile boolean _done = true;
|
private volatile boolean _done = true;
|
||||||
|
private Dumpable _dumpable;
|
||||||
|
|
||||||
private Map<Long,ExtThreadInfo> _extInfo;
|
private Map<Long,ThreadMonitorInfo> _monitorInfo;
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
|
@ -52,7 +56,32 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
||||||
*/
|
*/
|
||||||
public ThreadMonitor() throws Exception
|
public ThreadMonitor() throws Exception
|
||||||
{
|
{
|
||||||
this(5000, 95, 3);
|
this(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Instantiates a new thread monitor.
|
||||||
|
*
|
||||||
|
* @param intervalMs scan interval
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ThreadMonitor(int intervalMs) throws Exception
|
||||||
|
{
|
||||||
|
this(intervalMs, 95);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Instantiates a new thread monitor.
|
||||||
|
*
|
||||||
|
* @param intervalMs scan interval
|
||||||
|
* @param threshold busy threshold
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ThreadMonitor(int intervalMs, int threshold) throws Exception
|
||||||
|
{
|
||||||
|
this(intervalMs, threshold, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -65,52 +94,195 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public ThreadMonitor(int intervalMs, int threshold, int depth) throws Exception
|
public ThreadMonitor(int intervalMs, int threshold, int depth) throws Exception
|
||||||
|
{
|
||||||
|
this(intervalMs, threshold, depth, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Instantiates a new thread monitor.
|
||||||
|
*
|
||||||
|
* @param intervalMs scan interval
|
||||||
|
* @param threshold busy threshold
|
||||||
|
* @param depth stack compare depth
|
||||||
|
* @param trail length of stack trail
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public ThreadMonitor(int intervalMs, int threshold, int depth, int trail) throws Exception
|
||||||
{
|
{
|
||||||
_scanInterval = intervalMs;
|
_scanInterval = intervalMs;
|
||||||
_busyThreshold = threshold;
|
_busyThreshold = threshold;
|
||||||
_stackDepth = depth;
|
_stackDepth = depth;
|
||||||
|
_trailLength = trail;
|
||||||
|
|
||||||
_logger = Log.getLogger(getClass().getName());
|
_logger = Log.getLogger(ThreadMonitor.class.getName());
|
||||||
_extInfo = new HashMap<Long, ExtThreadInfo>();
|
_monitorInfo = new HashMap<Long, ThreadMonitorInfo>();
|
||||||
|
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Gets the scan interval.
|
||||||
|
*
|
||||||
|
* @return the scan interval
|
||||||
|
*/
|
||||||
public int getScanInterval()
|
public int getScanInterval()
|
||||||
{
|
{
|
||||||
return _scanInterval;
|
return _scanInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Sets the scan interval.
|
||||||
|
*
|
||||||
|
* @param ms the new scan interval
|
||||||
|
*/
|
||||||
public void setScanInterval(int ms)
|
public void setScanInterval(int ms)
|
||||||
{
|
{
|
||||||
_scanInterval = ms;
|
_scanInterval = ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public int getBusyThreshold()
|
/**
|
||||||
|
* Gets the log interval.
|
||||||
|
*
|
||||||
|
* @return the log interval
|
||||||
|
*/
|
||||||
|
public int getLogInterval()
|
||||||
|
{
|
||||||
|
return _logInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Sets the log interval.
|
||||||
|
*
|
||||||
|
* @param ms the new log interval
|
||||||
|
*/
|
||||||
|
public void setLogInterval(int ms)
|
||||||
|
{
|
||||||
|
_logInterval = ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Gets the busy threshold.
|
||||||
|
*
|
||||||
|
* @return the busy threshold
|
||||||
|
*/
|
||||||
|
public int getBusyThreshold()
|
||||||
{
|
{
|
||||||
return _busyThreshold;
|
return _busyThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Sets the busy threshold.
|
||||||
|
*
|
||||||
|
* @param percent the new busy threshold
|
||||||
|
*/
|
||||||
public void setBusyThreshold(int percent)
|
public void setBusyThreshold(int percent)
|
||||||
{
|
{
|
||||||
_busyThreshold = percent;
|
_busyThreshold = percent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Gets the log threshold.
|
||||||
|
*
|
||||||
|
* @return the log threshold
|
||||||
|
*/
|
||||||
|
public int getLogThreshold()
|
||||||
|
{
|
||||||
|
return _logThreshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Sets the log threshold.
|
||||||
|
*
|
||||||
|
* @param percent the new log threshold
|
||||||
|
*/
|
||||||
|
public void setLogThreshold(int percent)
|
||||||
|
{
|
||||||
|
_logThreshold = percent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Gets the stack depth.
|
||||||
|
*
|
||||||
|
* @return the stack depth
|
||||||
|
*/
|
||||||
public int getStackDepth()
|
public int getStackDepth()
|
||||||
{
|
{
|
||||||
return _stackDepth;
|
return _stackDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Sets the stack depth.
|
||||||
|
*
|
||||||
|
* @param stackDepth the new stack depth
|
||||||
|
*/
|
||||||
public void setStackDepth(int stackDepth)
|
public void setStackDepth(int stackDepth)
|
||||||
{
|
{
|
||||||
_stackDepth = stackDepth;
|
_stackDepth = stackDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Sets the stack trace trail length.
|
||||||
|
*
|
||||||
|
* @param trailLength the new trail length
|
||||||
|
*/
|
||||||
|
public void setTrailLength(int trailLength)
|
||||||
|
{
|
||||||
|
_trailLength = trailLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Gets the stack trace trail length.
|
||||||
|
*
|
||||||
|
* @return the trail length
|
||||||
|
*/
|
||||||
|
public int getTrailLength()
|
||||||
|
{
|
||||||
|
return _trailLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Enable logging of CPU usage.
|
||||||
|
*
|
||||||
|
* @param frequencyMs the logging frequency
|
||||||
|
* @param thresholdPercent the logging threshold
|
||||||
|
*/
|
||||||
|
public void logCpuUsage(int frequencyMs, int thresholdPercent)
|
||||||
|
{
|
||||||
|
setLogInterval(frequencyMs);
|
||||||
|
setLogThreshold(thresholdPercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* @return A {@link Dumpable} that is dumped whenever spinning threads are detected
|
||||||
|
*/
|
||||||
|
public Dumpable getDumpable()
|
||||||
|
{
|
||||||
|
return _dumpable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* @param dumpable A {@link Dumpable} that is dumped whenever spinning threads are detected
|
||||||
|
*/
|
||||||
|
public void setDumpable(Dumpable dumpable)
|
||||||
|
{
|
||||||
|
_dumpable = dumpable;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
|
@ -121,6 +293,7 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
||||||
_done = false;
|
_done = false;
|
||||||
|
|
||||||
_runner = new Thread(this);
|
_runner = new Thread(this);
|
||||||
|
_runner.setDaemon(true);
|
||||||
_runner.start();
|
_runner.start();
|
||||||
|
|
||||||
Log.info("Thread Monitor started successfully");
|
Log.info("Thread Monitor started successfully");
|
||||||
|
@ -143,49 +316,6 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
|
||||||
* Initialize JMX objects.
|
|
||||||
*/
|
|
||||||
protected void init()
|
|
||||||
{
|
|
||||||
_threadBean = ManagementFactory.getThreadMXBean();
|
|
||||||
if (_threadBean.isThreadCpuTimeSupported())
|
|
||||||
{
|
|
||||||
_threadBean.setThreadCpuTimeEnabled(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
String versionStr = System.getProperty("java.version");
|
|
||||||
float version = Float.valueOf(versionStr.substring(0,versionStr.lastIndexOf('.')));
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (version < 1.6)
|
|
||||||
{
|
|
||||||
findDeadlockedThreadsMethod = ThreadMXBean.class.getMethod("findMonitorDeadlockedThreads");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
findDeadlockedThreadsMethod = ThreadMXBean.class.getMethod("findDeadlockedThreads");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.debug(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
|
||||||
* Find deadlocked threads.
|
|
||||||
*
|
|
||||||
* @return array of the deadlocked thread ids
|
|
||||||
* @throws Exception the exception
|
|
||||||
*/
|
|
||||||
protected long[] findDeadlockedThreads() throws Exception
|
|
||||||
{
|
|
||||||
return (long[]) findDeadlockedThreadsMethod.invoke(_threadBean,(Object[])null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* Retrieve all avaliable thread ids
|
* Retrieve all avaliable thread ids
|
||||||
|
@ -196,7 +326,7 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
||||||
{
|
{
|
||||||
return _threadBean.getAllThreadIds();
|
return _threadBean.getAllThreadIds();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* Retrieve the cpu time for specified thread.
|
* Retrieve the cpu time for specified thread.
|
||||||
|
@ -211,79 +341,62 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* Retrieve thread info.
|
* Initialize JMX objects.
|
||||||
*
|
|
||||||
* @param id thread id
|
|
||||||
* @param maxDepth maximum stack depth
|
|
||||||
* @return thread info
|
|
||||||
*/
|
*/
|
||||||
protected ThreadInfo getThreadInfo(long id, int maxDepth)
|
protected void init()
|
||||||
{
|
{
|
||||||
return _threadBean.getThreadInfo(id,maxDepth);
|
_threadBean = ManagementFactory.getThreadMXBean();
|
||||||
}
|
if (_threadBean.isThreadCpuTimeSupported())
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
|
||||||
* Output thread info to log.
|
|
||||||
*
|
|
||||||
* @param threads thread info list
|
|
||||||
*/
|
|
||||||
protected void dump(final List<ThreadInfo> threads)
|
|
||||||
{
|
|
||||||
if (threads != null && threads.size() > 0)
|
|
||||||
{
|
{
|
||||||
for (ThreadInfo info : threads)
|
_threadBean.setThreadCpuTimeEnabled(true);
|
||||||
{
|
|
||||||
StringBuffer msg = new StringBuffer();
|
|
||||||
if (info.getLockOwnerId() < 0)
|
|
||||||
{
|
|
||||||
msg.append(String.format("Thread %s[%d] is spinning", info.getThreadName(), info.getThreadId()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
msg.append(String.format("Thread %s[%d] is %s", info.getThreadName(), info.getThreadId(), info.getThreadState()));
|
|
||||||
msg.append(String.format(" on %s owned by %s[%d]", info.getLockName(), info.getLockOwnerName(), info.getLockOwnerId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
_logger.warn(new ThreadMonitorException(msg.toString(), info.getStackTrace()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* @see java.lang.Runnable#run()
|
* @see java.lang.Runnable#run()
|
||||||
*/
|
*/
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
long currTime;
|
// Initialize repeat flag
|
||||||
long lastTime = 0;
|
boolean repeat = false;
|
||||||
|
boolean scanNow, logNow;
|
||||||
|
|
||||||
|
// Set next scan time and log time
|
||||||
|
long nextScanTime = System.currentTimeMillis();
|
||||||
|
long nextLogTime = nextScanTime + _logInterval;
|
||||||
|
|
||||||
while (!_done)
|
while (!_done)
|
||||||
{
|
{
|
||||||
currTime = System.currentTimeMillis();
|
long currTime = System.currentTimeMillis();
|
||||||
if (currTime < lastTime + _scanInterval)
|
scanNow = (currTime > nextScanTime);
|
||||||
|
logNow = (_logInterval > 0 && currTime > nextLogTime);
|
||||||
|
if (repeat || scanNow || logNow)
|
||||||
|
{
|
||||||
|
repeat = collectThreadInfo();
|
||||||
|
logThreadInfo(logNow);
|
||||||
|
|
||||||
|
if (scanNow)
|
||||||
|
{
|
||||||
|
nextScanTime = System.currentTimeMillis() + _scanInterval;
|
||||||
|
}
|
||||||
|
if (logNow)
|
||||||
|
{
|
||||||
|
nextLogTime = System.currentTimeMillis() + _logInterval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep only if not going to repeat scanning immediately
|
||||||
|
if (!repeat)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Thread.sleep(50);
|
Thread.sleep(100);
|
||||||
}
|
}
|
||||||
catch (InterruptedException ex)
|
catch (InterruptedException ex)
|
||||||
{
|
{
|
||||||
Log.ignore(ex);
|
Log.ignore(ex);
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ThreadInfo> threadInfo = new ArrayList<ThreadInfo>();
|
|
||||||
|
|
||||||
findSpinningThreads(threadInfo);
|
|
||||||
findDeadlockedThreads(threadInfo);
|
|
||||||
|
|
||||||
lastTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
if (threadInfo.size() > 0)
|
|
||||||
{
|
|
||||||
dump(threadInfo);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,104 +404,158 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* Find spinning threads.
|
* Collect thread info.
|
||||||
*
|
|
||||||
* @param threadInfo thread info list to add the results
|
|
||||||
* @return thread info list
|
|
||||||
*/
|
*/
|
||||||
private List<ThreadInfo> findSpinningThreads(final List<ThreadInfo> threadInfo)
|
private boolean collectThreadInfo()
|
||||||
{
|
{
|
||||||
if (threadInfo != null)
|
boolean repeat = false;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
try
|
// Retrieve stack traces for all threads at once as it
|
||||||
|
// was proven to be an order of magnitude faster when
|
||||||
|
// retrieving a single thread stack trace.
|
||||||
|
Map<Thread,StackTraceElement[]> all = Thread.getAllStackTraces();
|
||||||
|
|
||||||
|
for (Map.Entry<Thread,StackTraceElement[]> entry : all.entrySet())
|
||||||
{
|
{
|
||||||
long[] allThreadId = getAllThreadIds();
|
Thread thread = entry.getKey();
|
||||||
for (int idx=0; idx < allThreadId.length; idx++)
|
long threadId = thread.getId();
|
||||||
|
|
||||||
|
// Skip our own runner thread
|
||||||
|
if (threadId == _runner.getId())
|
||||||
{
|
{
|
||||||
long currId = allThreadId[idx];
|
continue;
|
||||||
|
}
|
||||||
if (currId == _runner.getId())
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
long currCpuTime = getThreadCpuTime(currId);
|
|
||||||
long currNanoTime = System.nanoTime();
|
|
||||||
|
|
||||||
ExtThreadInfo currExtInfo = _extInfo.get(Long.valueOf(currId));
|
|
||||||
if (currExtInfo != null)
|
|
||||||
{
|
|
||||||
long elapsedCpuTime = currCpuTime - currExtInfo.getLastCpuTime();
|
|
||||||
long elapsedNanoTime = currNanoTime - currExtInfo.getLastSampleTime();
|
|
||||||
|
|
||||||
if (((elapsedCpuTime * 100.0) / elapsedNanoTime) > _busyThreshold)
|
|
||||||
{
|
|
||||||
ThreadInfo currInfo = getThreadInfo(currId, Integer.MAX_VALUE);
|
|
||||||
if (currInfo != null)
|
|
||||||
{
|
|
||||||
StackTraceElement[] lastStackTrace = currExtInfo.getStackTrace();
|
|
||||||
currExtInfo.setStackTrace(currInfo.getStackTrace());
|
|
||||||
|
|
||||||
if (lastStackTrace != null
|
ThreadMonitorInfo currMonitorInfo = _monitorInfo.get(Long.valueOf(threadId));
|
||||||
&& matchStackTraces(lastStackTrace, currInfo.getStackTrace())) {
|
if (currMonitorInfo == null)
|
||||||
threadInfo.add(currInfo);
|
{
|
||||||
}
|
// Create thread info object for a new thread
|
||||||
|
currMonitorInfo = new ThreadMonitorInfo(thread);
|
||||||
|
currMonitorInfo.setStackTrace(entry.getValue());
|
||||||
|
currMonitorInfo.setCpuTime(getThreadCpuTime(threadId));
|
||||||
|
currMonitorInfo.setSampleTime(System.nanoTime());
|
||||||
|
_monitorInfo.put(Long.valueOf(threadId), currMonitorInfo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Update the existing thread info object
|
||||||
|
currMonitorInfo.setStackTrace(entry.getValue());
|
||||||
|
currMonitorInfo.setCpuTime(getThreadCpuTime(threadId));
|
||||||
|
currMonitorInfo.setSampleTime(System.nanoTime());
|
||||||
|
|
||||||
|
// Stack trace count holds logging state
|
||||||
|
int count = currMonitorInfo.getTraceCount();
|
||||||
|
if (count >= 0 && currMonitorInfo.isSpinning())
|
||||||
|
{
|
||||||
|
// Thread was spinning and was logged before
|
||||||
|
if (count < _trailLength)
|
||||||
|
{
|
||||||
|
// Log another stack trace
|
||||||
|
currMonitorInfo.setTraceCount(count+1);
|
||||||
|
repeat = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset spin flag and trace count
|
||||||
|
currMonitorInfo.setSpinning(false);
|
||||||
|
currMonitorInfo.setTraceCount(-1);
|
||||||
|
}
|
||||||
|
if (currMonitorInfo.getCpuUtilization() > _busyThreshold)
|
||||||
|
{
|
||||||
|
// Thread is busy
|
||||||
|
StackTraceElement[] lastStackTrace = currMonitorInfo.getStackTrace();
|
||||||
|
|
||||||
|
if (lastStackTrace != null
|
||||||
|
&& matchStackTraces(lastStackTrace, entry.getValue()))
|
||||||
|
{
|
||||||
|
// Thread is spinning
|
||||||
|
currMonitorInfo.setSpinning(true);
|
||||||
|
if (count < 0)
|
||||||
|
{
|
||||||
|
// Enable logging of spin status and stack traces
|
||||||
|
// only if the incoming trace count is negative
|
||||||
|
// that indicates a new scan for this thread
|
||||||
|
currMonitorInfo.setTraceCount(0);
|
||||||
|
repeat = (_trailLength > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
currExtInfo = new ExtThreadInfo(currId);
|
|
||||||
_extInfo.put(Long.valueOf(currId), currExtInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
currExtInfo.setLastCpuTime(currCpuTime);
|
|
||||||
currExtInfo.setLastSampleTime(currNanoTime);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.debug(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
return threadInfo;
|
{
|
||||||
|
Log.debug(ex);
|
||||||
|
}
|
||||||
|
return repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
protected void logThreadInfo(boolean logAll)
|
||||||
* Find deadlocked threads.
|
|
||||||
*
|
|
||||||
* @param threadInfo thread info list to add the results
|
|
||||||
* @return thread info list
|
|
||||||
*/
|
|
||||||
private List<ThreadInfo> findDeadlockedThreads(final List<ThreadInfo> threadInfo)
|
|
||||||
{
|
{
|
||||||
if (threadInfo != null)
|
if (_monitorInfo.size() > 0)
|
||||||
{
|
{
|
||||||
try
|
// Select thread objects for all live threads
|
||||||
|
long[] running = getAllThreadIds();
|
||||||
|
List<ThreadMonitorInfo> all = new ArrayList<ThreadMonitorInfo>();
|
||||||
|
for (int idx=0; idx<running.length; idx++)
|
||||||
{
|
{
|
||||||
long[] threads = findDeadlockedThreads();
|
ThreadMonitorInfo info = _monitorInfo.get(running[idx]);
|
||||||
if (threads != null && threads.length > 0)
|
if (info != null)
|
||||||
{
|
{
|
||||||
ThreadInfo currInfo;
|
all.add(info);
|
||||||
for (int idx=0; idx < threads.length; idx++)
|
|
||||||
{
|
|
||||||
currInfo = getThreadInfo(threads[idx], Integer.MAX_VALUE);
|
|
||||||
if (currInfo != null)
|
|
||||||
{
|
|
||||||
threadInfo.add(currInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
|
||||||
|
// Sort selected thread objects by their CPU utilization
|
||||||
|
Collections.sort(all, new Comparator<ThreadMonitorInfo>()
|
||||||
{
|
{
|
||||||
Log.debug(ex);
|
/* ------------------------------------------------------------ */
|
||||||
|
public int compare(ThreadMonitorInfo info1, ThreadMonitorInfo info2)
|
||||||
|
{
|
||||||
|
return (int)Math.signum(info2.getCpuUtilization()-info1.getCpuUtilization());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
String format = "Thread '%2$s'[%3$s,id:%1$d,cpu:%4$.2f%%]%5$s";
|
||||||
|
|
||||||
|
// Log thread information for threads that exceed logging threshold
|
||||||
|
// or log spinning threads if their trace count is zero
|
||||||
|
boolean spinning=false;
|
||||||
|
for (ThreadMonitorInfo info : all)
|
||||||
|
{
|
||||||
|
if (logAll && info.getCpuUtilization() > _logThreshold
|
||||||
|
|| info.isSpinning() && info.getTraceCount() == 0)
|
||||||
|
{
|
||||||
|
String message = String.format(format,
|
||||||
|
info.getThreadId(), info.getThreadName(),
|
||||||
|
info.getThreadState(), info.getCpuUtilization(),
|
||||||
|
info.isSpinning() ? " SPINNING" : "");
|
||||||
|
_logger.info(message);
|
||||||
|
spinning=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump info
|
||||||
|
if (spinning && _dumpable!=null)
|
||||||
|
{
|
||||||
|
System.err.println(_dumpable.dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log stack traces for spinning threads with positive trace count
|
||||||
|
for (ThreadMonitorInfo info : all)
|
||||||
|
{
|
||||||
|
if (info.isSpinning() && info.getTraceCount() >= 0)
|
||||||
|
{
|
||||||
|
String message = String.format(format,
|
||||||
|
info.getThreadId(), info.getThreadName(),
|
||||||
|
info.getThreadState(), info.getCpuUtilization(),
|
||||||
|
" STACK TRACE");
|
||||||
|
_logger.warn(new ThreadMonitorException(message, info.getStackTrace()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return threadInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -414,91 +581,4 @@ public class ThreadMonitor extends AbstractLifeCycle implements Runnable
|
||||||
}
|
}
|
||||||
return match;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
private class ExtThreadInfo
|
|
||||||
{
|
|
||||||
private long _threadId;
|
|
||||||
|
|
||||||
private long _lastCpuTime;
|
|
||||||
private long _lastSampleTime;
|
|
||||||
private StackTraceElement[] _stackTrace;
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
public ExtThreadInfo(long threadId)
|
|
||||||
{
|
|
||||||
_threadId = threadId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
|
||||||
* @return thread id associated with the instance
|
|
||||||
*/
|
|
||||||
public long getThreadId()
|
|
||||||
{
|
|
||||||
return _threadId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
|
||||||
* @return the last CPU time of the thread
|
|
||||||
*/
|
|
||||||
public long getLastCpuTime()
|
|
||||||
{
|
|
||||||
return _lastCpuTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
|
||||||
* Set the last CPU time.
|
|
||||||
*
|
|
||||||
* @param lastCpuTime new last CPU time
|
|
||||||
*/
|
|
||||||
public void setLastCpuTime(long lastCpuTime)
|
|
||||||
{
|
|
||||||
this._lastCpuTime = lastCpuTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
|
||||||
* @return the time of last sample
|
|
||||||
*/
|
|
||||||
public long getLastSampleTime()
|
|
||||||
{
|
|
||||||
return _lastSampleTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
|
||||||
* Sets the last sample time.
|
|
||||||
*
|
|
||||||
* @param lastSampleTime the time of last sample
|
|
||||||
*/
|
|
||||||
public void setLastSampleTime(long lastSampleTime)
|
|
||||||
{
|
|
||||||
_lastSampleTime = lastSampleTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
|
||||||
* Gets the stack trace.
|
|
||||||
*
|
|
||||||
* @return the stack trace
|
|
||||||
*/
|
|
||||||
public StackTraceElement[] getStackTrace()
|
|
||||||
{
|
|
||||||
return _stackTrace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
|
||||||
/**
|
|
||||||
* Sets the stack trace.
|
|
||||||
*
|
|
||||||
* @param stackTrace the new stack trace
|
|
||||||
*/
|
|
||||||
public void setStackTrace(StackTraceElement[] stackTrace)
|
|
||||||
{
|
|
||||||
_stackTrace = stackTrace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,198 @@
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) Webtide LLC
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// All rights reserved. This program and the accompanying materials
|
||||||
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
|
// The Eclipse Public License is available at
|
||||||
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
// The Apache License v2.0 is available at
|
||||||
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
|
||||||
|
package org.eclipse.jetty.monitor;
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public class ThreadMonitorInfo
|
||||||
|
{
|
||||||
|
private Thread _thread;
|
||||||
|
private StackTraceElement[] _stackTrace;
|
||||||
|
|
||||||
|
private boolean _threadSpinning = false;
|
||||||
|
private int _traceCount = -1;
|
||||||
|
|
||||||
|
private long _prevCpuTime;
|
||||||
|
private long _prevSampleTime;
|
||||||
|
private long _currCpuTime;
|
||||||
|
private long _currSampleTime;
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Instantiates a new thread monitor info.
|
||||||
|
*
|
||||||
|
* @param threadInfo the thread info
|
||||||
|
*/
|
||||||
|
public ThreadMonitorInfo(Thread thread)
|
||||||
|
{
|
||||||
|
_thread = thread;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* @return Id of the thread
|
||||||
|
*/
|
||||||
|
public long getThreadId()
|
||||||
|
{
|
||||||
|
return _thread.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Gets the thread name.
|
||||||
|
*
|
||||||
|
* @return the thread name
|
||||||
|
*/
|
||||||
|
public String getThreadName()
|
||||||
|
{
|
||||||
|
return _thread.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Gets the thread state.
|
||||||
|
*
|
||||||
|
* @return the thread state
|
||||||
|
*/
|
||||||
|
public String getThreadState()
|
||||||
|
{
|
||||||
|
return _thread.getState().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Gets the stack trace.
|
||||||
|
*
|
||||||
|
* @return the stack trace
|
||||||
|
*/
|
||||||
|
public StackTraceElement[] getStackTrace()
|
||||||
|
{
|
||||||
|
return _stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Sets the stack trace.
|
||||||
|
*
|
||||||
|
* @param stackTrace the new stack trace
|
||||||
|
*/
|
||||||
|
public void setStackTrace(StackTraceElement[] stackTrace)
|
||||||
|
{
|
||||||
|
_stackTrace = stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Checks if is spinning.
|
||||||
|
*
|
||||||
|
* @return true, if is spinning
|
||||||
|
*/
|
||||||
|
public boolean isSpinning()
|
||||||
|
{
|
||||||
|
return _threadSpinning;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Sets the spinning flag.
|
||||||
|
*
|
||||||
|
* @param value the new value
|
||||||
|
*/
|
||||||
|
public void setSpinning(boolean value)
|
||||||
|
{
|
||||||
|
_threadSpinning = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Sets the trace count.
|
||||||
|
*
|
||||||
|
* @param traceCount the new trace count
|
||||||
|
*/
|
||||||
|
public void setTraceCount(int traceCount)
|
||||||
|
{
|
||||||
|
_traceCount = traceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Gets the trace count.
|
||||||
|
*
|
||||||
|
* @return the trace count
|
||||||
|
*/
|
||||||
|
public int getTraceCount()
|
||||||
|
{
|
||||||
|
return _traceCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* @return the CPU time of the thread
|
||||||
|
*/
|
||||||
|
public long getCpuTime()
|
||||||
|
{
|
||||||
|
return _currCpuTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Set the CPU time.
|
||||||
|
*
|
||||||
|
* @param ns new CPU time
|
||||||
|
*/
|
||||||
|
public void setCpuTime(long ns)
|
||||||
|
{
|
||||||
|
_prevCpuTime = _currCpuTime;
|
||||||
|
_currCpuTime = ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* @return the time of sample
|
||||||
|
*/
|
||||||
|
public long getSampleTime()
|
||||||
|
{
|
||||||
|
return _currSampleTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Sets the sample time.
|
||||||
|
*
|
||||||
|
* @param ns the time of sample
|
||||||
|
*/
|
||||||
|
public void setSampleTime(long ns)
|
||||||
|
{
|
||||||
|
_prevSampleTime = _currSampleTime;
|
||||||
|
_currSampleTime = ns;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Gets the CPU utilization.
|
||||||
|
*
|
||||||
|
* @return the CPU utilization percentage
|
||||||
|
*/
|
||||||
|
public float getCpuUtilization()
|
||||||
|
{
|
||||||
|
long elapsedCpuTime = _currCpuTime - _prevCpuTime;
|
||||||
|
long elapsedNanoTime = _currSampleTime - _prevSampleTime;
|
||||||
|
|
||||||
|
return elapsedNanoTime > 0 ? Math.min((elapsedCpuTime * 100.0f) / elapsedNanoTime, 100.0f) : 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,10 +16,13 @@ package org.eclipse.jetty.monitor;
|
||||||
|
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
import java.lang.management.ThreadInfo;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.Random;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.component.Dumpable;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.StdErrLog;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,24 +31,55 @@ import org.junit.Test;
|
||||||
*/
|
*/
|
||||||
public class ThreadMonitorTest
|
public class ThreadMonitorTest
|
||||||
{
|
{
|
||||||
public final static int DURATION=9000;
|
public final static int DURATION=4000;
|
||||||
private AtomicInteger count=new AtomicInteger(0);
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void monitorTest() throws Exception
|
public void monitorTest() throws Exception
|
||||||
{
|
{
|
||||||
|
((StdErrLog)Log.getLogger(ThreadMonitor.class.getName())).setHideStacks(true);
|
||||||
|
((StdErrLog)Log.getLogger(ThreadMonitor.class.getName())).setSource(false);
|
||||||
|
|
||||||
ThreadMonitor monitor = new ThreadMonitor(1000,50,2)
|
final AtomicInteger countLogs=new AtomicInteger(0);
|
||||||
|
final AtomicInteger countSpin=new AtomicInteger(0);
|
||||||
|
|
||||||
|
ThreadMonitor monitor = new ThreadMonitor(1000,50,1,1)
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
protected void dump(List<ThreadInfo> threads)
|
protected void logThreadInfo(boolean logAll)
|
||||||
{
|
{
|
||||||
count.incrementAndGet();
|
if (logAll)
|
||||||
super.dump(threads);
|
countLogs.incrementAndGet();
|
||||||
|
else
|
||||||
|
countSpin.incrementAndGet();
|
||||||
|
super.logThreadInfo(logAll);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
monitor.setDumpable(new Dumpable()
|
||||||
|
{
|
||||||
|
public void dump(Appendable out, String indent) throws IOException
|
||||||
|
{
|
||||||
|
out.append(dump());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String dump()
|
||||||
|
{
|
||||||
|
return "Dump Spinning";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
monitor.logCpuUsage(2000,0);
|
||||||
monitor.start();
|
monitor.start();
|
||||||
|
|
||||||
|
Random rnd = new Random();
|
||||||
|
for (long cnt=0; cnt<100; cnt++)
|
||||||
|
{
|
||||||
|
long value = rnd.nextLong() % 50 + 50;
|
||||||
|
Sleeper sleeper = new Sleeper(value);
|
||||||
|
Thread runner = new Thread(sleeper);
|
||||||
|
runner.setDaemon(true);
|
||||||
|
runner.start();
|
||||||
|
}
|
||||||
|
|
||||||
Spinner spinner = new Spinner();
|
Spinner spinner = new Spinner();
|
||||||
Thread runner = new Thread(spinner);
|
Thread runner = new Thread(spinner);
|
||||||
runner.start();
|
runner.start();
|
||||||
|
@ -55,7 +89,8 @@ public class ThreadMonitorTest
|
||||||
spinner.setDone();
|
spinner.setDone();
|
||||||
monitor.stop();
|
monitor.stop();
|
||||||
|
|
||||||
assertTrue(count.get() >= 2);
|
assertTrue(countLogs.get() >= 1);
|
||||||
|
assertTrue(countSpin.get() >= 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -64,8 +99,6 @@ public class ThreadMonitorTest
|
||||||
private volatile boolean done = false;
|
private volatile boolean done = false;
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public void setDone()
|
public void setDone()
|
||||||
{
|
{
|
||||||
done = true;
|
done = true;
|
||||||
|
@ -91,6 +124,36 @@ public class ThreadMonitorTest
|
||||||
if (result==42)
|
if (result==42)
|
||||||
System.err.println("Bingo!");
|
System.err.println("Bingo!");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Sleeper implements Runnable
|
||||||
|
{
|
||||||
|
private long _value;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
public Sleeper(long value)
|
||||||
|
{
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fn(_value);
|
||||||
|
}
|
||||||
|
catch (InterruptedException e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
public long fn(long value) throws InterruptedException
|
||||||
|
{
|
||||||
|
long result = value > 1 ? fn(value-1) : 1;
|
||||||
|
|
||||||
|
Thread.sleep(50);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<!-- the web.xml file. -->
|
<!-- the web.xml file. -->
|
||||||
<!-- =========================================================== -->
|
<!-- =========================================================== -->
|
||||||
<!--
|
<!--
|
||||||
<Call name="addLoginService">
|
<Call name="addBean">
|
||||||
<Arg>
|
<Arg>
|
||||||
<New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
|
<New class="org.eclipse.jetty.plus.jaas.JAASLoginService">
|
||||||
<Set name="name">xyzrealm</Set>
|
<Set name="name">xyzrealm</Set>
|
||||||
|
|
|
@ -667,8 +667,8 @@ public class Response implements HttpServletResponse
|
||||||
if (encoding==null)
|
if (encoding==null)
|
||||||
{
|
{
|
||||||
/* implementation of educated defaults */
|
/* implementation of educated defaults */
|
||||||
if(_mimeType!=null)
|
if(_cachedMimeType != null)
|
||||||
encoding = null; // TODO getHttpContext().getEncodingByMimeType(_mimeType);
|
encoding = MimeTypes.getCharsetFromContentType(_cachedMimeType);
|
||||||
|
|
||||||
if (encoding==null)
|
if (encoding==null)
|
||||||
encoding = StringUtil.__ISO_8859_1;
|
encoding = StringUtil.__ISO_8859_1;
|
||||||
|
|
|
@ -48,7 +48,7 @@ import org.eclipse.jetty.util.log.Log;
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and CPU cycles. If this handler is used for static content,
|
* Compressing the content can greatly improve the network bandwidth usage, but at a cost of memory and CPU cycles. If this handler is used for static content,
|
||||||
* then use of efficient direct NIO may be prevented, thus use of the gzip mechanism of the {@link org.eclipse.jetty.servlet.DefaultServlet} is advised instead.
|
* then use of efficient direct NIO may be prevented, thus use of the gzip mechanism of the <code>org.eclipse.jetty.servlet.DefaultServlet</code> is advised instead.
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class GzipHandler extends HandlerWrapper
|
public class GzipHandler extends HandlerWrapper
|
||||||
|
|
|
@ -81,7 +81,7 @@ public abstract class AbstractSession implements AbstractSessionManager.SessionI
|
||||||
|
|
||||||
/* ------------------------------------------------------------- */
|
/* ------------------------------------------------------------- */
|
||||||
/**
|
/**
|
||||||
* @return True is the session is invalid or passivated.
|
* asserts that the session is valid
|
||||||
*/
|
*/
|
||||||
protected void checkValid() throws IllegalStateException
|
protected void checkValid() throws IllegalStateException
|
||||||
{
|
{
|
||||||
|
|
|
@ -682,7 +682,7 @@ public class JDBCSessionManager extends AbstractSessionManager
|
||||||
/**
|
/**
|
||||||
* Add a newly created session to our in-memory list for this node and persist it.
|
* Add a newly created session to our in-memory list for this node and persist it.
|
||||||
*
|
*
|
||||||
* @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSessionManager.AbstractSession)
|
* @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void addSession(AbstractSession session)
|
protected void addSession(AbstractSession session)
|
||||||
|
|
|
@ -65,7 +65,7 @@ public abstract class HttpServerTestBase extends HttpServerTestFixture
|
||||||
private static final String REQUEST2_HEADER=
|
private static final String REQUEST2_HEADER=
|
||||||
"POST / HTTP/1.0\n"+
|
"POST / HTTP/1.0\n"+
|
||||||
"Host: localhost\n"+
|
"Host: localhost\n"+
|
||||||
"Content-Type: text/xml\n"+
|
"Content-Type: text/xml;charset=ISO-8859-1\n"+
|
||||||
"Content-Length: ";
|
"Content-Length: ";
|
||||||
private static final String REQUEST2_CONTENT=
|
private static final String REQUEST2_CONTENT=
|
||||||
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+
|
"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n"+
|
||||||
|
|
|
@ -116,11 +116,20 @@ public class ResponseTest
|
||||||
assertEquals("foo2/bar2;charset=ISO-8859-1",response.getContentType());
|
assertEquals("foo2/bar2;charset=ISO-8859-1",response.getContentType());
|
||||||
|
|
||||||
response.recycle();
|
response.recycle();
|
||||||
|
|
||||||
response.setContentType("text/xml;charset=ISO-8859-7");
|
response.setContentType("text/xml;charset=ISO-8859-7");
|
||||||
response.getWriter();
|
response.getWriter();
|
||||||
response.setContentType("text/html;charset=UTF-8");
|
response.setContentType("text/html;charset=UTF-8");
|
||||||
assertEquals("text/html;charset=ISO-8859-7",response.getContentType());
|
assertEquals("text/html;charset=ISO-8859-7",response.getContentType());
|
||||||
|
|
||||||
|
response.recycle();
|
||||||
|
response.setContentType("text/html;charset=US-ASCII");
|
||||||
|
response.getWriter();
|
||||||
|
assertEquals("text/html;charset=US-ASCII",response.getContentType());
|
||||||
|
|
||||||
|
response.recycle();
|
||||||
|
response.setContentType("text/json");
|
||||||
|
response.getWriter();
|
||||||
|
assertEquals("text/json;charset=UTF-8", response.getContentType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -0,0 +1,146 @@
|
||||||
|
package org.eclipse.jetty.servlets;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.Servlet;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.gzip.GzipResponseWrapper;
|
||||||
|
import org.eclipse.jetty.servlet.FilterHolder;
|
||||||
|
import org.eclipse.jetty.servlets.gzip.GzipTester;
|
||||||
|
import org.eclipse.jetty.servlets.gzip.TestServletLengthTypeStreamWrite;
|
||||||
|
import org.eclipse.jetty.servlets.gzip.TestServletStreamLengthTypeWrite;
|
||||||
|
import org.eclipse.jetty.servlets.gzip.TestServletStreamTypeLengthWrite;
|
||||||
|
import org.eclipse.jetty.servlets.gzip.TestServletTypeLengthStreamWrite;
|
||||||
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the GzipFilter support for Content-Length setting variations.
|
||||||
|
*
|
||||||
|
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||||
|
*/
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class GzipFilterContentLengthTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* These are the junit parameters for running this test.
|
||||||
|
* <p>
|
||||||
|
* We have 4 test servlets, that arrange the content-length/content-type/get stream in different orders so as to
|
||||||
|
* simulate the real world scenario that caused the bug in <a
|
||||||
|
* href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||||
|
* <p>
|
||||||
|
* This test case will be run with each entry in the array below as setup parameters for the test case.
|
||||||
|
*
|
||||||
|
* @return the junit parameters
|
||||||
|
*/
|
||||||
|
@Parameters
|
||||||
|
public static List<Object[]> data()
|
||||||
|
{
|
||||||
|
return Arrays.asList(new Object[][]
|
||||||
|
{
|
||||||
|
{ TestServletLengthTypeStreamWrite.class },
|
||||||
|
{ TestServletStreamLengthTypeWrite.class },
|
||||||
|
{ TestServletStreamTypeLengthWrite.class },
|
||||||
|
{ TestServletTypeLengthStreamWrite.class } });
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int LARGE = GzipResponseWrapper.DEFAULT_BUFFER_SIZE * 8;
|
||||||
|
private static final int MEDIUM = GzipResponseWrapper.DEFAULT_BUFFER_SIZE;
|
||||||
|
private static final int SMALL = GzipResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TestingDir testingdir = new TestingDir();
|
||||||
|
|
||||||
|
private Class<? extends Servlet> testServlet;
|
||||||
|
|
||||||
|
public GzipFilterContentLengthTest(Class<? extends Servlet> testServlet)
|
||||||
|
{
|
||||||
|
this.testServlet = testServlet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIsGzipCompressed(Class<? extends Servlet> servletClass, int filesize) throws Exception
|
||||||
|
{
|
||||||
|
GzipTester tester = new GzipTester(testingdir);
|
||||||
|
|
||||||
|
// Test content that is smaller than the buffer.
|
||||||
|
tester.prepareServerFile("file.txt",filesize);
|
||||||
|
|
||||||
|
FilterHolder holder = tester.setContentServlet(servletClass);
|
||||||
|
holder.setInitParameter("mimeTypes","text/plain");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tester.start();
|
||||||
|
tester.assertIsResponseGzipCompressed("file.txt");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
tester.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertIsNotGzipCompressed(Class<? extends Servlet> servletClass, int filesize) throws Exception
|
||||||
|
{
|
||||||
|
GzipTester tester = new GzipTester(testingdir);
|
||||||
|
|
||||||
|
// Test content that is smaller than the buffer.
|
||||||
|
tester.prepareServerFile("file.mp3",filesize);
|
||||||
|
|
||||||
|
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
|
||||||
|
holder.setInitParameter("mimeTypes","text/plain");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tester.start();
|
||||||
|
tester.assertIsResponseNotGzipCompressed("file.mp3",filesize);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
tester.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsGzipCompressedTiny() throws Exception
|
||||||
|
{
|
||||||
|
assertIsGzipCompressed(testServlet,SMALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for Length>Type>Stream>Write problems encountered in GzipFilter
|
||||||
|
*
|
||||||
|
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testIsGzipCompressedMedium() throws Exception
|
||||||
|
{
|
||||||
|
assertIsGzipCompressed(testServlet,MEDIUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for Length>Type>Stream>Write problems encountered in GzipFilter
|
||||||
|
*
|
||||||
|
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testIsGzipCompressedLarge() throws Exception
|
||||||
|
{
|
||||||
|
assertIsGzipCompressed(testServlet,LARGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for Length>Type>Stream>Write problems encountered in GzipFilter
|
||||||
|
*
|
||||||
|
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testIsNotGzipCompressed() throws Exception
|
||||||
|
{
|
||||||
|
assertIsNotGzipCompressed(TestServletLengthTypeStreamWrite.class,LARGE);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package org.eclipse.jetty.servlets;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
|
import org.eclipse.jetty.servlet.FilterHolder;
|
||||||
|
import org.eclipse.jetty.servlets.gzip.GzipTester;
|
||||||
|
import org.eclipse.jetty.toolchain.test.IO;
|
||||||
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link GzipFilter} in combination with {@link DefaultServlet} for
|
||||||
|
* ability to configure {@link GzipFilter} to ignore recompress situations
|
||||||
|
* from upstream.
|
||||||
|
*/
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class GzipFilterDefaultNoRecompressTest
|
||||||
|
{
|
||||||
|
@Parameters
|
||||||
|
public static List<Object[]> data()
|
||||||
|
{
|
||||||
|
return Arrays.asList(new Object[][]
|
||||||
|
{
|
||||||
|
// Some already compressed files
|
||||||
|
{ "test_quotes.gz" },
|
||||||
|
{ "test_quotes.bz2" },
|
||||||
|
{ "test_quotes.zip" },
|
||||||
|
{ "test_quotes.rar" },
|
||||||
|
// Some images (common first)
|
||||||
|
{ "jetty_logo.png" },
|
||||||
|
{ "jetty_logo.gif" },
|
||||||
|
{ "jetty_logo.jpeg" },
|
||||||
|
{ "jetty_logo.jpg" },
|
||||||
|
// Lesser encountered images (usually found being requested from non-browser clients)
|
||||||
|
{ "jetty_logo.bmp" },
|
||||||
|
{ "jetty_logo.tga" },
|
||||||
|
{ "jetty_logo.tif" },
|
||||||
|
{ "jetty_logo.tiff" },
|
||||||
|
{ "jetty_logo.xcf" },
|
||||||
|
{ "jetty_logo.jp2" } });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TestingDir testingdir = new TestingDir();
|
||||||
|
|
||||||
|
private String alreadyCompressedFilename;
|
||||||
|
|
||||||
|
public GzipFilterDefaultNoRecompressTest(String testFilename) {
|
||||||
|
this.alreadyCompressedFilename = testFilename;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore("Cannot find a configuration that would allow this to pass")
|
||||||
|
public void testNotGzipFiltered_Default_AlreadyCompressed() throws Exception
|
||||||
|
{
|
||||||
|
GzipTester tester = new GzipTester(testingdir);
|
||||||
|
|
||||||
|
copyTestFileToServer(alreadyCompressedFilename);
|
||||||
|
|
||||||
|
// Using DefaultServlet, with default GzipFilter setup
|
||||||
|
FilterHolder holder = tester.setContentServlet(DefaultServlet.class);
|
||||||
|
// TODO: find a configuration of the GzipFilter to allow
|
||||||
|
// each of these test cases to pass.
|
||||||
|
|
||||||
|
StringBuilder mimeTypes = new StringBuilder();
|
||||||
|
mimeTypes.append("images/png");
|
||||||
|
mimeTypes.append(",images/jpeg");
|
||||||
|
mimeTypes.append(",images/gif");
|
||||||
|
mimeTypes.append(",images/jp2");
|
||||||
|
|
||||||
|
holder.setInitParameter("mimeTypes", mimeTypes.toString());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tester.start();
|
||||||
|
tester.assertIsResponseNotGzipFiltered(alreadyCompressedFilename,
|
||||||
|
alreadyCompressedFilename + ".sha1");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
tester.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void copyTestFileToServer(String testFilename) throws IOException
|
||||||
|
{
|
||||||
|
File testFile = MavenTestingUtils.getTestResourceFile(testFilename);
|
||||||
|
File outFile = testingdir.getFile(testFilename);
|
||||||
|
IO.copy(testFile,outFile);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package org.eclipse.jetty.servlets;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.gzip.GzipResponseWrapper;
|
||||||
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
|
import org.eclipse.jetty.servlet.FilterHolder;
|
||||||
|
import org.eclipse.jetty.servlets.gzip.GzipTester;
|
||||||
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the GzipFilter support built into the {@link DefaultServlet}
|
||||||
|
*/
|
||||||
|
public class GzipFilterDefaultTest
|
||||||
|
{
|
||||||
|
@Rule
|
||||||
|
public TestingDir testingdir = new TestingDir();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsGzipCompressedTiny() throws Exception
|
||||||
|
{
|
||||||
|
GzipTester tester = new GzipTester(testingdir);
|
||||||
|
|
||||||
|
// Test content that is smaller than the buffer.
|
||||||
|
int filesize = GzipResponseWrapper.DEFAULT_BUFFER_SIZE / 4;
|
||||||
|
tester.prepareServerFile("file.txt",filesize);
|
||||||
|
|
||||||
|
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
|
||||||
|
holder.setInitParameter("mimeTypes","text/plain");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tester.start();
|
||||||
|
tester.assertIsResponseGzipCompressed("file.txt");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
tester.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsGzipCompressedLarge() throws Exception
|
||||||
|
{
|
||||||
|
GzipTester tester = new GzipTester(testingdir);
|
||||||
|
|
||||||
|
// Test content that is smaller than the buffer.
|
||||||
|
int filesize = GzipResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
|
||||||
|
tester.prepareServerFile("file.txt",filesize);
|
||||||
|
|
||||||
|
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
|
||||||
|
holder.setInitParameter("mimeTypes","text/plain");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tester.start();
|
||||||
|
tester.assertIsResponseGzipCompressed("file.txt");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
tester.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIsNotGzipCompressed() throws Exception
|
||||||
|
{
|
||||||
|
GzipTester tester = new GzipTester(testingdir);
|
||||||
|
|
||||||
|
// Test content that is smaller than the buffer.
|
||||||
|
int filesize = GzipResponseWrapper.DEFAULT_BUFFER_SIZE * 4;
|
||||||
|
tester.prepareServerFile("file.mp3",filesize);
|
||||||
|
|
||||||
|
FilterHolder holder = tester.setContentServlet(org.eclipse.jetty.servlet.DefaultServlet.class);
|
||||||
|
holder.setInitParameter("mimeTypes","text/plain");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
tester.start();
|
||||||
|
tester.assertIsResponseNotGzipCompressed("file.mp3", filesize);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
tester.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,115 +0,0 @@
|
||||||
// ========================================================================
|
|
||||||
// Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// All rights reserved. This program and the accompanying materials
|
|
||||||
// are made available under the terms of the Eclipse Public License v1.0
|
|
||||||
// and Apache License v2.0 which accompanies this distribution.
|
|
||||||
// The Eclipse Public License is available at
|
|
||||||
// http://www.eclipse.org/legal/epl-v10.html
|
|
||||||
// The Apache License v2.0 is available at
|
|
||||||
// http://www.opensource.org/licenses/apache2.0.php
|
|
||||||
// You may elect to redistribute this code under either of these licenses.
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
package org.eclipse.jetty.servlets;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.zip.GZIPInputStream;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.io.ByteArrayBuffer;
|
|
||||||
import org.eclipse.jetty.servlet.FilterHolder;
|
|
||||||
import org.eclipse.jetty.testing.HttpTester;
|
|
||||||
import org.eclipse.jetty.testing.ServletTester;
|
|
||||||
import org.eclipse.jetty.toolchain.test.TestingDir;
|
|
||||||
import org.eclipse.jetty.util.IO;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
public class GzipFilterTest
|
|
||||||
{
|
|
||||||
private static String __content =
|
|
||||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc. "+
|
|
||||||
"Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque "+
|
|
||||||
"habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. "+
|
|
||||||
"Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam "+
|
|
||||||
"at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate "+
|
|
||||||
"velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum. "+
|
|
||||||
"Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum "+
|
|
||||||
"eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa "+
|
|
||||||
"sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam "+
|
|
||||||
"consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque. "+
|
|
||||||
"Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse "+
|
|
||||||
"et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.";
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public TestingDir testdir = new TestingDir();
|
|
||||||
|
|
||||||
private ServletTester tester;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception
|
|
||||||
{
|
|
||||||
testdir.ensureEmpty();
|
|
||||||
|
|
||||||
File testFile = testdir.getFile("file.txt");
|
|
||||||
BufferedOutputStream testOut = new BufferedOutputStream(new FileOutputStream(testFile));
|
|
||||||
ByteArrayInputStream testIn = new ByteArrayInputStream(__content.getBytes("ISO8859_1"));
|
|
||||||
IO.copy(testIn,testOut);
|
|
||||||
testOut.close();
|
|
||||||
|
|
||||||
tester=new ServletTester();
|
|
||||||
tester.setContextPath("/context");
|
|
||||||
tester.setResourceBase(testdir.getDir().getCanonicalPath());
|
|
||||||
tester.addServlet(org.eclipse.jetty.servlet.DefaultServlet.class, "/");
|
|
||||||
FilterHolder holder = tester.addFilter(GzipFilter.class,"/*",null);
|
|
||||||
holder.setInitParameter("mimeTypes","text/plain");
|
|
||||||
tester.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void tearDown() throws Exception
|
|
||||||
{
|
|
||||||
tester.stop();
|
|
||||||
IO.delete(testdir.getDir());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGzipFilter() throws Exception
|
|
||||||
{
|
|
||||||
// generated and parsed test
|
|
||||||
HttpTester request = new HttpTester();
|
|
||||||
HttpTester response = new HttpTester();
|
|
||||||
|
|
||||||
request.setMethod("GET");
|
|
||||||
request.setVersion("HTTP/1.0");
|
|
||||||
request.setHeader("Host","tester");
|
|
||||||
request.setHeader("accept-encoding","gzip");
|
|
||||||
request.setURI("/context/file.txt");
|
|
||||||
|
|
||||||
ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
|
|
||||||
ByteArrayBuffer respBuff = tester.getResponses(reqsBuff);
|
|
||||||
response.parse(respBuff.asArray());
|
|
||||||
|
|
||||||
assertTrue(response.getMethod()==null);
|
|
||||||
assertTrue(response.getHeader("Content-Encoding").equalsIgnoreCase("gzip"));
|
|
||||||
assertEquals(HttpServletResponse.SC_OK,response.getStatus());
|
|
||||||
|
|
||||||
InputStream testIn = new GZIPInputStream(new ByteArrayInputStream(response.getContentBytes()));
|
|
||||||
ByteArrayOutputStream testOut = new ByteArrayOutputStream();
|
|
||||||
IO.copy(testIn,testOut);
|
|
||||||
|
|
||||||
assertEquals(__content, testOut.toString("ISO8859_1"));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,371 @@
|
||||||
|
package org.eclipse.jetty.servlets.gzip;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.DigestOutputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
import javax.servlet.DispatcherType;
|
||||||
|
import javax.servlet.Servlet;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.io.ByteArrayBuffer;
|
||||||
|
import org.eclipse.jetty.servlet.FilterHolder;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.eclipse.jetty.servlets.GzipFilter;
|
||||||
|
import org.eclipse.jetty.testing.HttpTester;
|
||||||
|
import org.eclipse.jetty.testing.ServletTester;
|
||||||
|
import org.eclipse.jetty.toolchain.test.IO;
|
||||||
|
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
|
||||||
|
import org.eclipse.jetty.toolchain.test.TestingDir;
|
||||||
|
import org.junit.Assert;
|
||||||
|
|
||||||
|
public class GzipTester
|
||||||
|
{
|
||||||
|
private String encoding = "ISO8859_1";
|
||||||
|
private ServletTester servletTester;
|
||||||
|
private TestingDir testdir;
|
||||||
|
|
||||||
|
public GzipTester(TestingDir testingdir)
|
||||||
|
{
|
||||||
|
this.testdir = testingdir;
|
||||||
|
// Make sure we start with a clean testing directory.
|
||||||
|
this.testdir.ensureEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertIsResponseGzipCompressed(String filename) throws Exception
|
||||||
|
{
|
||||||
|
assertIsResponseGzipCompressed(filename,filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void assertIsResponseGzipCompressed(String requestedFilename, String serverFilename) throws Exception
|
||||||
|
{
|
||||||
|
System.err.printf("[GzipTester] requesting /context/%s%n",requestedFilename);
|
||||||
|
HttpTester request = new HttpTester();
|
||||||
|
HttpTester response = new HttpTester();
|
||||||
|
|
||||||
|
request.setMethod("GET");
|
||||||
|
request.setVersion("HTTP/1.0");
|
||||||
|
request.setHeader("Host","tester");
|
||||||
|
request.setHeader("Accept-Encoding","gzip");
|
||||||
|
request.setURI("/context/" + requestedFilename);
|
||||||
|
|
||||||
|
// Issue the request
|
||||||
|
ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
|
||||||
|
// Collect the response(s)
|
||||||
|
ByteArrayBuffer respBuff = servletTester.getResponses(reqsBuff);
|
||||||
|
response.parse(respBuff.asArray());
|
||||||
|
|
||||||
|
// Assert the response headers
|
||||||
|
Assert.assertThat("Response.method",response.getMethod(),nullValue());
|
||||||
|
Assert.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
|
||||||
|
Assert.assertThat("Response.header[Content-Length]",response.getHeader("Content-Length"),notNullValue());
|
||||||
|
Assert.assertThat("Response.header[Content-Encoding]",response.getHeader("Content-Encoding"),containsString("gzip"));
|
||||||
|
|
||||||
|
// Assert that the decompressed contents are what we expect.
|
||||||
|
File serverFile = testdir.getFile(serverFilename);
|
||||||
|
String expected = IO.readToString(serverFile);
|
||||||
|
String actual = null;
|
||||||
|
|
||||||
|
ByteArrayInputStream bais = null;
|
||||||
|
InputStream in = null;
|
||||||
|
ByteArrayOutputStream out = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bais = new ByteArrayInputStream(response.getContentBytes());
|
||||||
|
in = new GZIPInputStream(bais);
|
||||||
|
out = new ByteArrayOutputStream();
|
||||||
|
IO.copy(in,out);
|
||||||
|
|
||||||
|
actual = out.toString(encoding);
|
||||||
|
Assert.assertEquals("Uncompressed contents",expected,actual);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IO.close(out);
|
||||||
|
IO.close(in);
|
||||||
|
IO.close(bais);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure that the response contains an unfiltered file contents.
|
||||||
|
* <p>
|
||||||
|
* This is used to test exclusions and passthroughs in the GzipFilter.
|
||||||
|
* <p>
|
||||||
|
* An example is to test that it is possible to configure GzipFilter to not recompress content that shouldn't be
|
||||||
|
* compressed by the GzipFilter.
|
||||||
|
*
|
||||||
|
* @param requestedFilename
|
||||||
|
* the filename used to on the GET request,.
|
||||||
|
* @param testResourceSha1Sum
|
||||||
|
* the sha1sum file that contains the SHA1SUM checksum that will be used to verify that the response
|
||||||
|
* contents are what is intended.
|
||||||
|
*/
|
||||||
|
public void assertIsResponseNotGzipFiltered(String requestedFilename, String testResourceSha1Sum) throws Exception
|
||||||
|
{
|
||||||
|
System.err.printf("[GzipTester] requesting /context/%s%n",requestedFilename);
|
||||||
|
HttpTester request = new HttpTester();
|
||||||
|
HttpTester response = new HttpTester();
|
||||||
|
|
||||||
|
request.setMethod("GET");
|
||||||
|
request.setVersion("HTTP/1.0");
|
||||||
|
request.setHeader("Host","tester");
|
||||||
|
request.setHeader("Accept-Encoding","gzip");
|
||||||
|
request.setURI("/context/" + requestedFilename);
|
||||||
|
|
||||||
|
// Issue the request
|
||||||
|
ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
|
||||||
|
// Collect the response(s)
|
||||||
|
ByteArrayBuffer respBuff = servletTester.getResponses(reqsBuff);
|
||||||
|
response.parse(respBuff.asArray());
|
||||||
|
|
||||||
|
dumpHeaders(requestedFilename + " / Response Headers",response);
|
||||||
|
|
||||||
|
// Assert the response headers
|
||||||
|
Assert.assertThat(requestedFilename + " / Response.method",response.getMethod(),nullValue());
|
||||||
|
Assert.assertThat(requestedFilename + " / Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
|
||||||
|
Assert.assertThat(requestedFilename + " / Response.header[Content-Length]",response.getHeader("Content-Length"),notNullValue());
|
||||||
|
Assert.assertThat(requestedFilename + " / Response.header[Content-Encoding] (should not be recompressed by GzipFilter)",
|
||||||
|
response.getHeader("Content-Encoding"),nullValue());
|
||||||
|
Assert.assertThat(requestedFilename + " / Response.header[Content-Type] (should have a Content-Type associated with it)",
|
||||||
|
response.getHeader("Content-Type"),notNullValue());
|
||||||
|
|
||||||
|
ByteArrayInputStream bais = null;
|
||||||
|
DigestOutputStream digester = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA1");
|
||||||
|
bais = new ByteArrayInputStream(response.getContentBytes());
|
||||||
|
digester = new DigestOutputStream(new NoOpOutputStream(),digest);
|
||||||
|
IO.copy(bais,digester);
|
||||||
|
|
||||||
|
String actualSha1Sum = Hex.asHex(digest.digest());
|
||||||
|
String expectedSha1Sum = loadExpectedSha1Sum(testResourceSha1Sum);
|
||||||
|
Assert.assertEquals(requestedFilename + " / SHA1Sum of content",expectedSha1Sum,actualSha1Sum);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IO.close(digester);
|
||||||
|
IO.close(bais);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dumpHeaders(String prefix, HttpTester http)
|
||||||
|
{
|
||||||
|
System.out.println(prefix);
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Enumeration<String> names = http.getHeaderNames();
|
||||||
|
while (names.hasMoreElements())
|
||||||
|
{
|
||||||
|
String name = names.nextElement();
|
||||||
|
String value = http.getHeader(name);
|
||||||
|
System.out.printf(" [%s] = %s%n",name,value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String loadExpectedSha1Sum(String testResourceSha1Sum) throws IOException
|
||||||
|
{
|
||||||
|
File sha1File = MavenTestingUtils.getTestResourceFile(testResourceSha1Sum);
|
||||||
|
String contents = IO.readToString(sha1File);
|
||||||
|
Pattern pat = Pattern.compile("^[0-9A-Fa-f]*");
|
||||||
|
Matcher mat = pat.matcher(contents);
|
||||||
|
Assert.assertTrue("Should have found HEX code in SHA1 file: " + sha1File,mat.find());
|
||||||
|
return mat.group();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that the requested filename results in a properly structured GzipFilter response, where the content is
|
||||||
|
* not compressed, and the content-length is returned appropriately.
|
||||||
|
*
|
||||||
|
* @param filename
|
||||||
|
* the filename used for the request, and also used to compare the response to the server file, assumes
|
||||||
|
* that the file is suitable for {@link Assert#assertEquals(Object, Object)} use. (in other words, the
|
||||||
|
* contents of the file are text)
|
||||||
|
* @param expectedFilesize
|
||||||
|
* the expected filesize to be specified on the Content-Length portion of the response headers. (note:
|
||||||
|
* passing -1 will disable the Content-Length assertion)
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public void assertIsResponseNotGzipCompressed(String filename, int expectedFilesize) throws Exception
|
||||||
|
{
|
||||||
|
System.err.printf("[GzipTester] requesting /context/%s%n",filename);
|
||||||
|
HttpTester request = new HttpTester();
|
||||||
|
HttpTester response = new HttpTester();
|
||||||
|
|
||||||
|
request.setMethod("GET");
|
||||||
|
request.setVersion("HTTP/1.0");
|
||||||
|
request.setHeader("Host","tester");
|
||||||
|
request.setHeader("Accept-Encoding","gzip");
|
||||||
|
request.setURI("/context/" + filename);
|
||||||
|
|
||||||
|
// Issue the request
|
||||||
|
ByteArrayBuffer reqsBuff = new ByteArrayBuffer(request.generate().getBytes());
|
||||||
|
// Collect the response(s)
|
||||||
|
ByteArrayBuffer respBuff = servletTester.getResponses(reqsBuff);
|
||||||
|
response.parse(respBuff.asArray());
|
||||||
|
|
||||||
|
// Assert the response headers
|
||||||
|
Assert.assertThat("Response.method",response.getMethod(),nullValue());
|
||||||
|
Assert.assertThat("Response.status",response.getStatus(),is(HttpServletResponse.SC_OK));
|
||||||
|
if (expectedFilesize != (-1))
|
||||||
|
{
|
||||||
|
Assert.assertThat("Response.header[Content-Length]",response.getHeader("Content-Length"),notNullValue());
|
||||||
|
int serverLength = Integer.parseInt(response.getHeader("Content-Length"));
|
||||||
|
Assert.assertThat("Response.header[Content-Length]",serverLength,is(expectedFilesize));
|
||||||
|
}
|
||||||
|
Assert.assertThat("Response.header[Content-Encoding]",response.getHeader("Content-Encoding"),not(containsString("gzip")));
|
||||||
|
|
||||||
|
// Assert that the contents are what we expect.
|
||||||
|
File serverFile = testdir.getFile(filename);
|
||||||
|
String expected = IO.readToString(serverFile);
|
||||||
|
String actual = null;
|
||||||
|
|
||||||
|
InputStream in = null;
|
||||||
|
ByteArrayOutputStream out = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
in = new ByteArrayInputStream(response.getContentBytes());
|
||||||
|
out = new ByteArrayOutputStream();
|
||||||
|
IO.copy(in,out);
|
||||||
|
|
||||||
|
actual = out.toString(encoding);
|
||||||
|
Assert.assertEquals("Server contents",expected,actual);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IO.close(out);
|
||||||
|
IO.close(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate string content of arbitrary length.
|
||||||
|
*
|
||||||
|
* @param length
|
||||||
|
* the length of the string to generate.
|
||||||
|
* @return the string content.
|
||||||
|
*/
|
||||||
|
public String generateContent(int length)
|
||||||
|
{
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
do
|
||||||
|
{
|
||||||
|
builder.append("Lorem ipsum dolor sit amet, consectetur adipiscing elit. In quis felis nunc.\n");
|
||||||
|
builder.append("Quisque suscipit mauris et ante auctor ornare rhoncus lacus aliquet. Pellentesque\n");
|
||||||
|
builder.append("habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.\n");
|
||||||
|
builder.append("Vestibulum sit amet felis augue, vel convallis dolor. Cras accumsan vehicula diam\n");
|
||||||
|
builder.append("at faucibus. Etiam in urna turpis, sed congue mi. Morbi et lorem eros. Donec vulputate\n");
|
||||||
|
builder.append("velit in risus suscipit lobortis. Aliquam id urna orci, nec sollicitudin ipsum.\n");
|
||||||
|
builder.append("Cras a orci turpis. Donec suscipit vulputate cursus. Mauris nunc tellus, fermentum\n");
|
||||||
|
builder.append("eu auctor ut, mollis at diam. Quisque porttitor ultrices metus, vitae tincidunt massa\n");
|
||||||
|
builder.append("sollicitudin a. Vivamus porttitor libero eget purus hendrerit cursus. Integer aliquam\n");
|
||||||
|
builder.append("consequat mauris quis luctus. Cras enim nibh, dignissim eu faucibus ac, mollis nec neque.\n");
|
||||||
|
builder.append("Aliquam purus mauris, consectetur nec convallis lacinia, porta sed ante. Suspendisse\n");
|
||||||
|
builder.append("et cursus magna. Donec orci enim, molestie a lobortis eu, imperdiet vitae neque.\n");
|
||||||
|
}
|
||||||
|
while (builder.length() < length);
|
||||||
|
|
||||||
|
// Make sure we are exactly at requested length. (truncate the extra)
|
||||||
|
if (builder.length() > length)
|
||||||
|
{
|
||||||
|
builder.setLength(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEncoding()
|
||||||
|
{
|
||||||
|
return encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a file on the server resource path of a specified filename and size.
|
||||||
|
*
|
||||||
|
* @param filename
|
||||||
|
* the filename to create
|
||||||
|
* @param filesize
|
||||||
|
* the file size to create (Note: this isn't suitable for creating large multi-megabyte files)
|
||||||
|
*/
|
||||||
|
public void prepareServerFile(String filename, int filesize) throws IOException
|
||||||
|
{
|
||||||
|
File testFile = testdir.getFile(filename);
|
||||||
|
|
||||||
|
FileOutputStream fos = null;
|
||||||
|
BufferedOutputStream out = null;
|
||||||
|
ByteArrayInputStream in = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
fos = new FileOutputStream(testFile,false);
|
||||||
|
out = new BufferedOutputStream(fos);
|
||||||
|
in = new ByteArrayInputStream(generateContent(filesize).getBytes(encoding));
|
||||||
|
IO.copy(in,out);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IO.close(in);
|
||||||
|
IO.close(out);
|
||||||
|
IO.close(fos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the servlet that provides content for the GzipFilter in being tested.
|
||||||
|
*
|
||||||
|
* @param servletClass
|
||||||
|
* the servlet that will provide content.
|
||||||
|
* @return the FilterHolder for configuring the GzipFilter's initParameters with
|
||||||
|
*/
|
||||||
|
public FilterHolder setContentServlet(Class<? extends Servlet> servletClass) throws IOException
|
||||||
|
{
|
||||||
|
servletTester = new ServletTester();
|
||||||
|
servletTester.setContextPath("/context");
|
||||||
|
servletTester.setResourceBase(testdir.getDir().getCanonicalPath());
|
||||||
|
ServletHolder servletHolder = servletTester.addServlet(servletClass,"/");
|
||||||
|
servletHolder.setInitParameter("baseDir",testdir.getDir().getAbsolutePath());
|
||||||
|
FilterHolder holder = servletTester.addFilter(GzipFilter.class,"/*",EnumSet.allOf(DispatcherType.class));
|
||||||
|
return holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEncoding(String encoding)
|
||||||
|
{
|
||||||
|
this.encoding = encoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void start() throws Exception
|
||||||
|
{
|
||||||
|
Assert.assertThat("No servlet defined yet. Did you use #setContentServlet()?",servletTester,notNullValue());
|
||||||
|
servletTester.dump();
|
||||||
|
servletTester.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop()
|
||||||
|
{
|
||||||
|
// NOTE: Do not cleanup the testdir. Failures can't be diagnosed if you do that.
|
||||||
|
// IO.delete(testdir.getDir()):
|
||||||
|
try
|
||||||
|
{
|
||||||
|
servletTester.stop();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// Don't toss this out into Junit as this would be the last exception
|
||||||
|
// that junit will report as being the cause of the test failure.
|
||||||
|
// when in reality, the earlier setup issue is the real cause.
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package org.eclipse.jetty.servlets.gzip;
|
||||||
|
|
||||||
|
public final class Hex
|
||||||
|
{
|
||||||
|
private static final char[] hexcodes = "0123456789abcdef".toCharArray();
|
||||||
|
|
||||||
|
public static byte[] asByteArray(String id, int size)
|
||||||
|
{
|
||||||
|
if ((id.length() < 0) || (id.length() > (size * 2)))
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException(String.format("Invalid ID length of <%d> expected range of <0> to <%d>",id.length(),(size * 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
byte buf[] = new byte[size];
|
||||||
|
byte hex;
|
||||||
|
int len = id.length();
|
||||||
|
|
||||||
|
int idx = (int)Math.floor(((size * 2) - (double)len) / 2);
|
||||||
|
int i = 0;
|
||||||
|
if ((len % 2) != 0)
|
||||||
|
{ // deal with odd numbered chars
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < len; i++)
|
||||||
|
{
|
||||||
|
hex = 0;
|
||||||
|
if (i >= 0)
|
||||||
|
{
|
||||||
|
hex = (byte)(Character.digit(id.charAt(i),16) << 4);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
hex += (byte)(Character.digit(id.charAt(i),16));
|
||||||
|
|
||||||
|
buf[idx] = hex;
|
||||||
|
idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String asHex(byte buf[])
|
||||||
|
{
|
||||||
|
int len = buf.length;
|
||||||
|
char out[] = new char[len * 2];
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
out[i * 2] = hexcodes[(buf[i] & 0xF0) >> 4];
|
||||||
|
out[(i * 2) + 1] = hexcodes[(buf[i] & 0x0F)];
|
||||||
|
}
|
||||||
|
return String.valueOf(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Hex()
|
||||||
|
{
|
||||||
|
/* prevent instantiation */
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package org.eclipse.jetty.servlets.gzip;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stream that does nothing. (Used by SHA1SUM routines)
|
||||||
|
*/
|
||||||
|
public class NoOpOutputStream extends OutputStream
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException
|
||||||
|
{
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() throws IOException
|
||||||
|
{
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b) throws IOException
|
||||||
|
{
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] b, int off, int len) throws IOException
|
||||||
|
{
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(int b) throws IOException
|
||||||
|
{
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package org.eclipse.jetty.servlets.gzip;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletConfig;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServlet;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.toolchain.test.PathAssert;
|
||||||
|
import org.eclipse.jetty.util.IO;
|
||||||
|
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class TestDirContentServlet extends HttpServlet
|
||||||
|
{
|
||||||
|
private File basedir;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(ServletConfig config) throws ServletException
|
||||||
|
{
|
||||||
|
basedir = new File(config.getInitParameter("baseDir"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getTestFile(String filename)
|
||||||
|
{
|
||||||
|
File testfile = new File(basedir,filename);
|
||||||
|
PathAssert.assertFileExists("Content File should exist",testfile);
|
||||||
|
return testfile;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] loadContentFileBytes(final String fileName) throws IOException
|
||||||
|
{
|
||||||
|
String relPath = fileName;
|
||||||
|
relPath = relPath.replaceFirst("^/context/","");
|
||||||
|
relPath = relPath.replaceFirst("^/","");
|
||||||
|
|
||||||
|
File contentFile = getTestFile(relPath);
|
||||||
|
|
||||||
|
FileInputStream in = null;
|
||||||
|
ByteArrayOutputStream out = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
in = new FileInputStream(contentFile);
|
||||||
|
out = new ByteArrayOutputStream();
|
||||||
|
IO.copy(in,out);
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IO.close(out);
|
||||||
|
IO.close(in);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.eclipse.jetty.servlets.gzip;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.servlets.GzipFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sample servlet to serve static content, using a order of construction that has caused problems for
|
||||||
|
* {@link GzipFilter} in the past.
|
||||||
|
*
|
||||||
|
* Using a real-world pattern of:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1) set content length
|
||||||
|
* 2) set content type
|
||||||
|
* 3) get stream
|
||||||
|
* 4) write
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class TestServletLengthTypeStreamWrite extends TestDirContentServlet
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||||
|
{
|
||||||
|
String fileName = request.getServletPath();
|
||||||
|
byte[] dataBytes = loadContentFileBytes(fileName);
|
||||||
|
|
||||||
|
response.setContentLength(dataBytes.length);
|
||||||
|
|
||||||
|
if (fileName.endsWith("txt"))
|
||||||
|
response.setContentType("text/plain");
|
||||||
|
else if (fileName.endsWith("mp3"))
|
||||||
|
response.setContentType("audio/mpeg");
|
||||||
|
|
||||||
|
ServletOutputStream out = response.getOutputStream();
|
||||||
|
out.write(dataBytes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package org.eclipse.jetty.servlets.gzip;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.servlets.GzipFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sample servlet to serve static content, using a order of construction that has caused problems for
|
||||||
|
* {@link GzipFilter} in the past.
|
||||||
|
*
|
||||||
|
* Using a real-world pattern of:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1) get stream
|
||||||
|
* 2) set content length
|
||||||
|
* 3) set content type
|
||||||
|
* 4) write
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class TestServletStreamLengthTypeWrite extends TestDirContentServlet
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||||
|
{
|
||||||
|
String fileName = request.getServletPath();
|
||||||
|
byte[] dataBytes = loadContentFileBytes(fileName);
|
||||||
|
|
||||||
|
ServletOutputStream out = response.getOutputStream();
|
||||||
|
|
||||||
|
response.setContentLength(dataBytes.length);
|
||||||
|
|
||||||
|
if (fileName.endsWith("txt"))
|
||||||
|
response.setContentType("text/plain");
|
||||||
|
else if (fileName.endsWith("mp3"))
|
||||||
|
response.setContentType("audio/mpeg");
|
||||||
|
|
||||||
|
out.write(dataBytes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package org.eclipse.jetty.servlets.gzip;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.servlets.GzipFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sample servlet to serve static content, using a order of construction that has caused problems for
|
||||||
|
* {@link GzipFilter} in the past.
|
||||||
|
*
|
||||||
|
* Using a real-world pattern of:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1) get stream
|
||||||
|
* 2) set content type
|
||||||
|
* 2) set content length
|
||||||
|
* 4) write
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class TestServletStreamTypeLengthWrite extends TestDirContentServlet
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||||
|
{
|
||||||
|
String fileName = request.getServletPath();
|
||||||
|
byte[] dataBytes = loadContentFileBytes(fileName);
|
||||||
|
|
||||||
|
ServletOutputStream out = response.getOutputStream();
|
||||||
|
|
||||||
|
if (fileName.endsWith("txt"))
|
||||||
|
response.setContentType("text/plain");
|
||||||
|
else if (fileName.endsWith("mp3"))
|
||||||
|
response.setContentType("audio/mpeg");
|
||||||
|
|
||||||
|
response.setContentLength(dataBytes.length);
|
||||||
|
|
||||||
|
out.write(dataBytes);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.eclipse.jetty.servlets.gzip;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.servlets.GzipFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A sample servlet to serve static content, using a order of construction that has caused problems for
|
||||||
|
* {@link GzipFilter} in the past.
|
||||||
|
*
|
||||||
|
* Using a real-world pattern of:
|
||||||
|
*
|
||||||
|
* <pre>
|
||||||
|
* 1) set content type
|
||||||
|
* 2) set content length
|
||||||
|
* 3) get stream
|
||||||
|
* 4) write
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see <a href="Eclipse Bug 354014">http://bugs.eclipse.org/354014</a>
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class TestServletTypeLengthStreamWrite extends TestDirContentServlet
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||||
|
{
|
||||||
|
String fileName = request.getServletPath();
|
||||||
|
byte[] dataBytes = loadContentFileBytes(fileName);
|
||||||
|
|
||||||
|
if (fileName.endsWith("txt"))
|
||||||
|
response.setContentType("text/plain");
|
||||||
|
else if (fileName.endsWith("mp3"))
|
||||||
|
response.setContentType("audio/mpeg");
|
||||||
|
|
||||||
|
response.setContentLength(dataBytes.length);
|
||||||
|
|
||||||
|
ServletOutputStream out = response.getOutputStream();
|
||||||
|
out.write(dataBytes);
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 89 KiB |
|
@ -0,0 +1 @@
|
||||||
|
6d51985fd71ae74564202f98cf993e0390fae3fe jetty_logo.bmp
|
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1 @@
|
||||||
|
3bceab0485ead22e90af4f077c10684addd5dcb5 jetty_logo.gif
|
|
@ -0,0 +1 @@
|
||||||
|
c6f62de568243be3afbb7f489ce9096dc1808859 jetty_logo.jp2
|
After Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1 @@
|
||||||
|
c00ce14c7b266640544dc527277995e25d0c91b8 jetty_logo.jpeg
|
After Width: | Height: | Size: 6.6 KiB |
|
@ -0,0 +1 @@
|
||||||
|
c00ce14c7b266640544dc527277995e25d0c91b8 jetty_logo.jpg
|
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1 @@
|
||||||
|
813bfd8bfa2fb8381cc4b296f3b962a24797ed8f jetty_logo.png
|
After Width: | Height: | Size: 88 KiB |
|
@ -0,0 +1 @@
|
||||||
|
2603caf728690e1fddaf747a3eef8b5cfe20eee4 jetty_logo.tga
|
|
@ -0,0 +1 @@
|
||||||
|
35bbf5d78d6834531d4c43c686bdc49cded4c982 jetty_logo.tif
|
|
@ -0,0 +1 @@
|
||||||
|
3f7fa94449b96c4670b8754850ec8fbe526db3f6 jetty_logo.tiff
|
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1 @@
|
||||||
|
3ec782dc77c0b81420317d8d445f8d8c1ec25d84 jetty_logo.xcf
|
|
@ -0,0 +1 @@
|
||||||
|
1f8b327125e1ba9c25804734730dfe5367093216 test_quotes.bz2
|
|
@ -0,0 +1 @@
|
||||||
|
f43ed550786662ba8a245a1769dfaa330c49fdcc test_quotes.gz
|
|
@ -0,0 +1 @@
|
||||||
|
8071787493e74d60a273bc0787d3666968dc9eb9 test_quotes.rar
|
|
@ -0,0 +1,19 @@
|
||||||
|
Quotes attributed to Mark Twain:
|
||||||
|
|
||||||
|
+ A person with a new idea is a crank until the idea succeeds.
|
||||||
|
+ A person who won't read has no advantage over one who can't read.
|
||||||
|
+ Action speaks louder than words but not nearly as often.
|
||||||
|
+ Buy land, they're not making it anymore.
|
||||||
|
+ Good friends, good books and a sleepy conscience: this is the ideal life.
|
||||||
|
+ It's no wonder that truth is stranger than fiction. Fiction has to make sense
|
||||||
|
+ My books are like water; those of the great geniuses are wine. (Fortunately) everybody drinks water.
|
||||||
|
+ My mother had a great deal of trouble with me, but I think she enjoyed it.
|
||||||
|
+ Name the greatest of all inventors. Accident.
|
||||||
|
+ Necessity is the mother of taking chances.
|
||||||
|
+ Never put off till tomorrow what you can do the day after tomorrow.
|
||||||
|
+ Only one thing is impossible for God: To find any sense in any copyright law on the planet.
|
||||||
|
+ Part of the secret of a success in life is to eat what you like and let the food fight it out inside.
|
||||||
|
+ There are lies, damned lies and statistics.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
7dfe23b2baa0ad8fd1673bcbc9b981c4a564492a test_quotes.txt
|
|
@ -0,0 +1 @@
|
||||||
|
bd1edce4dfc9e57b8e55314ee9ac29e3fbb3f671 test_quotes.zip
|
|
@ -26,6 +26,6 @@ public interface Attributes
|
||||||
public void removeAttribute(String name);
|
public void removeAttribute(String name);
|
||||||
public void setAttribute(String name, Object attribute);
|
public void setAttribute(String name, Object attribute);
|
||||||
public Object getAttribute(String name);
|
public Object getAttribute(String name);
|
||||||
public Enumeration getAttributeNames();
|
public Enumeration<String> getAttributeNames();
|
||||||
public void clearAttributes();
|
public void clearAttributes();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -109,10 +110,9 @@ public class AttributesMap implements Attributes
|
||||||
{
|
{
|
||||||
if (attrs instanceof AttributesMap)
|
if (attrs instanceof AttributesMap)
|
||||||
return Collections.enumeration(((AttributesMap)attrs)._map.keySet());
|
return Collections.enumeration(((AttributesMap)attrs)._map.keySet());
|
||||||
ArrayList names = new ArrayList();
|
|
||||||
Enumeration e = attrs.getAttributeNames();
|
List<String> names = new ArrayList<String>();
|
||||||
while (e.hasMoreElements())
|
names.addAll(Collections.list(attrs.getAttributeNames()));
|
||||||
names.add(e.nextElement());
|
|
||||||
return Collections.enumeration(names);
|
return Collections.enumeration(names);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public E peek()
|
public E peek()
|
||||||
{
|
{
|
||||||
if (_size.get() == 0)
|
if (_size.get() == 0)
|
||||||
|
@ -218,6 +219,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public E poll()
|
public E poll()
|
||||||
{
|
{
|
||||||
if (_size.get() == 0)
|
if (_size.get() == 0)
|
||||||
|
@ -253,6 +255,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
||||||
* @return the head of this queue
|
* @return the head of this queue
|
||||||
* @throws InterruptedException if interrupted while waiting.
|
* @throws InterruptedException if interrupted while waiting.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public E take() throws InterruptedException
|
public E take() throws InterruptedException
|
||||||
{
|
{
|
||||||
E e = null;
|
E e = null;
|
||||||
|
@ -301,6 +304,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
||||||
* specified waiting time elapses before an element is present.
|
* specified waiting time elapses before an element is present.
|
||||||
* @throws InterruptedException if interrupted while waiting.
|
* @throws InterruptedException if interrupted while waiting.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public E poll(long time, TimeUnit unit) throws InterruptedException
|
public E poll(long time, TimeUnit unit) throws InterruptedException
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -390,6 +394,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public E get(int index)
|
public E get(int index)
|
||||||
{
|
{
|
||||||
|
@ -434,6 +439,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
||||||
int i = _head+index;
|
int i = _head+index;
|
||||||
if (i>=_capacity)
|
if (i>=_capacity)
|
||||||
i-=_capacity;
|
i-=_capacity;
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
E old=(E)_elements[i];
|
E old=(E)_elements[i];
|
||||||
|
|
||||||
if (i<_tail)
|
if (i<_tail)
|
||||||
|
@ -490,6 +496,7 @@ public class BlockingArrayQueue<E> extends AbstractList<E> implements BlockingQu
|
||||||
int i = _head+index;
|
int i = _head+index;
|
||||||
if (i>=_capacity)
|
if (i>=_capacity)
|
||||||
i-=_capacity;
|
i-=_capacity;
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
E old=(E)_elements[i];
|
E old=(E)_elements[i];
|
||||||
_elements[i]=e;
|
_elements[i]=e;
|
||||||
return old;
|
return old;
|
||||||
|
|
|
@ -21,8 +21,10 @@ import java.util.Map;
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
public class HostMap<TYPE> extends HashMap<String, TYPE>
|
public class HostMap<TYPE> extends HashMap<String, TYPE>
|
||||||
{
|
{
|
||||||
|
|
||||||
/* --------------------------------------------------------------- */
|
/* --------------------------------------------------------------- */
|
||||||
/** Construct empty HostMap.
|
/** Construct empty HostMap.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -36,6 +36,7 @@ import java.util.StringTokenizer;
|
||||||
* a,b,... - a list of wildcard specifications
|
* a,b,... - a list of wildcard specifications
|
||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
public class IPAddressMap<TYPE> extends HashMap<String, TYPE>
|
public class IPAddressMap<TYPE> extends HashMap<String, TYPE>
|
||||||
{
|
{
|
||||||
private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>();
|
private final HashMap<String,IPAddrPattern> _patterns = new HashMap<String,IPAddrPattern>();
|
||||||
|
|
|
@ -45,7 +45,7 @@ public class IntrospectionUtil
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Method findMethod (Class clazz, String methodName, Class[] args, boolean checkInheritance, boolean strictArgs)
|
public static Method findMethod (Class<?> clazz, String methodName, Class<?>[] args, boolean checkInheritance, boolean strictArgs)
|
||||||
throws NoSuchMethodException
|
throws NoSuchMethodException
|
||||||
{
|
{
|
||||||
if (clazz == null)
|
if (clazz == null)
|
||||||
|
@ -78,7 +78,7 @@ public class IntrospectionUtil
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static Field findField (Class clazz, String targetName, Class targetType, boolean checkInheritance, boolean strictType)
|
public static Field findField (Class<?> clazz, String targetName, Class<?> targetType, boolean checkInheritance, boolean strictType)
|
||||||
throws NoSuchFieldException
|
throws NoSuchFieldException
|
||||||
{
|
{
|
||||||
if (clazz == null)
|
if (clazz == null)
|
||||||
|
@ -137,7 +137,7 @@ public class IntrospectionUtil
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static boolean checkParams (Class[] formalParams, Class[] actualParams, boolean strict)
|
public static boolean checkParams (Class<?>[] formalParams, Class<?>[] actualParams, boolean strict)
|
||||||
{
|
{
|
||||||
if (formalParams==null && actualParams==null)
|
if (formalParams==null && actualParams==null)
|
||||||
return true;
|
return true;
|
||||||
|
@ -182,8 +182,8 @@ public class IntrospectionUtil
|
||||||
if (methodB==null)
|
if (methodB==null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
List parameterTypesA = Arrays.asList(methodA.getParameterTypes());
|
List<Class<?>> parameterTypesA = Arrays.asList(methodA.getParameterTypes());
|
||||||
List parameterTypesB = Arrays.asList(methodB.getParameterTypes());
|
List<Class<?>> parameterTypesB = Arrays.asList(methodB.getParameterTypes());
|
||||||
|
|
||||||
if (methodA.getName().equals(methodB.getName())
|
if (methodA.getName().equals(methodB.getName())
|
||||||
&&
|
&&
|
||||||
|
@ -193,7 +193,7 @@ public class IntrospectionUtil
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isTypeCompatible (Class formalType, Class actualType, boolean strict)
|
public static boolean isTypeCompatible (Class<?> formalType, Class<?> actualType, boolean strict)
|
||||||
{
|
{
|
||||||
if (formalType==null && actualType != null)
|
if (formalType==null && actualType != null)
|
||||||
return false;
|
return false;
|
||||||
|
@ -211,7 +211,7 @@ public class IntrospectionUtil
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static boolean containsSameMethodSignature (Method method, Class c, boolean checkPackage)
|
public static boolean containsSameMethodSignature (Method method, Class<?> c, boolean checkPackage)
|
||||||
{
|
{
|
||||||
if (checkPackage)
|
if (checkPackage)
|
||||||
{
|
{
|
||||||
|
@ -230,7 +230,7 @@ public class IntrospectionUtil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean containsSameFieldName(Field field, Class c, boolean checkPackage)
|
public static boolean containsSameFieldName(Field field, Class<?> c, boolean checkPackage)
|
||||||
{
|
{
|
||||||
if (checkPackage)
|
if (checkPackage)
|
||||||
{
|
{
|
||||||
|
@ -250,7 +250,7 @@ public class IntrospectionUtil
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected static Method findInheritedMethod (Package pack, Class clazz, String methodName, Class[] args, boolean strictArgs)
|
protected static Method findInheritedMethod (Package pack, Class<?> clazz, String methodName, Class<?>[] args, boolean strictArgs)
|
||||||
throws NoSuchMethodException
|
throws NoSuchMethodException
|
||||||
{
|
{
|
||||||
if (clazz==null)
|
if (clazz==null)
|
||||||
|
@ -275,7 +275,7 @@ public class IntrospectionUtil
|
||||||
return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
|
return findInheritedMethod(clazz.getPackage(), clazz.getSuperclass(), methodName, args, strictArgs);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static Field findInheritedField (Package pack, Class clazz, String fieldName, Class fieldType, boolean strictType)
|
protected static Field findInheritedField (Package pack, Class<?> clazz, String fieldName, Class<?> fieldType, boolean strictType)
|
||||||
throws NoSuchFieldException
|
throws NoSuchFieldException
|
||||||
{
|
{
|
||||||
if (clazz==null)
|
if (clazz==null)
|
||||||
|
|
|
@ -28,13 +28,13 @@ import java.util.ListIterator;
|
||||||
* creation. If a method needs to create a List to return, but it is
|
* creation. If a method needs to create a List to return, but it is
|
||||||
* expected that this will either be empty or frequently contain a
|
* expected that this will either be empty or frequently contain a
|
||||||
* single item, then using LazyList will avoid additional object
|
* single item, then using LazyList will avoid additional object
|
||||||
* creations by using Collections.EMPTY_LIST or
|
* creations by using {@link Collections#EMPTY_LIST} or
|
||||||
* Collections.singletonList where possible.
|
* {@link Collections#singletonList(Object)} where possible.
|
||||||
* <p>
|
* <p>
|
||||||
* LazyList works by passing an opaque representation of the list in
|
* LazyList works by passing an opaque representation of the list in
|
||||||
* and out of all the LazyList methods. This opaque object is either
|
* and out of all the LazyList methods. This opaque object is either
|
||||||
* null for an empty list, an Object for a list with a single entry
|
* null for an empty list, an Object for a list with a single entry
|
||||||
* or an ArrayList<Object> for a list of items.
|
* or an {@link ArrayList} for a list of items.
|
||||||
*
|
*
|
||||||
* <p><h4>Usage</h4>
|
* <p><h4>Usage</h4>
|
||||||
* <pre>
|
* <pre>
|
||||||
|
@ -124,7 +124,7 @@ public class LazyList
|
||||||
List<Object> l=new ArrayList<Object>();
|
List<Object> l=new ArrayList<Object>();
|
||||||
l.add(list);
|
l.add(list);
|
||||||
l.add(index,item);
|
l.add(index,item);
|
||||||
return l;
|
return l;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -155,7 +155,7 @@ public class LazyList
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Ensure the capcity of the underlying list.
|
/** Ensure the capacity of the underlying list.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public static Object ensureSize(Object list, int initialSize)
|
public static Object ensureSize(Object list, int initialSize)
|
||||||
|
@ -284,7 +284,6 @@ public class LazyList
|
||||||
* @param clazz The class of the array, which may be a primitive type
|
* @param clazz The class of the array, which may be a primitive type
|
||||||
* @return array of the lazylist entries passed in
|
* @return array of the lazylist entries passed in
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static Object toArray(Object list,Class<?> clazz)
|
public static Object toArray(Object list,Class<?> clazz)
|
||||||
{
|
{
|
||||||
if (list==null)
|
if (list==null)
|
||||||
|
@ -429,21 +428,23 @@ public class LazyList
|
||||||
* @param type The type of the array (in case of null array)
|
* @param type The type of the array (in case of null array)
|
||||||
* @return new array with contents of array plus item
|
* @return new array with contents of array plus item
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
public static<T> T[] addToArray(T[] array, T item, Class<?> type)
|
||||||
public static Object[] addToArray(Object[] array, Object item, Class<?> type)
|
|
||||||
{
|
{
|
||||||
if (array==null)
|
if (array==null)
|
||||||
{
|
{
|
||||||
if (type==null && item!=null)
|
if (type==null && item!=null)
|
||||||
type= item.getClass();
|
type= item.getClass();
|
||||||
Object[] na = (Object[])Array.newInstance(type, 1);
|
@SuppressWarnings("unchecked")
|
||||||
|
T[] na = (T[])Array.newInstance(type, 1);
|
||||||
na[0]=item;
|
na[0]=item;
|
||||||
return na;
|
return na;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// TODO: Replace with Arrays.copyOf(T[] original, int newLength) from Java 1.6+
|
||||||
Class<?> c = array.getClass().getComponentType();
|
Class<?> c = array.getClass().getComponentType();
|
||||||
Object[] na = (Object[])Array.newInstance(c, Array.getLength(array)+1);
|
@SuppressWarnings("unchecked")
|
||||||
|
T[] na = (T[])Array.newInstance(c, Array.getLength(array)+1);
|
||||||
System.arraycopy(array, 0, na, 0, array.length);
|
System.arraycopy(array, 0, na, 0, array.length);
|
||||||
na[array.length]=item;
|
na[array.length]=item;
|
||||||
return na;
|
return na;
|
||||||
|
@ -451,8 +452,7 @@ public class LazyList
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
@SuppressWarnings("unchecked")
|
public static<T> T[] removeFromArray(T[] array, Object item)
|
||||||
public static Object removeFromArray(Object[] array, Object item)
|
|
||||||
{
|
{
|
||||||
if (item==null || array==null)
|
if (item==null || array==null)
|
||||||
return array;
|
return array;
|
||||||
|
@ -461,7 +461,8 @@ public class LazyList
|
||||||
if (item.equals(array[i]))
|
if (item.equals(array[i]))
|
||||||
{
|
{
|
||||||
Class<?> c = array==null?item.getClass():array.getClass().getComponentType();
|
Class<?> c = array==null?item.getClass():array.getClass().getComponentType();
|
||||||
Object[] na = (Object[])Array.newInstance(c, Array.getLength(array)-1);
|
@SuppressWarnings("unchecked")
|
||||||
|
T[] na = (T[])Array.newInstance(c, Array.getLength(array)-1);
|
||||||
if (i>0)
|
if (i>0)
|
||||||
System.arraycopy(array, 0, na, 0, i);
|
System.arraycopy(array, 0, na, 0, i);
|
||||||
if (i+1<array.length)
|
if (i+1<array.length)
|
||||||
|
|
|
@ -37,7 +37,7 @@ import java.util.ResourceBundle;
|
||||||
public class Loader
|
public class Loader
|
||||||
{
|
{
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public static URL getResource(Class loadClass,String name, boolean checkParents)
|
public static URL getResource(Class<?> loadClass,String name, boolean checkParents)
|
||||||
throws ClassNotFoundException
|
throws ClassNotFoundException
|
||||||
{
|
{
|
||||||
URL url =null;
|
URL url =null;
|
||||||
|
@ -64,6 +64,7 @@ public class Loader
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
public static Class loadClass(Class loadClass,String name)
|
public static Class loadClass(Class loadClass,String name)
|
||||||
throws ClassNotFoundException
|
throws ClassNotFoundException
|
||||||
{
|
{
|
||||||
|
@ -79,11 +80,12 @@ public class Loader
|
||||||
* @return Class
|
* @return Class
|
||||||
* @throws ClassNotFoundException
|
* @throws ClassNotFoundException
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
public static Class loadClass(Class loadClass,String name,boolean checkParents)
|
public static Class loadClass(Class loadClass,String name,boolean checkParents)
|
||||||
throws ClassNotFoundException
|
throws ClassNotFoundException
|
||||||
{
|
{
|
||||||
ClassNotFoundException ex=null;
|
ClassNotFoundException ex=null;
|
||||||
Class c =null;
|
Class<?> c =null;
|
||||||
ClassLoader loader=Thread.currentThread().getContextClassLoader();
|
ClassLoader loader=Thread.currentThread().getContextClassLoader();
|
||||||
while (c==null && loader!=null )
|
while (c==null && loader!=null )
|
||||||
{
|
{
|
||||||
|
@ -111,7 +113,7 @@ public class Loader
|
||||||
throw ex;
|
throw ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceBundle getResourceBundle(Class loadClass,String name,boolean checkParents, Locale locale)
|
public static ResourceBundle getResourceBundle(Class<?> loadClass,String name,boolean checkParents, Locale locale)
|
||||||
throws MissingResourceException
|
throws MissingResourceException
|
||||||
{
|
{
|
||||||
MissingResourceException ex=null;
|
MissingResourceException ex=null;
|
||||||
|
|
|
@ -24,6 +24,7 @@ import java.util.List;
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
public class MultiException extends Exception
|
public class MultiException extends Exception
|
||||||
{
|
{
|
||||||
private Object nested;
|
private Object nested;
|
||||||
|
@ -54,7 +55,7 @@ public class MultiException extends Exception
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public List getThrowables()
|
public List<Throwable> getThrowables()
|
||||||
{
|
{
|
||||||
return LazyList.getList(nested);
|
return LazyList.getList(nested);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import java.io.Serializable;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -46,7 +45,7 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
||||||
_map=new HashMap<K, Object>();
|
_map=new HashMap<K, Object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultiMap(Map map)
|
public MultiMap(Map<K,Object> map)
|
||||||
{
|
{
|
||||||
if (map instanceof ConcurrentMap)
|
if (map instanceof ConcurrentMap)
|
||||||
_map=_cmap=new ConcurrentHashMap<K, Object>(map);
|
_map=_cmap=new ConcurrentHashMap<K, Object>(map);
|
||||||
|
@ -173,7 +172,7 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
||||||
* @param values The List of multiple values.
|
* @param values The List of multiple values.
|
||||||
* @return The previous value or null.
|
* @return The previous value or null.
|
||||||
*/
|
*/
|
||||||
public Object putValues(K name, List values)
|
public Object putValues(K name, List<? extends Object> values)
|
||||||
{
|
{
|
||||||
return _map.put(name,values);
|
return _map.put(name,values);
|
||||||
}
|
}
|
||||||
|
@ -184,12 +183,12 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
||||||
* @param values The String array of multiple values.
|
* @param values The String array of multiple values.
|
||||||
* @return The previous value or null.
|
* @return The previous value or null.
|
||||||
*/
|
*/
|
||||||
public Object putValues(K name, String[] values)
|
public Object putValues(K name, String... values)
|
||||||
{
|
{
|
||||||
Object list=null;
|
Object list=null;
|
||||||
for (int i=0;i<values.length;i++)
|
for (int i=0;i<values.length;i++)
|
||||||
list=LazyList.add(list,values[i]);
|
list=LazyList.add(list,values[i]);
|
||||||
return put(name,list);
|
return _map.put(name,list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -215,7 +214,7 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
||||||
* @param name The entry key.
|
* @param name The entry key.
|
||||||
* @param values The List of multiple values.
|
* @param values The List of multiple values.
|
||||||
*/
|
*/
|
||||||
public void addValues(K name, List values)
|
public void addValues(K name, List<? extends Object> values)
|
||||||
{
|
{
|
||||||
Object lo = _map.get(name);
|
Object lo = _map.get(name);
|
||||||
Object ln = LazyList.addCollection(lo,values);
|
Object ln = LazyList.addCollection(lo,values);
|
||||||
|
@ -260,21 +259,25 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
||||||
return LazyList.size(ln)!=s;
|
return LazyList.size(ln)!=s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Put all contents of map.
|
/** Put all contents of map.
|
||||||
* @param m Map
|
* @param m Map
|
||||||
*/
|
*/
|
||||||
public void putAll(Map m)
|
public void putAll(Map<? extends K, ? extends Object> m)
|
||||||
{
|
{
|
||||||
Iterator i = m.entrySet().iterator();
|
boolean multi = (m instanceof MultiMap);
|
||||||
boolean multi=m instanceof MultiMap;
|
|
||||||
while(i.hasNext())
|
if (multi)
|
||||||
{
|
{
|
||||||
Map.Entry entry = (Map.Entry)i.next();
|
for (Map.Entry<? extends K, ? extends Object> entry : m.entrySet())
|
||||||
if (multi)
|
{
|
||||||
_map.put((K)(entry.getKey()),LazyList.clone(entry.getValue()));
|
_map.put(entry.getKey(),LazyList.clone(entry.getValue()));
|
||||||
else
|
}
|
||||||
put((K)(entry.getKey()),entry.getValue());
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_map.putAll(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,19 +285,13 @@ public class MultiMap<K> implements ConcurrentMap<K,Object>, Serializable
|
||||||
/**
|
/**
|
||||||
* @return Map of String arrays
|
* @return Map of String arrays
|
||||||
*/
|
*/
|
||||||
public Map toStringArrayMap()
|
public Map<K,String[]> toStringArrayMap()
|
||||||
{
|
{
|
||||||
HashMap map = new HashMap(_map.size()*3/2);
|
HashMap<K,String[]> map = new HashMap<K,String[]>(_map.size()*3/2);
|
||||||
|
|
||||||
Iterator i = _map.entrySet().iterator();
|
for(Map.Entry<K,Object> entry: _map.entrySet())
|
||||||
while(i.hasNext())
|
|
||||||
{
|
{
|
||||||
Map.Entry entry = (Map.Entry)i.next();
|
String[] a = LazyList.toStringArray(entry.getValue());
|
||||||
Object l = entry.getValue();
|
|
||||||
String[] a = LazyList.toStringArray(l);
|
|
||||||
// for (int j=a.length;j-->0;)
|
|
||||||
// if (a[j]==null)
|
|
||||||
// a[j]="";
|
|
||||||
map.put(entry.getKey(),a);
|
map.put(entry.getKey(),a);
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
|
|
|
@ -4,16 +4,17 @@
|
||||||
// All rights reserved. This program and the accompanying materials
|
// All rights reserved. This program and the accompanying materials
|
||||||
// are made available under the terms of the Eclipse Public License v1.0
|
// are made available under the terms of the Eclipse Public License v1.0
|
||||||
// and Apache License v2.0 which accompanies this distribution.
|
// and Apache License v2.0 which accompanies this distribution.
|
||||||
// The Eclipse Public License is available at
|
// The Eclipse Public License is available at
|
||||||
// http://www.eclipse.org/legal/epl-v10.html
|
// http://www.eclipse.org/legal/epl-v10.html
|
||||||
// The Apache License v2.0 is available at
|
// The Apache License v2.0 is available at
|
||||||
// http://www.opensource.org/licenses/apache2.0.php
|
// http://www.opensource.org/licenses/apache2.0.php
|
||||||
// You may elect to redistribute this code under either of these licenses.
|
// You may elect to redistribute this code under either of these licenses.
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
||||||
package org.eclipse.jetty.util;
|
package org.eclipse.jetty.util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.StringTokenizer;
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ import java.util.StringTokenizer;
|
||||||
* Quotes can be escaped with '\'.
|
* Quotes can be escaped with '\'.
|
||||||
*
|
*
|
||||||
* @see java.util.StringTokenizer
|
* @see java.util.StringTokenizer
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class QuotedStringTokenizer
|
public class QuotedStringTokenizer
|
||||||
extends StringTokenizer
|
extends StringTokenizer
|
||||||
|
@ -43,7 +44,7 @@ public class QuotedStringTokenizer
|
||||||
private int _lastStart=0;
|
private int _lastStart=0;
|
||||||
private boolean _double=true;
|
private boolean _double=true;
|
||||||
private boolean _single=true;
|
private boolean _single=true;
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public QuotedStringTokenizer(String str,
|
public QuotedStringTokenizer(String str,
|
||||||
String delim,
|
String delim,
|
||||||
|
@ -56,11 +57,11 @@ public class QuotedStringTokenizer
|
||||||
_delim=delim;
|
_delim=delim;
|
||||||
_returnDelimiters=returnDelimiters;
|
_returnDelimiters=returnDelimiters;
|
||||||
_returnQuotes=returnQuotes;
|
_returnQuotes=returnQuotes;
|
||||||
|
|
||||||
if (_delim.indexOf('\'')>=0 ||
|
if (_delim.indexOf('\'')>=0 ||
|
||||||
_delim.indexOf('"')>=0)
|
_delim.indexOf('"')>=0)
|
||||||
throw new Error("Can't use quotes as delimiters: "+_delim);
|
throw new Error("Can't use quotes as delimiters: "+_delim);
|
||||||
|
|
||||||
_token=new StringBuffer(_string.length()>1024?512:_string.length()/2);
|
_token=new StringBuffer(_string.length()>1024?512:_string.length()/2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ public class QuotedStringTokenizer
|
||||||
{
|
{
|
||||||
this(str,delim,returnDelimiters,false);
|
this(str,delim,returnDelimiters,false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public QuotedStringTokenizer(String str,
|
public QuotedStringTokenizer(String str,
|
||||||
String delim)
|
String delim)
|
||||||
|
@ -92,15 +93,15 @@ public class QuotedStringTokenizer
|
||||||
// Already found a token
|
// Already found a token
|
||||||
if (_hasToken)
|
if (_hasToken)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
_lastStart=_i;
|
_lastStart=_i;
|
||||||
|
|
||||||
int state=0;
|
int state=0;
|
||||||
boolean escape=false;
|
boolean escape=false;
|
||||||
while (_i<_string.length())
|
while (_i<_string.length())
|
||||||
{
|
{
|
||||||
char c=_string.charAt(_i++);
|
char c=_string.charAt(_i++);
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case 0: // Start
|
case 0: // Start
|
||||||
|
@ -130,8 +131,8 @@ public class QuotedStringTokenizer
|
||||||
_hasToken=true;
|
_hasToken=true;
|
||||||
state=1;
|
state=1;
|
||||||
}
|
}
|
||||||
continue;
|
break;
|
||||||
|
|
||||||
case 1: // Token
|
case 1: // Token
|
||||||
_hasToken=true;
|
_hasToken=true;
|
||||||
if(_delim.indexOf(c)>=0)
|
if(_delim.indexOf(c)>=0)
|
||||||
|
@ -153,10 +154,11 @@ public class QuotedStringTokenizer
|
||||||
state=3;
|
state=3;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_token.append(c);
|
_token.append(c);
|
||||||
continue;
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
case 2: // Single Quote
|
case 2: // Single Quote
|
||||||
_hasToken=true;
|
_hasToken=true;
|
||||||
if (escape)
|
if (escape)
|
||||||
|
@ -177,10 +179,11 @@ public class QuotedStringTokenizer
|
||||||
escape=true;
|
escape=true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_token.append(c);
|
_token.append(c);
|
||||||
continue;
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
case 3: // Double Quote
|
case 3: // Double Quote
|
||||||
_hasToken=true;
|
_hasToken=true;
|
||||||
if (escape)
|
if (escape)
|
||||||
|
@ -201,8 +204,10 @@ public class QuotedStringTokenizer
|
||||||
escape=true;
|
escape=true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
_token.append(c);
|
_token.append(c);
|
||||||
continue;
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,7 +217,7 @@ public class QuotedStringTokenizer
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
@Override
|
@Override
|
||||||
public String nextToken()
|
public String nextToken()
|
||||||
throws NoSuchElementException
|
throws NoSuchElementException
|
||||||
{
|
{
|
||||||
if (!hasMoreTokens() || _token==null)
|
if (!hasMoreTokens() || _token==null)
|
||||||
throw new NoSuchElementException();
|
throw new NoSuchElementException();
|
||||||
|
@ -225,7 +230,7 @@ public class QuotedStringTokenizer
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
@Override
|
@Override
|
||||||
public String nextToken(String delim)
|
public String nextToken(String delim)
|
||||||
throws NoSuchElementException
|
throws NoSuchElementException
|
||||||
{
|
{
|
||||||
_delim=delim;
|
_delim=delim;
|
||||||
_i=_lastStart;
|
_i=_lastStart;
|
||||||
|
@ -244,7 +249,7 @@ public class QuotedStringTokenizer
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
@Override
|
@Override
|
||||||
public Object nextElement()
|
public Object nextElement()
|
||||||
throws NoSuchElementException
|
throws NoSuchElementException
|
||||||
{
|
{
|
||||||
return nextToken();
|
return nextToken();
|
||||||
}
|
}
|
||||||
|
@ -258,13 +263,14 @@ public class QuotedStringTokenizer
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Quote a string.
|
/** Quote a string.
|
||||||
* The string is quoted only if quoting is required due to
|
* The string is quoted only if quoting is required due to
|
||||||
* embeded delimiters, quote characters or the
|
* embedded delimiters, quote characters or the
|
||||||
* empty string.
|
* empty string.
|
||||||
* @param s The string to quote.
|
* @param s The string to quote.
|
||||||
|
* @param delim the delimiter to use to quote the string
|
||||||
* @return quoted string
|
* @return quoted string
|
||||||
*/
|
*/
|
||||||
public static String quoteIfNeeded(String s, String delim)
|
public static String quoteIfNeeded(String s, String delim)
|
||||||
|
@ -274,7 +280,7 @@ public class QuotedStringTokenizer
|
||||||
if (s.length()==0)
|
if (s.length()==0)
|
||||||
return "\"\"";
|
return "\"\"";
|
||||||
|
|
||||||
|
|
||||||
for (int i=0;i<s.length();i++)
|
for (int i=0;i<s.length();i++)
|
||||||
{
|
{
|
||||||
char c = s.charAt(i);
|
char c = s.charAt(i);
|
||||||
|
@ -285,7 +291,7 @@ public class QuotedStringTokenizer
|
||||||
return b.toString();
|
return b.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,81 +309,73 @@ public class QuotedStringTokenizer
|
||||||
return null;
|
return null;
|
||||||
if (s.length()==0)
|
if (s.length()==0)
|
||||||
return "\"\"";
|
return "\"\"";
|
||||||
|
|
||||||
StringBuffer b=new StringBuffer(s.length()+8);
|
StringBuffer b=new StringBuffer(s.length()+8);
|
||||||
quote(b,s);
|
quote(b,s);
|
||||||
return b.toString();
|
return b.toString();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final char[] escapes = new char[32];
|
||||||
|
static
|
||||||
|
{
|
||||||
|
Arrays.fill(escapes, (char)0xFFFF);
|
||||||
|
escapes['\b'] = 'b';
|
||||||
|
escapes['\t'] = 't';
|
||||||
|
escapes['\n'] = 'n';
|
||||||
|
escapes['\f'] = 'f';
|
||||||
|
escapes['\r'] = 'r';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Quote a string into an Appendable.
|
/** Quote a string into an Appendable.
|
||||||
* The characters ", \, \n, \r, \t, \f and \b are escaped
|
* The characters ", \, \n, \r, \t, \f and \b are escaped
|
||||||
* @param buf The Appendable
|
* @param buffer The Appendable
|
||||||
* @param s The String to quote.
|
* @param input The String to quote.
|
||||||
*/
|
*/
|
||||||
public static void quote(Appendable buf, String s)
|
public static void quote(Appendable buffer, String input)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
buf.append('"');
|
buffer.append('"');
|
||||||
|
for (int i = 0; i < input.length(); ++i)
|
||||||
for (int i=0;i<s.length();i++)
|
|
||||||
{
|
{
|
||||||
char c = s.charAt(i);
|
char c = input.charAt(i);
|
||||||
switch(c)
|
if (c >= 32)
|
||||||
{
|
{
|
||||||
case '"':
|
if (c == '"' || c == '\\')
|
||||||
buf.append("\\\"");
|
buffer.append('\\');
|
||||||
continue;
|
buffer.append(c);
|
||||||
case '\\':
|
}
|
||||||
buf.append("\\\\");
|
else
|
||||||
continue;
|
{
|
||||||
case '\n':
|
char escape = escapes[c];
|
||||||
buf.append("\\n");
|
if (escape == 0xFFFF)
|
||||||
continue;
|
{
|
||||||
case '\r':
|
// Unicode escape
|
||||||
buf.append("\\r");
|
buffer.append('\\').append('u').append('0').append('0');
|
||||||
continue;
|
if (c < 0x10)
|
||||||
case '\t':
|
buffer.append('0');
|
||||||
buf.append("\\t");
|
buffer.append(Integer.toString(c, 16));
|
||||||
continue;
|
}
|
||||||
case '\f':
|
else
|
||||||
buf.append("\\f");
|
{
|
||||||
continue;
|
buffer.append('\\').append(escape);
|
||||||
case '\b':
|
}
|
||||||
buf.append("\\b");
|
|
||||||
continue;
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (c<0x10)
|
|
||||||
{
|
|
||||||
buf.append("\\u000");
|
|
||||||
buf.append(Integer.toString(c,16));
|
|
||||||
}
|
|
||||||
else if (c<=0x1f)
|
|
||||||
{
|
|
||||||
buf.append("\\u00");
|
|
||||||
buf.append(Integer.toString(c,16));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
buf.append(c);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
buffer.append('"');
|
||||||
buf.append('"');
|
}
|
||||||
}
|
catch (IOException x)
|
||||||
catch(IOException e)
|
|
||||||
{
|
{
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Quote a string into a StringBuffer only if needed.
|
/** Quote a string into a StringBuffer only if needed.
|
||||||
* Quotes are forced if any delim characters are present.
|
* Quotes are forced if any delim characters are present.
|
||||||
*
|
*
|
||||||
* @param buf The StringBuffer
|
* @param buf The StringBuffer
|
||||||
* @param s The String to quote.
|
* @param s The String to quote.
|
||||||
* @param delim String of characters that must be quoted.
|
* @param delim String of characters that must be quoted.
|
||||||
|
@ -394,7 +392,7 @@ public class QuotedStringTokenizer
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
buf.append(s);
|
buf.append(s);
|
||||||
|
@ -405,7 +403,7 @@ public class QuotedStringTokenizer
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/** Unquote a string.
|
/** Unquote a string.
|
||||||
* @param s The string to unquote.
|
* @param s The string to unquote.
|
||||||
|
@ -422,68 +420,66 @@ public class QuotedStringTokenizer
|
||||||
char last=s.charAt(s.length()-1);
|
char last=s.charAt(s.length()-1);
|
||||||
if (first!=last || (first!='"' && first!='\''))
|
if (first!=last || (first!='"' && first!='\''))
|
||||||
return s;
|
return s;
|
||||||
|
|
||||||
StringBuffer b=new StringBuffer(s.length()-2);
|
|
||||||
synchronized(b)
|
|
||||||
{
|
|
||||||
boolean escape=false;
|
|
||||||
for (int i=1;i<s.length()-1;i++)
|
|
||||||
{
|
|
||||||
char c = s.charAt(i);
|
|
||||||
|
|
||||||
if (escape)
|
StringBuilder b = new StringBuilder(s.length() - 2);
|
||||||
|
boolean escape=false;
|
||||||
|
for (int i=1;i<s.length()-1;i++)
|
||||||
|
{
|
||||||
|
char c = s.charAt(i);
|
||||||
|
|
||||||
|
if (escape)
|
||||||
|
{
|
||||||
|
escape=false;
|
||||||
|
switch (c)
|
||||||
{
|
{
|
||||||
escape=false;
|
case 'n':
|
||||||
switch (c)
|
b.append('\n');
|
||||||
{
|
break;
|
||||||
case 'n':
|
case 'r':
|
||||||
b.append('\n');
|
b.append('\r');
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 't':
|
||||||
b.append('\r');
|
b.append('\t');
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 'f':
|
||||||
b.append('\t');
|
b.append('\f');
|
||||||
break;
|
break;
|
||||||
case 'f':
|
case 'b':
|
||||||
b.append('\f');
|
b.append('\b');
|
||||||
break;
|
break;
|
||||||
case 'b':
|
case '\\':
|
||||||
b.append('\b');
|
b.append('\\');
|
||||||
break;
|
break;
|
||||||
case '\\':
|
case '/':
|
||||||
b.append('\\');
|
b.append('/');
|
||||||
break;
|
break;
|
||||||
case '/':
|
case '"':
|
||||||
b.append('/');
|
b.append('"');
|
||||||
break;
|
break;
|
||||||
case '"':
|
case 'u':
|
||||||
b.append('"');
|
b.append((char)(
|
||||||
break;
|
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
|
||||||
case 'u':
|
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
|
||||||
b.append((char)(
|
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
|
||||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<24)+
|
(TypeUtil.convertHexDigit((byte)s.charAt(i++)))
|
||||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<16)+
|
)
|
||||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++))<<8)+
|
);
|
||||||
(TypeUtil.convertHexDigit((byte)s.charAt(i++)))
|
break;
|
||||||
)
|
default:
|
||||||
);
|
b.append(c);
|
||||||
break;
|
|
||||||
default:
|
|
||||||
b.append(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (c=='\\')
|
|
||||||
{
|
|
||||||
escape=true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
b.append(c);
|
|
||||||
}
|
}
|
||||||
|
else if (c=='\\')
|
||||||
return b.toString();
|
{
|
||||||
|
escape=true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
b.append(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return b.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -522,15 +518,3 @@ public class QuotedStringTokenizer
|
||||||
_single=single;
|
_single=single;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,8 +32,6 @@ import java.util.Set;
|
||||||
* objects from being created just to look up in the map.
|
* objects from being created just to look up in the map.
|
||||||
*
|
*
|
||||||
* This map is NOT synchronized.
|
* This map is NOT synchronized.
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public class StringMap extends AbstractMap implements Externalizable
|
public class StringMap extends AbstractMap implements Externalizable
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
package org.eclipse.jetty.util.log;
|
package org.eclipse.jetty.util.log;
|
||||||
|
|
||||||
import java.security.AccessControlException;
|
import java.security.AccessControlException;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
import org.eclipse.jetty.util.DateCache;
|
import org.eclipse.jetty.util.DateCache;
|
||||||
|
|
||||||
|
@ -43,6 +45,8 @@ public class StdErrLog implements Logger
|
||||||
System.getProperty("org.eclipse.jetty.util.log.SOURCE",
|
System.getProperty("org.eclipse.jetty.util.log.SOURCE",
|
||||||
System.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE", "false")));
|
System.getProperty("org.eclipse.jetty.util.log.stderr.SOURCE", "false")));
|
||||||
|
|
||||||
|
private final static ConcurrentMap<String,StdErrLog> __loggers = new ConcurrentHashMap<String, StdErrLog>();
|
||||||
|
|
||||||
static
|
static
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -315,9 +319,21 @@ public class StdErrLog implements Logger
|
||||||
|
|
||||||
public Logger getLogger(String name)
|
public Logger getLogger(String name)
|
||||||
{
|
{
|
||||||
if ((name == null && this._name == null) || (name != null && name.equals(this._name)))
|
String fullname=_name == null || _name.length() == 0?name:_name + "." + name;
|
||||||
|
|
||||||
|
if ((name == null && this._name == null) || fullname.equals(_name))
|
||||||
return this;
|
return this;
|
||||||
return new StdErrLog(_name == null || _name.length() == 0?name:_name + "." + name);
|
|
||||||
|
StdErrLog logger = __loggers.get(name);
|
||||||
|
if (logger==null)
|
||||||
|
{
|
||||||
|
StdErrLog sel=new StdErrLog(fullname);
|
||||||
|
logger=__loggers.putIfAbsent(fullname,sel);
|
||||||
|
if (logger==null)
|
||||||
|
logger=sel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,540 @@
|
||||||
|
package org.eclipse.jetty.util;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class MultiMapTest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#put(Object, Object)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPut()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
mm.put(key,"gzip");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#put(Object, Object)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPut_Null()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
mm.put(key,null);
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,new Object[]
|
||||||
|
{ null });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#put(Object, Object)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPut_Replace()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
Object ret;
|
||||||
|
|
||||||
|
ret = mm.put(key,"gzip");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip");
|
||||||
|
Assert.assertNull("Should not have replaced anything", ret);
|
||||||
|
Object orig = mm.get(key);
|
||||||
|
|
||||||
|
// Now replace it
|
||||||
|
ret = mm.put(key,"jar");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"jar");
|
||||||
|
Assert.assertEquals("Should have replaced original", orig, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#putValues(Object, List)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPutValues_List()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
List<String> input = new ArrayList<String>();
|
||||||
|
input.add("gzip");
|
||||||
|
input.add("jar");
|
||||||
|
input.add("pack200");
|
||||||
|
|
||||||
|
mm.putValues(key,input);
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","jar","pack200");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#putValues(Object, String...)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPutValues_StringArray()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
String input[] = { "gzip", "jar", "pack200" };
|
||||||
|
mm.putValues(key,input);
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","jar","pack200");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#putValues(Object, String...)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPutValues_VarArgs()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
mm.putValues(key,"gzip", "jar", "pack200");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","jar","pack200");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#add(Object, Object)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAdd()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
// Setup the key
|
||||||
|
mm.put(key,"gzip");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip");
|
||||||
|
|
||||||
|
// Add to the key
|
||||||
|
mm.add(key,"jar");
|
||||||
|
mm.add(key,"pack200");
|
||||||
|
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","jar","pack200");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#addValues(Object, List)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAddValues_List()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
// Setup the key
|
||||||
|
mm.put(key,"gzip");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip");
|
||||||
|
|
||||||
|
// Add to the key
|
||||||
|
List<String> extras = new ArrayList<String>();
|
||||||
|
extras.add("jar");
|
||||||
|
extras.add("pack200");
|
||||||
|
extras.add("zip");
|
||||||
|
mm.addValues(key,extras);
|
||||||
|
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","jar","pack200","zip");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#addValues(Object, List)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAddValues_List_Empty()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
// Setup the key
|
||||||
|
mm.put(key,"gzip");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip");
|
||||||
|
|
||||||
|
// Add to the key
|
||||||
|
List<String> extras = new ArrayList<String>();
|
||||||
|
mm.addValues(key,extras);
|
||||||
|
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#addValues(Object, String[])}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAddValues_StringArray()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
// Setup the key
|
||||||
|
mm.put(key,"gzip");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip");
|
||||||
|
|
||||||
|
// Add to the key
|
||||||
|
String extras[] = { "jar", "pack200", "zip" };
|
||||||
|
mm.addValues(key,extras);
|
||||||
|
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","jar","pack200","zip");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#addValues(Object, String[])}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAddValues_StringArray_Empty()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
// Setup the key
|
||||||
|
mm.put(key,"gzip");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip");
|
||||||
|
|
||||||
|
// Add to the key
|
||||||
|
String extras[] = new String[0];
|
||||||
|
mm.addValues(key,extras);
|
||||||
|
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#removeValue(Object, Object)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRemoveValue()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
// Setup the key
|
||||||
|
mm.putValues(key,"gzip","jar","pack200");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","jar","pack200");
|
||||||
|
|
||||||
|
// Remove a value
|
||||||
|
mm.removeValue(key,"jar");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","pack200");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#removeValue(Object, Object)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRemoveValue_InvalidItem()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
// Setup the key
|
||||||
|
mm.putValues(key,"gzip","jar","pack200");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","jar","pack200");
|
||||||
|
|
||||||
|
// Remove a value that isn't there
|
||||||
|
mm.removeValue(key,"msi");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","jar","pack200");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#removeValue(Object, Object)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRemoveValue_AllItems()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
// Setup the key
|
||||||
|
mm.putValues(key,"gzip","jar","pack200");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","jar","pack200");
|
||||||
|
|
||||||
|
// Remove a value
|
||||||
|
mm.removeValue(key,"jar");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"gzip","pack200");
|
||||||
|
|
||||||
|
// Remove another value
|
||||||
|
mm.removeValue(key,"gzip");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertValues(mm,key,"pack200");
|
||||||
|
|
||||||
|
// Remove last value
|
||||||
|
mm.removeValue(key,"pack200");
|
||||||
|
assertMapSize(mm,0); // should be empty now
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#removeValue(Object, Object)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testRemoveValue_FromEmpty()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
String key = "formats";
|
||||||
|
|
||||||
|
// Setup the key
|
||||||
|
mm.putValues(key,new String[0]);
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertEmptyValues(mm,key);
|
||||||
|
|
||||||
|
// Remove a value that isn't in the underlying values
|
||||||
|
mm.removeValue(key,"jar");
|
||||||
|
assertMapSize(mm,1);
|
||||||
|
assertEmptyValues(mm,key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#putAll(java.util.Map)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPutAll_Map()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
assertMapSize(mm,0); // Shouldn't have anything yet.
|
||||||
|
|
||||||
|
Map<String,String> input = new HashMap<String,String>();
|
||||||
|
input.put("food","apple");
|
||||||
|
input.put("color","red");
|
||||||
|
input.put("amount","bushel");
|
||||||
|
|
||||||
|
mm.putAll(input);
|
||||||
|
|
||||||
|
assertMapSize(mm,3);
|
||||||
|
assertValues(mm,"food","apple");
|
||||||
|
assertValues(mm,"color","red");
|
||||||
|
assertValues(mm,"amount","bushel");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#putAll(java.util.Map)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPutAll_MultiMap_Simple()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
assertMapSize(mm,0); // Shouldn't have anything yet.
|
||||||
|
|
||||||
|
MultiMap<String> input = new MultiMap<String>();
|
||||||
|
input.put("food","apple");
|
||||||
|
input.put("color","red");
|
||||||
|
input.put("amount","bushel");
|
||||||
|
|
||||||
|
mm.putAll(input);
|
||||||
|
|
||||||
|
assertMapSize(mm,3);
|
||||||
|
assertValues(mm,"food","apple");
|
||||||
|
assertValues(mm,"color","red");
|
||||||
|
assertValues(mm,"amount","bushel");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#putAll(java.util.Map)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testPutAll_MultiMapComplex()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
|
||||||
|
assertMapSize(mm,0); // Shouldn't have anything yet.
|
||||||
|
|
||||||
|
MultiMap<String> input = new MultiMap<String>();
|
||||||
|
input.putValues("food","apple","cherry","raspberry");
|
||||||
|
input.put("color","red");
|
||||||
|
input.putValues("amount","bushel","pint");
|
||||||
|
|
||||||
|
mm.putAll(input);
|
||||||
|
|
||||||
|
assertMapSize(mm,3);
|
||||||
|
assertValues(mm,"food","apple","cherry","raspberry");
|
||||||
|
assertValues(mm,"color","red");
|
||||||
|
assertValues(mm,"amount","bushel","pint");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#toStringArrayMap()}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testToStringArrayMap()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
mm.putValues("food","apple","cherry","raspberry");
|
||||||
|
mm.put("color","red");
|
||||||
|
mm.putValues("amount","bushel","pint");
|
||||||
|
|
||||||
|
assertMapSize(mm,3);
|
||||||
|
|
||||||
|
Map<String,String[]> sam = mm.toStringArrayMap();
|
||||||
|
Assert.assertEquals("String Array Map.size",3,sam.size());
|
||||||
|
|
||||||
|
assertArray("toStringArrayMap(food)", sam.get("food"), "apple","cherry","raspberry");
|
||||||
|
assertArray("toStringArrayMap(color)", sam.get("color"), "red");
|
||||||
|
assertArray("toStringArrayMap(amount)", sam.get("amount"), "bushel","pint");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#toString()}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testToString()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
mm.put("color","red");
|
||||||
|
|
||||||
|
Assert.assertEquals("{color=red}", mm.toString());
|
||||||
|
|
||||||
|
mm.putValues("food","apple","cherry","raspberry");
|
||||||
|
|
||||||
|
Assert.assertEquals("{color=red, food=[apple, cherry, raspberry]}", mm.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#clear()}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testClear()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
mm.putValues("food","apple","cherry","raspberry");
|
||||||
|
mm.put("color","red");
|
||||||
|
mm.putValues("amount","bushel","pint");
|
||||||
|
|
||||||
|
assertMapSize(mm,3);
|
||||||
|
|
||||||
|
mm.clear();
|
||||||
|
|
||||||
|
assertMapSize(mm,0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#containsKey(Object)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testContainsKey()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
mm.putValues("food","apple","cherry","raspberry");
|
||||||
|
mm.put("color","red");
|
||||||
|
mm.putValues("amount","bushel","pint");
|
||||||
|
|
||||||
|
Assert.assertTrue("Contains Key [color]", mm.containsKey("color"));
|
||||||
|
Assert.assertFalse("Contains Key [nutrition]", mm.containsKey("nutrition"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#containsValue(Object)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testContainsValue()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
mm.putValues("food","apple","cherry","raspberry");
|
||||||
|
mm.put("color","red");
|
||||||
|
mm.putValues("amount","bushel","pint");
|
||||||
|
|
||||||
|
Assert.assertTrue("Contains Value [red]", mm.containsValue("red"));
|
||||||
|
Assert.assertFalse("Contains Value [nutrition]", mm.containsValue("nutrition"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link MultiMap#containsValue(Object)}
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testContainsValue_LazyList()
|
||||||
|
{
|
||||||
|
MultiMap<String> mm = new MultiMap<String>();
|
||||||
|
mm.putValues("food","apple","cherry","raspberry");
|
||||||
|
mm.put("color","red");
|
||||||
|
mm.putValues("amount","bushel","pint");
|
||||||
|
|
||||||
|
Object list = LazyList.add(null, "bushel");
|
||||||
|
list = LazyList.add(list, "pint");
|
||||||
|
|
||||||
|
Assert.assertTrue("Contains Value [" + list + "]", mm.containsValue(list));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertArray(String prefix, Object[] actualValues, Object ...expectedValues)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(prefix + ".size",expectedValues.length,actualValues.length);
|
||||||
|
int len = actualValues.length;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(prefix + "[" + i + "]",expectedValues[i],actualValues[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertValues(MultiMap<String> mm, String key, Object... expectedValues)
|
||||||
|
{
|
||||||
|
List<Object> values = mm.getValues(key);
|
||||||
|
|
||||||
|
String prefix = "MultiMap.getValues(" + key + ")";
|
||||||
|
|
||||||
|
Assert.assertNotNull(prefix,values);
|
||||||
|
Assert.assertEquals(prefix + ".size",expectedValues.length,values.size());
|
||||||
|
int len = values.size();
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
Assert.assertEquals(prefix + "[" + i + "]",expectedValues[i],values.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertEmptyValues(MultiMap<String> mm, String key)
|
||||||
|
{
|
||||||
|
List<Object> values = mm.getValues(key);
|
||||||
|
|
||||||
|
String prefix = "MultiMap.getValues(" + key + ")";
|
||||||
|
|
||||||
|
Assert.assertEquals(prefix + ".size",0,LazyList.size(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertMapSize(MultiMap<String> mm, int expectedSize)
|
||||||
|
{
|
||||||
|
Assert.assertEquals("MultiMap.size",expectedSize,mm.size());
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,8 @@ import static org.junit.Assert.assertTrue;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
|
|
||||||
|
import junit.framework.Assert;
|
||||||
|
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -123,8 +125,12 @@ public class LogTest
|
||||||
public void testStdErrLogName()
|
public void testStdErrLogName()
|
||||||
{
|
{
|
||||||
StdErrLog log = new StdErrLog("test");
|
StdErrLog log = new StdErrLog("test");
|
||||||
|
Assert.assertEquals("test",log.getName());
|
||||||
|
|
||||||
Logger next=log.getLogger("next");
|
Logger next=log.getLogger("next");
|
||||||
|
|
||||||
|
Assert.assertEquals("test.next",next.getName());
|
||||||
|
|
||||||
next.info("testing {} {}","next","info");
|
next.info("testing {} {}","next","info");
|
||||||
logContains(":test.next:testing next info");
|
logContains(":test.next:testing next info");
|
||||||
|
|
||||||
|
|
|
@ -17,16 +17,16 @@
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>${servlet.spec.groupId}</groupId>
|
<groupId>${servlet.spec.groupId}</groupId>
|
||||||
<artifactId>${servlet.spec.artifactId}</artifactId>
|
<artifactId>${servlet.spec.artifactId}</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty.toolchain</groupId>
|
||||||
|
<artifactId>jetty-test-helper</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
|
@ -6,7 +6,6 @@ import java.util.Map;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.Buffer;
|
import org.eclipse.jetty.io.Buffer;
|
||||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
|
||||||
import org.eclipse.jetty.websocket.WebSocketParser.FrameHandler;
|
import org.eclipse.jetty.websocket.WebSocketParser.FrameHandler;
|
||||||
|
|
||||||
public class AbstractExtension implements Extension
|
public class AbstractExtension implements Extension
|
||||||
|
|
|
@ -8,7 +8,6 @@ import java.util.zip.Inflater;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.Buffer;
|
import org.eclipse.jetty.io.Buffer;
|
||||||
import org.eclipse.jetty.io.ByteArrayBuffer;
|
import org.eclipse.jetty.io.ByteArrayBuffer;
|
||||||
import org.eclipse.jetty.util.ByteArrayOutputStream2;
|
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
|
||||||
public class DeflateFrameExtension extends AbstractExtension
|
public class DeflateFrameExtension extends AbstractExtension
|
||||||
|
|
|
@ -1,32 +1,18 @@
|
||||||
package org.eclipse.jetty.websocket;
|
package org.eclipse.jetty.websocket;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.BufferedWriter;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.CyclicBarrier;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.Buffer;
|
|
||||||
import org.eclipse.jetty.io.bio.SocketEndPoint;
|
|
||||||
import org.eclipse.jetty.util.B64Code;
|
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
import org.eclipse.jetty.util.TypeUtil;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
|
||||||
import org.eclipse.jetty.websocket.WebSocket.Connection;
|
|
||||||
import org.eclipse.jetty.websocket.WebSocket.FrameConnection;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @version $Revision$ $Date$
|
* @version $Revision$ $Date$
|
||||||
|
@ -68,14 +54,6 @@ public class TestClient implements WebSocket.OnFrame
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
System.err.println("onError: "+message);
|
|
||||||
if (ex!=null)
|
|
||||||
ex.printStackTrace();
|
|
||||||
_handshook.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClose(int closeCode, String message)
|
public void onClose(int closeCode, String message)
|
||||||
{
|
{
|
||||||
_handshook.countDown();
|
_handshook.countDown();
|
||||||
|
@ -141,8 +119,10 @@ public class TestClient implements WebSocket.OnFrame
|
||||||
|
|
||||||
private void open() throws Exception
|
private void open() throws Exception
|
||||||
{
|
{
|
||||||
__client.open(new URI("ws://"+_host+":"+_port+"/"),this,_protocol,_timeout);
|
WebSocketClient client = new WebSocketClient(__client);
|
||||||
_handshook.await(10,TimeUnit.SECONDS);
|
client.setProtocol(_protocol);
|
||||||
|
client.setMaxIdleTime(_timeout);
|
||||||
|
client.open(new URI("ws://"+_host+":"+_port+"/"),this).get(10,TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ping(byte opcode,byte[] data,int fragment) throws Exception
|
public void ping(byte opcode,byte[] data,int fragment) throws Exception
|
||||||
|
|
|
@ -107,12 +107,6 @@ public class TestServer extends Server
|
||||||
System.err.printf("%s#onOpen %s\n",this.getClass().getSimpleName(),connection);
|
System.err.printf("%s#onOpen %s\n",this.getClass().getSimpleName(),connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
if (_verbose)
|
|
||||||
System.err.printf("%s#onOpen %s\n",this.getClass().getSimpleName(),message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onHandshake(FrameConnection connection)
|
public void onHandshake(FrameConnection connection)
|
||||||
{
|
{
|
||||||
if (_verbose)
|
if (_verbose)
|
||||||
|
|
|
@ -29,13 +29,6 @@ public interface WebSocket
|
||||||
*/
|
*/
|
||||||
void onOpen(Connection connection);
|
void onOpen(Connection connection);
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a new websocket connection cannot be created
|
|
||||||
* @param message The error message
|
|
||||||
* @param ex The exception or null
|
|
||||||
*/
|
|
||||||
void onError(String message, Throwable ex);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an established websocket connection closes
|
* Called when an established websocket connection closes
|
||||||
* @param closeCode
|
* @param closeCode
|
||||||
|
@ -155,7 +148,10 @@ public interface WebSocket
|
||||||
byte textOpcode();
|
byte textOpcode();
|
||||||
byte continuationOpcode();
|
byte continuationOpcode();
|
||||||
byte finMask();
|
byte finMask();
|
||||||
|
String getProtocol();
|
||||||
|
void setFakeFragments(boolean fake);
|
||||||
|
|
||||||
|
boolean isFakeFragments();
|
||||||
boolean isControl(byte opcode);
|
boolean isControl(byte opcode);
|
||||||
boolean isText(byte opcode);
|
boolean isText(byte opcode);
|
||||||
boolean isBinary(byte opcode);
|
boolean isBinary(byte opcode);
|
||||||
|
|
|
@ -15,11 +15,8 @@ package org.eclipse.jetty.websocket;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.Buffer;
|
import org.eclipse.jetty.io.Buffer;
|
||||||
import org.eclipse.jetty.io.Buffers;
|
import org.eclipse.jetty.io.Buffers;
|
||||||
import org.eclipse.jetty.io.BuffersFactory;
|
|
||||||
import org.eclipse.jetty.io.Buffers.Type;
|
import org.eclipse.jetty.io.Buffers.Type;
|
||||||
import org.eclipse.jetty.io.ThreadLocalBuffers;
|
import org.eclipse.jetty.io.BuffersFactory;
|
||||||
import org.eclipse.jetty.io.nio.DirectNIOBuffer;
|
|
||||||
import org.eclipse.jetty.io.nio.IndirectNIOBuffer;
|
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
|
|
@ -3,14 +3,21 @@ package org.eclipse.jetty.websocket;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.ProtocolException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.nio.channels.ByteChannel;
|
import java.nio.channels.ByteChannel;
|
||||||
import java.nio.channels.SelectionKey;
|
import java.nio.channels.SelectionKey;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.nio.channels.UnsupportedAddressTypeException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
import org.eclipse.jetty.http.HttpParser;
|
import org.eclipse.jetty.http.HttpParser;
|
||||||
|
@ -29,150 +36,227 @@ import org.eclipse.jetty.util.component.AggregateLifeCycle;
|
||||||
import org.eclipse.jetty.util.log.Logger;
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.eclipse.jetty.util.thread.ThreadPool;
|
import org.eclipse.jetty.util.thread.ThreadPool;
|
||||||
import org.eclipse.jetty.util.thread.Timeout;
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** WebSocket Client
|
||||||
|
* <p>This WebSocket Client class can create multiple websocket connections to multiple destinations.
|
||||||
|
* It uses the same {@link WebSocket} endpoint API as the server.
|
||||||
|
* Simple usage is as follows: <pre>
|
||||||
|
* WebSocketClient client = new WebSocketClient();
|
||||||
|
* client.setMaxIdleTime(500);
|
||||||
|
* client.start();
|
||||||
|
*
|
||||||
|
* WebSocket.Connection connection = client.open(new URI("ws://127.0.0.1:8080/"),new WebSocket.OnTextMessage()
|
||||||
|
* {
|
||||||
|
* public void onOpen(Connection connection)
|
||||||
|
* {
|
||||||
|
* // open notification
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* public void onClose(int closeCode, String message)
|
||||||
|
* {
|
||||||
|
* // close notification
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* public void onMessage(String data)
|
||||||
|
* {
|
||||||
|
* // handle incoming message
|
||||||
|
* }
|
||||||
|
* }).get(5,TimeUnit.SECONDS);
|
||||||
|
*
|
||||||
|
* connection.sendMessage("Hello World");
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
public class WebSocketClient extends AggregateLifeCycle
|
public class WebSocketClient extends AggregateLifeCycle
|
||||||
{
|
{
|
||||||
private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClient.class.getCanonicalName());
|
private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClient.class.getCanonicalName());
|
||||||
private final static Random __random = new Random();
|
private final static Random __random = new Random();
|
||||||
private final static ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept");
|
private final static ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept");
|
||||||
|
|
||||||
|
private final WebSocketClient _root;
|
||||||
|
private final WebSocketClient _parent;
|
||||||
private final ThreadPool _threadPool;
|
private final ThreadPool _threadPool;
|
||||||
private final Selector _selector=new Selector();
|
private final WebSocketClientSelector _selector;
|
||||||
private final Timeout _connectQ=new Timeout();
|
|
||||||
private int _connectTimeout=30000;
|
private final Map<String,String> _cookies=new ConcurrentHashMap<String, String>();
|
||||||
|
private final List<String> _extensions=new CopyOnWriteArrayList<String>();
|
||||||
|
|
||||||
private int _bufferSize=64*1024;
|
private int _bufferSize=64*1024;
|
||||||
private boolean _blockingConnect=false;
|
private String _protocol;
|
||||||
|
private int _maxIdleTime=-1;
|
||||||
|
|
||||||
private WebSocketBuffers _buffers;
|
private WebSocketBuffers _buffers;
|
||||||
|
|
||||||
public WebSocketClient(ThreadPool threadpool)
|
|
||||||
{
|
/* ------------------------------------------------------------ */
|
||||||
_threadPool=threadpool;
|
/** Create a WebSocket Client with default configuration.
|
||||||
addBean(_selector);
|
*/
|
||||||
addBean(_threadPool);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebSocketClient()
|
public WebSocketClient()
|
||||||
{
|
{
|
||||||
this(new QueuedThreadPool());
|
this(new QueuedThreadPool());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Create a WebSocket Client with shared threadpool.
|
||||||
|
* @param threadpool
|
||||||
|
*/
|
||||||
|
public WebSocketClient(ThreadPool threadpool)
|
||||||
|
{
|
||||||
|
_root=this;
|
||||||
|
_parent=null;
|
||||||
|
_threadPool=threadpool;
|
||||||
|
_selector=new WebSocketClientSelector();
|
||||||
|
addBean(_selector);
|
||||||
|
addBean(_threadPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Create a WebSocket Client from another.
|
||||||
|
* <p>If multiple clients are required so that connections created may have different
|
||||||
|
* configurations, then it is more efficient to create a client based on another, so
|
||||||
|
* that the thread pool and IO infrastructure may be shared.
|
||||||
|
*/
|
||||||
|
public WebSocketClient(WebSocketClient parent)
|
||||||
|
{
|
||||||
|
_root=parent._root;
|
||||||
|
_parent=parent;
|
||||||
|
_threadPool=parent._threadPool;
|
||||||
|
_selector=parent._selector;
|
||||||
|
_parent.addBean(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Get the selectorManager. Used to configure the manager.
|
||||||
|
* @return The {@link SelectorManager} instance.
|
||||||
|
*/
|
||||||
public SelectorManager getSelectorManager()
|
public SelectorManager getSelectorManager()
|
||||||
{
|
{
|
||||||
return _selector;
|
return _selector;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Get the ThreadPool.
|
||||||
|
* <p>Used to set/query the thread pool configuration.
|
||||||
|
* @return The {@link ThreadPool}
|
||||||
|
*/
|
||||||
public ThreadPool getThreadPool()
|
public ThreadPool getThreadPool()
|
||||||
{
|
{
|
||||||
return _threadPool;
|
return _threadPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getConnectTimeout()
|
/* ------------------------------------------------------------ */
|
||||||
{
|
/** Get the maxIdleTime for connections opened by this client.
|
||||||
return _connectTimeout;
|
* @return The maxIdleTime in ms, or -1 if the default from {@link #getSelectorManager()} is used.
|
||||||
}
|
*/
|
||||||
|
|
||||||
public void setConnectTimeout(int connectTimeout)
|
|
||||||
{
|
|
||||||
if (isRunning())
|
|
||||||
throw new IllegalStateException(getState());
|
|
||||||
_connectTimeout = connectTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxIdleTime()
|
public int getMaxIdleTime()
|
||||||
{
|
{
|
||||||
return (int)_selector.getMaxIdleTime();
|
return _maxIdleTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Set the maxIdleTime for connections opened by this client.
|
||||||
|
* @param maxIdleTime max idle time in ms
|
||||||
|
*/
|
||||||
public void setMaxIdleTime(int maxIdleTime)
|
public void setMaxIdleTime(int maxIdleTime)
|
||||||
{
|
{
|
||||||
_selector.setMaxIdleTime(maxIdleTime);
|
_maxIdleTime=maxIdleTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Get the WebSocket Buffer size for connections opened by this client.
|
||||||
|
* @return the buffer size in bytes.
|
||||||
|
*/
|
||||||
public int getBufferSize()
|
public int getBufferSize()
|
||||||
{
|
{
|
||||||
return _bufferSize;
|
return _bufferSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Set the WebSocket Buffer size for connections opened by this client.
|
||||||
|
* @param bufferSize the buffer size in bytes.
|
||||||
|
*/
|
||||||
public void setBufferSize(int bufferSize)
|
public void setBufferSize(int bufferSize)
|
||||||
{
|
{
|
||||||
|
if (isRunning())
|
||||||
|
throw new IllegalStateException(getState());
|
||||||
_bufferSize = bufferSize;
|
_bufferSize = bufferSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBlockingConnect()
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Get the subprotocol string for connections opened by this client.
|
||||||
|
* @return The subprotocol
|
||||||
|
*/
|
||||||
|
public String getProtocol()
|
||||||
{
|
{
|
||||||
return _blockingConnect;
|
return _protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBlockingConnect(boolean blockingConnect)
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Set the subprotocol string for connections opened by this client.
|
||||||
|
* @param protocol The subprotocol
|
||||||
|
*/
|
||||||
|
public void setProtocol(String protocol)
|
||||||
{
|
{
|
||||||
_blockingConnect = blockingConnect;
|
_protocol = protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/* ------------------------------------------------------------ */
|
||||||
protected void doStart() throws Exception
|
public Map<String,String> getCookies()
|
||||||
{
|
{
|
||||||
_buffers = new WebSocketBuffers(_bufferSize);
|
return _cookies;
|
||||||
|
}
|
||||||
super.doStart();
|
|
||||||
for (int i=0;i<_selector.getSelectSets();i++)
|
/* ------------------------------------------------------------ */
|
||||||
|
public List<String> getExtensions()
|
||||||
|
{
|
||||||
|
return _extensions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Open a WebSocket connection.
|
||||||
|
* Open a websocket connection to the URI and block until the connection is accepted or there is an error.
|
||||||
|
* @param uri The URI to connect to.
|
||||||
|
* @param websocket The {@link WebSocket} instance to handle incoming events.
|
||||||
|
* @param maxConnectTime The interval to wait for a successful connection
|
||||||
|
* @param units the units of the maxConnectTime
|
||||||
|
* @return A {@link WebSocket.Connection}
|
||||||
|
* @throws IOException
|
||||||
|
* @throws InterruptedException
|
||||||
|
* @throws TimeoutException
|
||||||
|
*/
|
||||||
|
public WebSocket.Connection open(URI uri, WebSocket websocket,long maxConnectTime,TimeUnit units) throws IOException, InterruptedException, TimeoutException
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
final int id=i;
|
return open(uri,websocket).get(maxConnectTime,units);
|
||||||
_threadPool.dispatch(new Runnable(){
|
}
|
||||||
public void run()
|
catch (ExecutionException e)
|
||||||
{
|
{
|
||||||
while(isRunning())
|
Throwable cause = e.getCause();
|
||||||
{
|
if (cause instanceof IOException)
|
||||||
try
|
throw (IOException)cause;
|
||||||
{
|
if (cause instanceof Error)
|
||||||
_selector.doSelect(id);
|
throw (Error)cause;
|
||||||
}
|
if (cause instanceof RuntimeException)
|
||||||
catch (IOException e)
|
throw (RuntimeException)cause;
|
||||||
{
|
throw new RuntimeException(cause);
|
||||||
__log.warn(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_connectQ.setDuration(_connectTimeout);
|
|
||||||
_threadPool.dispatch(new Runnable(){
|
|
||||||
public void run()
|
|
||||||
{
|
|
||||||
while(isRunning())
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Thread.sleep(200); // TODO configure?
|
|
||||||
_connectQ.tick(System.currentTimeMillis());
|
|
||||||
}
|
|
||||||
catch(Exception e)
|
|
||||||
{
|
|
||||||
__log.warn(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void open(URI uri, WebSocket websocket) throws IOException
|
|
||||||
{
|
|
||||||
open(uri,websocket,null,(int)_selector.getMaxIdleTime(),null,null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void open(URI uri, WebSocket websocket, String protocol,int maxIdleTime) throws IOException
|
/* ------------------------------------------------------------ */
|
||||||
{
|
/** Asynchronously open a websocket connection.
|
||||||
open(uri,websocket,protocol,(int)_selector.getMaxIdleTime(),null,null);
|
* Open a websocket connection and return a {@link Future} to obtain the connection.
|
||||||
}
|
* The caller must call {@link Future#get(long, TimeUnit)} if they wish to impose a connect timeout on the open.
|
||||||
|
*
|
||||||
public void open(URI uri, WebSocket websocket, String protocol,int maxIdleTime,Map<String,String> cookies) throws IOException
|
* @param uri The URI to connect to.
|
||||||
{
|
* @param websocket The {@link WebSocket} instance to handle incoming events.
|
||||||
open(uri,websocket,protocol,(int)_selector.getMaxIdleTime(),cookies,null);
|
* @return A {@link Future} to the {@link WebSocket.Connection}
|
||||||
}
|
* @throws IOException
|
||||||
|
*/
|
||||||
public void open(URI uri, WebSocket websocket, String protocol,int maxIdleTime,Map<String,String> cookies,List<String> extensions) throws IOException
|
public Future<WebSocket.Connection> open(URI uri, WebSocket websocket) throws IOException
|
||||||
{
|
{
|
||||||
if (!isStarted())
|
if (!isStarted())
|
||||||
throw new IllegalStateException("!started");
|
throw new IllegalStateException("!started");
|
||||||
|
@ -184,40 +268,66 @@ public class WebSocketClient extends AggregateLifeCycle
|
||||||
|
|
||||||
SocketChannel channel = SocketChannel.open();
|
SocketChannel channel = SocketChannel.open();
|
||||||
channel.socket().setTcpNoDelay(true);
|
channel.socket().setTcpNoDelay(true);
|
||||||
channel.socket().setSoTimeout(getMaxIdleTime());
|
int maxIdleTime = getMaxIdleTime();
|
||||||
|
if (maxIdleTime<0)
|
||||||
|
maxIdleTime=(int)_selector.getMaxIdleTime();
|
||||||
|
if (maxIdleTime>0)
|
||||||
|
channel.socket().setSoTimeout(maxIdleTime);
|
||||||
|
|
||||||
InetSocketAddress address=new InetSocketAddress(uri.getHost(),uri.getPort());
|
InetSocketAddress address=new InetSocketAddress(uri.getHost(),uri.getPort());
|
||||||
|
|
||||||
WebSocketHolder holder=new WebSocketHolder(websocket,uri,protocol,maxIdleTime,cookies,extensions,channel);
|
final WebSocketFuture holder=new WebSocketFuture(websocket,uri,_protocol,maxIdleTime,_cookies,_extensions,channel);
|
||||||
|
|
||||||
|
|
||||||
_connectQ.schedule(holder);
|
channel.configureBlocking(false);
|
||||||
boolean thrown=true;
|
channel.connect(address);
|
||||||
try
|
_selector.register( channel, holder);
|
||||||
{
|
|
||||||
if (isBlockingConnect())
|
|
||||||
{
|
|
||||||
channel.socket().connect(address,0);
|
|
||||||
channel.configureBlocking(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
channel.configureBlocking(false);
|
|
||||||
channel.connect(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
_selector.register( channel, holder);
|
return holder;
|
||||||
thrown=false;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (thrown)
|
|
||||||
holder.cancel();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStart() throws Exception
|
||||||
|
{
|
||||||
|
if (_parent!=null && !_parent.isRunning())
|
||||||
|
throw new IllegalStateException("parent:"+getState());
|
||||||
|
|
||||||
|
_buffers = new WebSocketBuffers(_bufferSize);
|
||||||
|
|
||||||
|
super.doStart();
|
||||||
|
|
||||||
|
// Start a selector and timer if this is the root client
|
||||||
|
if (_parent==null)
|
||||||
|
{
|
||||||
|
for (int i=0;i<_selector.getSelectSets();i++)
|
||||||
|
{
|
||||||
|
final int id=i;
|
||||||
|
_threadPool.dispatch(new Runnable()
|
||||||
|
{
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
while(isRunning())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_selector.doSelect(id);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
__log.warn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Selector extends SelectorManager
|
/* ------------------------------------------------------------ */
|
||||||
|
/** WebSocket Client Selector Manager
|
||||||
|
*/
|
||||||
|
class WebSocketClientSelector extends SelectorManager
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public boolean dispatch(Runnable task)
|
public boolean dispatch(Runnable task)
|
||||||
|
@ -234,18 +344,20 @@ public class WebSocketClient extends AggregateLifeCycle
|
||||||
@Override
|
@Override
|
||||||
protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint)
|
protected Connection newConnection(SocketChannel channel, SelectChannelEndPoint endpoint)
|
||||||
{
|
{
|
||||||
WebSocketHolder holder = (WebSocketHolder) endpoint.getSelectionKey().attachment();
|
WebSocketFuture holder = (WebSocketFuture) endpoint.getSelectionKey().attachment();
|
||||||
return new HandshakeConnection(endpoint,holder);
|
return new HandshakeConnection(endpoint,holder);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void endPointOpened(SelectChannelEndPoint endpoint)
|
protected void endPointOpened(SelectChannelEndPoint endpoint)
|
||||||
{
|
{
|
||||||
|
// TODO expose on outer class
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
|
protected void endPointUpgraded(ConnectedEndPoint endpoint, Connection oldConnection)
|
||||||
{
|
{
|
||||||
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -257,29 +369,34 @@ public class WebSocketClient extends AggregateLifeCycle
|
||||||
@Override
|
@Override
|
||||||
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
|
protected void connectionFailed(SocketChannel channel, Throwable ex, Object attachment)
|
||||||
{
|
{
|
||||||
if (!(attachment instanceof WebSocketHolder))
|
if (!(attachment instanceof WebSocketFuture))
|
||||||
super.connectionFailed(channel,ex,attachment);
|
super.connectionFailed(channel,ex,attachment);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
__log.debug(ex);
|
__log.debug(ex);
|
||||||
WebSocketHolder holder = (WebSocketHolder)attachment;
|
WebSocketFuture holder = (WebSocketFuture)attachment;
|
||||||
holder.cancel();
|
|
||||||
holder.getWebSocket().onError(ex.toString(),ex);
|
holder.handshakeFailed(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/** Handshake Connection.
|
||||||
|
* Handles the connection until the handshake succeeds or fails.
|
||||||
|
*/
|
||||||
class HandshakeConnection extends AbstractConnection
|
class HandshakeConnection extends AbstractConnection
|
||||||
{
|
{
|
||||||
private final SelectChannelEndPoint _endp;
|
private final SelectChannelEndPoint _endp;
|
||||||
private final WebSocketHolder _holder;
|
private final WebSocketFuture _holder;
|
||||||
private final String _key;
|
private final String _key;
|
||||||
private final HttpParser _parser;
|
private final HttpParser _parser;
|
||||||
private String _accept;
|
private String _accept;
|
||||||
private String _error;
|
private String _error;
|
||||||
|
|
||||||
|
|
||||||
public HandshakeConnection(SelectChannelEndPoint endpoint, WebSocketHolder holder)
|
public HandshakeConnection(SelectChannelEndPoint endpoint, WebSocketFuture holder)
|
||||||
{
|
{
|
||||||
super(endpoint,System.currentTimeMillis());
|
super(endpoint,System.currentTimeMillis());
|
||||||
_endp=endpoint;
|
_endp=endpoint;
|
||||||
|
@ -328,15 +445,18 @@ public class WebSocketClient extends AggregateLifeCycle
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
String path=_holder.getURI().getPath();
|
||||||
|
if (path==null || path.length()==0)
|
||||||
|
path="/";
|
||||||
|
|
||||||
String request=
|
String request=
|
||||||
"GET "+_holder.getURI().getPath()+" HTTP/1.1\r\n"+
|
"GET "+path+" HTTP/1.1\r\n"+
|
||||||
"Host: "+holder.getURI().getHost()+":"+_holder.getURI().getPort()+"\r\n"+
|
"Host: "+holder.getURI().getHost()+":"+_holder.getURI().getPort()+"\r\n"+
|
||||||
"Upgrade: websocket\r\n"+
|
"Upgrade: websocket\r\n"+
|
||||||
"Connection: Upgrade\r\n"+
|
"Connection: Upgrade\r\n"+
|
||||||
"Sec-WebSocket-Key: "+_key+"\r\n"+
|
"Sec-WebSocket-Key: "+_key+"\r\n"+
|
||||||
"Sec-WebSocket-Origin: http://example.com\r\n"+
|
"Sec-WebSocket-Origin: http://example.com\r\n"+
|
||||||
"Sec-WebSocket-Version: 8\r\n";
|
"Sec-WebSocket-Version: "+WebSocketConnectionD10.VERSION+"\r\n";
|
||||||
|
|
||||||
if (holder.getProtocol()!=null)
|
if (holder.getProtocol()!=null)
|
||||||
request+="Sec-WebSocket-Protocol: "+holder.getProtocol()+"\r\n";
|
request+="Sec-WebSocket-Protocol: "+holder.getProtocol()+"\r\n";
|
||||||
|
@ -356,16 +476,16 @@ public class WebSocketClient extends AggregateLifeCycle
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ByteArrayBuffer handshake = new ByteArrayBuffer(request);
|
Buffer handshake = new ByteArrayBuffer(request,false);
|
||||||
int len=handshake.length();
|
int len=handshake.length();
|
||||||
if (len!=_endp.flush(handshake))
|
if (len!=_endp.flush(handshake))
|
||||||
throw new IOException("incomplete");
|
throw new IOException("incomplete");
|
||||||
}
|
}
|
||||||
catch(IOException e)
|
catch(IOException e)
|
||||||
{
|
{
|
||||||
__log.debug(e);
|
holder.handshakeFailed(e);
|
||||||
_holder.getWebSocket().onError("Handshake failed",e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Connection handle() throws IOException
|
public Connection handle() throws IOException
|
||||||
|
@ -375,8 +495,7 @@ public class WebSocketClient extends AggregateLifeCycle
|
||||||
switch (_parser.parseAvailable())
|
switch (_parser.parseAvailable())
|
||||||
{
|
{
|
||||||
case -1:
|
case -1:
|
||||||
_holder.cancel();
|
_holder.handshakeFailed(new IOException("Incomplete handshake response"));
|
||||||
_holder.getWebSocket().onError("EOF",new EOFException());
|
|
||||||
return this;
|
return this;
|
||||||
case 0:
|
case 0:
|
||||||
return this;
|
return this;
|
||||||
|
@ -384,25 +503,25 @@ public class WebSocketClient extends AggregateLifeCycle
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (_error==null)
|
||||||
if (_error==null && _accept==null)
|
|
||||||
_error="No Sec-WebSocket-Accept";
|
|
||||||
else if (_error==null && !WebSocketConnectionD10.hashKey(_key).equals(_accept))
|
|
||||||
_error="Bad Sec-WebSocket-Accept";
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
Buffer header=_parser.getHeaderBuffer();
|
if (_accept==null)
|
||||||
WebSocketConnectionD10 connection = new WebSocketConnectionD10(_holder.getWebSocket(),_endp,_buffers,System.currentTimeMillis(),_holder.getMaxIdleTime(),_holder.getProtocol(),null,10, new WebSocketGeneratorD10.RandomMaskGen());
|
_error="No Sec-WebSocket-Accept";
|
||||||
|
else if (!WebSocketConnectionD10.hashKey(_key).equals(_accept))
|
||||||
if (header.hasContent())
|
_error="Bad Sec-WebSocket-Accept";
|
||||||
connection.fillBuffersFrom(header);
|
else
|
||||||
_buffers.returnBuffer(header);
|
{
|
||||||
|
Buffer header=_parser.getHeaderBuffer();
|
||||||
|
WebSocketConnectionD10 connection = new WebSocketConnectionD10(_holder.getWebSocket(),_endp,_buffers,System.currentTimeMillis(),_holder.getMaxIdleTime(),_holder.getProtocol(),null,10, new WebSocketGeneratorD10.RandomMaskGen());
|
||||||
|
|
||||||
if (_holder.getWebSocket() instanceof WebSocket.OnFrame)
|
if (header.hasContent())
|
||||||
((WebSocket.OnFrame)_holder.getWebSocket()).onHandshake((WebSocket.FrameConnection)connection.getConnection());
|
connection.fillBuffersFrom(header);
|
||||||
_holder.cancel();
|
_buffers.returnBuffer(header);
|
||||||
_holder.getWebSocket().onOpen(connection.getConnection());
|
|
||||||
return connection;
|
_holder.onConnection(connection);
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_endp.close();
|
_endp.close();
|
||||||
|
@ -421,13 +540,18 @@ public class WebSocketClient extends AggregateLifeCycle
|
||||||
|
|
||||||
public void closed()
|
public void closed()
|
||||||
{
|
{
|
||||||
_holder.cancel();
|
if (_error!=null)
|
||||||
_holder.getWebSocket().onError(_error==null?"EOF":_error,null);
|
_holder.handshakeFailed(new ProtocolException(_error));
|
||||||
|
else
|
||||||
|
_holder.handshakeFailed(new EOFException());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class WebSocketHolder extends Timeout.Task
|
/* ------------------------------------------------------------ */
|
||||||
|
/** The Future Websocket Connection.
|
||||||
|
*/
|
||||||
|
class WebSocketFuture implements Future<WebSocket.Connection>
|
||||||
{
|
{
|
||||||
final WebSocket _websocket;;
|
final WebSocket _websocket;;
|
||||||
final URI _uri;
|
final URI _uri;
|
||||||
|
@ -435,9 +559,13 @@ public class WebSocketClient extends AggregateLifeCycle
|
||||||
final int _maxIdleTime;
|
final int _maxIdleTime;
|
||||||
final Map<String,String> _cookies;
|
final Map<String,String> _cookies;
|
||||||
final List<String> _extensions;
|
final List<String> _extensions;
|
||||||
final ByteChannel _channel;
|
final CountDownLatch _done = new CountDownLatch(1);
|
||||||
|
|
||||||
|
ByteChannel _channel;
|
||||||
|
WebSocketConnection _connection;
|
||||||
|
Throwable _exception;
|
||||||
|
|
||||||
public WebSocketHolder(WebSocket websocket, URI uri, String protocol, int maxIdleTime, Map<String,String> cookies,List<String> extensions, ByteChannel channel)
|
public WebSocketFuture(WebSocket websocket, URI uri, String protocol, int maxIdleTime, Map<String,String> cookies,List<String> extensions, ByteChannel channel)
|
||||||
{
|
{
|
||||||
_websocket=websocket;
|
_websocket=websocket;
|
||||||
_uri=uri;
|
_uri=uri;
|
||||||
|
@ -448,6 +576,60 @@ public class WebSocketClient extends AggregateLifeCycle
|
||||||
_channel=channel;
|
_channel=channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onConnection(WebSocketConnection connection)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
synchronized (this)
|
||||||
|
{
|
||||||
|
if (_channel!=null)
|
||||||
|
_connection=connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_connection!=null)
|
||||||
|
{
|
||||||
|
if (_websocket instanceof WebSocket.OnFrame)
|
||||||
|
((WebSocket.OnFrame)_websocket).onHandshake((WebSocket.FrameConnection)connection.getConnection());
|
||||||
|
|
||||||
|
_websocket.onOpen(connection.getConnection());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_done.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void handshakeFailed(Throwable ex)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteChannel channel=null;
|
||||||
|
synchronized (this)
|
||||||
|
{
|
||||||
|
if (_channel!=null)
|
||||||
|
{
|
||||||
|
channel=_channel;
|
||||||
|
_channel=null;
|
||||||
|
_exception=ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel!=null)
|
||||||
|
{
|
||||||
|
if (ex instanceof ProtocolException)
|
||||||
|
closeChannel(channel,WebSocketConnectionD10.CLOSE_PROTOCOL,ex.getMessage());
|
||||||
|
else
|
||||||
|
closeChannel(channel,WebSocketConnectionD10.CLOSE_NOCLOSE,ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_done.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String,String> getCookies()
|
public Map<String,String> getCookies()
|
||||||
{
|
{
|
||||||
return _cookies;
|
return _cookies;
|
||||||
|
@ -472,26 +654,116 @@ public class WebSocketClient extends AggregateLifeCycle
|
||||||
{
|
{
|
||||||
return _maxIdleTime;
|
return _maxIdleTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void expired()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
__log.debug("expired "+this);
|
|
||||||
getWebSocket().onError("expired",null);
|
|
||||||
_channel.close();
|
|
||||||
}
|
|
||||||
catch(IOException e)
|
|
||||||
{
|
|
||||||
__log.ignore(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString()
|
public String toString()
|
||||||
{
|
{
|
||||||
return "[" + _uri + ","+_websocket+"]@"+hashCode();
|
return "[" + _uri + ","+_websocket+"]@"+hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean cancel(boolean mayInterruptIfRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ByteChannel channel=null;
|
||||||
|
synchronized (this)
|
||||||
|
{
|
||||||
|
if (_connection==null && _exception==null && _channel!=null)
|
||||||
|
{
|
||||||
|
channel=_channel;
|
||||||
|
_channel=null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel!=null)
|
||||||
|
{
|
||||||
|
closeChannel(channel,WebSocketConnectionD10.CLOSE_NOCLOSE,"cancelled");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_done.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCancelled()
|
||||||
|
{
|
||||||
|
synchronized (this)
|
||||||
|
{
|
||||||
|
return _channel==null && _connection==null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDone()
|
||||||
|
{
|
||||||
|
synchronized (this)
|
||||||
|
{
|
||||||
|
return _connection!=null && _exception==null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public org.eclipse.jetty.websocket.WebSocket.Connection get() throws InterruptedException, ExecutionException
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return get(Long.MAX_VALUE,TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
catch(TimeoutException e)
|
||||||
|
{
|
||||||
|
throw new IllegalStateException("The universe has ended",e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public org.eclipse.jetty.websocket.WebSocket.Connection get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
|
||||||
|
TimeoutException
|
||||||
|
{
|
||||||
|
_done.await(timeout,unit);
|
||||||
|
ByteChannel channel=null;
|
||||||
|
org.eclipse.jetty.websocket.WebSocket.Connection connection=null;
|
||||||
|
Throwable exception=null;
|
||||||
|
synchronized (this)
|
||||||
|
{
|
||||||
|
exception=_exception;
|
||||||
|
if (_connection==null)
|
||||||
|
{
|
||||||
|
exception=_exception;
|
||||||
|
channel=_channel;
|
||||||
|
_channel=null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
connection=_connection.getConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel!=null)
|
||||||
|
closeChannel(channel,WebSocketConnectionD10.CLOSE_NOCLOSE,"timeout");
|
||||||
|
if (exception!=null)
|
||||||
|
throw new ExecutionException(exception);
|
||||||
|
if (connection!=null)
|
||||||
|
return connection;
|
||||||
|
throw new TimeoutException();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeChannel(ByteChannel channel,int code, String message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_websocket.onClose(code,message);
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
__log.warn(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
channel.close();
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
__log.debug(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,4 +17,6 @@ public interface WebSocketConnection extends Connection
|
||||||
void handshake(HttpServletRequest request, HttpServletResponse response, String origin, String subprotocol) throws IOException;
|
void handshake(HttpServletRequest request, HttpServletResponse response, String origin, String subprotocol) throws IOException;
|
||||||
|
|
||||||
List<Extension> getExtensions();
|
List<Extension> getExtensions();
|
||||||
|
|
||||||
|
WebSocket.Connection getConnection();
|
||||||
}
|
}
|
|
@ -87,6 +87,13 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
public org.eclipse.jetty.websocket.WebSocket.Connection getConnection()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public void setHixieKeys(String key1,String key2)
|
public void setHixieKeys(String key1,String key2)
|
||||||
{
|
{
|
||||||
|
@ -523,4 +530,16 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFakeFragments(boolean fake)
|
||||||
|
{
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFakeFragments()
|
||||||
|
{
|
||||||
|
// TODO Auto-generated method stub
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,10 +33,10 @@ import org.eclipse.jetty.util.B64Code;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
|
|
||||||
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
|
|
||||||
import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
|
import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
|
||||||
import org.eclipse.jetty.websocket.WebSocket.OnControl;
|
import org.eclipse.jetty.websocket.WebSocket.OnControl;
|
||||||
|
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
|
||||||
|
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
|
||||||
|
|
||||||
public class WebSocketConnectionD06 extends AbstractConnection implements WebSocketConnection
|
public class WebSocketConnectionD06 extends AbstractConnection implements WebSocketConnection
|
||||||
{
|
{
|
||||||
|
@ -491,6 +491,15 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc
|
||||||
{
|
{
|
||||||
return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort();
|
return this.getClass().getSimpleName()+"@"+_endp.getLocalAddr()+":"+_endp.getLocalPort()+"<->"+_endp.getRemoteAddr()+":"+_endp.getRemotePort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setFakeFragments(boolean fake)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFakeFragments()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
|
|
@ -33,10 +33,10 @@ import org.eclipse.jetty.util.B64Code;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
|
|
||||||
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
|
|
||||||
import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
|
import org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage;
|
||||||
import org.eclipse.jetty.websocket.WebSocket.OnControl;
|
import org.eclipse.jetty.websocket.WebSocket.OnControl;
|
||||||
|
import org.eclipse.jetty.websocket.WebSocket.OnFrame;
|
||||||
|
import org.eclipse.jetty.websocket.WebSocket.OnTextMessage;
|
||||||
import org.eclipse.jetty.websocket.WebSocketGeneratorD10.MaskGen;
|
import org.eclipse.jetty.websocket.WebSocketGeneratorD10.MaskGen;
|
||||||
|
|
||||||
public class WebSocketConnectionD10 extends AbstractConnection implements WebSocketConnection
|
public class WebSocketConnectionD10 extends AbstractConnection implements WebSocketConnection
|
||||||
|
@ -45,7 +45,8 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
final static byte OP_TEXT = 0x01;
|
final static byte OP_TEXT = 0x01;
|
||||||
final static byte OP_BINARY = 0x02;
|
final static byte OP_BINARY = 0x02;
|
||||||
final static byte OP_EXT_DATA = 0x03;
|
final static byte OP_EXT_DATA = 0x03;
|
||||||
|
|
||||||
|
final static byte OP_CONTROL = 0x08;
|
||||||
final static byte OP_CLOSE = 0x08;
|
final static byte OP_CLOSE = 0x08;
|
||||||
final static byte OP_PING = 0x09;
|
final static byte OP_PING = 0x09;
|
||||||
final static byte OP_PONG = 0x0A;
|
final static byte OP_PONG = 0x0A;
|
||||||
|
@ -60,14 +61,18 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
final static int CLOSE_NOCLOSE=1006;
|
final static int CLOSE_NOCLOSE=1006;
|
||||||
final static int CLOSE_NOTUTF8=1007;
|
final static int CLOSE_NOTUTF8=1007;
|
||||||
|
|
||||||
|
final static int FLAG_FIN=0x8;
|
||||||
|
|
||||||
|
final static int VERSION=8;
|
||||||
|
|
||||||
static boolean isLastFrame(byte flags)
|
static boolean isLastFrame(byte flags)
|
||||||
{
|
{
|
||||||
return (flags&0x8)!=0;
|
return (flags&FLAG_FIN)!=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isControlFrame(byte opcode)
|
static boolean isControlFrame(byte opcode)
|
||||||
{
|
{
|
||||||
return (opcode&0x8)!=0;
|
return (opcode&OP_CONTROL)!=0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static byte[] MAGIC;
|
private final static byte[] MAGIC;
|
||||||
|
@ -84,9 +89,10 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
private final OnControl _onControl;
|
private final OnControl _onControl;
|
||||||
private final String _protocol;
|
private final String _protocol;
|
||||||
private final int _draft;
|
private final int _draft;
|
||||||
|
private final ClassLoader _context;
|
||||||
private int _close;
|
private int _close;
|
||||||
private boolean _closedIn;
|
private volatile boolean _closedIn;
|
||||||
private boolean _closedOut;
|
private volatile boolean _closedOut;
|
||||||
private int _maxTextMessageSize;
|
private int _maxTextMessageSize;
|
||||||
private int _maxBinaryMessageSize=-1;
|
private int _maxBinaryMessageSize=-1;
|
||||||
|
|
||||||
|
@ -103,12 +109,12 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final WebSocketParser.FrameHandler _frameHandler= new FrameHandlerD07();
|
private final WebSocketParser.FrameHandler _frameHandler= new WSFrameHandler();
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
private final WebSocket.FrameConnection _connection = new FrameConnectionD10();
|
private final WebSocket.FrameConnection _connection = new WSFrameConnection();
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -124,6 +130,8 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
{
|
{
|
||||||
super(endpoint,timestamp);
|
super(endpoint,timestamp);
|
||||||
|
|
||||||
|
_context=Thread.currentThread().getContextClassLoader();
|
||||||
|
|
||||||
// TODO - can we use the endpoint idle mechanism?
|
// TODO - can we use the endpoint idle mechanism?
|
||||||
if (endpoint instanceof AsyncEndPoint)
|
if (endpoint instanceof AsyncEndPoint)
|
||||||
((AsyncEndPoint)endpoint).cancelIdle();
|
((AsyncEndPoint)endpoint).cancelIdle();
|
||||||
|
@ -204,6 +212,9 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public Connection handle() throws IOException
|
public Connection handle() throws IOException
|
||||||
{
|
{
|
||||||
|
Thread current = Thread.currentThread();
|
||||||
|
ClassLoader oldcontext = current.getContextClassLoader();
|
||||||
|
current.setContextClassLoader(_context);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// handle the framing protocol
|
// handle the framing protocol
|
||||||
|
@ -237,6 +248,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
current.setContextClassLoader(oldcontext);
|
||||||
if (_endp.isOpen())
|
if (_endp.isOpen())
|
||||||
{
|
{
|
||||||
_generator.idle();
|
_generator.idle();
|
||||||
|
@ -339,7 +351,6 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
_close=code;
|
_close=code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (closed)
|
if (closed)
|
||||||
|
@ -358,7 +369,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
|
byte[] bytes = ("xx"+(message==null?"":message)).getBytes(StringUtil.__ISO_8859_1);
|
||||||
bytes[0]=(byte)(code/0x100);
|
bytes[0]=(byte)(code/0x100);
|
||||||
bytes[1]=(byte)(code%0x100);
|
bytes[1]=(byte)(code%0x100);
|
||||||
_outbound.addFrame((byte)0x8,WebSocketConnectionD10.OP_CLOSE,bytes,0,bytes.length);
|
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD10.OP_CLOSE,bytes,0,bytes.length);
|
||||||
}
|
}
|
||||||
_outbound.flush();
|
_outbound.flush();
|
||||||
|
|
||||||
|
@ -388,29 +399,29 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
private class FrameConnectionD10 implements WebSocket.FrameConnection
|
private class WSFrameConnection implements WebSocket.FrameConnection
|
||||||
{
|
{
|
||||||
volatile boolean _disconnecting;
|
volatile boolean _disconnecting;
|
||||||
int _maxTextMessage=WebSocketConnectionD10.this._maxTextMessageSize;
|
int _maxTextMessage=WebSocketConnectionD10.this._maxTextMessageSize;
|
||||||
int _maxBinaryMessage=WebSocketConnectionD10.this._maxBinaryMessageSize;
|
int _maxBinaryMessage=WebSocketConnectionD10.this._maxBinaryMessageSize;
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public synchronized void sendMessage(String content) throws IOException
|
public void sendMessage(String content) throws IOException
|
||||||
{
|
{
|
||||||
if (_closedOut)
|
if (_closedOut)
|
||||||
throw new IOException("closing");
|
throw new IOException("closing");
|
||||||
byte[] data = content.getBytes(StringUtil.__UTF8);
|
byte[] data = content.getBytes(StringUtil.__UTF8);
|
||||||
_outbound.addFrame((byte)0x8,WebSocketConnectionD10.OP_TEXT,data,0,data.length);
|
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD10.OP_TEXT,data,0,data.length);
|
||||||
checkWriteable();
|
checkWriteable();
|
||||||
_idle.access(_endp);
|
_idle.access(_endp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public synchronized void sendMessage(byte[] content, int offset, int length) throws IOException
|
public void sendMessage(byte[] content, int offset, int length) throws IOException
|
||||||
{
|
{
|
||||||
if (_closedOut)
|
if (_closedOut)
|
||||||
throw new IOException("closing");
|
throw new IOException("closing");
|
||||||
_outbound.addFrame((byte)0x8,WebSocketConnectionD10.OP_BINARY,content,offset,length);
|
_outbound.addFrame((byte)FLAG_FIN,WebSocketConnectionD10.OP_BINARY,content,offset,length);
|
||||||
checkWriteable();
|
checkWriteable();
|
||||||
_idle.access(_endp);
|
_idle.access(_endp);
|
||||||
}
|
}
|
||||||
|
@ -430,7 +441,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
{
|
{
|
||||||
if (_closedOut)
|
if (_closedOut)
|
||||||
throw new IOException("closing");
|
throw new IOException("closing");
|
||||||
_outbound.addFrame((byte)0x8,ctrl,data,offset,length);
|
_outbound.addFrame((byte)FLAG_FIN,ctrl,data,offset,length);
|
||||||
checkWriteable();
|
checkWriteable();
|
||||||
_idle.access(_endp);
|
_idle.access(_endp);
|
||||||
}
|
}
|
||||||
|
@ -507,7 +518,7 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public byte finMask()
|
public byte finMask()
|
||||||
{
|
{
|
||||||
return 0x8;
|
return FLAG_FIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -557,6 +568,18 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
{
|
{
|
||||||
close(CLOSE_NORMAL,null);
|
close(CLOSE_NORMAL,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
public void setFakeFragments(boolean fake)
|
||||||
|
{
|
||||||
|
_parser.setFakeFragments(fake);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
public boolean isFakeFragments()
|
||||||
|
{
|
||||||
|
return _parser.isFakeFragments();
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public String toString()
|
public String toString()
|
||||||
|
@ -568,13 +591,13 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
private class FrameHandlerD07 implements WebSocketParser.FrameHandler
|
private class WSFrameHandler implements WebSocketParser.FrameHandler
|
||||||
{
|
{
|
||||||
private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
|
private final Utf8StringBuilder _utf8 = new Utf8StringBuilder();
|
||||||
private ByteArrayBuffer _aggregate;
|
private ByteArrayBuffer _aggregate;
|
||||||
private byte _opcode=-1;
|
private byte _opcode=-1;
|
||||||
|
|
||||||
public void onFrame(byte flags, byte opcode, Buffer buffer)
|
public void onFrame(final byte flags, final byte opcode, final Buffer buffer)
|
||||||
{
|
{
|
||||||
boolean lastFrame = isLastFrame(flags);
|
boolean lastFrame = isLastFrame(flags);
|
||||||
|
|
||||||
|
@ -583,176 +606,176 @@ public class WebSocketConnectionD10 extends AbstractConnection implements WebSoc
|
||||||
// Ignore incoming after a close
|
// Ignore incoming after a close
|
||||||
if (_closedIn)
|
if (_closedIn)
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
byte[] array=buffer.array();
|
byte[] array=buffer.array();
|
||||||
|
|
||||||
// Deliver frame if websocket is a FrameWebSocket
|
// Deliver frame if websocket is a FrameWebSocket
|
||||||
if (_onFrame!=null)
|
if (_onFrame!=null)
|
||||||
|
{
|
||||||
|
if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_onControl!=null && isControlFrame(opcode))
|
||||||
|
{
|
||||||
|
if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(opcode)
|
||||||
|
{
|
||||||
|
case WebSocketConnectionD10.OP_CONTINUATION:
|
||||||
{
|
{
|
||||||
if (_onFrame.onFrame(flags,opcode,array,buffer.getIndex(),buffer.length()))
|
// If text, append to the message buffer
|
||||||
return;
|
if (_onTextMessage!=null && _opcode==WebSocketConnectionD10.OP_TEXT)
|
||||||
}
|
|
||||||
|
|
||||||
if (_onControl!=null && isControlFrame(opcode))
|
|
||||||
{
|
|
||||||
if (_onControl.onControl(opcode,array,buffer.getIndex(),buffer.length()))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(opcode)
|
|
||||||
{
|
|
||||||
case WebSocketConnectionD10.OP_CONTINUATION:
|
|
||||||
{
|
{
|
||||||
// If text, append to the message buffer
|
if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
|
||||||
if (_opcode==WebSocketConnectionD10.OP_TEXT && _connection.getMaxTextMessageSize()>=0)
|
|
||||||
{
|
{
|
||||||
if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
|
// If this is the last fragment, deliver the text buffer
|
||||||
|
if (lastFrame)
|
||||||
{
|
{
|
||||||
// If this is the last fragment, deliver the text buffer
|
_opcode=-1;
|
||||||
if (lastFrame && _onTextMessage!=null)
|
String msg =_utf8.toString();
|
||||||
|
_utf8.reset();
|
||||||
|
_onTextMessage.onMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
textMessageTooLarge();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
|
||||||
|
{
|
||||||
|
if (checkBinaryMessageSize(_aggregate.length(),buffer.length()))
|
||||||
|
{
|
||||||
|
_aggregate.put(buffer);
|
||||||
|
|
||||||
|
// If this is the last fragment, deliver
|
||||||
|
if (lastFrame && _onBinaryMessage!=null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
|
||||||
|
}
|
||||||
|
finally
|
||||||
{
|
{
|
||||||
_opcode=-1;
|
_opcode=-1;
|
||||||
String msg =_utf8.toString();
|
_aggregate.clear();
|
||||||
_utf8.reset();
|
|
||||||
_onTextMessage.onMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
|
|
||||||
_utf8.reset();
|
|
||||||
_opcode=-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (_opcode>=0 && _connection.getMaxBinaryMessageSize()>=0)
|
|
||||||
{
|
|
||||||
if (_aggregate.space()<_aggregate.length())
|
|
||||||
{
|
|
||||||
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
|
|
||||||
_aggregate.clear();
|
|
||||||
_opcode=-1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_aggregate.put(buffer);
|
|
||||||
|
|
||||||
// If this is the last fragment, deliver
|
|
||||||
if (lastFrame && _onBinaryMessage!=null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_onBinaryMessage.onMessage(_aggregate.array(),_aggregate.getIndex(),_aggregate.length());
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_opcode=-1;
|
|
||||||
_aggregate.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case WebSocketConnectionD10.OP_PING:
|
break;
|
||||||
{
|
|
||||||
Log.debug("PING {}",this);
|
|
||||||
if (!_closedOut)
|
|
||||||
_connection.sendControl(WebSocketConnectionD10.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case WebSocketConnectionD10.OP_PONG:
|
|
||||||
{
|
|
||||||
Log.debug("PONG {}",this);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case WebSocketConnectionD10.OP_CLOSE:
|
|
||||||
{
|
|
||||||
int code=WebSocketConnectionD10.CLOSE_NOCODE;
|
|
||||||
String message=null;
|
|
||||||
if (buffer.length()>=2)
|
|
||||||
{
|
|
||||||
code=buffer.array()[buffer.getIndex()]*0x100+buffer.array()[buffer.getIndex()+1];
|
|
||||||
if (buffer.length()>2)
|
|
||||||
message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
|
|
||||||
}
|
|
||||||
closeIn(code,message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
case WebSocketConnectionD10.OP_TEXT:
|
|
||||||
{
|
|
||||||
if(_onTextMessage!=null)
|
|
||||||
{
|
|
||||||
if (lastFrame)
|
|
||||||
{
|
|
||||||
// Deliver the message
|
|
||||||
_onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_connection.getMaxTextMessageSize()>=0)
|
|
||||||
{
|
|
||||||
// If this is a text fragment, append to buffer
|
|
||||||
if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
|
|
||||||
_opcode=WebSocketConnectionD10.OP_TEXT;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_utf8.reset();
|
|
||||||
_opcode=-1;
|
|
||||||
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
{
|
|
||||||
if (_onBinaryMessage!=null)
|
|
||||||
{
|
|
||||||
if (lastFrame)
|
|
||||||
{
|
|
||||||
_onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_connection.getMaxBinaryMessageSize()>=0)
|
|
||||||
{
|
|
||||||
if (buffer.length()>_connection.getMaxBinaryMessageSize())
|
|
||||||
{
|
|
||||||
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
|
|
||||||
if (_aggregate!=null)
|
|
||||||
_aggregate.clear();
|
|
||||||
_opcode=-1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_opcode=opcode;
|
|
||||||
if (_aggregate==null)
|
|
||||||
_aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
|
|
||||||
_aggregate.put(buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
case WebSocketConnectionD10.OP_PING:
|
||||||
catch(ThreadDeath th)
|
{
|
||||||
{
|
Log.debug("PING {}",this);
|
||||||
throw th;
|
if (!_closedOut)
|
||||||
}
|
_connection.sendControl(WebSocketConnectionD10.OP_PONG,buffer.array(),buffer.getIndex(),buffer.length());
|
||||||
catch(Throwable th)
|
break;
|
||||||
{
|
}
|
||||||
Log.warn(th);
|
|
||||||
|
case WebSocketConnectionD10.OP_PONG:
|
||||||
|
{
|
||||||
|
Log.debug("PONG {}",this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WebSocketConnectionD10.OP_CLOSE:
|
||||||
|
{
|
||||||
|
int code=WebSocketConnectionD10.CLOSE_NOCODE;
|
||||||
|
String message=null;
|
||||||
|
if (buffer.length()>=2)
|
||||||
|
{
|
||||||
|
code=buffer.array()[buffer.getIndex()]*0x100+buffer.array()[buffer.getIndex()+1];
|
||||||
|
if (buffer.length()>2)
|
||||||
|
message=new String(buffer.array(),buffer.getIndex()+2,buffer.length()-2,StringUtil.__UTF8);
|
||||||
|
}
|
||||||
|
closeIn(code,message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case WebSocketConnectionD10.OP_TEXT:
|
||||||
|
{
|
||||||
|
if(_onTextMessage!=null)
|
||||||
|
{
|
||||||
|
if (_connection.getMaxTextMessageSize()<=0)
|
||||||
|
{
|
||||||
|
// No size limit, so handle only final frames
|
||||||
|
if (lastFrame)
|
||||||
|
_onTextMessage.onMessage(buffer.toString(StringUtil.__UTF8));
|
||||||
|
}
|
||||||
|
else if (_utf8.append(buffer.array(),buffer.getIndex(),buffer.length(),_connection.getMaxTextMessageSize()))
|
||||||
|
{
|
||||||
|
if (lastFrame)
|
||||||
|
{
|
||||||
|
String msg =_utf8.toString();
|
||||||
|
_utf8.reset();
|
||||||
|
_onTextMessage.onMessage(msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_opcode=WebSocketConnectionD10.OP_TEXT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
textMessageTooLarge();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
if (_onBinaryMessage!=null && checkBinaryMessageSize(0,buffer.length()))
|
||||||
|
{
|
||||||
|
if (lastFrame)
|
||||||
|
{
|
||||||
|
_onBinaryMessage.onMessage(array,buffer.getIndex(),buffer.length());
|
||||||
|
}
|
||||||
|
else if (_connection.getMaxBinaryMessageSize()>=0)
|
||||||
|
{
|
||||||
|
_opcode=opcode;
|
||||||
|
// TODO use a growing buffer rather than a fixed one.
|
||||||
|
if (_aggregate==null)
|
||||||
|
_aggregate=new ByteArrayBuffer(_connection.getMaxBinaryMessageSize());
|
||||||
|
_aggregate.put(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch(ThreadDeath th)
|
||||||
|
{
|
||||||
|
throw th;
|
||||||
|
}
|
||||||
|
catch(Throwable th)
|
||||||
|
{
|
||||||
|
Log.warn(th);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkBinaryMessageSize(int bufferLen, int length)
|
||||||
|
{
|
||||||
|
int max = _connection.getMaxBinaryMessageSize();
|
||||||
|
if (max>0 && (bufferLen+length)>max)
|
||||||
|
{
|
||||||
|
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Message size > "+_connection.getMaxBinaryMessageSize());
|
||||||
|
_opcode=-1;
|
||||||
|
if (_aggregate!=null)
|
||||||
|
_aggregate.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void textMessageTooLarge()
|
||||||
|
{
|
||||||
|
_connection.close(WebSocketConnectionD10.CLOSE_LARGE,"Text message size > "+_connection.getMaxTextMessageSize()+" chars");
|
||||||
|
|
||||||
|
_opcode=-1;
|
||||||
|
_utf8.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close(int code,String message)
|
public void close(int code,String message)
|
||||||
|
|
|
@ -20,7 +20,6 @@ import java.util.Random;
|
||||||
import org.eclipse.jetty.io.Buffer;
|
import org.eclipse.jetty.io.Buffer;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.EofException;
|
import org.eclipse.jetty.io.EofException;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
|
|
@ -14,13 +14,11 @@
|
||||||
package org.eclipse.jetty.websocket;
|
package org.eclipse.jetty.websocket;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.eclipse.jetty.io.Buffer;
|
import org.eclipse.jetty.io.Buffer;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.EofException;
|
import org.eclipse.jetty.io.EofException;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
|
|
@ -18,8 +18,6 @@ import java.io.IOException;
|
||||||
import org.eclipse.jetty.io.Buffer;
|
import org.eclipse.jetty.io.Buffer;
|
||||||
import org.eclipse.jetty.io.Buffers;
|
import org.eclipse.jetty.io.Buffers;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
|
||||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,6 @@ import java.io.IOException;
|
||||||
import org.eclipse.jetty.io.Buffer;
|
import org.eclipse.jetty.io.Buffer;
|
||||||
import org.eclipse.jetty.io.Buffers;
|
import org.eclipse.jetty.io.Buffers;
|
||||||
import org.eclipse.jetty.io.EndPoint;
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
|
||||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
|
||||||
import org.eclipse.jetty.util.log.Log;
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,7 +46,6 @@ public class WebSocketParserD10 implements WebSocketParser
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
private final WebSocketBuffers _buffers;
|
private final WebSocketBuffers _buffers;
|
||||||
private final EndPoint _endp;
|
private final EndPoint _endp;
|
||||||
private final FrameHandler _handler;
|
private final FrameHandler _handler;
|
||||||
|
@ -63,6 +60,7 @@ public class WebSocketParserD10 implements WebSocketParser
|
||||||
private final byte[] _mask = new byte[4];
|
private final byte[] _mask = new byte[4];
|
||||||
private int _m;
|
private int _m;
|
||||||
private boolean _skip;
|
private boolean _skip;
|
||||||
|
private boolean _fakeFragments=true;
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
|
@ -81,6 +79,24 @@ public class WebSocketParserD10 implements WebSocketParser
|
||||||
_state=State.START;
|
_state=State.START;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* @return True if fake fragments should be created for frames larger than the buffer.
|
||||||
|
*/
|
||||||
|
public boolean isFakeFragments()
|
||||||
|
{
|
||||||
|
return _fakeFragments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* @param fakeFragments True if fake fragments should be created for frames larger than the buffer.
|
||||||
|
*/
|
||||||
|
public void setFakeFragments(boolean fakeFragments)
|
||||||
|
{
|
||||||
|
_fakeFragments = fakeFragments;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public boolean isBufferEmpty()
|
public boolean isBufferEmpty()
|
||||||
{
|
{
|
||||||
|
@ -122,7 +138,33 @@ public class WebSocketParserD10 implements WebSocketParser
|
||||||
|
|
||||||
// if no space, then the data is too big for buffer
|
// if no space, then the data is too big for buffer
|
||||||
if (_buffer.space() == 0)
|
if (_buffer.space() == 0)
|
||||||
|
{
|
||||||
|
// Can we send a fake frame?
|
||||||
|
if (_fakeFragments && _state==State.DATA)
|
||||||
|
{
|
||||||
|
Buffer data =_buffer.get(4*(available/4));
|
||||||
|
_buffer.compact();
|
||||||
|
if (_masked)
|
||||||
|
{
|
||||||
|
if (data.array()==null)
|
||||||
|
data=_buffer.asMutableBuffer();
|
||||||
|
byte[] array = data.array();
|
||||||
|
final int end=data.putIndex();
|
||||||
|
for (int i=data.getIndex();i<end;i++)
|
||||||
|
array[i]^=_mask[_m++%4];
|
||||||
|
}
|
||||||
|
|
||||||
|
// System.err.printf("%s %s %s >>\n",TypeUtil.toHexString(_flags),TypeUtil.toHexString(_opcode),data.length());
|
||||||
|
events++;
|
||||||
|
_bytesNeeded-=data.length();
|
||||||
|
_handler.onFrame((byte)(_flags&(0xff^WebSocketConnectionD10.FLAG_FIN)), _opcode, data);
|
||||||
|
|
||||||
|
_opcode=WebSocketConnectionD10.OP_CONTINUATION;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_buffer.space() == 0)
|
||||||
throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
|
throw new IllegalStateException("FULL: "+_state+" "+_bytesNeeded+">"+_buffer.capacity());
|
||||||
|
}
|
||||||
|
|
||||||
// catch IOExceptions (probably EOF) and try to parse what we have
|
// catch IOExceptions (probably EOF) and try to parse what we have
|
||||||
try
|
try
|
||||||
|
@ -202,7 +244,7 @@ public class WebSocketParserD10 implements WebSocketParser
|
||||||
_length = _length*0x100 + (0xff&b);
|
_length = _length*0x100 + (0xff&b);
|
||||||
if (--_bytesNeeded==0)
|
if (--_bytesNeeded==0)
|
||||||
{
|
{
|
||||||
if (_length>_buffer.capacity())
|
if (_length>_buffer.capacity() && !_fakeFragments)
|
||||||
{
|
{
|
||||||
events++;
|
events++;
|
||||||
_handler.close(WebSocketConnectionD10.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());
|
_handler.close(WebSocketConnectionD10.CLOSE_LARGE,"frame size "+_length+">"+_buffer.capacity());
|
||||||
|
|
|
@ -2,27 +2,48 @@ package org.eclipse.jetty.websocket;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.ConnectException;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.concurrent.BlockingQueue;
|
import java.util.concurrent.BlockingQueue;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Exchanger;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import org.eclipse.jetty.util.BlockingArrayQueue;
|
import org.eclipse.jetty.util.BlockingArrayQueue;
|
||||||
import org.eclipse.jetty.websocket.WebSocket.Connection;
|
import org.eclipse.jetty.util.IO;
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
||||||
public class WebSocketClientTest
|
public class WebSocketClientTest
|
||||||
{
|
{
|
||||||
|
private ServerSocket _server;
|
||||||
|
private int _serverPort;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void startServer() throws IOException {
|
||||||
|
_server = new ServerSocket();
|
||||||
|
_server.bind(null);
|
||||||
|
_serverPort = _server.getLocalPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void stopServer() throws IOException {
|
||||||
|
if(_server != null) {
|
||||||
|
_server.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBadURL() throws Exception
|
public void testBadURL() throws Exception
|
||||||
{
|
{
|
||||||
|
@ -39,10 +60,6 @@ public class WebSocketClientTest
|
||||||
{
|
{
|
||||||
open.set(true);
|
open.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClose(int closeCode, String message)
|
public void onClose(int closeCode, String message)
|
||||||
{}
|
{}
|
||||||
|
@ -57,553 +74,287 @@ public class WebSocketClientTest
|
||||||
Assert.assertTrue(bad);
|
Assert.assertTrue(bad);
|
||||||
Assert.assertFalse(open.get());
|
Assert.assertFalse(open.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBlockingConnectionRefused() throws Exception
|
|
||||||
{
|
|
||||||
WebSocketClient client = new WebSocketClient();
|
|
||||||
client.start();
|
|
||||||
client.setBlockingConnect(true);
|
|
||||||
|
|
||||||
boolean bad=false;
|
|
||||||
final AtomicBoolean open = new AtomicBoolean();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
client.open(new URI("ws://127.0.0.1:1"),new WebSocket()
|
|
||||||
{
|
|
||||||
public void onOpen(Connection connection)
|
|
||||||
{
|
|
||||||
open.set(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClose(int closeCode, String message)
|
|
||||||
{}
|
|
||||||
});
|
|
||||||
|
|
||||||
Assert.fail();
|
|
||||||
}
|
|
||||||
catch(IOException e)
|
|
||||||
{
|
|
||||||
bad=true;
|
|
||||||
}
|
|
||||||
Assert.assertTrue(bad);
|
|
||||||
Assert.assertFalse(open.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAsyncConnectionRefused() throws Exception
|
public void testAsyncConnectionRefused() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient();
|
||||||
client.setConnectTimeout(1000);
|
|
||||||
client.start();
|
client.start();
|
||||||
client.setBlockingConnect(false);
|
|
||||||
|
|
||||||
boolean bad=false;
|
|
||||||
final AtomicBoolean open = new AtomicBoolean();
|
final AtomicBoolean open = new AtomicBoolean();
|
||||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
|
||||||
final AtomicInteger close = new AtomicInteger();
|
final AtomicInteger close = new AtomicInteger();
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
|
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:1"),new WebSocket()
|
||||||
|
{
|
||||||
|
public void onOpen(Connection connection)
|
||||||
|
{
|
||||||
|
open.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClose(int closeCode, String message)
|
||||||
|
{
|
||||||
|
close.set(closeCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Throwable error=null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.open(new URI("ws://127.0.0.1:1"),new WebSocket()
|
future.get(1,TimeUnit.SECONDS);
|
||||||
{
|
Assert.fail();
|
||||||
public void onOpen(Connection connection)
|
|
||||||
{
|
|
||||||
open.set(true);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
error.set(message);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClose(int closeCode, String message)
|
|
||||||
{
|
|
||||||
close.set(closeCode);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch(IOException e)
|
catch(ExecutionException e)
|
||||||
{
|
{
|
||||||
bad=true;
|
error=e.getCause();
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.assertFalse(bad);
|
|
||||||
Assert.assertFalse(open.get());
|
Assert.assertFalse(open.get());
|
||||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
|
||||||
Assert.assertNotNull(error.get());
|
Assert.assertTrue(error instanceof ConnectException);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testBlockingConnectionNotAccepted() throws Exception
|
|
||||||
{
|
|
||||||
WebSocketClient client = new WebSocketClient();
|
|
||||||
client.setConnectTimeout(500);
|
|
||||||
client.setBlockingConnect(true);
|
|
||||||
client.start();
|
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket();
|
|
||||||
server.bind(null);
|
|
||||||
int port = server.getLocalPort();
|
|
||||||
|
|
||||||
|
|
||||||
boolean bad=false;
|
|
||||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
|
||||||
{
|
|
||||||
public void onOpen(Connection connection)
|
|
||||||
{
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
error.set(message);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClose(int closeCode, String message)
|
|
||||||
{
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch(IOException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
bad=true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
|
||||||
Assert.assertTrue(bad||error.get()!=null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAsyncConnectionNotAccepted() throws Exception
|
public void testConnectionNotAccepted() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient();
|
||||||
client.setBlockingConnect(true);
|
|
||||||
client.setConnectTimeout(300);
|
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket();
|
|
||||||
server.bind(null);
|
|
||||||
int port = server.getLocalPort();
|
|
||||||
|
|
||||||
|
|
||||||
boolean bad=false;
|
|
||||||
final AtomicBoolean open = new AtomicBoolean();
|
final AtomicBoolean open = new AtomicBoolean();
|
||||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
|
||||||
final AtomicInteger close = new AtomicInteger();
|
final AtomicInteger close = new AtomicInteger();
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort),new WebSocket()
|
||||||
|
{
|
||||||
|
public void onOpen(Connection connection)
|
||||||
|
{
|
||||||
|
open.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClose(int closeCode, String message)
|
||||||
|
{
|
||||||
|
close.set(closeCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Throwable error=null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
future.get(250,TimeUnit.MILLISECONDS);
|
||||||
{
|
Assert.fail();
|
||||||
public void onOpen(Connection connection)
|
|
||||||
{
|
|
||||||
open.set(true);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
error.set(message);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClose(int closeCode, String message)
|
|
||||||
{
|
|
||||||
close.set(closeCode);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch(IOException e)
|
catch(TimeoutException e)
|
||||||
{
|
{
|
||||||
bad=true;
|
error=e;
|
||||||
}
|
}
|
||||||
|
|
||||||
Assert.assertFalse(bad);
|
|
||||||
Assert.assertFalse(open.get());
|
Assert.assertFalse(open.get());
|
||||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
|
||||||
Assert.assertNotNull(error.get());
|
Assert.assertTrue(error instanceof TimeoutException);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBlockingConnectionTimeout() throws Exception
|
public void testConnectionTimeout() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient();
|
||||||
client.setConnectTimeout(500);
|
|
||||||
client.setBlockingConnect(true);
|
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket();
|
|
||||||
server.bind(null);
|
|
||||||
int port = server.getLocalPort();
|
|
||||||
|
|
||||||
|
|
||||||
boolean bad=false;
|
|
||||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
|
||||||
{
|
|
||||||
public void onOpen(Connection connection)
|
|
||||||
{
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
error.set(message);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClose(int closeCode, String message)
|
|
||||||
{
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch(IOException e)
|
|
||||||
{
|
|
||||||
e.printStackTrace();
|
|
||||||
bad=true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.assertNotNull(server.accept());
|
|
||||||
|
|
||||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
|
||||||
Assert.assertTrue(bad||error.get()!=null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testAsyncConnectionTimeout() throws Exception
|
|
||||||
{
|
|
||||||
WebSocketClient client = new WebSocketClient();
|
|
||||||
client.setBlockingConnect(true);
|
|
||||||
client.setConnectTimeout(300);
|
|
||||||
client.start();
|
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket();
|
|
||||||
server.bind(null);
|
|
||||||
int port = server.getLocalPort();
|
|
||||||
|
|
||||||
|
|
||||||
boolean bad=false;
|
|
||||||
final AtomicBoolean open = new AtomicBoolean();
|
final AtomicBoolean open = new AtomicBoolean();
|
||||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
|
||||||
final AtomicInteger close = new AtomicInteger();
|
final AtomicInteger close = new AtomicInteger();
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort),new WebSocket()
|
||||||
|
{
|
||||||
|
public void onOpen(Connection connection)
|
||||||
|
{
|
||||||
|
open.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClose(int closeCode, String message)
|
||||||
|
{
|
||||||
|
close.set(closeCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.assertNotNull(_server.accept());
|
||||||
|
|
||||||
|
Throwable error=null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
future.get(250,TimeUnit.MILLISECONDS);
|
||||||
{
|
Assert.fail();
|
||||||
public void onOpen(Connection connection)
|
|
||||||
{
|
|
||||||
open.set(true);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
error.set(message);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClose(int closeCode, String message)
|
|
||||||
{
|
|
||||||
close.set(closeCode);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch(IOException e)
|
catch(TimeoutException e)
|
||||||
{
|
{
|
||||||
bad=true;
|
error=e;
|
||||||
}
|
}
|
||||||
Assert.assertNotNull(server.accept());
|
|
||||||
|
|
||||||
Assert.assertFalse(bad);
|
|
||||||
Assert.assertFalse(open.get());
|
Assert.assertFalse(open.get());
|
||||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
|
||||||
Assert.assertNotNull(error.get());
|
Assert.assertTrue(error instanceof TimeoutException);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBadHandshake() throws Exception
|
public void testBadHandshake() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient();
|
||||||
client.setBlockingConnect(true);
|
|
||||||
client.setConnectTimeout(300);
|
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket();
|
|
||||||
server.bind(null);
|
|
||||||
int port = server.getLocalPort();
|
|
||||||
|
|
||||||
|
|
||||||
boolean bad=false;
|
|
||||||
final AtomicBoolean open = new AtomicBoolean();
|
final AtomicBoolean open = new AtomicBoolean();
|
||||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
|
||||||
final AtomicInteger close = new AtomicInteger();
|
final AtomicInteger close = new AtomicInteger();
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
|
||||||
|
{
|
||||||
|
public void onOpen(Connection connection)
|
||||||
|
{
|
||||||
|
open.set(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClose(int closeCode, String message)
|
||||||
|
{
|
||||||
|
close.set(closeCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Socket connection = _server.accept();
|
||||||
|
respondToClient(connection, "HTTP/1.1 404 NOT FOUND\r\n\r\n");
|
||||||
|
|
||||||
|
Throwable error=null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
future.get(250,TimeUnit.MILLISECONDS);
|
||||||
{
|
Assert.fail();
|
||||||
public void onOpen(Connection connection)
|
|
||||||
{
|
|
||||||
open.set(true);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
|
||||||
{
|
|
||||||
error.set(message);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClose(int closeCode, String message)
|
|
||||||
{
|
|
||||||
close.set(closeCode);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch(IOException e)
|
catch(ExecutionException e)
|
||||||
{
|
{
|
||||||
bad=true;
|
error=e.getCause();
|
||||||
}
|
}
|
||||||
|
|
||||||
Socket connection = server.accept();
|
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
|
||||||
for (String line=in.readLine();line!=null;line=in.readLine())
|
|
||||||
{
|
|
||||||
// System.err.println(line);
|
|
||||||
if (line.length()==0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.getOutputStream().write("HTTP/1.1 404 NOT FOUND\r\n\r\n".getBytes());
|
|
||||||
|
|
||||||
Assert.assertFalse(bad);
|
|
||||||
Assert.assertFalse(open.get());
|
Assert.assertFalse(open.get());
|
||||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
Assert.assertEquals(WebSocketConnectionD10.CLOSE_PROTOCOL,close.get());
|
||||||
Assert.assertNotNull(error.get());
|
Assert.assertTrue(error instanceof IOException);
|
||||||
|
Assert.assertTrue(error.getMessage().indexOf("404 NOT FOUND")>0);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBadUpgrade() throws Exception
|
public void testBadUpgrade() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient();
|
||||||
client.setBlockingConnect(true);
|
|
||||||
client.setConnectTimeout(10000);
|
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket();
|
|
||||||
server.bind(null);
|
|
||||||
int port = server.getLocalPort();
|
|
||||||
|
|
||||||
|
|
||||||
boolean bad=false;
|
|
||||||
final AtomicBoolean open = new AtomicBoolean();
|
final AtomicBoolean open = new AtomicBoolean();
|
||||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
|
||||||
final AtomicInteger close = new AtomicInteger();
|
final AtomicInteger close = new AtomicInteger();
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
|
||||||
try
|
|
||||||
{
|
{
|
||||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
public void onOpen(Connection connection)
|
||||||
{
|
{
|
||||||
public void onOpen(Connection connection)
|
open.set(true);
|
||||||
{
|
}
|
||||||
open.set(true);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
public void onClose(int closeCode, String message)
|
||||||
{
|
{
|
||||||
error.set(message);
|
close.set(closeCode);
|
||||||
latch.countDown();
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
public void onClose(int closeCode, String message)
|
Socket connection = _server.accept();
|
||||||
{
|
respondToClient(connection,
|
||||||
close.set(closeCode);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch(IOException e)
|
|
||||||
{
|
|
||||||
bad=true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Socket connection = server.accept();
|
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
|
||||||
for (String line=in.readLine();line!=null;line=in.readLine())
|
|
||||||
{
|
|
||||||
// System.err.println(line);
|
|
||||||
if (line.length()==0)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.getOutputStream().write((
|
|
||||||
"HTTP/1.1 101 Upgrade\r\n" +
|
"HTTP/1.1 101 Upgrade\r\n" +
|
||||||
"Sec-WebSocket-Accept: rubbish\r\n" +
|
"Sec-WebSocket-Accept: rubbish\r\n" +
|
||||||
"\r\n").getBytes());
|
"\r\n" );
|
||||||
|
|
||||||
Assert.assertFalse(bad);
|
|
||||||
Assert.assertFalse(open.get());
|
|
||||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
|
||||||
Assert.assertNotNull(error.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Throwable error=null;
|
||||||
@Test
|
|
||||||
public void testUpgrade() throws Exception
|
|
||||||
{
|
|
||||||
WebSocketClient client = new WebSocketClient();
|
|
||||||
client.setBlockingConnect(true);
|
|
||||||
client.setConnectTimeout(10000);
|
|
||||||
client.start();
|
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket();
|
|
||||||
server.bind(null);
|
|
||||||
int port = server.getLocalPort();
|
|
||||||
|
|
||||||
|
|
||||||
boolean bad=false;
|
|
||||||
final AtomicBoolean open = new AtomicBoolean();
|
|
||||||
final AtomicReference<String> error = new AtomicReference<String>(null);
|
|
||||||
final AtomicInteger close = new AtomicInteger();
|
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
future.get(250,TimeUnit.MILLISECONDS);
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
catch(ExecutionException e)
|
||||||
|
{
|
||||||
|
error=e.getCause();
|
||||||
|
}
|
||||||
|
Assert.assertFalse(open.get());
|
||||||
|
Assert.assertEquals(WebSocketConnectionD10.CLOSE_PROTOCOL,close.get());
|
||||||
|
Assert.assertTrue(error instanceof IOException);
|
||||||
|
Assert.assertTrue(error.getMessage().indexOf("Bad Sec-WebSocket-Accept")>=0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpgradeThenTCPClose() throws Exception
|
||||||
|
{
|
||||||
|
WebSocketClient client = new WebSocketClient();
|
||||||
|
client.start();
|
||||||
|
|
||||||
|
final AtomicBoolean open = new AtomicBoolean();
|
||||||
|
final AtomicInteger close = new AtomicInteger();
|
||||||
|
final CountDownLatch _latch = new CountDownLatch(1);
|
||||||
|
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
|
||||||
|
{
|
||||||
|
public void onOpen(Connection connection)
|
||||||
{
|
{
|
||||||
public void onOpen(Connection connection)
|
open.set(true);
|
||||||
{
|
}
|
||||||
open.set(true);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
public void onClose(int closeCode, String message)
|
||||||
{
|
{
|
||||||
error.set(message);
|
close.set(closeCode);
|
||||||
latch.countDown();
|
_latch.countDown();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
public void onClose(int closeCode, String message)
|
|
||||||
{
|
|
||||||
close.set(closeCode);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch(IOException e)
|
|
||||||
{
|
|
||||||
bad=true;
|
|
||||||
}
|
|
||||||
Assert.assertFalse(bad);
|
|
||||||
|
|
||||||
String key="not sent";
|
Socket socket = _server.accept();
|
||||||
Socket connection = server.accept();
|
accept(socket);
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
|
||||||
for (String line=in.readLine();line!=null;line=in.readLine())
|
|
||||||
{
|
|
||||||
if (line.length()==0)
|
|
||||||
break;
|
|
||||||
if (line.startsWith("Sec-WebSocket-Key:"))
|
|
||||||
key=line.substring(18).trim();
|
|
||||||
}
|
|
||||||
connection.getOutputStream().write((
|
|
||||||
"HTTP/1.1 101 Upgrade\r\n" +
|
|
||||||
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
|
|
||||||
"\r\n").getBytes());
|
|
||||||
|
|
||||||
Assert.assertTrue(latch.await(1,TimeUnit.SECONDS));
|
WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
|
||||||
Assert.assertNull(error.get());
|
Assert.assertNotNull(connection);
|
||||||
Assert.assertTrue(open.get());
|
Assert.assertTrue(open.get());
|
||||||
|
Assert.assertEquals(0,close.get());
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
_latch.await(10,TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NOCLOSE,close.get());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIdle() throws Exception
|
public void testIdle() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient();
|
||||||
client.setBlockingConnect(true);
|
|
||||||
client.setConnectTimeout(10000);
|
|
||||||
client.setMaxIdleTime(500);
|
client.setMaxIdleTime(500);
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket();
|
|
||||||
server.bind(null);
|
|
||||||
int port = server.getLocalPort();
|
|
||||||
|
|
||||||
boolean bad=false;
|
|
||||||
final AtomicBoolean open = new AtomicBoolean();
|
final AtomicBoolean open = new AtomicBoolean();
|
||||||
final AtomicInteger close = new AtomicInteger();
|
final AtomicInteger close = new AtomicInteger();
|
||||||
final CountDownLatch latch = new CountDownLatch(2);
|
final CountDownLatch _latch = new CountDownLatch(1);
|
||||||
try
|
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket()
|
||||||
{
|
{
|
||||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket()
|
public void onOpen(Connection connection)
|
||||||
{
|
{
|
||||||
public void onOpen(Connection connection)
|
open.set(true);
|
||||||
{
|
}
|
||||||
open.set(true);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
public void onClose(int closeCode, String message)
|
||||||
{
|
{
|
||||||
latch.countDown();
|
close.set(closeCode);
|
||||||
}
|
_latch.countDown();
|
||||||
|
}
|
||||||
public void onClose(int closeCode, String message)
|
});
|
||||||
{
|
|
||||||
close.set(closeCode);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch(IOException e)
|
|
||||||
{
|
|
||||||
bad=true;
|
|
||||||
}
|
|
||||||
Assert.assertFalse(bad);
|
|
||||||
|
|
||||||
String key="not sent";
|
Socket socket = _server.accept();
|
||||||
Socket connection = server.accept();
|
accept(socket);
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
|
||||||
for (String line=in.readLine();line!=null;line=in.readLine())
|
|
||||||
{
|
|
||||||
if (line.length()==0)
|
|
||||||
break;
|
|
||||||
if (line.startsWith("Sec-WebSocket-Key:"))
|
|
||||||
key=line.substring(18).trim();
|
|
||||||
}
|
|
||||||
connection.getOutputStream().write((
|
|
||||||
"HTTP/1.1 101 Upgrade\r\n" +
|
|
||||||
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
|
|
||||||
"\r\n").getBytes());
|
|
||||||
|
|
||||||
Assert.assertTrue(latch.await(10,TimeUnit.SECONDS));
|
WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
|
||||||
|
Assert.assertNotNull(connection);
|
||||||
Assert.assertTrue(open.get());
|
Assert.assertTrue(open.get());
|
||||||
|
Assert.assertEquals(0,close.get());
|
||||||
|
|
||||||
|
long start=System.currentTimeMillis();
|
||||||
|
_latch.await(10,TimeUnit.SECONDS);
|
||||||
|
Assert.assertTrue(System.currentTimeMillis()-start<5000);
|
||||||
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NORMAL,close.get());
|
Assert.assertEquals(WebSocketConnectionD10.CLOSE_NORMAL,close.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -612,77 +363,41 @@ public class WebSocketClientTest
|
||||||
public void testNotIdle() throws Exception
|
public void testNotIdle() throws Exception
|
||||||
{
|
{
|
||||||
WebSocketClient client = new WebSocketClient();
|
WebSocketClient client = new WebSocketClient();
|
||||||
client.setBlockingConnect(true);
|
|
||||||
client.setConnectTimeout(10000);
|
|
||||||
client.setMaxIdleTime(500);
|
client.setMaxIdleTime(500);
|
||||||
client.start();
|
client.start();
|
||||||
|
|
||||||
ServerSocket server = new ServerSocket();
|
|
||||||
server.bind(null);
|
|
||||||
int port = server.getLocalPort();
|
|
||||||
|
|
||||||
boolean bad=false;
|
|
||||||
final AtomicBoolean open = new AtomicBoolean();
|
final AtomicBoolean open = new AtomicBoolean();
|
||||||
final Exchanger<Integer> close = new Exchanger<Integer>();
|
final AtomicInteger close = new AtomicInteger();
|
||||||
final CountDownLatch latch = new CountDownLatch(1);
|
final CountDownLatch _latch = new CountDownLatch(1);
|
||||||
final AtomicReference<WebSocket.Connection> connection = new AtomicReference<WebSocket.Connection>();
|
|
||||||
final BlockingQueue<String> queue = new BlockingArrayQueue<String>();
|
final BlockingQueue<String> queue = new BlockingArrayQueue<String>();
|
||||||
try
|
Future<WebSocket.Connection> future=client.open(new URI("ws://127.0.0.1:"+_serverPort+"/"),new WebSocket.OnTextMessage()
|
||||||
{
|
{
|
||||||
client.open(new URI("ws://127.0.0.1:"+port),new WebSocket.OnTextMessage()
|
public void onOpen(Connection connection)
|
||||||
{
|
{
|
||||||
public void onOpen(Connection c)
|
open.set(true);
|
||||||
{
|
}
|
||||||
open.set(true);
|
|
||||||
connection.set(c);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onError(String message, Throwable ex)
|
public void onClose(int closeCode, String message)
|
||||||
{
|
{
|
||||||
latch.countDown();
|
close.set(closeCode);
|
||||||
}
|
_latch.countDown();
|
||||||
|
}
|
||||||
public void onClose(int closeCode, String message)
|
|
||||||
{
|
public void onMessage(String data)
|
||||||
try
|
{
|
||||||
{
|
queue.add(data);
|
||||||
close.exchange(closeCode);
|
}
|
||||||
}
|
});
|
||||||
catch(InterruptedException ex)
|
|
||||||
{}
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onMessage(String data)
|
|
||||||
{
|
|
||||||
queue.add(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch(IOException e)
|
|
||||||
{
|
|
||||||
bad=true;
|
|
||||||
}
|
|
||||||
Assert.assertFalse(bad);
|
|
||||||
|
|
||||||
String key="not sent";
|
Socket socket = _server.accept();
|
||||||
Socket socket = server.accept();
|
accept(socket);
|
||||||
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
|
||||||
for (String line=in.readLine();line!=null;line=in.readLine())
|
|
||||||
{
|
|
||||||
if (line.length()==0)
|
|
||||||
break;
|
|
||||||
if (line.startsWith("Sec-WebSocket-Key:"))
|
|
||||||
key=line.substring(18).trim();
|
|
||||||
}
|
|
||||||
socket.getOutputStream().write((
|
|
||||||
"HTTP/1.1 101 Upgrade\r\n" +
|
|
||||||
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
|
|
||||||
"\r\n").getBytes());
|
|
||||||
|
|
||||||
Assert.assertTrue(latch.await(10,TimeUnit.SECONDS));
|
WebSocket.Connection connection = future.get(250,TimeUnit.MILLISECONDS);
|
||||||
|
Assert.assertNotNull(connection);
|
||||||
Assert.assertTrue(open.get());
|
Assert.assertTrue(open.get());
|
||||||
|
Assert.assertEquals(0,close.get());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Send some messages client to server
|
// Send some messages client to server
|
||||||
byte[] recv = new byte[1024];
|
byte[] recv = new byte[1024];
|
||||||
|
@ -690,7 +405,7 @@ public class WebSocketClientTest
|
||||||
for (int i=0;i<10;i++)
|
for (int i=0;i<10;i++)
|
||||||
{
|
{
|
||||||
Thread.sleep(250);
|
Thread.sleep(250);
|
||||||
connection.get().sendMessage("Hello");
|
connection.sendMessage("Hello");
|
||||||
len=socket.getInputStream().read(recv,0,recv.length);
|
len=socket.getInputStream().read(recv,0,recv.length);
|
||||||
Assert.assertTrue(len>0);
|
Assert.assertTrue(len>0);
|
||||||
}
|
}
|
||||||
|
@ -706,10 +421,68 @@ public class WebSocketClientTest
|
||||||
Assert.assertEquals("Hi",queue.poll(1,TimeUnit.SECONDS));
|
Assert.assertEquals("Hi",queue.poll(1,TimeUnit.SECONDS));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close with code
|
||||||
|
long start=System.currentTimeMillis();
|
||||||
socket.getOutputStream().write(new byte[]{(byte)0x88, (byte) 0x02, (byte)4, (byte)87 },0,4);
|
socket.getOutputStream().write(new byte[]{(byte)0x88, (byte) 0x02, (byte)4, (byte)87 },0,4);
|
||||||
socket.getOutputStream().flush();
|
socket.getOutputStream().flush();
|
||||||
|
|
||||||
|
_latch.await(10,TimeUnit.SECONDS);
|
||||||
|
Assert.assertTrue(System.currentTimeMillis()-start<5000);
|
||||||
|
Assert.assertEquals(1111,close.get());
|
||||||
|
|
||||||
Assert.assertEquals(new Integer(1111),close.exchange(null,1,TimeUnit.SECONDS));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void respondToClient(Socket connection, String serverResponse) throws IOException
|
||||||
|
{
|
||||||
|
InputStream in = null;
|
||||||
|
InputStreamReader isr = null;
|
||||||
|
BufferedReader buf = null;
|
||||||
|
OutputStream out = null;
|
||||||
|
try {
|
||||||
|
in = connection.getInputStream();
|
||||||
|
isr = new InputStreamReader(in);
|
||||||
|
buf = new BufferedReader(isr);
|
||||||
|
String line;
|
||||||
|
while((line = buf.readLine())!=null)
|
||||||
|
{
|
||||||
|
// System.err.println(line);
|
||||||
|
if(line.length() == 0)
|
||||||
|
{
|
||||||
|
// Got the "\r\n" line.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System.out.println("[Server-Out] " + serverResponse);
|
||||||
|
out = connection.getOutputStream();
|
||||||
|
out.write(serverResponse.getBytes());
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IO.close(buf);
|
||||||
|
IO.close(isr);
|
||||||
|
IO.close(in);
|
||||||
|
IO.close(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void accept(Socket connection) throws IOException
|
||||||
|
{
|
||||||
|
String key="not sent";
|
||||||
|
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||||
|
for (String line=in.readLine();line!=null;line=in.readLine())
|
||||||
|
{
|
||||||
|
if (line.length()==0)
|
||||||
|
break;
|
||||||
|
if (line.startsWith("Sec-WebSocket-Key:"))
|
||||||
|
key=line.substring(18).trim();
|
||||||
|
}
|
||||||
|
connection.getOutputStream().write((
|
||||||
|
"HTTP/1.1 101 Upgrade\r\n" +
|
||||||
|
"Sec-WebSocket-Accept: "+ WebSocketConnectionD10.hashKey(key) +"\r\n" +
|
||||||
|
"\r\n").getBytes());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.log.Log;
|
|
||||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
|
@ -116,10 +115,6 @@ public class WebSocketLoadD10Test
|
||||||
{
|
{
|
||||||
this.outbound = outbound;
|
this.outbound = outbound;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onError(String message,Throwable ex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onMessage(String data)
|
public void onMessage(String data)
|
||||||
{
|
{
|
||||||
|
|
|
@ -249,10 +249,6 @@ public class WebSocketMessageD00Test
|
||||||
{
|
{
|
||||||
return latch.await(time, TimeUnit.MILLISECONDS);
|
return latch.await(time, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onError(String message,Throwable ex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onClose(int code,String message)
|
public void onClose(int code,String message)
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,7 +22,6 @@ import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.handler.DefaultHandler;
|
import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
import org.eclipse.jetty.util.TypeUtil;
|
|
||||||
import org.eclipse.jetty.util.Utf8StringBuilder;
|
import org.eclipse.jetty.util.Utf8StringBuilder;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
|
@ -756,10 +755,6 @@ public class WebSocketMessageD06Test
|
||||||
{
|
{
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onError(String message,Throwable ex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onOpen(Connection connection)
|
public void onOpen(Connection connection)
|
||||||
{
|
{
|
||||||
|
|