Merge remote-tracking branch 'origin/master' into jetty-8
This commit is contained in:
commit
3141b50fa0
|
@ -29,6 +29,7 @@ import org.eclipse.jetty.server.handler.DefaultHandler;
|
||||||
import org.eclipse.jetty.server.handler.HandlerCollection;
|
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||||
import org.eclipse.jetty.server.handler.RequestLogHandler;
|
import org.eclipse.jetty.server.handler.RequestLogHandler;
|
||||||
import org.eclipse.jetty.server.handler.StatisticsHandler;
|
import org.eclipse.jetty.server.handler.StatisticsHandler;
|
||||||
|
import org.eclipse.jetty.server.nio.BlockingChannelConnector;
|
||||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||||
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
|
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
|
||||||
import org.eclipse.jetty.server.ssl.SslSocketConnector;
|
import org.eclipse.jetty.server.ssl.SslSocketConnector;
|
||||||
|
@ -69,6 +70,13 @@ public class LikeJettyXml
|
||||||
server.setConnectors(new Connector[]
|
server.setConnectors(new Connector[]
|
||||||
{ connector });
|
{ connector });
|
||||||
|
|
||||||
|
BlockingChannelConnector bConnector = new BlockingChannelConnector();
|
||||||
|
bConnector.setPort(8888);
|
||||||
|
bConnector.setMaxIdleTime(30000);
|
||||||
|
bConnector.setConfidentialPort(8443);
|
||||||
|
bConnector.setAcceptors(1);
|
||||||
|
server.addConnector(bConnector);
|
||||||
|
|
||||||
SslSelectChannelConnector ssl_connector = new SslSelectChannelConnector();
|
SslSelectChannelConnector ssl_connector = new SslSelectChannelConnector();
|
||||||
ssl_connector.setPort(8443);
|
ssl_connector.setPort(8443);
|
||||||
SslContextFactory cf = ssl_connector.getSslContextFactory();
|
SslContextFactory cf = ssl_connector.getSslContextFactory();
|
||||||
|
|
|
@ -179,7 +179,7 @@ public abstract class AbstractHttpConnection extends AbstractConnection implemen
|
||||||
_generator.setVersion(_exchange.getVersion());
|
_generator.setVersion(_exchange.getVersion());
|
||||||
|
|
||||||
String method=_exchange.getMethod();
|
String method=_exchange.getMethod();
|
||||||
String uri = _exchange.getURI();
|
String uri = _exchange.getRequestURI();
|
||||||
if (_destination.isProxied() && !HttpMethods.CONNECT.equals(method) && uri.startsWith("/"))
|
if (_destination.isProxied() && !HttpMethods.CONNECT.equals(method) && uri.startsWith("/"))
|
||||||
{
|
{
|
||||||
boolean secure = _destination.isSecure();
|
boolean secure = _destination.isSecure();
|
||||||
|
@ -394,7 +394,11 @@ public abstract class AbstractHttpConnection extends AbstractConnection implemen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_endp.close();
|
if (_endp.isOpen())
|
||||||
|
{
|
||||||
|
_endp.close();
|
||||||
|
_destination.returnConnection(this, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIdleTimeout()
|
public void setIdleTimeout()
|
||||||
|
|
|
@ -14,15 +14,8 @@
|
||||||
|
|
||||||
package org.eclipse.jetty.client;
|
package org.eclipse.jetty.client;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.is;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.ServletOutputStream;
|
import javax.servlet.ServletOutputStream;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
@ -36,18 +29,22 @@ import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||||
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.StdErrLog;
|
import org.eclipse.jetty.util.log.StdErrLog;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractHttpExchangeCancelTest
|
public abstract class AbstractHttpExchangeCancelTest
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(AbstractHttpExchangeCancelTest.TestHttpExchange.class);
|
|
||||||
|
|
||||||
private Server server;
|
private Server server;
|
||||||
private Connector connector;
|
private Connector connector;
|
||||||
|
|
||||||
|
@ -371,6 +368,26 @@ public abstract class AbstractHttpExchangeCancelTest
|
||||||
assertFalse(exchange.isAssociated());
|
assertFalse(exchange.isAssociated());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHttpExchangeCancelReturnsConnection() throws Exception
|
||||||
|
{
|
||||||
|
TestHttpExchange exchange = new TestHttpExchange();
|
||||||
|
Address address = newAddress();
|
||||||
|
exchange.setAddress(address);
|
||||||
|
long delay = 5000;
|
||||||
|
exchange.setRequestURI("/?action=wait" + delay);
|
||||||
|
|
||||||
|
HttpClient httpClient = getHttpClient();
|
||||||
|
HttpDestination destination = httpClient.getDestination(address, false);
|
||||||
|
int connections = destination.getConnections();
|
||||||
|
httpClient.send(exchange);
|
||||||
|
Thread.sleep(delay / 2);
|
||||||
|
Assert.assertEquals(connections + 1, destination.getConnections());
|
||||||
|
|
||||||
|
exchange.cancel();
|
||||||
|
Assert.assertEquals(connections, destination.getConnections());
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
protected abstract HttpClient getHttpClient();
|
protected abstract HttpClient getHttpClient();
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,13 @@ public class BlockingHttpConnection extends AbstractHttpConnection
|
||||||
_endp.shutdownOutput();
|
_endp.shutdownOutput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we don't have a committed response and we are not suspended
|
||||||
|
if (_endp.isInputShutdown() && _generator.isIdle() && !_request.getAsyncContinuation().isSuspended())
|
||||||
|
{
|
||||||
|
// then no more can happen, so close.
|
||||||
|
_endp.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -112,6 +112,14 @@ import org.eclipse.jetty.util.log.Logger;
|
||||||
* to avoid reparsing headers and cookies that are likely to be the same for
|
* to avoid reparsing headers and cookies that are likely to be the same for
|
||||||
* requests from the same connection.
|
* requests from the same connection.
|
||||||
*
|
*
|
||||||
|
* <p>
|
||||||
|
* The form content that a request can process is limited to protect from Denial of Service
|
||||||
|
* attacks. The size in bytes is limited by {@link ContextHandler#getMaxFormContentSize()} or if there is no
|
||||||
|
* context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server} attribute.
|
||||||
|
* The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no
|
||||||
|
* context then the "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute.
|
||||||
|
*
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public class Request implements HttpServletRequest
|
public class Request implements HttpServletRequest
|
||||||
{
|
{
|
||||||
|
@ -257,14 +265,19 @@ public class Request implements HttpServletRequest
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int maxFormContentSize=-1;
|
int maxFormContentSize=-1;
|
||||||
|
int maxFormKeys=-1;
|
||||||
|
|
||||||
if (_context!=null)
|
if (_context!=null)
|
||||||
|
{
|
||||||
maxFormContentSize=_context.getContextHandler().getMaxFormContentSize();
|
maxFormContentSize=_context.getContextHandler().getMaxFormContentSize();
|
||||||
|
maxFormKeys=_context.getContextHandler().getMaxFormKeys();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Integer size = (Integer)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
|
Number size = (Number)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
|
||||||
if (size!=null)
|
maxFormContentSize=size==null?200000:size.intValue();
|
||||||
maxFormContentSize =size.intValue();
|
Number keys = (Number)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys");
|
||||||
|
maxFormKeys =keys==null?1000:keys.intValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content_length>maxFormContentSize && maxFormContentSize > 0)
|
if (content_length>maxFormContentSize && maxFormContentSize > 0)
|
||||||
|
@ -274,7 +287,7 @@ public class Request implements HttpServletRequest
|
||||||
InputStream in = getInputStream();
|
InputStream in = getInputStream();
|
||||||
|
|
||||||
// Add form params to query params
|
// Add form params to query params
|
||||||
UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1);
|
UrlEncoded.decodeTo(in, _baseParameters, encoding,content_length<0?maxFormContentSize:-1,maxFormKeys);
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -130,6 +130,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
|
||||||
private EventListener[] _eventListeners;
|
private EventListener[] _eventListeners;
|
||||||
private Logger _logger;
|
private Logger _logger;
|
||||||
private boolean _allowNullPathInfo;
|
private boolean _allowNullPathInfo;
|
||||||
|
private int _maxFormKeys = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormKeys",1000).intValue();
|
||||||
private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",200000).intValue();
|
private int _maxFormContentSize = Integer.getInteger("org.eclipse.jetty.server.Request.maxFormContentSize",200000).intValue();
|
||||||
private boolean _compactPath = false;
|
private boolean _compactPath = false;
|
||||||
private boolean _aliases = false;
|
private boolean _aliases = false;
|
||||||
|
@ -1382,11 +1383,31 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Set the maximum size of a form post, to protect against DOS attacks from large forms.
|
||||||
|
* @param maxSize
|
||||||
|
*/
|
||||||
public void setMaxFormContentSize(int maxSize)
|
public void setMaxFormContentSize(int maxSize)
|
||||||
{
|
{
|
||||||
_maxFormContentSize = maxSize;
|
_maxFormContentSize = maxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
public int getMaxFormKeys()
|
||||||
|
{
|
||||||
|
return _maxFormKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* Set the maximum number of form Keys to protect against DOS attack from crafted hash keys.
|
||||||
|
* @param max
|
||||||
|
*/
|
||||||
|
public void setMaxFormKeys(int max)
|
||||||
|
{
|
||||||
|
_maxFormKeys = max;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* @return True if URLs are compacted to replace multiple '/'s with a single '/'
|
* @return True if URLs are compacted to replace multiple '/'s with a single '/'
|
||||||
|
|
|
@ -17,6 +17,7 @@ import java.io.IOException;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.nio.channels.ByteChannel;
|
import java.nio.channels.ByteChannel;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
import java.nio.channels.ServerSocketChannel;
|
import java.nio.channels.ServerSocketChannel;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -341,5 +342,20 @@ public class BlockingChannelConnector extends AbstractNIOConnector
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
@Override
|
||||||
|
public String toString()
|
||||||
|
{
|
||||||
|
return String.format("BCEP@%x{l(%s)<->r(%s),open=%b,ishut=%b,oshut=%b}-{%s}",
|
||||||
|
hashCode(),
|
||||||
|
_socket.getRemoteSocketAddress(),
|
||||||
|
_socket.getLocalSocketAddress(),
|
||||||
|
isOpen(),
|
||||||
|
isInputShutdown(),
|
||||||
|
isOutputShutdown(),
|
||||||
|
_connection);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,16 @@ import static org.junit.Assert.assertNotSame;
|
||||||
import static org.junit.Assert.assertSame;
|
import static org.junit.Assert.assertSame;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
|
@ -34,9 +38,11 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import junit.framework.Assert;
|
import junit.framework.Assert;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.http.MimeTypes;
|
||||||
import org.eclipse.jetty.server.handler.AbstractHandler;
|
import org.eclipse.jetty.server.handler.AbstractHandler;
|
||||||
import org.eclipse.jetty.util.IO;
|
import org.eclipse.jetty.util.IO;
|
||||||
import org.eclipse.jetty.util.StringUtil;
|
import org.eclipse.jetty.util.StringUtil;
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -742,6 +748,56 @@ public class RequestTest
|
||||||
assertEquals(null,cookie[1]);
|
assertEquals(null,cookie[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHashDOS() throws Exception
|
||||||
|
{
|
||||||
|
_server.setAttribute("org.eclipse.jetty.server.Request.maxFormContentSize",-1);
|
||||||
|
_server.setAttribute("org.eclipse.jetty.server.Request.maxFormKeys",1000);
|
||||||
|
|
||||||
|
// This file is not distributed - as it is dangerous
|
||||||
|
File evil_keys = new File("/tmp/keys_mapping_to_zero_2m");
|
||||||
|
if (!evil_keys.exists())
|
||||||
|
{
|
||||||
|
Log.info("testHashDOS skipped");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedReader in = new BufferedReader(new FileReader(evil_keys));
|
||||||
|
StringBuilder buf = new StringBuilder(4000000);
|
||||||
|
|
||||||
|
String key=null;
|
||||||
|
buf.append("a=b");
|
||||||
|
while((key=in.readLine())!=null)
|
||||||
|
{
|
||||||
|
buf.append("&").append(key).append("=").append("x");
|
||||||
|
}
|
||||||
|
buf.append("&c=d");
|
||||||
|
|
||||||
|
_handler._checker = new RequestTester()
|
||||||
|
{
|
||||||
|
public boolean check(HttpServletRequest request,HttpServletResponse response)
|
||||||
|
{
|
||||||
|
return "b".equals(request.getParameter("a")) && request.getParameter("c")==null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
String request="POST / HTTP/1.1\r\n"+
|
||||||
|
"Host: whatever\r\n"+
|
||||||
|
"Content-Type: "+MimeTypes.FORM_ENCODED+"\r\n"+
|
||||||
|
"Content-Length: "+buf.length()+"\r\n"+
|
||||||
|
"Connection: close\r\n"+
|
||||||
|
"\r\n"+
|
||||||
|
buf;
|
||||||
|
|
||||||
|
long start=System.currentTimeMillis();
|
||||||
|
String response = _connector.getResponses(request);
|
||||||
|
assertTrue(response.contains("200 OK"));
|
||||||
|
long now=System.currentTimeMillis();
|
||||||
|
assertTrue((now-start)<5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
interface RequestTester
|
interface RequestTester
|
||||||
{
|
{
|
||||||
boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException;
|
boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException;
|
||||||
|
@ -756,13 +812,15 @@ public class RequestTest
|
||||||
{
|
{
|
||||||
((Request)request).setHandled(true);
|
((Request)request).setHandled(true);
|
||||||
|
|
||||||
if (request.getContentLength()>0)
|
if (request.getContentLength()>0 && !MimeTypes.FORM_ENCODED.equals(request.getContentType()))
|
||||||
_content=IO.toString(request.getInputStream());
|
_content=IO.toString(request.getInputStream());
|
||||||
|
|
||||||
if (_checker!=null && _checker.check(request,response))
|
if (_checker!=null && _checker.check(request,response))
|
||||||
response.setStatus(200);
|
response.setStatus(200);
|
||||||
else
|
else
|
||||||
response.sendError(500);
|
response.sendError(500);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
|
import java.io.FilenameFilter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
@ -30,7 +31,6 @@ import java.lang.reflect.Method;
|
||||||
import java.net.ConnectException;
|
import java.net.ConnectException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.security.Policy;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -40,17 +40,15 @@ import java.util.List;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
/*-------------------------------------------*/
|
/*-------------------------------------------*/
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It
|
* Main start class. This class is intended to be the main class listed in the MANIFEST.MF of the start.jar archive. It allows an application to be started with
|
||||||
* allows an application to be started with the command "java -jar start.jar".
|
* the command "java -jar start.jar".
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file
|
* The behaviour of Main is controlled by the parsing of the {@link Config} "org/eclipse/start/start.config" file obtained as a resource or file.
|
||||||
* obtained as a resource or file.
|
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
public class Main
|
public class Main
|
||||||
|
@ -67,257 +65,240 @@ public class Main
|
||||||
private boolean _dryRun = false;
|
private boolean _dryRun = false;
|
||||||
private boolean _exec = false;
|
private boolean _exec = false;
|
||||||
private final Config _config = new Config();
|
private final Config _config = new Config();
|
||||||
private Set<String> _sysProps = new HashSet<String>();
|
private final Set<String> _sysProps = new HashSet<String>();
|
||||||
private List<String> _jvmArgs = new ArrayList<String>();
|
private final List<String> _jvmArgs = new ArrayList<String>();
|
||||||
private String _startConfig = null;
|
private String _startConfig = null;
|
||||||
|
|
||||||
private String _jettyHome;
|
private String _jettyHome;
|
||||||
|
|
||||||
public static void main(String[] args)
|
public static void main(String[] args)
|
||||||
{
|
|
||||||
Main main = new Main();
|
|
||||||
main.parseCommandLine(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void parseCommandLine(String[] args)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<String> arguments = new ArrayList<String>();
|
Main main = new Main();
|
||||||
|
List<String> arguments = main.expandCommandLine(args);
|
||||||
// add the command line args and look for start.ini args
|
List<String> xmls = main.processCommandLine(arguments);
|
||||||
boolean ini=false;
|
if (xmls!=null)
|
||||||
for (String arg : args)
|
main.start(xmls);
|
||||||
{
|
|
||||||
if (arg.startsWith("--ini=")||arg.equals("--ini"))
|
|
||||||
{
|
|
||||||
ini=true;
|
|
||||||
if (arg.length()>6)
|
|
||||||
{
|
|
||||||
arguments.addAll(loadStartIni(arg.substring(6)));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (arg.startsWith("--config="))
|
|
||||||
{
|
|
||||||
_startConfig=arg.substring(9);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
arguments.add(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no non-option inis, add the start.ini
|
|
||||||
if (!ini)
|
|
||||||
{
|
|
||||||
arguments.addAll(0,loadStartIni(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The XML Configuration Files to initialize with
|
|
||||||
List<String> xmls = new ArrayList<String>();
|
|
||||||
|
|
||||||
// Process the arguments
|
|
||||||
int startup=0;
|
|
||||||
for (String arg : arguments)
|
|
||||||
{
|
|
||||||
if ("--help".equals(arg) || "-?".equals(arg))
|
|
||||||
{
|
|
||||||
_showUsage = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("--stop".equals(arg))
|
|
||||||
{
|
|
||||||
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
|
|
||||||
String key = Config.getProperty("STOP.KEY",null);
|
|
||||||
stop(port,key);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
|
|
||||||
{
|
|
||||||
_dumpVersions = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("--list-modes".equals(arg) || "--list-options".equals(arg))
|
|
||||||
{
|
|
||||||
_listOptions = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("--list-config".equals(arg))
|
|
||||||
{
|
|
||||||
_listConfig=true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("--exec-print".equals(arg)||"--dry-run".equals(arg))
|
|
||||||
{
|
|
||||||
_dryRun = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("--exec".equals(arg))
|
|
||||||
{
|
|
||||||
_exec = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special internal indicator that jetty was started by the jetty.sh Daemon
|
|
||||||
if ("--daemon".equals(arg))
|
|
||||||
{
|
|
||||||
File startDir = new File(System.getProperty("jetty.logs","logs"));
|
|
||||||
if (!startDir.exists() || !startDir.canWrite() )
|
|
||||||
startDir = new File(".");
|
|
||||||
File startLog = new File(startDir,"start.log");
|
|
||||||
if (!startLog.exists() && !startLog.createNewFile())
|
|
||||||
{
|
|
||||||
// Output about error is lost in majority of cases.
|
|
||||||
System.err.println("Unable to create: " + startLog.getAbsolutePath());
|
|
||||||
// Toss a unique exit code indicating this failure.
|
|
||||||
usageExit(ERR_LOGGING);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!startLog.canWrite())
|
|
||||||
{
|
|
||||||
// Output about error is lost in majority of cases.
|
|
||||||
System.err.println("Unable to write to: " + startLog.getAbsolutePath());
|
|
||||||
// Toss a unique exit code indicating this failure.
|
|
||||||
usageExit(ERR_LOGGING);
|
|
||||||
}
|
|
||||||
PrintStream logger = new PrintStream(new FileOutputStream(startLog,false));
|
|
||||||
System.setOut(logger);
|
|
||||||
System.setErr(logger);
|
|
||||||
System.out.println("Establishing start.log on " + new Date());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg.startsWith("--pre="))
|
|
||||||
{
|
|
||||||
xmls.add(startup++,arg.substring(6));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg.startsWith("-D"))
|
|
||||||
{
|
|
||||||
String[] assign = arg.substring(2).split("=",2);
|
|
||||||
_sysProps.add(assign[0]);
|
|
||||||
switch(assign.length)
|
|
||||||
{
|
|
||||||
case 2:
|
|
||||||
System.setProperty(assign[0],assign[1]);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
System.setProperty(assign[0],"");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg.startsWith("-"))
|
|
||||||
{
|
|
||||||
_jvmArgs.add(arg);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is this a Property?
|
|
||||||
if (arg.indexOf('=') >= 0)
|
|
||||||
{
|
|
||||||
String[] assign = arg.split("=",2);
|
|
||||||
|
|
||||||
switch(assign.length)
|
|
||||||
{
|
|
||||||
case 2:
|
|
||||||
if ("OPTIONS".equals(assign[0]))
|
|
||||||
{
|
|
||||||
String opts[] = assign[1].split(",");
|
|
||||||
for (String opt : opts)
|
|
||||||
_config.addActiveOption(opt);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this._config.setProperty(assign[0],assign[1]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
this._config.setProperty(assign[0],null);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Anything else is considered an XML file.
|
|
||||||
if (xmls.contains(arg))
|
|
||||||
{
|
|
||||||
System.out.println("WARN: Argument '"+arg+"' specified multiple times. Check start.ini?");
|
|
||||||
System.out.println("Use \"java -jar start.jar --help\" for more information.");
|
|
||||||
}
|
|
||||||
xmls.add(arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
start(xmls);
|
|
||||||
}
|
}
|
||||||
catch (Throwable t)
|
catch (Throwable e)
|
||||||
{
|
|
||||||
usageExit(t,ERR_UNKNOWN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If a start.ini is present in the CWD, then load it into the argument list.
|
|
||||||
*/
|
|
||||||
private List<String> loadStartIni(String ini)
|
|
||||||
{
|
|
||||||
String jettyHome=System.getProperty("jetty.home");
|
|
||||||
File startIniFile = ini==null?((jettyHome!=null)? new File(jettyHome,"start.ini"):new File("start.ini")):new File(ini);
|
|
||||||
if (!startIniFile.exists())
|
|
||||||
{
|
|
||||||
if (ini != null)
|
|
||||||
{
|
|
||||||
System.err.println("Warning - can't find ini file: " + ini);
|
|
||||||
}
|
|
||||||
// No start.ini found, skip load.
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> args = new ArrayList<String>();
|
|
||||||
|
|
||||||
FileReader reader = null;
|
|
||||||
BufferedReader buf = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
reader = new FileReader(startIniFile);
|
|
||||||
buf = new BufferedReader(reader);
|
|
||||||
|
|
||||||
String arg;
|
|
||||||
while ((arg = buf.readLine()) != null)
|
|
||||||
{
|
|
||||||
arg = arg.trim();
|
|
||||||
if (arg.length() == 0 || arg.startsWith("#"))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
args.add(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (IOException e)
|
|
||||||
{
|
{
|
||||||
usageExit(e,ERR_UNKNOWN);
|
usageExit(e,ERR_UNKNOWN);
|
||||||
}
|
}
|
||||||
finally
|
}
|
||||||
|
|
||||||
|
Main() throws IOException
|
||||||
|
{
|
||||||
|
_jettyHome = System.getProperty("jetty.home",".");
|
||||||
|
_jettyHome = new File(_jettyHome).getCanonicalPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> expandCommandLine(String[] args) throws Exception
|
||||||
|
{
|
||||||
|
List<String> arguments = new ArrayList<String>();
|
||||||
|
|
||||||
|
// add the command line args and look for start.ini args
|
||||||
|
boolean ini = false;
|
||||||
|
for (String arg : args)
|
||||||
{
|
{
|
||||||
close(buf);
|
if (arg.startsWith("--ini=") || arg.equals("--ini"))
|
||||||
close(reader);
|
{
|
||||||
|
ini = true;
|
||||||
|
if (arg.length() > 6)
|
||||||
|
{
|
||||||
|
arguments.addAll(loadStartIni(new File(arg.substring(6))));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (arg.startsWith("--config="))
|
||||||
|
{
|
||||||
|
_startConfig = arg.substring(9);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
arguments.add(arg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return args;
|
// if no non-option inis, add the start.ini and start.d
|
||||||
|
if (!ini)
|
||||||
|
{
|
||||||
|
List<String> ini_args=new ArrayList<String>();
|
||||||
|
File start_ini = new File(_jettyHome,"start.ini");
|
||||||
|
if (start_ini.exists())
|
||||||
|
ini_args.addAll(loadStartIni(start_ini));
|
||||||
|
|
||||||
|
File start_d = new File(_jettyHome,"start.d");
|
||||||
|
if (start_d.isDirectory())
|
||||||
|
{
|
||||||
|
File[] inis = start_d.listFiles(new FilenameFilter()
|
||||||
|
{
|
||||||
|
public boolean accept(File dir, String name)
|
||||||
|
{
|
||||||
|
return name.toLowerCase().endsWith(".ini");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Arrays.sort(inis);
|
||||||
|
for (File i : inis)
|
||||||
|
ini_args.addAll(loadStartIni(i));
|
||||||
|
}
|
||||||
|
arguments.addAll(0,ini_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return arguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> processCommandLine(List<String> arguments) throws Exception
|
||||||
|
{
|
||||||
|
// The XML Configuration Files to initialize with
|
||||||
|
List<String> xmls = new ArrayList<String>();
|
||||||
|
|
||||||
|
// Process the arguments
|
||||||
|
int startup = 0;
|
||||||
|
for (String arg : arguments)
|
||||||
|
{
|
||||||
|
if ("--help".equals(arg) || "-?".equals(arg))
|
||||||
|
{
|
||||||
|
_showUsage = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("--stop".equals(arg))
|
||||||
|
{
|
||||||
|
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
|
||||||
|
String key = Config.getProperty("STOP.KEY",null);
|
||||||
|
stop(port,key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
|
||||||
|
{
|
||||||
|
_dumpVersions = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("--list-modes".equals(arg) || "--list-options".equals(arg))
|
||||||
|
{
|
||||||
|
_listOptions = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("--list-config".equals(arg))
|
||||||
|
{
|
||||||
|
_listConfig = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("--exec-print".equals(arg) || "--dry-run".equals(arg))
|
||||||
|
{
|
||||||
|
_dryRun = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("--exec".equals(arg))
|
||||||
|
{
|
||||||
|
_exec = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special internal indicator that jetty was started by the jetty.sh Daemon
|
||||||
|
if ("--daemon".equals(arg))
|
||||||
|
{
|
||||||
|
File startDir = new File(System.getProperty("jetty.logs","logs"));
|
||||||
|
if (!startDir.exists() || !startDir.canWrite())
|
||||||
|
startDir = new File(".");
|
||||||
|
File startLog = new File(startDir,"start.log");
|
||||||
|
if (!startLog.exists() && !startLog.createNewFile())
|
||||||
|
{
|
||||||
|
// Output about error is lost in majority of cases.
|
||||||
|
System.err.println("Unable to create: " + startLog.getAbsolutePath());
|
||||||
|
// Toss a unique exit code indicating this failure.
|
||||||
|
usageExit(ERR_LOGGING);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!startLog.canWrite())
|
||||||
|
{
|
||||||
|
// Output about error is lost in majority of cases.
|
||||||
|
System.err.println("Unable to write to: " + startLog.getAbsolutePath());
|
||||||
|
// Toss a unique exit code indicating this failure.
|
||||||
|
usageExit(ERR_LOGGING);
|
||||||
|
}
|
||||||
|
PrintStream logger = new PrintStream(new FileOutputStream(startLog,false));
|
||||||
|
System.setOut(logger);
|
||||||
|
System.setErr(logger);
|
||||||
|
System.out.println("Establishing start.log on " + new Date());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.startsWith("--pre="))
|
||||||
|
{
|
||||||
|
xmls.add(startup++,arg.substring(6));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.startsWith("-D"))
|
||||||
|
{
|
||||||
|
String[] assign = arg.substring(2).split("=",2);
|
||||||
|
_sysProps.add(assign[0]);
|
||||||
|
switch (assign.length)
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
System.setProperty(assign[0],assign[1]);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
System.setProperty(assign[0],"");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arg.startsWith("-"))
|
||||||
|
{
|
||||||
|
_jvmArgs.add(arg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is this a Property?
|
||||||
|
if (arg.indexOf('=') >= 0)
|
||||||
|
{
|
||||||
|
String[] assign = arg.split("=",2);
|
||||||
|
|
||||||
|
switch (assign.length)
|
||||||
|
{
|
||||||
|
case 2:
|
||||||
|
if ("OPTIONS".equals(assign[0]))
|
||||||
|
{
|
||||||
|
String opts[] = assign[1].split(",");
|
||||||
|
for (String opt : opts)
|
||||||
|
_config.addActiveOption(opt);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this._config.setProperty(assign[0],assign[1]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
this._config.setProperty(assign[0],null);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything else is considered an XML file.
|
||||||
|
if (xmls.contains(arg))
|
||||||
|
{
|
||||||
|
System.out.println("WARN: Argument '" + arg + "' specified multiple times. Check start.ini?");
|
||||||
|
System.out.println("Use \"java -jar start.jar --help\" for more information.");
|
||||||
|
}
|
||||||
|
xmls.add(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return xmls;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void usage()
|
private void usage()
|
||||||
|
@ -339,10 +320,10 @@ public class Main
|
||||||
|
|
||||||
while ((line = buf.readLine()) != null)
|
while ((line = buf.readLine()) != null)
|
||||||
{
|
{
|
||||||
if (line.endsWith("@") && line.indexOf('@')!=line.lastIndexOf('@'))
|
if (line.endsWith("@") && line.indexOf('@') != line.lastIndexOf('@'))
|
||||||
{
|
{
|
||||||
String indent=line.substring(0,line.indexOf("@"));
|
String indent = line.substring(0,line.indexOf("@"));
|
||||||
String info=line.substring(line.indexOf('@'),line.lastIndexOf('@'));
|
String info = line.substring(line.indexOf('@'),line.lastIndexOf('@'));
|
||||||
|
|
||||||
if (info.equals("@OPTIONS"))
|
if (info.equals("@OPTIONS"))
|
||||||
{
|
{
|
||||||
|
@ -352,7 +333,7 @@ public class Main
|
||||||
|
|
||||||
for (String option : sortedOptions)
|
for (String option : sortedOptions)
|
||||||
{
|
{
|
||||||
if ("*".equals(option) || option.trim().length()==0)
|
if ("*".equals(option) || option.trim().length() == 0)
|
||||||
continue;
|
continue;
|
||||||
System.out.print(indent);
|
System.out.print(indent);
|
||||||
System.out.println(option);
|
System.out.println(option);
|
||||||
|
@ -396,7 +377,7 @@ public class Main
|
||||||
else if (info.equals("@STARTINI"))
|
else if (info.equals("@STARTINI"))
|
||||||
{
|
{
|
||||||
List<String> ini = loadStartIni(null);
|
List<String> ini = loadStartIni(null);
|
||||||
if (ini!=null && ini.size()>0)
|
if (ini != null && ini.size() > 0)
|
||||||
{
|
{
|
||||||
for (String a : ini)
|
for (String a : ini)
|
||||||
{
|
{
|
||||||
|
@ -429,7 +410,7 @@ public class Main
|
||||||
}
|
}
|
||||||
|
|
||||||
public void invokeMain(ClassLoader classloader, String classname, List<String> args) throws IllegalAccessException, InvocationTargetException,
|
public void invokeMain(ClassLoader classloader, String classname, List<String> args) throws IllegalAccessException, InvocationTargetException,
|
||||||
NoSuchMethodException, ClassNotFoundException
|
NoSuchMethodException, ClassNotFoundException
|
||||||
{
|
{
|
||||||
Class<?> invoked_class = null;
|
Class<?> invoked_class = null;
|
||||||
|
|
||||||
|
@ -462,10 +443,12 @@ public class Main
|
||||||
|
|
||||||
String argArray[] = args.toArray(new String[0]);
|
String argArray[] = args.toArray(new String[0]);
|
||||||
|
|
||||||
Class<?>[] method_param_types = new Class[] { argArray.getClass() };
|
Class<?>[] method_param_types = new Class[]
|
||||||
|
{ argArray.getClass() };
|
||||||
|
|
||||||
Method main = invoked_class.getDeclaredMethod("main",method_param_types);
|
Method main = invoked_class.getDeclaredMethod("main",method_param_types);
|
||||||
Object[] method_params = new Object[] { argArray };
|
Object[] method_params = new Object[]
|
||||||
|
{ argArray };
|
||||||
main.invoke(null,method_params);
|
main.invoke(null,method_params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,13 +475,12 @@ public class Main
|
||||||
// Setup Start / Stop Monitoring
|
// Setup Start / Stop Monitoring
|
||||||
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
|
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
|
||||||
String key = Config.getProperty("STOP.KEY",null);
|
String key = Config.getProperty("STOP.KEY",null);
|
||||||
Monitor monitor=new Monitor(port,key);
|
Monitor monitor = new Monitor(port,key);
|
||||||
|
|
||||||
|
|
||||||
// Load potential Config (start.config)
|
// Load potential Config (start.config)
|
||||||
List<String> configuredXmls = loadConfig(xmls);
|
List<String> configuredXmls = loadConfig(xmls);
|
||||||
|
|
||||||
// No XML defined in start.config or command line. Can't execute.
|
// No XML defined in start.config or command line. Can't execute.
|
||||||
if (configuredXmls.isEmpty())
|
if (configuredXmls.isEmpty())
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException("No XML configuration files specified in start.config or command line.");
|
throw new FileNotFoundException("No XML configuration files specified in start.config or command line.");
|
||||||
|
@ -565,6 +547,7 @@ public class Main
|
||||||
final Process process = Runtime.getRuntime().exec(cmd);
|
final Process process = Runtime.getRuntime().exec(cmd);
|
||||||
Runtime.getRuntime().addShutdownHook(new Thread()
|
Runtime.getRuntime().addShutdownHook(new Thread()
|
||||||
{
|
{
|
||||||
|
@Override
|
||||||
public void run()
|
public void run()
|
||||||
{
|
{
|
||||||
Config.debug("Destroying " + process);
|
Config.debug("Destroying " + process);
|
||||||
|
@ -617,7 +600,7 @@ public class Main
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyInThread(final InputStream in,final OutputStream out)
|
private void copyInThread(final InputStream in, final OutputStream out)
|
||||||
{
|
{
|
||||||
new Thread(new Runnable()
|
new Thread(new Runnable()
|
||||||
{
|
{
|
||||||
|
@ -625,15 +608,15 @@ public class Main
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
byte[] buf=new byte[1024];
|
byte[] buf = new byte[1024];
|
||||||
int len=in.read(buf);
|
int len = in.read(buf);
|
||||||
while(len>0)
|
while (len > 0)
|
||||||
{
|
{
|
||||||
out.write(buf,0,len);
|
out.write(buf,0,len);
|
||||||
len=in.read(buf);
|
len = in.read(buf);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
// e.printStackTrace();
|
// e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
@ -675,14 +658,14 @@ public class Main
|
||||||
{
|
{
|
||||||
StringBuilder cmd = new StringBuilder();
|
StringBuilder cmd = new StringBuilder();
|
||||||
cmd.append(findJavaBin());
|
cmd.append(findJavaBin());
|
||||||
for (String x:_jvmArgs)
|
for (String x : _jvmArgs)
|
||||||
cmd.append(' ').append(x);
|
cmd.append(' ').append(x);
|
||||||
cmd.append(" -Djetty.home=").append(_jettyHome);
|
cmd.append(" -Djetty.home=").append(_jettyHome);
|
||||||
for (String p:_sysProps)
|
for (String p : _sysProps)
|
||||||
{
|
{
|
||||||
cmd.append(" -D").append(p);
|
cmd.append(" -D").append(p);
|
||||||
String v=System.getProperty(p);
|
String v = System.getProperty(p);
|
||||||
if (v!=null && v.length()>0)
|
if (v != null && v.length() > 0)
|
||||||
cmd.append('=').append(v);
|
cmd.append('=').append(v);
|
||||||
}
|
}
|
||||||
cmd.append(" -cp ").append(classpath.toString());
|
cmd.append(" -cp ").append(classpath.toString());
|
||||||
|
@ -690,7 +673,7 @@ public class Main
|
||||||
|
|
||||||
// Check if we need to pass properties as a file
|
// Check if we need to pass properties as a file
|
||||||
Properties properties = Config.getProperties();
|
Properties properties = Config.getProperties();
|
||||||
if (properties.size()>0)
|
if (properties.size() > 0)
|
||||||
{
|
{
|
||||||
File prop_file = File.createTempFile("start",".properties");
|
File prop_file = File.createTempFile("start",".properties");
|
||||||
if (!_dryRun)
|
if (!_dryRun)
|
||||||
|
@ -875,11 +858,10 @@ public class Main
|
||||||
|
|
||||||
private String getZipVersion(File element)
|
private String getZipVersion(File element)
|
||||||
{
|
{
|
||||||
// TODO - find version in zip file. Look for META-INF/MANIFEST.MF ?
|
// TODO - find version in zip file. Look for META-INF/MANIFEST.MF ?
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<String> resolveXmlConfigs(List<String> xmls) throws FileNotFoundException
|
private List<String> resolveXmlConfigs(List<String> xmls) throws FileNotFoundException
|
||||||
{
|
{
|
||||||
List<String> ret = new ArrayList<String>();
|
List<String> ret = new ArrayList<String>();
|
||||||
|
@ -896,15 +878,15 @@ public class Main
|
||||||
InputStream cfgstream = null;
|
InputStream cfgstream = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
cfgstream=getConfigStream();
|
cfgstream = getConfigStream();
|
||||||
byte[] buf=new byte[4096];
|
byte[] buf = new byte[4096];
|
||||||
|
|
||||||
int len=0;
|
int len = 0;
|
||||||
|
|
||||||
while (len>=0)
|
while (len >= 0)
|
||||||
{
|
{
|
||||||
len=cfgstream.read(buf);
|
len = cfgstream.read(buf);
|
||||||
if (len>0)
|
if (len > 0)
|
||||||
System.out.write(buf,0,len);
|
System.out.write(buf,0,len);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -921,8 +903,8 @@ public class Main
|
||||||
/**
|
/**
|
||||||
* Load Configuration.
|
* Load Configuration.
|
||||||
*
|
*
|
||||||
* No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to
|
* No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to execute the {@link Class} specified by
|
||||||
* execute the {@link Class} specified by {@link Config#getMainClassname()} is executed.
|
* {@link Config#getMainClassname()} is executed.
|
||||||
*
|
*
|
||||||
* @param xmls
|
* @param xmls
|
||||||
* the command line specified xml configuration options.
|
* the command line specified xml configuration options.
|
||||||
|
@ -936,12 +918,12 @@ public class Main
|
||||||
// Pass in xmls.size into Config so that conditions based on "nargs" work.
|
// Pass in xmls.size into Config so that conditions based on "nargs" work.
|
||||||
_config.setArgCount(xmls.size());
|
_config.setArgCount(xmls.size());
|
||||||
|
|
||||||
cfgstream=getConfigStream();
|
cfgstream = getConfigStream();
|
||||||
|
|
||||||
// parse the config
|
// parse the config
|
||||||
_config.parse(cfgstream);
|
_config.parse(cfgstream);
|
||||||
|
|
||||||
_jettyHome = Config.getProperty("jetty.home");
|
_jettyHome = Config.getProperty("jetty.home",_jettyHome);
|
||||||
if (_jettyHome != null)
|
if (_jettyHome != null)
|
||||||
{
|
{
|
||||||
_jettyHome = new File(_jettyHome).getCanonicalPath();
|
_jettyHome = new File(_jettyHome).getCanonicalPath();
|
||||||
|
@ -975,7 +957,7 @@ public class Main
|
||||||
|
|
||||||
private InputStream getConfigStream() throws FileNotFoundException
|
private InputStream getConfigStream() throws FileNotFoundException
|
||||||
{
|
{
|
||||||
String config=_startConfig;
|
String config = _startConfig;
|
||||||
if (config == null || config.length() == 0)
|
if (config == null || config.length() == 0)
|
||||||
{
|
{
|
||||||
config = System.getProperty("START","org/eclipse/jetty/start/start.config");
|
config = System.getProperty("START","org/eclipse/jetty/start/start.config");
|
||||||
|
@ -995,7 +977,6 @@ public class Main
|
||||||
return cfgstream;
|
return cfgstream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop a running jetty instance.
|
* Stop a running jetty instance.
|
||||||
*/
|
*/
|
||||||
|
@ -1039,7 +1020,6 @@ public class Main
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void usageExit(Throwable t, int exit)
|
static void usageExit(Throwable t, int exit)
|
||||||
{
|
{
|
||||||
t.printStackTrace(System.err);
|
t.printStackTrace(System.err);
|
||||||
|
@ -1048,6 +1028,7 @@ public class Main
|
||||||
System.err.println(" java -jar start.jar --help # for more information");
|
System.err.println(" java -jar start.jar --help # for more information");
|
||||||
System.exit(exit);
|
System.exit(exit);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void usageExit(int exit)
|
static void usageExit(int exit)
|
||||||
{
|
{
|
||||||
System.err.println();
|
System.err.println();
|
||||||
|
@ -1055,4 +1036,53 @@ public class Main
|
||||||
System.err.println(" java -jar start.jar --help # for more information");
|
System.err.println(" java -jar start.jar --help # for more information");
|
||||||
System.exit(exit);
|
System.exit(exit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a start.ini format file into an argument list.
|
||||||
|
*/
|
||||||
|
static List<String> loadStartIni(File ini)
|
||||||
|
{
|
||||||
|
File startIniFile = ini;
|
||||||
|
if (!startIniFile.exists())
|
||||||
|
{
|
||||||
|
if (ini != null)
|
||||||
|
{
|
||||||
|
System.err.println("Warning - can't find ini file: " + ini);
|
||||||
|
}
|
||||||
|
// No start.ini found, skip load.
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> args = new ArrayList<String>();
|
||||||
|
|
||||||
|
FileReader reader = null;
|
||||||
|
BufferedReader buf = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
reader = new FileReader(ini);
|
||||||
|
buf = new BufferedReader(reader);
|
||||||
|
|
||||||
|
String arg;
|
||||||
|
while ((arg = buf.readLine()) != null)
|
||||||
|
{
|
||||||
|
arg = arg.trim();
|
||||||
|
if (arg.length() == 0 || arg.startsWith("#"))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
args.add(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
// usageExit(e,ERR_UNKNOWN);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Main.close(buf);
|
||||||
|
Main.close(reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
return args;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,8 +116,11 @@ Available Configurations:
|
||||||
Defaults:
|
Defaults:
|
||||||
A start.ini file may be used to specify default arguments to start.jar,
|
A start.ini file may be used to specify default arguments to start.jar,
|
||||||
which are used if no command line arguments are provided and override
|
which are used if no command line arguments are provided and override
|
||||||
the defaults in the start.config file. If --ini options are provided on
|
the defaults in the start.config file. If the directory start.d exists,
|
||||||
the command line, then start.ini will no be read. The current start.ini
|
then multiple *.ini files will be read from that directory in alphabetical
|
||||||
arguments are:
|
order. If --ini options are provided on the command line, then start.ini
|
||||||
|
and start.d will not be read.
|
||||||
|
|
||||||
|
The current start.ini arguments are:
|
||||||
|
|
||||||
@STARTINI@
|
@STARTINI@
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
// ========================================================================
|
||||||
|
// Copyright (c) 2009-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.start;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
public class MainTest
|
||||||
|
{
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
/**
|
||||||
|
* @throws java.lang.Exception
|
||||||
|
*/
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception
|
||||||
|
{
|
||||||
|
System.setProperty("jetty.home",this.getClass().getResource("/jetty.home").getFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test method for {@link org.eclipse.jetty.start.StartIniParser#loadStartIni(java.lang.String)}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testLoadStartIni()
|
||||||
|
{
|
||||||
|
URL startIni = this.getClass().getResource("/jetty.home/start.ini");
|
||||||
|
String startIniFileName = startIni.getFile();
|
||||||
|
List<String> args = Main.loadStartIni(new File(startIniFileName));
|
||||||
|
assertEquals("Expected 5 uncommented lines in start.ini",5,args.size());
|
||||||
|
assertEquals("First uncommented line in start.ini doesn't match expected result","OPTIONS=Server,jsp,resources,websocket,ext",args.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExpandCommandLine() throws Exception
|
||||||
|
{
|
||||||
|
Main main = new Main();
|
||||||
|
List<String> args = main.expandCommandLine(new String[]{});
|
||||||
|
assertEquals("start.ini OPTIONS","OPTIONS=Server,jsp,resources,websocket,ext",args.get(0));
|
||||||
|
assertEquals("start.d/jmx OPTIONS","OPTIONS=jmx",args.get(5));
|
||||||
|
assertEquals("start.d/jmx XML","--pre=etc/jetty-jmx.xml",args.get(6));
|
||||||
|
assertEquals("start.d/websocket OPTIONS","OPTIONS=websocket",args.get(7));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testProcessCommandLine() throws Exception
|
||||||
|
{
|
||||||
|
Main main = new Main();
|
||||||
|
List<String> args = main.expandCommandLine(new String[]{});
|
||||||
|
List<String> xmls = main.processCommandLine(args);
|
||||||
|
|
||||||
|
assertEquals("jmx --pre","etc/jetty-jmx.xml",xmls.get(0));
|
||||||
|
assertEquals("start.ini","etc/jetty.xml",xmls.get(1));
|
||||||
|
assertEquals("start.d","etc/jetty-testrealm.xml",xmls.get(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
#===========================================================
|
||||||
|
# Additional Jetty start.jar arguments
|
||||||
|
# Each line of this file is prepended to the command line
|
||||||
|
# arguments # of a call to:
|
||||||
|
# java -jar start.jar [arg...]
|
||||||
|
#===========================================================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#===========================================================
|
||||||
|
#-----------------------------------------------------------
|
||||||
|
OPTIONS=jmx
|
||||||
|
#-----------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
#===========================================================
|
||||||
|
# Configuration files.
|
||||||
|
# For a full list of available configuration files do
|
||||||
|
# java -jar start.jar --help
|
||||||
|
#-----------------------------------------------------------
|
||||||
|
--pre=etc/jetty-jmx.xml
|
||||||
|
#===========================================================
|
|
@ -0,0 +1,13 @@
|
||||||
|
#===========================================================
|
||||||
|
# Additional Jetty start.jar arguments
|
||||||
|
# Each line of this file is prepended to the command line
|
||||||
|
# arguments # of a call to:
|
||||||
|
# java -jar start.jar [arg...]
|
||||||
|
#===========================================================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#===========================================================
|
||||||
|
#-----------------------------------------------------------
|
||||||
|
OPTIONS=websocket
|
||||||
|
#-----------------------------------------------------------
|
|
@ -0,0 +1 @@
|
||||||
|
etc/jetty-testrealm.xml
|
|
@ -0,0 +1,65 @@
|
||||||
|
#===========================================================
|
||||||
|
# Jetty start.jar arguments
|
||||||
|
# Each line of this file is prepended to the command line
|
||||||
|
# arguments # of a call to:
|
||||||
|
# java -jar start.jar [arg...]
|
||||||
|
#===========================================================
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#===========================================================
|
||||||
|
# If the arguements in this file include JVM arguments
|
||||||
|
# (eg -Xmx512m) or JVM System properties (eg com.sun.???),
|
||||||
|
# then these will not take affect unless the --exec
|
||||||
|
# parameter is included or if the output from --dry-run
|
||||||
|
# is executed like:
|
||||||
|
# eval $(java -jar start.jar --dry-run)
|
||||||
|
#
|
||||||
|
# Below are some recommended options for Sun's JRE
|
||||||
|
#-----------------------------------------------------------
|
||||||
|
# --exec
|
||||||
|
# -Dorg.apache.jasper.compiler.disablejsr199=true
|
||||||
|
# -Dcom.sun.management.jmxremote
|
||||||
|
# -Dorg.eclipse.jetty.util.log.IGNORED=true
|
||||||
|
# -Dorg.eclipse.jetty.util.log.stderr.DEBUG=true
|
||||||
|
# -Dorg.eclipse.jetty.util.log.stderr.SOURCE=true
|
||||||
|
# -Xmx2000m
|
||||||
|
# -Xmn512m
|
||||||
|
# -verbose:gc
|
||||||
|
# -XX:+PrintGCDateStamps
|
||||||
|
# -XX:+PrintGCTimeStamps
|
||||||
|
# -XX:+PrintGCDetails
|
||||||
|
# -XX:+PrintTenuringDistribution
|
||||||
|
# -XX:+PrintCommandLineFlags
|
||||||
|
# -XX:+DisableExplicitGC
|
||||||
|
# -XX:+UseConcMarkSweepGC
|
||||||
|
# -XX:ParallelCMSThreads=2
|
||||||
|
# -XX:+CMSClassUnloadingEnabled
|
||||||
|
# -XX:+UseCMSCompactAtFullCollection
|
||||||
|
# -XX:CMSInitiatingOccupancyFraction=80
|
||||||
|
#-----------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
#===========================================================
|
||||||
|
# Start classpath OPTIONS.
|
||||||
|
# These control what classes are on the classpath
|
||||||
|
# for a full listing do
|
||||||
|
# java -jar start.jar --list-options
|
||||||
|
#-----------------------------------------------------------
|
||||||
|
OPTIONS=Server,jsp,resources,websocket,ext
|
||||||
|
#-----------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
#===========================================================
|
||||||
|
# Configuration files.
|
||||||
|
# For a full list of available configuration files do
|
||||||
|
# java -jar start.jar --help
|
||||||
|
#-----------------------------------------------------------
|
||||||
|
etc/jetty.xml
|
||||||
|
# etc/jetty-ssl.xml
|
||||||
|
# etc/jetty-requestlog.xml
|
||||||
|
etc/jetty-deploy.xml
|
||||||
|
#etc/jetty-overlay.xml
|
||||||
|
etc/jetty-webapps.xml
|
||||||
|
etc/jetty-contexts.xml
|
||||||
|
#===========================================================
|
|
@ -16,6 +16,7 @@ package org.eclipse.jetty.util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.StringWriter;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -78,13 +79,13 @@ public class UrlEncoded extends MultiMap implements Cloneable
|
||||||
/* ----------------------------------------------------------------- */
|
/* ----------------------------------------------------------------- */
|
||||||
public void decode(String query)
|
public void decode(String query)
|
||||||
{
|
{
|
||||||
decodeTo(query,this,ENCODING);
|
decodeTo(query,this,ENCODING,-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------- */
|
/* ----------------------------------------------------------------- */
|
||||||
public void decode(String query,String charset)
|
public void decode(String query,String charset)
|
||||||
{
|
{
|
||||||
decodeTo(query,this,charset);
|
decodeTo(query,this,charset,-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------- */
|
/* -------------------------------------------------------------- */
|
||||||
|
@ -177,6 +178,15 @@ public class UrlEncoded extends MultiMap implements Cloneable
|
||||||
* @param content the string containing the encoded parameters
|
* @param content the string containing the encoded parameters
|
||||||
*/
|
*/
|
||||||
public static void decodeTo(String content, MultiMap map, String charset)
|
public static void decodeTo(String content, MultiMap map, String charset)
|
||||||
|
{
|
||||||
|
decodeTo(content,map,charset,-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------- */
|
||||||
|
/** Decoded parameters to Map.
|
||||||
|
* @param content the string containing the encoded parameters
|
||||||
|
*/
|
||||||
|
public static void decodeTo(String content, MultiMap map, String charset, int maxKeys)
|
||||||
{
|
{
|
||||||
if (charset==null)
|
if (charset==null)
|
||||||
charset=ENCODING;
|
charset=ENCODING;
|
||||||
|
@ -208,6 +218,11 @@ public class UrlEncoded extends MultiMap implements Cloneable
|
||||||
}
|
}
|
||||||
key = null;
|
key = null;
|
||||||
value=null;
|
value=null;
|
||||||
|
if (maxKeys>0 && map.size()>maxKeys)
|
||||||
|
{
|
||||||
|
LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
|
||||||
|
return;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case '=':
|
case '=':
|
||||||
if (key!=null)
|
if (key!=null)
|
||||||
|
@ -343,9 +358,10 @@ public class UrlEncoded extends MultiMap implements Cloneable
|
||||||
/** Decoded parameters to Map.
|
/** Decoded parameters to Map.
|
||||||
* @param in InputSteam to read
|
* @param in InputSteam to read
|
||||||
* @param map MultiMap to add parameters to
|
* @param map MultiMap to add parameters to
|
||||||
* @param maxLength maximum length of content to read 0r -1 for no limit
|
* @param maxLength maximum length of content to read or -1 for no limit
|
||||||
|
* @param maxLength maximum number of keys to read or -1 for no limit
|
||||||
*/
|
*/
|
||||||
public static void decode88591To(InputStream in, MultiMap map, int maxLength)
|
public static void decode88591To(InputStream in, MultiMap map, int maxLength, int maxKeys)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
synchronized(map)
|
synchronized(map)
|
||||||
|
@ -375,6 +391,11 @@ public class UrlEncoded extends MultiMap implements Cloneable
|
||||||
}
|
}
|
||||||
key = null;
|
key = null;
|
||||||
value=null;
|
value=null;
|
||||||
|
if (maxKeys>0 && map.size()>maxKeys)
|
||||||
|
{
|
||||||
|
LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
|
||||||
|
return;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '=':
|
case '=':
|
||||||
|
@ -423,9 +444,10 @@ public class UrlEncoded extends MultiMap implements Cloneable
|
||||||
/** Decoded parameters to Map.
|
/** Decoded parameters to Map.
|
||||||
* @param in InputSteam to read
|
* @param in InputSteam to read
|
||||||
* @param map MultiMap to add parameters to
|
* @param map MultiMap to add parameters to
|
||||||
* @param maxLength maximum length of content to read 0r -1 for no limit
|
* @param maxLength maximum length of content to read or -1 for no limit
|
||||||
|
* @param maxLength maximum number of keys to read or -1 for no limit
|
||||||
*/
|
*/
|
||||||
public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength)
|
public static void decodeUtf8To(InputStream in, MultiMap map, int maxLength, int maxKeys)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
synchronized(map)
|
synchronized(map)
|
||||||
|
@ -455,6 +477,11 @@ public class UrlEncoded extends MultiMap implements Cloneable
|
||||||
}
|
}
|
||||||
key = null;
|
key = null;
|
||||||
value=null;
|
value=null;
|
||||||
|
if (maxKeys>0 && map.size()>maxKeys)
|
||||||
|
{
|
||||||
|
LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
|
||||||
|
return;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '=':
|
case '=':
|
||||||
|
@ -500,25 +527,20 @@ public class UrlEncoded extends MultiMap implements Cloneable
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------- */
|
/* -------------------------------------------------------------- */
|
||||||
public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength) throws IOException
|
public static void decodeUtf16To(InputStream in, MultiMap map, int maxLength, int maxKeys) throws IOException
|
||||||
{
|
{
|
||||||
InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16);
|
InputStreamReader input = new InputStreamReader(in,StringUtil.__UTF16);
|
||||||
StringBuffer buf = new StringBuffer();
|
StringWriter buf = new StringWriter(8192);
|
||||||
|
IO.copy(input,buf,maxLength);
|
||||||
|
|
||||||
int c;
|
decodeTo(buf.getBuffer().toString(),map,ENCODING,maxKeys);
|
||||||
int length=0;
|
|
||||||
if (maxLength<0)
|
|
||||||
maxLength=Integer.MAX_VALUE;
|
|
||||||
while ((c=input.read())>0 && length++<maxLength)
|
|
||||||
buf.append((char)c);
|
|
||||||
decodeTo(buf.toString(),map,ENCODING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* -------------------------------------------------------------- */
|
/* -------------------------------------------------------------- */
|
||||||
/** Decoded parameters to Map.
|
/** Decoded parameters to Map.
|
||||||
* @param in the stream containing the encoded parameters
|
* @param in the stream containing the encoded parameters
|
||||||
*/
|
*/
|
||||||
public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength)
|
public static void decodeTo(InputStream in, MultiMap map, String charset, int maxLength, int maxKeys)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
//no charset present, use the configured default
|
//no charset present, use the configured default
|
||||||
|
@ -527,22 +549,21 @@ public class UrlEncoded extends MultiMap implements Cloneable
|
||||||
charset=ENCODING;
|
charset=ENCODING;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (StringUtil.__UTF8.equalsIgnoreCase(charset))
|
if (StringUtil.__UTF8.equalsIgnoreCase(charset))
|
||||||
{
|
{
|
||||||
decodeUtf8To(in,map,maxLength);
|
decodeUtf8To(in,map,maxLength,maxKeys);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtil.__ISO_8859_1.equals(charset))
|
if (StringUtil.__ISO_8859_1.equals(charset))
|
||||||
{
|
{
|
||||||
decode88591To(in,map,maxLength);
|
decode88591To(in,map,maxLength,maxKeys);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StringUtil.__UTF16.equalsIgnoreCase(charset)) // Should be all 2 byte encodings
|
if (StringUtil.__UTF16.equalsIgnoreCase(charset)) // Should be all 2 byte encodings
|
||||||
{
|
{
|
||||||
decodeUtf16To(in,map,maxLength);
|
decodeUtf16To(in,map,maxLength,maxKeys);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -178,7 +178,7 @@ public class URLEncodedTest
|
||||||
{
|
{
|
||||||
ByteArrayInputStream in = new ByteArrayInputStream("name\n=value+%30&name1=&name2&n\u00e3me3=value+3".getBytes(charsets[i][0]));
|
ByteArrayInputStream in = new ByteArrayInputStream("name\n=value+%30&name1=&name2&n\u00e3me3=value+3".getBytes(charsets[i][0]));
|
||||||
MultiMap m = new MultiMap();
|
MultiMap m = new MultiMap();
|
||||||
UrlEncoded.decodeTo(in, m, charsets[i][1], -1);
|
UrlEncoded.decodeTo(in, m, charsets[i][1], -1,-1);
|
||||||
System.err.println(m);
|
System.err.println(m);
|
||||||
assertEquals(i+" stream length",4,m.size());
|
assertEquals(i+" stream length",4,m.size());
|
||||||
assertEquals(i+" stream name\\n","value 0",m.getString("name\n"));
|
assertEquals(i+" stream name\\n","value 0",m.getString("name\n"));
|
||||||
|
@ -192,7 +192,7 @@ public class URLEncodedTest
|
||||||
{
|
{
|
||||||
ByteArrayInputStream in2 = new ByteArrayInputStream ("name=%83e%83X%83g".getBytes());
|
ByteArrayInputStream in2 = new ByteArrayInputStream ("name=%83e%83X%83g".getBytes());
|
||||||
MultiMap m2 = new MultiMap();
|
MultiMap m2 = new MultiMap();
|
||||||
UrlEncoded.decodeTo(in2, m2, "Shift_JIS", -1);
|
UrlEncoded.decodeTo(in2, m2, "Shift_JIS", -1,-1);
|
||||||
assertEquals("stream length",1,m2.size());
|
assertEquals("stream length",1,m2.size());
|
||||||
assertEquals("stream name","\u30c6\u30b9\u30c8",m2.getString("name"));
|
assertEquals("stream name","\u30c6\u30b9\u30c8",m2.getString("name"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -349,7 +349,7 @@ public class WebSocketClient
|
||||||
return holder;
|
return holder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final InetSocketAddress toSocketAddress(URI uri)
|
public static InetSocketAddress toSocketAddress(URI uri)
|
||||||
{
|
{
|
||||||
String scheme = uri.getScheme();
|
String scheme = uri.getScheme();
|
||||||
if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme)))
|
if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme)))
|
||||||
|
@ -360,8 +360,7 @@ public class WebSocketClient
|
||||||
if (port < 0)
|
if (port < 0)
|
||||||
port = "ws".equals(scheme) ? 80 : 443;
|
port = "ws".equals(scheme) ? 80 : 443;
|
||||||
|
|
||||||
InetSocketAddress address = new InetSocketAddress(uri.getHost(), port);
|
return new InetSocketAddress(uri.getHost(), port);
|
||||||
return address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -371,16 +370,8 @@ public class WebSocketClient
|
||||||
{
|
{
|
||||||
final WebSocket _websocket;
|
final WebSocket _websocket;
|
||||||
final URI _uri;
|
final URI _uri;
|
||||||
final String _protocol;
|
final WebSocketClient _client;
|
||||||
final String _origin;
|
|
||||||
final MaskGen _maskGen;
|
|
||||||
final int _maxIdleTime;
|
|
||||||
final int _maxTextMessageSize;
|
|
||||||
final int _maxBinaryMessageSize;
|
|
||||||
final Map<String,String> _cookies;
|
|
||||||
final List<String> _extensions;
|
|
||||||
final CountDownLatch _done = new CountDownLatch(1);
|
final CountDownLatch _done = new CountDownLatch(1);
|
||||||
|
|
||||||
ByteChannel _channel;
|
ByteChannel _channel;
|
||||||
WebSocketConnection _connection;
|
WebSocketConnection _connection;
|
||||||
Throwable _exception;
|
Throwable _exception;
|
||||||
|
@ -389,14 +380,7 @@ public class WebSocketClient
|
||||||
{
|
{
|
||||||
_websocket=websocket;
|
_websocket=websocket;
|
||||||
_uri=uri;
|
_uri=uri;
|
||||||
_protocol=client._protocol;
|
_client=client;
|
||||||
_origin=client._origin;
|
|
||||||
_maskGen=client._maskGen;
|
|
||||||
_maxIdleTime=client._maxIdleTime;
|
|
||||||
_maxTextMessageSize=client._maxTextMessageSize;
|
|
||||||
_maxBinaryMessageSize=client._maxBinaryMessageSize;
|
|
||||||
_cookies=client._cookies;
|
|
||||||
_extensions=client._extensions;
|
|
||||||
_channel=channel;
|
_channel=channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,8 +388,10 @@ public class WebSocketClient
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize);
|
_client.getFactory().addConnection(connection);
|
||||||
connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize);
|
|
||||||
|
connection.getConnection().setMaxTextMessageSize(_client.getMaxTextMessageSize());
|
||||||
|
connection.getConnection().setMaxBinaryMessageSize(_client.getMaxBinaryMessageSize());
|
||||||
|
|
||||||
WebSocketConnection con;
|
WebSocketConnection con;
|
||||||
synchronized (this)
|
synchronized (this)
|
||||||
|
@ -460,12 +446,12 @@ public class WebSocketClient
|
||||||
|
|
||||||
public Map<String,String> getCookies()
|
public Map<String,String> getCookies()
|
||||||
{
|
{
|
||||||
return _cookies;
|
return _client.getCookies();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getProtocol()
|
public String getProtocol()
|
||||||
{
|
{
|
||||||
return _protocol;
|
return _client.getProtocol();
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebSocket getWebSocket()
|
public WebSocket getWebSocket()
|
||||||
|
@ -480,17 +466,17 @@ public class WebSocketClient
|
||||||
|
|
||||||
public int getMaxIdleTime()
|
public int getMaxIdleTime()
|
||||||
{
|
{
|
||||||
return _maxIdleTime;
|
return _client.getMaxIdleTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOrigin()
|
public String getOrigin()
|
||||||
{
|
{
|
||||||
return _origin;
|
return _client.getOrigin();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MaskGen getMaskGen()
|
public MaskGen getMaskGen()
|
||||||
{
|
{
|
||||||
return _maskGen;
|
return _client.getMaskGen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -20,8 +20,11 @@ import java.io.IOException;
|
||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
import java.nio.channels.SelectionKey;
|
import java.nio.channels.SelectionKey;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
|
|
||||||
import org.eclipse.jetty.http.HttpFields;
|
import org.eclipse.jetty.http.HttpFields;
|
||||||
|
@ -33,6 +36,7 @@ import org.eclipse.jetty.io.Buffers;
|
||||||
import org.eclipse.jetty.io.ByteArrayBuffer;
|
import org.eclipse.jetty.io.ByteArrayBuffer;
|
||||||
import org.eclipse.jetty.io.ConnectedEndPoint;
|
import org.eclipse.jetty.io.ConnectedEndPoint;
|
||||||
import org.eclipse.jetty.io.Connection;
|
import org.eclipse.jetty.io.Connection;
|
||||||
|
import org.eclipse.jetty.io.EndPoint;
|
||||||
import org.eclipse.jetty.io.SimpleBuffers;
|
import org.eclipse.jetty.io.SimpleBuffers;
|
||||||
import org.eclipse.jetty.io.nio.AsyncConnection;
|
import org.eclipse.jetty.io.nio.AsyncConnection;
|
||||||
import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
|
import org.eclipse.jetty.io.nio.SelectChannelEndPoint;
|
||||||
|
@ -60,8 +64,8 @@ public class WebSocketClientFactory extends AggregateLifeCycle
|
||||||
{
|
{
|
||||||
private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClientFactory.class.getName());
|
private final static Logger __log = org.eclipse.jetty.util.log.Log.getLogger(WebSocketClientFactory.class.getName());
|
||||||
private final static ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept");
|
private final static ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept");
|
||||||
|
private final Queue<WebSocketConnection> connections = new ConcurrentLinkedQueue<WebSocketConnection>();
|
||||||
private SslContextFactory _sslContextFactory = new SslContextFactory();
|
private final SslContextFactory _sslContextFactory = new SslContextFactory();
|
||||||
private final ThreadPool _threadPool;
|
private final ThreadPool _threadPool;
|
||||||
private final WebSocketClientSelector _selector;
|
private final WebSocketClientSelector _selector;
|
||||||
private MaskGen _maskGen;
|
private MaskGen _maskGen;
|
||||||
|
@ -200,6 +204,12 @@ public class WebSocketClientFactory extends AggregateLifeCycle
|
||||||
return _buffers.getBufferSize();
|
return _buffers.getBufferSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStop() throws Exception
|
||||||
|
{
|
||||||
|
closeConnections();
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* <p>Creates and returns a new instance of a {@link WebSocketClient}, configured with this
|
* <p>Creates and returns a new instance of a {@link WebSocketClient}, configured with this
|
||||||
|
@ -231,6 +241,22 @@ public class WebSocketClientFactory extends AggregateLifeCycle
|
||||||
return sslEngine;
|
return sslEngine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean addConnection(WebSocketConnection connection)
|
||||||
|
{
|
||||||
|
return isRunning() && connections.add(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean removeConnection(WebSocketConnection connection)
|
||||||
|
{
|
||||||
|
return connections.remove(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void closeConnections()
|
||||||
|
{
|
||||||
|
for (WebSocketConnection connection : connections)
|
||||||
|
connection.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* WebSocket Client Selector Manager
|
* WebSocket Client Selector Manager
|
||||||
|
@ -457,18 +483,9 @@ public class WebSocketClientFactory extends AggregateLifeCycle
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Buffer header = _parser.getHeaderBuffer();
|
WebSocketConnection connection = newWebSocketConnection();
|
||||||
MaskGen maskGen = _future.getMaskGen();
|
|
||||||
WebSocketConnectionRFC6455 connection =
|
|
||||||
new WebSocketConnectionRFC6455(_future.getWebSocket(),
|
|
||||||
_endp,
|
|
||||||
_buffers, System.currentTimeMillis(),
|
|
||||||
_future.getMaxIdleTime(),
|
|
||||||
_future.getProtocol(),
|
|
||||||
null,
|
|
||||||
WebSocketConnectionRFC6455.VERSION,
|
|
||||||
maskGen);
|
|
||||||
|
|
||||||
|
Buffer header = _parser.getHeaderBuffer();
|
||||||
if (header.hasContent())
|
if (header.hasContent())
|
||||||
connection.fillBuffersFrom(header);
|
connection.fillBuffersFrom(header);
|
||||||
_buffers.returnBuffer(header);
|
_buffers.returnBuffer(header);
|
||||||
|
@ -483,6 +500,21 @@ public class WebSocketClientFactory extends AggregateLifeCycle
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private WebSocketConnection newWebSocketConnection() throws IOException
|
||||||
|
{
|
||||||
|
return new WebSocketClientConnection(
|
||||||
|
_future._client.getFactory(),
|
||||||
|
_future.getWebSocket(),
|
||||||
|
_endp,
|
||||||
|
_buffers,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
_future.getMaxIdleTime(),
|
||||||
|
_future.getProtocol(),
|
||||||
|
null,
|
||||||
|
WebSocketConnectionRFC6455.VERSION,
|
||||||
|
_future.getMaskGen());
|
||||||
|
}
|
||||||
|
|
||||||
public void onInputShutdown() throws IOException
|
public void onInputShutdown() throws IOException
|
||||||
{
|
{
|
||||||
_endp.close();
|
_endp.close();
|
||||||
|
@ -506,4 +538,22 @@ public class WebSocketClientFactory extends AggregateLifeCycle
|
||||||
_future.handshakeFailed(new EOFException());
|
_future.handshakeFailed(new EOFException());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class WebSocketClientConnection extends WebSocketConnectionRFC6455
|
||||||
|
{
|
||||||
|
private final WebSocketClientFactory factory;
|
||||||
|
|
||||||
|
public WebSocketClientConnection(WebSocketClientFactory factory, WebSocket webSocket, EndPoint endPoint, WebSocketBuffers buffers, long timeStamp, int maxIdleTime, String protocol, List<Extension> extensions, int draftVersion, MaskGen maskGen) throws IOException
|
||||||
|
{
|
||||||
|
super(webSocket, endPoint, buffers, timeStamp, maxIdleTime, protocol, extensions, draftVersion, maskGen);
|
||||||
|
this.factory = factory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose()
|
||||||
|
{
|
||||||
|
super.onClose();
|
||||||
|
factory.removeConnection(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,4 +28,6 @@ public interface WebSocketConnection extends AsyncConnection
|
||||||
List<Extension> getExtensions();
|
List<Extension> getExtensions();
|
||||||
|
|
||||||
WebSocket.Connection getConnection();
|
WebSocket.Connection getConnection();
|
||||||
|
|
||||||
|
void shutdown();
|
||||||
}
|
}
|
|
@ -293,6 +293,11 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public void fillBuffersFrom(Buffer buffer)
|
public void fillBuffersFrom(Buffer buffer)
|
||||||
{
|
{
|
||||||
|
|
|
@ -274,6 +274,13 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
final WebSocket.Connection connection = _connection;
|
||||||
|
if (connection != null)
|
||||||
|
connection.close(CLOSE_SHUTDOWN, null);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public void fillBuffersFrom(Buffer buffer)
|
public void fillBuffersFrom(Buffer buffer)
|
||||||
{
|
{
|
||||||
|
|
|
@ -370,6 +370,13 @@ public class WebSocketConnectionD08 extends AbstractConnection implements WebSoc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
final WebSocket.Connection connection = _connection;
|
||||||
|
if (connection != null)
|
||||||
|
connection.close(CLOSE_SHUTDOWN, null);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public void fillBuffersFrom(Buffer buffer)
|
public void fillBuffersFrom(Buffer buffer)
|
||||||
{
|
{
|
||||||
|
|
|
@ -400,6 +400,13 @@ public class WebSocketConnectionRFC6455 extends AbstractConnection implements We
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shutdown()
|
||||||
|
{
|
||||||
|
final WebSocket.Connection connection = _connection;
|
||||||
|
if (connection != null)
|
||||||
|
connection.close(CLOSE_SHUTDOWN, null);
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public void fillBuffersFrom(Buffer buffer)
|
public void fillBuffersFrom(Buffer buffer)
|
||||||
{
|
{
|
||||||
|
@ -431,7 +438,7 @@ public class WebSocketConnectionRFC6455 extends AbstractConnection implements We
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
private class WSFrameConnection implements WebSocket.FrameConnection
|
private class WSFrameConnection implements WebSocket.FrameConnection
|
||||||
{
|
{
|
||||||
volatile boolean _disconnecting;
|
private volatile boolean _disconnecting;
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
public void sendMessage(String content) throws IOException
|
public void sendMessage(String content) throws IOException
|
||||||
|
|
|
@ -30,12 +30,12 @@ package org.eclipse.jetty.websocket;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
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.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
@ -45,15 +45,17 @@ import org.eclipse.jetty.io.ConnectedEndPoint;
|
||||||
import org.eclipse.jetty.server.AbstractHttpConnection;
|
import org.eclipse.jetty.server.AbstractHttpConnection;
|
||||||
import org.eclipse.jetty.server.BlockingHttpConnection;
|
import org.eclipse.jetty.server.BlockingHttpConnection;
|
||||||
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||||
|
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||||
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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory to create WebSocket connections
|
* Factory to create WebSocket connections
|
||||||
*/
|
*/
|
||||||
public class WebSocketFactory
|
public class WebSocketFactory extends AbstractLifeCycle
|
||||||
{
|
{
|
||||||
private static final Logger LOG = Log.getLogger(WebSocketFactory.class);
|
private static final Logger LOG = Log.getLogger(WebSocketFactory.class);
|
||||||
|
private final Queue<WebSocketServletConnection> connections = new ConcurrentLinkedQueue<WebSocketServletConnection>();
|
||||||
|
|
||||||
public interface Acceptor
|
public interface Acceptor
|
||||||
{
|
{
|
||||||
|
@ -87,7 +89,7 @@ public class WebSocketFactory
|
||||||
private final Acceptor _acceptor;
|
private final Acceptor _acceptor;
|
||||||
private WebSocketBuffers _buffers;
|
private WebSocketBuffers _buffers;
|
||||||
private int _maxIdleTime = 300000;
|
private int _maxIdleTime = 300000;
|
||||||
private int _maxTextMessageSize = 16*1024;
|
private int _maxTextMessageSize = 16 * 1024;
|
||||||
private int _maxBinaryMessageSize = -1;
|
private int _maxBinaryMessageSize = -1;
|
||||||
|
|
||||||
public WebSocketFactory(Acceptor acceptor)
|
public WebSocketFactory(Acceptor acceptor)
|
||||||
|
@ -101,7 +103,6 @@ public class WebSocketFactory
|
||||||
_acceptor = acceptor;
|
_acceptor = acceptor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return A modifiable map of extension name to extension class
|
* @return A modifiable map of extension name to extension class
|
||||||
*/
|
*/
|
||||||
|
@ -187,6 +188,12 @@ public class WebSocketFactory
|
||||||
_maxBinaryMessageSize = maxBinaryMessageSize;
|
_maxBinaryMessageSize = maxBinaryMessageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doStop() throws Exception
|
||||||
|
{
|
||||||
|
closeConnections();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upgrade the request/response to a WebSocket Connection.
|
* Upgrade the request/response to a WebSocket Connection.
|
||||||
* <p>This method will not normally return, but will instead throw a
|
* <p>This method will not normally return, but will instead throw a
|
||||||
|
@ -230,40 +237,49 @@ public class WebSocketFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
final WebSocketServletConnection connection;
|
final WebSocketServletConnection connection;
|
||||||
final List<Extension> extensions;
|
|
||||||
switch (draft)
|
switch (draft)
|
||||||
{
|
{
|
||||||
case -1: // unspecified draft/version
|
case -1: // unspecified draft/version
|
||||||
case 0: // Old school draft/version
|
case 0: // Old school draft/version
|
||||||
extensions=Collections.emptyList();
|
{
|
||||||
connection = new WebSocketServletConnectionD00(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
|
connection = new WebSocketServletConnectionD00(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 1:
|
case 1:
|
||||||
case 2:
|
case 2:
|
||||||
case 3:
|
case 3:
|
||||||
case 4:
|
case 4:
|
||||||
case 5:
|
case 5:
|
||||||
case 6:
|
case 6:
|
||||||
extensions=Collections.emptyList();
|
{
|
||||||
connection = new WebSocketServletConnectionD06(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
|
connection = new WebSocketServletConnectionD06(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case 7:
|
case 7:
|
||||||
case 8:
|
case 8:
|
||||||
extensions= initExtensions(extensions_requested,8-WebSocketConnectionD08.OP_EXT_DATA, 16-WebSocketConnectionD08.OP_EXT_CTRL,3);
|
{
|
||||||
connection = new WebSocketServletConnectionD08(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,extensions,draft);
|
List<Extension> extensions = initExtensions(extensions_requested, 8 - WebSocketConnectionD08.OP_EXT_DATA, 16 - WebSocketConnectionD08.OP_EXT_CTRL, 3);
|
||||||
|
connection = new WebSocketServletConnectionD08(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case WebSocketConnectionRFC6455.VERSION: // RFC 6455 Version
|
case WebSocketConnectionRFC6455.VERSION: // RFC 6455 Version
|
||||||
extensions= initExtensions(extensions_requested,8-WebSocketConnectionRFC6455.OP_EXT_DATA, 16-WebSocketConnectionRFC6455.OP_EXT_CTRL,3);
|
{
|
||||||
connection = new WebSocketServletConnectionRFC6455(websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol,extensions,draft);
|
List<Extension> extensions = initExtensions(extensions_requested, 8 - WebSocketConnectionRFC6455.OP_EXT_DATA, 16 - WebSocketConnectionRFC6455.OP_EXT_CTRL, 3);
|
||||||
|
connection = new WebSocketServletConnectionRFC6455(this, websocket, endp, _buffers, http.getTimeStamp(), _maxIdleTime, protocol, extensions, draft);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
LOG.warn("Unsupported Websocket version: "+draft);
|
{
|
||||||
|
LOG.warn("Unsupported Websocket version: " + draft);
|
||||||
// Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol
|
// Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol
|
||||||
// Using the examples as outlined
|
// Using the examples as outlined
|
||||||
response.setHeader("Sec-WebSocket-Version","13, 8, 6, 0");
|
response.setHeader("Sec-WebSocket-Version", "13, 8, 6, 0");
|
||||||
throw new HttpException(400, "Unsupported websocket version specification: " + draft);
|
throw new HttpException(400, "Unsupported websocket version specification: " + draft);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addConnection(connection);
|
||||||
|
|
||||||
// Set the defaults
|
// Set the defaults
|
||||||
connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize);
|
connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize);
|
||||||
connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize);
|
connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize);
|
||||||
|
@ -281,8 +297,6 @@ public class WebSocketFactory
|
||||||
request.setAttribute("org.eclipse.jetty.io.Connection", connection);
|
request.setAttribute("org.eclipse.jetty.io.Connection", connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
protected String[] parseProtocols(String protocol)
|
protected String[] parseProtocols(String protocol)
|
||||||
{
|
{
|
||||||
if (protocol == null)
|
if (protocol == null)
|
||||||
|
@ -296,8 +310,6 @@ public class WebSocketFactory
|
||||||
return protocols;
|
return protocols;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response)
|
public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
|
@ -353,8 +365,6 @@ public class WebSocketFactory
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public List<Extension> initExtensions(List<String> requested,int maxDataOpcodes,int maxControlOpcodes,int maxReservedBits)
|
public List<Extension> initExtensions(List<String> requested,int maxDataOpcodes,int maxControlOpcodes,int maxReservedBits)
|
||||||
{
|
{
|
||||||
List<Extension> extensions = new ArrayList<Extension>();
|
List<Extension> extensions = new ArrayList<Extension>();
|
||||||
|
@ -386,8 +396,6 @@ public class WebSocketFactory
|
||||||
return extensions;
|
return extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
private Extension newExtension(String name)
|
private Extension newExtension(String name)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -403,4 +411,20 @@ public class WebSocketFactory
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean addConnection(WebSocketServletConnection connection)
|
||||||
|
{
|
||||||
|
return isRunning() && connections.add(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean removeConnection(WebSocketServletConnection connection)
|
||||||
|
{
|
||||||
|
return connections.remove(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void closeConnections()
|
||||||
|
{
|
||||||
|
for (WebSocketServletConnection connection : connections)
|
||||||
|
connection.shutdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,31 +34,33 @@ import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.util.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.Logger;
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
* Servlet to upgrade connections to WebSocket
|
* Servlet to upgrade connections to WebSocket
|
||||||
* <p>
|
* <p/>
|
||||||
* The request must have the correct upgrade headers, else it is
|
* The request must have the correct upgrade headers, else it is
|
||||||
* handled as a normal servlet request.
|
* handled as a normal servlet request.
|
||||||
* <p>
|
* <p/>
|
||||||
* The initParameter "bufferSize" can be used to set the buffer size,
|
* The initParameter "bufferSize" can be used to set the buffer size,
|
||||||
* which is also the max frame byte size (default 8192).
|
* which is also the max frame byte size (default 8192).
|
||||||
* <p>
|
* <p/>
|
||||||
* The initParameter "maxIdleTime" can be used to set the time in ms
|
* The initParameter "maxIdleTime" can be used to set the time in ms
|
||||||
* that a websocket may be idle before closing.
|
* that a websocket may be idle before closing.
|
||||||
* <p>
|
* <p/>
|
||||||
* The initParameter "maxTextMessagesSize" can be used to set the size in characters
|
* The initParameter "maxTextMessagesSize" can be used to set the size in characters
|
||||||
* that a websocket may be accept before closing.
|
* that a websocket may be accept before closing.
|
||||||
* <p>
|
* <p/>
|
||||||
* The initParameter "maxBinaryMessagesSize" can be used to set the size in bytes
|
* The initParameter "maxBinaryMessagesSize" can be used to set the size in bytes
|
||||||
* that a websocket may be accept before closing.
|
* that a websocket may be accept before closing.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public abstract class WebSocketServlet extends HttpServlet implements WebSocketFactory.Acceptor
|
public abstract class WebSocketServlet extends HttpServlet implements WebSocketFactory.Acceptor
|
||||||
{
|
{
|
||||||
WebSocketFactory _webSocketFactory;
|
private final Logger LOG = Log.getLogger(getClass());
|
||||||
|
private WebSocketFactory _webSocketFactory;
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/**
|
/**
|
||||||
|
@ -67,20 +69,32 @@ public abstract class WebSocketServlet extends HttpServlet implements WebSocketF
|
||||||
@Override
|
@Override
|
||||||
public void init() throws ServletException
|
public void init() throws ServletException
|
||||||
{
|
{
|
||||||
String bs=getInitParameter("bufferSize");
|
try
|
||||||
_webSocketFactory = new WebSocketFactory(this,bs==null?8192:Integer.parseInt(bs));
|
{
|
||||||
String max=getInitParameter("maxIdleTime");
|
String bs = getInitParameter("bufferSize");
|
||||||
if (max!=null)
|
_webSocketFactory = new WebSocketFactory(this, bs == null ? 8192 : Integer.parseInt(bs));
|
||||||
_webSocketFactory.setMaxIdleTime(Integer.parseInt(max));
|
_webSocketFactory.start();
|
||||||
|
|
||||||
max=getInitParameter("maxTextMessageSize");
|
String max = getInitParameter("maxIdleTime");
|
||||||
if (max!=null)
|
if (max != null)
|
||||||
_webSocketFactory.setMaxTextMessageSize(Integer.parseInt(max));
|
_webSocketFactory.setMaxIdleTime(Integer.parseInt(max));
|
||||||
|
|
||||||
max=getInitParameter("maxBinaryMessageSize");
|
max = getInitParameter("maxTextMessageSize");
|
||||||
if (max!=null)
|
if (max != null)
|
||||||
_webSocketFactory.setMaxBinaryMessageSize(Integer.parseInt(max));
|
_webSocketFactory.setMaxTextMessageSize(Integer.parseInt(max));
|
||||||
|
|
||||||
|
max = getInitParameter("maxBinaryMessageSize");
|
||||||
|
if (max != null)
|
||||||
|
_webSocketFactory.setMaxBinaryMessageSize(Integer.parseInt(max));
|
||||||
|
}
|
||||||
|
catch (ServletException x)
|
||||||
|
{
|
||||||
|
throw x;
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
throw new ServletException(x);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -90,9 +104,9 @@ public abstract class WebSocketServlet extends HttpServlet implements WebSocketF
|
||||||
@Override
|
@Override
|
||||||
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
|
||||||
{
|
{
|
||||||
if (_webSocketFactory.acceptWebSocket(request,response) || response.isCommitted())
|
if (_webSocketFactory.acceptWebSocket(request, response) || response.isCommitted())
|
||||||
return;
|
return;
|
||||||
super.service(request,response);
|
super.service(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -101,6 +115,17 @@ public abstract class WebSocketServlet extends HttpServlet implements WebSocketF
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------------------ */
|
||||||
|
@Override
|
||||||
|
public void destroy()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_webSocketFactory.stop();
|
||||||
|
}
|
||||||
|
catch (Exception x)
|
||||||
|
{
|
||||||
|
LOG.ignore(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,10 +25,13 @@ import org.eclipse.jetty.util.QuotedStringTokenizer;
|
||||||
|
|
||||||
public class WebSocketServletConnectionD00 extends WebSocketConnectionD00 implements WebSocketServletConnection
|
public class WebSocketServletConnectionD00 extends WebSocketConnectionD00 implements WebSocketServletConnection
|
||||||
{
|
{
|
||||||
public WebSocketServletConnectionD00(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
|
private final WebSocketFactory factory;
|
||||||
|
|
||||||
|
public WebSocketServletConnectionD00(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol);
|
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol);
|
||||||
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
|
public void handshake(HttpServletRequest request, HttpServletResponse response, String subprotocol) throws IOException
|
||||||
|
@ -70,7 +73,7 @@ public class WebSocketServletConnectionD00 extends WebSocketConnectionD00 implem
|
||||||
{
|
{
|
||||||
response.addHeader("Sec-WebSocket-Protocol",subprotocol);
|
response.addHeader("Sec-WebSocket-Protocol",subprotocol);
|
||||||
}
|
}
|
||||||
response.sendError(101,"WebSocket Protocol Handshake");
|
response.sendError(101, "WebSocket Protocol Handshake");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -89,4 +92,11 @@ public class WebSocketServletConnectionD00 extends WebSocketConnectionD00 implem
|
||||||
onWebsocketOpen();
|
onWebsocketOpen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose()
|
||||||
|
{
|
||||||
|
super.onClose();
|
||||||
|
factory.removeConnection(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,13 @@ import org.eclipse.jetty.io.EndPoint;
|
||||||
|
|
||||||
public class WebSocketServletConnectionD06 extends WebSocketConnectionD06 implements WebSocketServletConnection
|
public class WebSocketServletConnectionD06 extends WebSocketConnectionD06 implements WebSocketServletConnection
|
||||||
{
|
{
|
||||||
public WebSocketServletConnectionD06(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
|
private final WebSocketFactory factory;
|
||||||
|
|
||||||
|
public WebSocketServletConnectionD06(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol);
|
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol);
|
||||||
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -47,4 +50,11 @@ public class WebSocketServletConnectionD06 extends WebSocketConnectionD06 implem
|
||||||
onFrameHandshake();
|
onFrameHandshake();
|
||||||
onWebSocketOpen();
|
onWebSocketOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose()
|
||||||
|
{
|
||||||
|
super.onClose();
|
||||||
|
factory.removeConnection(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,16 +24,13 @@ import org.eclipse.jetty.io.EndPoint;
|
||||||
|
|
||||||
public class WebSocketServletConnectionD08 extends WebSocketConnectionD08 implements WebSocketServletConnection
|
public class WebSocketServletConnectionD08 extends WebSocketConnectionD08 implements WebSocketServletConnection
|
||||||
{
|
{
|
||||||
public WebSocketServletConnectionD08(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
|
private final WebSocketFactory factory;
|
||||||
List<Extension> extensions, int draft, MaskGen maskgen) throws IOException
|
|
||||||
{
|
|
||||||
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,maskgen);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebSocketServletConnectionD08(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
|
public WebSocketServletConnectionD08(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
|
||||||
List<Extension> extensions, int draft) throws IOException
|
List<Extension> extensions, int draft) throws IOException
|
||||||
{
|
{
|
||||||
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft);
|
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft);
|
||||||
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -59,4 +56,11 @@ public class WebSocketServletConnectionD08 extends WebSocketConnectionD08 implem
|
||||||
onFrameHandshake();
|
onFrameHandshake();
|
||||||
onWebSocketOpen();
|
onWebSocketOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose()
|
||||||
|
{
|
||||||
|
super.onClose();
|
||||||
|
factory.removeConnection(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,16 +24,13 @@ import org.eclipse.jetty.io.EndPoint;
|
||||||
|
|
||||||
public class WebSocketServletConnectionRFC6455 extends WebSocketConnectionRFC6455 implements WebSocketServletConnection
|
public class WebSocketServletConnectionRFC6455 extends WebSocketConnectionRFC6455 implements WebSocketServletConnection
|
||||||
{
|
{
|
||||||
public WebSocketServletConnectionRFC6455(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
|
private final WebSocketFactory factory;
|
||||||
List<Extension> extensions, int draft, MaskGen maskgen) throws IOException
|
|
||||||
{
|
|
||||||
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,maskgen);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WebSocketServletConnectionRFC6455(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
|
public WebSocketServletConnectionRFC6455(WebSocketFactory factory, WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
|
||||||
List<Extension> extensions, int draft) throws IOException
|
List<Extension> extensions, int draft) throws IOException
|
||||||
{
|
{
|
||||||
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft);
|
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft);
|
||||||
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
|
@ -59,4 +56,11 @@ public class WebSocketServletConnectionRFC6455 extends WebSocketConnectionRFC645
|
||||||
onFrameHandshake();
|
onFrameHandshake();
|
||||||
onWebSocketOpen();
|
onWebSocketOpen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClose()
|
||||||
|
{
|
||||||
|
super.onClose();
|
||||||
|
factory.removeConnection(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,57 +25,80 @@ import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.net.SocketException;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import junit.framework.Assert;
|
||||||
|
|
||||||
import org.eclipse.jetty.server.Connector;
|
import org.eclipse.jetty.server.Connector;
|
||||||
import org.eclipse.jetty.server.Server;
|
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.log.Log;
|
||||||
|
import org.eclipse.jetty.util.log.StdErrLog;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class WebSocketMessageD00Test
|
public class WebSocketMessageD00Test
|
||||||
{
|
{
|
||||||
private static Server _server;
|
private static Server __server;
|
||||||
private static Connector _connector;
|
private static Connector __connector;
|
||||||
private static TestWebSocket _serverWebSocket;
|
private static TestWebSocket __serverWebSocket;
|
||||||
|
private static CountDownLatch __latch;
|
||||||
|
private static AtomicInteger __textCount = new AtomicInteger(0);
|
||||||
|
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void startServer() throws Exception
|
public static void startServer() throws Exception
|
||||||
{
|
{
|
||||||
_server = new Server();
|
__server = new Server();
|
||||||
_connector = new SelectChannelConnector();
|
__connector = new SelectChannelConnector();
|
||||||
_server.addConnector(_connector);
|
__server.addConnector(__connector);
|
||||||
WebSocketHandler wsHandler = new WebSocketHandler()
|
WebSocketHandler wsHandler = new WebSocketHandler()
|
||||||
{
|
{
|
||||||
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
|
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
|
||||||
{
|
{
|
||||||
_serverWebSocket = new TestWebSocket();
|
__serverWebSocket = new TestWebSocket();
|
||||||
_serverWebSocket.onConnect=("onConnect".equals(protocol));
|
__serverWebSocket._onConnect=("onConnect".equals(protocol));
|
||||||
return _serverWebSocket;
|
__serverWebSocket._echo=("echo".equals(protocol));
|
||||||
|
__serverWebSocket._latch=("latch".equals(protocol));
|
||||||
|
if (__serverWebSocket._latch)
|
||||||
|
__latch=new CountDownLatch(1);
|
||||||
|
return __serverWebSocket;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
wsHandler.setHandler(new DefaultHandler());
|
wsHandler.setHandler(new DefaultHandler());
|
||||||
_server.setHandler(wsHandler);
|
__server.setHandler(wsHandler);
|
||||||
_server.start();
|
__server.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@AfterClass
|
||||||
public static void stopServer() throws Exception
|
public static void stopServer() throws Exception
|
||||||
{
|
{
|
||||||
_server.stop();
|
__server.stop();
|
||||||
_server.join();
|
__server.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
__textCount.set(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testServerSendBigStringMessage() throws Exception
|
public void testServerSendBigStringMessage() throws Exception
|
||||||
{
|
{
|
||||||
Socket socket = new Socket("localhost", _connector.getLocalPort());
|
Socket socket = new Socket("localhost", __connector.getLocalPort());
|
||||||
OutputStream output = socket.getOutputStream();
|
OutputStream output = socket.getOutputStream();
|
||||||
output.write(
|
output.write(
|
||||||
("GET /test HTTP/1.1\r\n" +
|
("GET /test HTTP/1.1\r\n" +
|
||||||
|
@ -94,7 +117,6 @@ public class WebSocketMessageD00Test
|
||||||
InputStream input = socket.getInputStream();
|
InputStream input = socket.getInputStream();
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(input, "ISO-8859-1"));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(input, "ISO-8859-1"));
|
||||||
String responseLine = reader.readLine();
|
String responseLine = reader.readLine();
|
||||||
System.err.println(responseLine);
|
|
||||||
assertTrue(responseLine.startsWith("HTTP/1.1 101 WebSocket Protocol Handshake"));
|
assertTrue(responseLine.startsWith("HTTP/1.1 101 WebSocket Protocol Handshake"));
|
||||||
// Read until we find an empty line, which signals the end of the http response
|
// Read until we find an empty line, which signals the end of the http response
|
||||||
String line;
|
String line;
|
||||||
|
@ -102,8 +124,8 @@ public class WebSocketMessageD00Test
|
||||||
if (line.length() == 0)
|
if (line.length() == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
assertTrue(_serverWebSocket.awaitConnected(1000));
|
assertTrue(__serverWebSocket.awaitConnected(1000));
|
||||||
assertNotNull(_serverWebSocket.outbound);
|
assertNotNull(__serverWebSocket.outbound);
|
||||||
|
|
||||||
// read the hixie bytes
|
// read the hixie bytes
|
||||||
char[] hixie=new char[16]; // should be bytes, but we know this example is all ascii
|
char[] hixie=new char[16]; // should be bytes, but we know this example is all ascii
|
||||||
|
@ -125,7 +147,7 @@ public class WebSocketMessageD00Test
|
||||||
String text = "0123456789ABCDEF";
|
String text = "0123456789ABCDEF";
|
||||||
for (int i = 0; i < 64 * 1024 / text.length(); ++i)
|
for (int i = 0; i < 64 * 1024 / text.length(); ++i)
|
||||||
message.append(text);
|
message.append(text);
|
||||||
_serverWebSocket.outbound.sendMessage(message.toString());
|
__serverWebSocket.outbound.sendMessage(message.toString());
|
||||||
|
|
||||||
// Read until we get 0xFF
|
// Read until we get 0xFF
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
@ -147,7 +169,7 @@ public class WebSocketMessageD00Test
|
||||||
@Test
|
@Test
|
||||||
public void testServerSendOnConnect() throws Exception
|
public void testServerSendOnConnect() throws Exception
|
||||||
{
|
{
|
||||||
Socket socket = new Socket("localhost", _connector.getLocalPort());
|
Socket socket = new Socket("localhost", __connector.getLocalPort());
|
||||||
OutputStream output = socket.getOutputStream();
|
OutputStream output = socket.getOutputStream();
|
||||||
output.write(
|
output.write(
|
||||||
("GET /test HTTP/1.1\r\n" +
|
("GET /test HTTP/1.1\r\n" +
|
||||||
|
@ -200,8 +222,8 @@ public class WebSocketMessageD00Test
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
assertTrue(_serverWebSocket.awaitConnected(1000));
|
assertTrue(__serverWebSocket.awaitConnected(1000));
|
||||||
assertNotNull(_serverWebSocket.outbound);
|
assertNotNull(__serverWebSocket.outbound);
|
||||||
|
|
||||||
looking_for="8jKS'y:G*Co,Wxa-";
|
looking_for="8jKS'y:G*Co,Wxa-";
|
||||||
while(true)
|
while(true)
|
||||||
|
@ -233,16 +255,497 @@ public class WebSocketMessageD00Test
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class TestWebSocket implements WebSocket
|
|
||||||
|
@Test
|
||||||
|
public void testServerEcho() throws Exception
|
||||||
{
|
{
|
||||||
boolean onConnect=false;
|
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
|
||||||
private final CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
|
Socket socket = new Socket("localhost", __connector.getLocalPort());
|
||||||
|
socket.setSoTimeout(1000);
|
||||||
|
OutputStream output = socket.getOutputStream();
|
||||||
|
InputStream input = socket.getInputStream();
|
||||||
|
output.write(
|
||||||
|
("GET /test HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
"Upgrade: WebSocket\r\n" +
|
||||||
|
"Connection: Upgrade\r\n" +
|
||||||
|
"Sec-WebSocket-Protocol: echo\r\n" +
|
||||||
|
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
|
||||||
|
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
|
||||||
|
"\r\n"+
|
||||||
|
"^n:ds[4U").getBytes("ISO-8859-1"));
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
|
||||||
|
skipTo("\r\n\r\n",input);
|
||||||
|
lookFor("8jKS'y:G*Co,Wxa-",input);
|
||||||
|
|
||||||
|
assertTrue(__serverWebSocket.awaitConnected(1000));
|
||||||
|
assertNotNull(__serverWebSocket.connection);
|
||||||
|
|
||||||
|
|
||||||
|
output.write(0x00);
|
||||||
|
byte[] bytes="this is an echo".getBytes(StringUtil.__ISO_8859_1);
|
||||||
|
for (int i=0;i<bytes.length;i++)
|
||||||
|
output.write(bytes[i]);
|
||||||
|
output.write(0xff);
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
assertEquals("00",TypeUtil.toHexString((byte)(0xff&input.read())));
|
||||||
|
lookFor("this is an echo",input);
|
||||||
|
assertEquals(0xff,input.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBlockedConsumer() throws Exception
|
||||||
|
{
|
||||||
|
|
||||||
|
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
|
||||||
|
|
||||||
|
Socket socket = new Socket("localhost", __connector.getLocalPort());
|
||||||
|
socket.setSoTimeout(60000);
|
||||||
|
OutputStream output = socket.getOutputStream();
|
||||||
|
InputStream input = socket.getInputStream();
|
||||||
|
output.write(
|
||||||
|
("GET /test HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
"Upgrade: WebSocket\r\n" +
|
||||||
|
"Connection: Upgrade\r\n" +
|
||||||
|
"Sec-WebSocket-Protocol: latch\r\n" +
|
||||||
|
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
|
||||||
|
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
|
||||||
|
"\r\n"+
|
||||||
|
"^n:ds[4U").getBytes("ISO-8859-1"));
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
|
||||||
|
skipTo("\r\n\r\n",input);
|
||||||
|
lookFor("8jKS'y:G*Co,Wxa-",input);
|
||||||
|
|
||||||
|
assertTrue(__serverWebSocket.awaitConnected(1000));
|
||||||
|
assertNotNull(__serverWebSocket.connection);
|
||||||
|
__serverWebSocket.connection.setMaxIdleTime(60000);
|
||||||
|
|
||||||
|
|
||||||
|
byte[] bytes="This is a long message of text that we will send again and again".getBytes(StringUtil.__ISO_8859_1);
|
||||||
|
byte[] mesg=new byte[bytes.length+2];
|
||||||
|
mesg[0]=(byte)0x00;
|
||||||
|
for (int i=0;i<bytes.length;i++)
|
||||||
|
mesg[i+1]=(byte)(bytes[i]);
|
||||||
|
mesg[mesg.length-1]=(byte)0xFF;
|
||||||
|
|
||||||
|
final int count = 100000;
|
||||||
|
|
||||||
|
|
||||||
|
// Send and receive 1 message
|
||||||
|
output.write(mesg);
|
||||||
|
output.flush();
|
||||||
|
while(__textCount.get()==0)
|
||||||
|
Thread.sleep(10);
|
||||||
|
|
||||||
|
// unblock the latch in 4s
|
||||||
|
new Thread()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(4000);
|
||||||
|
__latch.countDown();
|
||||||
|
//System.err.println("latched");
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
|
||||||
|
// Send enough messages to fill receive buffer
|
||||||
|
long max=0;
|
||||||
|
long start=System.currentTimeMillis();
|
||||||
|
for (int i=0;i<count;i++)
|
||||||
|
{
|
||||||
|
output.write(mesg);
|
||||||
|
if (i%100==0)
|
||||||
|
{
|
||||||
|
// System.err.println(">>> "+i);
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
long now=System.currentTimeMillis();
|
||||||
|
long duration=now-start;
|
||||||
|
start=now;
|
||||||
|
if (max<duration)
|
||||||
|
max=duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Thread.sleep(50);
|
||||||
|
while(__textCount.get()<count+1)
|
||||||
|
{
|
||||||
|
System.err.println(__textCount.get()+"<"+(count+1));
|
||||||
|
Thread.sleep(10);
|
||||||
|
}
|
||||||
|
assertEquals(count+1,__textCount.get()); // all messages
|
||||||
|
assertTrue(max>2000); // was blocked
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBlockedProducer() throws Exception
|
||||||
|
{
|
||||||
|
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
|
||||||
|
|
||||||
|
final Socket socket = new Socket("localhost", __connector.getLocalPort());
|
||||||
|
socket.setSoTimeout(60000);
|
||||||
|
OutputStream output = socket.getOutputStream();
|
||||||
|
InputStream input = socket.getInputStream();
|
||||||
|
output.write(
|
||||||
|
("GET /test HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
"Upgrade: WebSocket\r\n" +
|
||||||
|
"Connection: Upgrade\r\n" +
|
||||||
|
"Sec-WebSocket-Protocol: latch\r\n" +
|
||||||
|
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
|
||||||
|
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
|
||||||
|
"\r\n"+
|
||||||
|
"^n:ds[4U").getBytes("ISO-8859-1"));
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
|
||||||
|
skipTo("\r\n\r\n",input);
|
||||||
|
lookFor("8jKS'y:G*Co,Wxa-",input);
|
||||||
|
|
||||||
|
assertTrue(__serverWebSocket.awaitConnected(1000));
|
||||||
|
assertNotNull(__serverWebSocket.connection);
|
||||||
|
|
||||||
|
|
||||||
|
final int count = 100000;
|
||||||
|
|
||||||
|
__serverWebSocket.connection.setMaxIdleTime(60000);
|
||||||
|
__latch.countDown();
|
||||||
|
|
||||||
|
// wait 2s and then consume messages
|
||||||
|
final AtomicLong totalB=new AtomicLong();
|
||||||
|
new Thread()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Thread.sleep(2000);
|
||||||
|
|
||||||
|
byte[] recv = new byte[32*1024];
|
||||||
|
|
||||||
|
int len=0;
|
||||||
|
while (len>=0)
|
||||||
|
{
|
||||||
|
totalB.addAndGet(len);
|
||||||
|
len=socket.getInputStream().read(recv,0,recv.length);
|
||||||
|
Thread.sleep(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start();
|
||||||
|
|
||||||
|
|
||||||
|
// Send enough messages to fill receive buffer
|
||||||
|
long max=0;
|
||||||
|
long start=System.currentTimeMillis();
|
||||||
|
String mesg="How Now Brown Cow";
|
||||||
|
for (int i=0;i<count;i++)
|
||||||
|
{
|
||||||
|
__serverWebSocket.connection.sendMessage(mesg);
|
||||||
|
if (i%100==0)
|
||||||
|
{
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
long now=System.currentTimeMillis();
|
||||||
|
long duration=now-start;
|
||||||
|
start=now;
|
||||||
|
if (max<duration)
|
||||||
|
max=duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while(totalB.get()<(count*(mesg.length()+2)))
|
||||||
|
Thread.sleep(100);
|
||||||
|
|
||||||
|
assertEquals(count*(mesg.length()+2),totalB.get()); // all messages
|
||||||
|
assertTrue(max>1000); // was blocked
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIdle() throws Exception
|
||||||
|
{
|
||||||
|
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
|
||||||
|
|
||||||
|
final Socket socket = new Socket("localhost", __connector.getLocalPort());
|
||||||
|
socket.setSoTimeout(10000);
|
||||||
|
OutputStream output = socket.getOutputStream();
|
||||||
|
InputStream input = socket.getInputStream();
|
||||||
|
output.write(
|
||||||
|
("GET /test HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
"Upgrade: WebSocket\r\n" +
|
||||||
|
"Connection: Upgrade\r\n" +
|
||||||
|
"Sec-WebSocket-Protocol: onConnect\r\n" +
|
||||||
|
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
|
||||||
|
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
|
||||||
|
"\r\n"+
|
||||||
|
"^n:ds[4U").getBytes("ISO-8859-1"));
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
|
||||||
|
skipTo("\r\n\r\n",input);
|
||||||
|
lookFor("8jKS'y:G*Co,Wxa-",input);
|
||||||
|
|
||||||
|
assertTrue(__serverWebSocket.awaitConnected(1000));
|
||||||
|
assertNotNull(__serverWebSocket.connection);
|
||||||
|
__serverWebSocket.connection.setMaxIdleTime(250);
|
||||||
|
assertEquals(0x00,input.read());
|
||||||
|
lookFor("sent on connect",input);
|
||||||
|
assertEquals(0xff,input.read());
|
||||||
|
|
||||||
|
assertEquals(-1,input.read());
|
||||||
|
socket.close();
|
||||||
|
|
||||||
|
assertTrue(__serverWebSocket.awaitDisconnected(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIdleBadClient() throws Exception
|
||||||
|
{
|
||||||
|
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
|
||||||
|
|
||||||
|
final Socket socket = new Socket("localhost", __connector.getLocalPort());
|
||||||
|
socket.setSoTimeout(10000);
|
||||||
|
OutputStream output = socket.getOutputStream();
|
||||||
|
InputStream input = socket.getInputStream();
|
||||||
|
output.write(
|
||||||
|
("GET /test HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
"Upgrade: WebSocket\r\n" +
|
||||||
|
"Connection: Upgrade\r\n" +
|
||||||
|
"Sec-WebSocket-Protocol: onConnect\r\n" +
|
||||||
|
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
|
||||||
|
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
|
||||||
|
"\r\n"+
|
||||||
|
"^n:ds[4U").getBytes("ISO-8859-1"));
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
|
||||||
|
skipTo("\r\n\r\n",input);
|
||||||
|
lookFor("8jKS'y:G*Co,Wxa-",input);
|
||||||
|
|
||||||
|
assertTrue(__serverWebSocket.awaitConnected(1000));
|
||||||
|
assertNotNull(__serverWebSocket.connection);
|
||||||
|
__serverWebSocket.connection.setMaxIdleTime(250);
|
||||||
|
assertEquals(0x00,input.read());
|
||||||
|
lookFor("sent on connect",input);
|
||||||
|
assertEquals(0xff,input.read());
|
||||||
|
|
||||||
|
assertEquals(-1,input.read());
|
||||||
|
|
||||||
|
assertTrue(__serverWebSocket.disconnected.getCount()>0);
|
||||||
|
assertTrue(__serverWebSocket.awaitDisconnected(1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTCPClose() throws Exception
|
||||||
|
{
|
||||||
|
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
|
||||||
|
|
||||||
|
final Socket socket = new Socket("localhost", __connector.getLocalPort());
|
||||||
|
socket.setSoTimeout(10000);
|
||||||
|
OutputStream output = socket.getOutputStream();
|
||||||
|
InputStream input = socket.getInputStream();
|
||||||
|
output.write(
|
||||||
|
("GET /test HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
"Upgrade: WebSocket\r\n" +
|
||||||
|
"Connection: Upgrade\r\n" +
|
||||||
|
"Sec-WebSocket-Protocol: onConnect\r\n" +
|
||||||
|
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
|
||||||
|
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
|
||||||
|
"\r\n"+
|
||||||
|
"^n:ds[4U").getBytes("ISO-8859-1"));
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
|
||||||
|
skipTo("\r\n\r\n",input);
|
||||||
|
lookFor("8jKS'y:G*Co,Wxa-",input);
|
||||||
|
|
||||||
|
assertTrue(__serverWebSocket.awaitConnected(1000));
|
||||||
|
assertNotNull(__serverWebSocket.connection);
|
||||||
|
__serverWebSocket.connection.setMaxIdleTime(250);
|
||||||
|
assertEquals(0x00,input.read());
|
||||||
|
lookFor("sent on connect",input);
|
||||||
|
assertEquals(0xff,input.read());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
socket.close();
|
||||||
|
|
||||||
|
assertTrue(__serverWebSocket.awaitDisconnected(500));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
__serverWebSocket.connection.sendMessage("Don't send");
|
||||||
|
assertTrue(false);
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
assertTrue(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTCPHalfClose() throws Exception
|
||||||
|
{
|
||||||
|
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
|
||||||
|
|
||||||
|
final Socket socket = new Socket("localhost", __connector.getLocalPort());
|
||||||
|
socket.setSoTimeout(10000);
|
||||||
|
OutputStream output = socket.getOutputStream();
|
||||||
|
InputStream input = socket.getInputStream();
|
||||||
|
output.write(
|
||||||
|
("GET /test HTTP/1.1\r\n" +
|
||||||
|
"Host: localhost\r\n" +
|
||||||
|
"Upgrade: WebSocket\r\n" +
|
||||||
|
"Connection: Upgrade\r\n" +
|
||||||
|
"Sec-WebSocket-Protocol: onConnect\r\n" +
|
||||||
|
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
|
||||||
|
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
|
||||||
|
"\r\n"+
|
||||||
|
"^n:ds[4U").getBytes("ISO-8859-1"));
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
|
||||||
|
skipTo("\r\n\r\n",input);
|
||||||
|
lookFor("8jKS'y:G*Co,Wxa-",input);
|
||||||
|
|
||||||
|
assertTrue(__serverWebSocket.awaitConnected(1000));
|
||||||
|
assertNotNull(__serverWebSocket.connection);
|
||||||
|
__serverWebSocket.connection.setMaxIdleTime(250);
|
||||||
|
assertEquals(0x00,input.read());
|
||||||
|
lookFor("sent on connect",input);
|
||||||
|
assertEquals(0xff,input.read());
|
||||||
|
|
||||||
|
|
||||||
|
socket.shutdownOutput();
|
||||||
|
|
||||||
|
assertTrue(__serverWebSocket.awaitDisconnected(500));
|
||||||
|
|
||||||
|
assertEquals(-1,input.read());
|
||||||
|
|
||||||
|
// look for broken pipe
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (int i=0;i<1000;i++)
|
||||||
|
output.write(0);
|
||||||
|
Assert.fail();
|
||||||
|
}
|
||||||
|
catch(SocketException e)
|
||||||
|
{
|
||||||
|
// expected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private void lookFor(String string,InputStream in)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
String orig=string;
|
||||||
|
Utf8StringBuilder scanned=new Utf8StringBuilder();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
int b = in.read();
|
||||||
|
if (b<0)
|
||||||
|
throw new EOFException();
|
||||||
|
scanned.append((byte)b);
|
||||||
|
assertEquals("looking for\""+orig+"\" in '"+scanned+"'",(int)string.charAt(0),b);
|
||||||
|
if (string.length()==1)
|
||||||
|
break;
|
||||||
|
string=string.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(IOException e)
|
||||||
|
{
|
||||||
|
System.err.println("IOE while looking for \""+orig+"\" in '"+scanned+"'");
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipTo(String string,InputStream in)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
int state=0;
|
||||||
|
|
||||||
|
while(true)
|
||||||
|
{
|
||||||
|
int b = in.read();
|
||||||
|
if (b<0)
|
||||||
|
throw new EOFException();
|
||||||
|
|
||||||
|
if (b==string.charAt(state))
|
||||||
|
{
|
||||||
|
state++;
|
||||||
|
if (state==string.length())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
state=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static class TestWebSocket implements WebSocket.OnFrame, WebSocket, WebSocket.OnTextMessage
|
||||||
|
{
|
||||||
|
protected boolean _latch;
|
||||||
|
boolean _echo=true;
|
||||||
|
boolean _onConnect=false;
|
||||||
private volatile Connection outbound;
|
private volatile Connection outbound;
|
||||||
|
private final CountDownLatch connected = new CountDownLatch(1);
|
||||||
|
private final CountDownLatch disconnected = new CountDownLatch(1);
|
||||||
|
private volatile FrameConnection connection;
|
||||||
|
|
||||||
|
public FrameConnection getConnection()
|
||||||
|
{
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onHandshake(FrameConnection connection)
|
||||||
|
{
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
public void onOpen(Connection outbound)
|
public void onOpen(Connection outbound)
|
||||||
{
|
{
|
||||||
this.outbound = outbound;
|
this.outbound = outbound;
|
||||||
if (onConnect)
|
if (_onConnect)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -253,16 +756,55 @@ public class WebSocketMessageD00Test
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
latch.countDown();
|
connected.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean awaitConnected(long time) throws InterruptedException
|
private boolean awaitConnected(long time) throws InterruptedException
|
||||||
{
|
{
|
||||||
return latch.await(time, TimeUnit.MILLISECONDS);
|
return connected.await(time, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean awaitDisconnected(long time) throws InterruptedException
|
||||||
|
{
|
||||||
|
return disconnected.await(time, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onClose(int code,String message)
|
public void onClose(int code,String message)
|
||||||
{
|
{
|
||||||
|
disconnected.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onFrame(byte flags, byte opcode, byte[] data, int offset, int length)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessage(String data)
|
||||||
|
{
|
||||||
|
__textCount.incrementAndGet();
|
||||||
|
if (_latch)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
__latch.await();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_echo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
outbound.sendMessage(data);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,176 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (c) 2011 Intalio, Inc.
|
||||||
|
* ======================================================================
|
||||||
|
* All rights reserved. This program and the accompanying materials
|
||||||
|
* are made available under the terms of the Eclipse Public License v1.0
|
||||||
|
* and Apache License v2.0 which accompanies this distribution.
|
||||||
|
*
|
||||||
|
* The Eclipse Public License is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-v10.html
|
||||||
|
*
|
||||||
|
* The Apache License v2.0 is available at
|
||||||
|
* http://www.opensource.org/licenses/apache2.0.php
|
||||||
|
*
|
||||||
|
* You may elect to redistribute this code under either of these licenses.
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
package org.eclipse.jetty.websocket;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.handler.HandlerCollection;
|
||||||
|
import org.eclipse.jetty.server.nio.SelectChannelConnector;
|
||||||
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class WebSocketRedeployTest
|
||||||
|
{
|
||||||
|
private Server server;
|
||||||
|
private ServletContextHandler context;
|
||||||
|
private String uri;
|
||||||
|
private WebSocketClientFactory wsFactory;
|
||||||
|
|
||||||
|
public void init(final WebSocket webSocket) throws Exception
|
||||||
|
{
|
||||||
|
server = new Server();
|
||||||
|
SelectChannelConnector connector = new SelectChannelConnector();
|
||||||
|
// connector.setPort(8080);
|
||||||
|
server.addConnector(connector);
|
||||||
|
|
||||||
|
HandlerCollection handlers = new HandlerCollection();
|
||||||
|
server.setHandler(handlers);
|
||||||
|
|
||||||
|
String contextPath = "/test_context";
|
||||||
|
context = new ServletContextHandler(handlers, contextPath, ServletContextHandler.SESSIONS);
|
||||||
|
|
||||||
|
WebSocketServlet servlet = new WebSocketServlet()
|
||||||
|
{
|
||||||
|
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
|
||||||
|
{
|
||||||
|
return webSocket;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
String servletPath = "/websocket";
|
||||||
|
context.addServlet(new ServletHolder(servlet), servletPath);
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
uri = "ws://localhost:" + connector.getLocalPort() + contextPath + servletPath;
|
||||||
|
|
||||||
|
wsFactory = new WebSocketClientFactory();
|
||||||
|
wsFactory.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void destroy() throws Exception
|
||||||
|
{
|
||||||
|
if (wsFactory != null)
|
||||||
|
wsFactory.stop();
|
||||||
|
if (server != null)
|
||||||
|
{
|
||||||
|
server.stop();
|
||||||
|
server.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStoppingContextClosesConnections() throws Exception
|
||||||
|
{
|
||||||
|
final CountDownLatch openLatch = new CountDownLatch(2);
|
||||||
|
final CountDownLatch closeLatch = new CountDownLatch(2);
|
||||||
|
init(new WebSocket.OnTextMessage()
|
||||||
|
{
|
||||||
|
public void onOpen(Connection connection)
|
||||||
|
{
|
||||||
|
openLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessage(String data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClose(int closeCode, String message)
|
||||||
|
{
|
||||||
|
closeLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
WebSocketClient client = wsFactory.newWebSocketClient();
|
||||||
|
client.open(new URI(uri), new WebSocket.OnTextMessage()
|
||||||
|
{
|
||||||
|
public void onOpen(Connection connection)
|
||||||
|
{
|
||||||
|
openLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessage(String data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClose(int closeCode, String message)
|
||||||
|
{
|
||||||
|
closeLatch.countDown();
|
||||||
|
}
|
||||||
|
}, 5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
context.stop();
|
||||||
|
|
||||||
|
Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStoppingClientFactoryClosesConnections() throws Exception
|
||||||
|
{
|
||||||
|
final CountDownLatch openLatch = new CountDownLatch(2);
|
||||||
|
final CountDownLatch closeLatch = new CountDownLatch(2);
|
||||||
|
init(new WebSocket.OnTextMessage()
|
||||||
|
{
|
||||||
|
public void onOpen(Connection connection)
|
||||||
|
{
|
||||||
|
openLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessage(String data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClose(int closeCode, String message)
|
||||||
|
{
|
||||||
|
closeLatch.countDown();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
WebSocketClient client = wsFactory.newWebSocketClient();
|
||||||
|
client.open(new URI(uri), new WebSocket.OnTextMessage()
|
||||||
|
{
|
||||||
|
public void onOpen(Connection connection)
|
||||||
|
{
|
||||||
|
openLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMessage(String data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onClose(int closeCode, String message)
|
||||||
|
{
|
||||||
|
closeLatch.countDown();
|
||||||
|
}
|
||||||
|
}, 5, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
wsFactory.stop();
|
||||||
|
|
||||||
|
Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,20 +110,22 @@ public class XmlConfiguration
|
||||||
__parser = new XmlParser();
|
__parser = new XmlParser();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
URL configURL = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true);
|
URL config60 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true);
|
||||||
__parser.redirectEntity("configure.dtd",configURL);
|
URL config71 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_1.dtd",true);
|
||||||
__parser.redirectEntity("configure_1_0.dtd",configURL);
|
__parser.redirectEntity("configure.dtd",config71);
|
||||||
__parser.redirectEntity("configure_1_1.dtd",configURL);
|
__parser.redirectEntity("configure_1_0.dtd",config60);
|
||||||
__parser.redirectEntity("configure_1_2.dtd",configURL);
|
__parser.redirectEntity("configure_1_1.dtd",config60);
|
||||||
__parser.redirectEntity("configure_1_3.dtd",configURL);
|
__parser.redirectEntity("configure_1_2.dtd",config60);
|
||||||
__parser.redirectEntity("configure_6_0.dtd",configURL);
|
__parser.redirectEntity("configure_1_3.dtd",config60);
|
||||||
|
__parser.redirectEntity("configure_6_0.dtd",config60);
|
||||||
|
__parser.redirectEntity("configure_7_1.dtd",config71);
|
||||||
|
|
||||||
__parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",configURL);
|
__parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",config71);
|
||||||
__parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",configURL);
|
__parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config71);
|
||||||
__parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",configURL);
|
__parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config71);
|
||||||
|
|
||||||
__parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",configURL);
|
__parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config71);
|
||||||
__parser.redirectEntity("-//Jetty//Configure//EN",configURL);
|
__parser.redirectEntity("-//Jetty//Configure//EN",config71);
|
||||||
}
|
}
|
||||||
catch (ClassNotFoundException e)
|
catch (ClassNotFoundException e)
|
||||||
{
|
{
|
||||||
|
@ -902,7 +904,7 @@ public class XmlConfiguration
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/*
|
/*
|
||||||
* Create a new value object.
|
* Get a Property.
|
||||||
*
|
*
|
||||||
* @param obj @param node @return @exception Exception
|
* @param obj @param node @return @exception Exception
|
||||||
*/
|
*/
|
||||||
|
@ -923,6 +925,7 @@ public class XmlConfiguration
|
||||||
return prop;
|
return prop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/*
|
/*
|
||||||
* Get the value of an element. If no value type is specified, then white space is trimmed out of the value. If it contains multiple value elements they
|
* Get the value of an element. If no value type is specified, then white space is trimmed out of the value. If it contains multiple value elements they
|
||||||
|
@ -1092,6 +1095,14 @@ public class XmlConfiguration
|
||||||
return System.getProperty(name,defaultValue);
|
return System.getProperty(name,defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ("Env".equals(tag))
|
||||||
|
{
|
||||||
|
String name = node.getAttribute("name");
|
||||||
|
String defaultValue = node.getAttribute("default");
|
||||||
|
String value=System.getenv(name);
|
||||||
|
return value==null?defaultValue:value;
|
||||||
|
}
|
||||||
|
|
||||||
LOG.warn("Unknown value tag: " + node,new Throwable());
|
LOG.warn("Unknown value tag: " + node,new Throwable());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ my be specified if a match is not achieved.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!ENTITY % CONFIG "Set|Get|Put|Call|New|Ref|Array|Map|Property">
|
<!ENTITY % CONFIG "Set|Get|Put|Call|New|Ref|Array|Map|Property">
|
||||||
<!ENTITY % VALUE "#PCDATA|Get|Call|New|Ref|Array|Map|SystemProperty|Property">
|
<!ENTITY % VALUE "#PCDATA|Get|Call|New|Ref|Array|Map|SystemProperty|Env|Property">
|
||||||
|
|
||||||
<!ENTITY % TYPEATTR "type CDATA #IMPLIED " > <!-- String|Character|Short|Byte|Integer|Long|Boolean|Float|Double|char|short|byte|int|long|boolean|float|double|URL|InetAddress|InetAddrPort| #classname -->
|
<!ENTITY % TYPEATTR "type CDATA #IMPLIED " > <!-- String|Character|Short|Byte|Integer|Long|Boolean|Float|Double|char|short|byte|int|long|boolean|float|double|URL|InetAddress|InetAddrPort| #classname -->
|
||||||
<!ENTITY % IMPLIEDCLASSATTR "class NMTOKEN #IMPLIED" >
|
<!ENTITY % IMPLIEDCLASSATTR "class NMTOKEN #IMPLIED" >
|
||||||
|
@ -245,6 +245,24 @@ This is equivalent to:
|
||||||
<!ELEMENT SystemProperty EMPTY>
|
<!ELEMENT SystemProperty EMPTY>
|
||||||
<!ATTLIST SystemProperty %NAMEATTR; %DEFAULTATTR; %IDATTR;>
|
<!ATTLIST SystemProperty %NAMEATTR; %DEFAULTATTR; %IDATTR;>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Environment variable Element.
|
||||||
|
This element allows OS Environment variables to be retrieved as
|
||||||
|
part of the value of elements such as Set, Put, Arg, etc.
|
||||||
|
The name attribute specifies the env variable name and the optional
|
||||||
|
default argument provides a default value.
|
||||||
|
|
||||||
|
<Env name="Test" default="value" />
|
||||||
|
|
||||||
|
This is equivalent to:
|
||||||
|
|
||||||
|
String v=System.getEnv("Test");
|
||||||
|
if (v==null) v="value";
|
||||||
|
|
||||||
|
-->
|
||||||
|
<!ELEMENT Env EMPTY>
|
||||||
|
<!ATTLIST Env %NAMEATTR; %DEFAULTATTR; %IDATTR;>
|
||||||
|
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Property Element.
|
Property Element.
|
||||||
|
|
|
@ -0,0 +1,265 @@
|
||||||
|
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This is the document type descriptor for the
|
||||||
|
org.eclipse.XmlConfiguration class. It allows a java object to be
|
||||||
|
configured by with a sequence of Set, Put and Call elements. These tags are
|
||||||
|
mapped to methods on the object to be configured as follows:
|
||||||
|
|
||||||
|
<Set name="Test">value</Set> == obj.setTest("value");
|
||||||
|
<Put name="Test">value</Put> == obj.put("Test","value");
|
||||||
|
<Call name="test"><Arg>value</Arg></Call> == obj.test("value");
|
||||||
|
|
||||||
|
Values themselves may be configured objects that are created with the
|
||||||
|
<New> tag or returned from a <Call> tag.
|
||||||
|
|
||||||
|
Values are matched to arguments on a best effort approach, but types
|
||||||
|
my be specified if a match is not achieved.
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!ENTITY % CONFIG "Set|Get|Put|Call|New|Ref|Array|Map|Property">
|
||||||
|
<!ENTITY % VALUE "#PCDATA|Get|Call|New|Ref|Array|Map|SystemProperty|Property">
|
||||||
|
|
||||||
|
<!ENTITY % TYPEATTR "type CDATA #IMPLIED " > <!-- String|Character|Short|Byte|Integer|Long|Boolean|Float|Double|char|short|byte|int|long|boolean|float|double|URL|InetAddress|InetAddrPort| #classname -->
|
||||||
|
<!ENTITY % IMPLIEDCLASSATTR "class NMTOKEN #IMPLIED" >
|
||||||
|
<!ENTITY % CLASSATTR "class NMTOKEN #REQUIRED" >
|
||||||
|
<!ENTITY % NAMEATTR "name NMTOKEN #REQUIRED" >
|
||||||
|
<!ENTITY % IMPLIEDNAMEATTR "name NMTOKEN #IMPLIED" >
|
||||||
|
<!ENTITY % DEFAULTATTR "default CDATA #IMPLIED" >
|
||||||
|
<!ENTITY % IDATTR "id NMTOKEN #IMPLIED" >
|
||||||
|
<!ENTITY % REQUIREDIDATTR "id NMTOKEN #REQUIRED" >
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Configure Element.
|
||||||
|
This is the root element that specifies the class of object that
|
||||||
|
can be configured:
|
||||||
|
|
||||||
|
<Configure class="com.acme.MyClass"> ... </Configure>
|
||||||
|
-->
|
||||||
|
<!ELEMENT Configure (%CONFIG;)* >
|
||||||
|
<!ATTLIST Configure %IMPLIEDCLASSATTR; %IDATTR; >
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Set Element.
|
||||||
|
This element maps to a call to a setter method or field on the current object.
|
||||||
|
The name and optional type attributes are used to select the setter
|
||||||
|
method. If the name given is xxx, then a setXxx method is used, or
|
||||||
|
the xxx field is used of setXxx cannot be found.
|
||||||
|
A Set element can contain value text and/or the value objects returned
|
||||||
|
by other elements such as Call, New, SystemProperty, etc.
|
||||||
|
If no value type is specified, then white
|
||||||
|
space is trimmed out of the value. If it contains multiple value
|
||||||
|
elements they are added as strings before being converted to any
|
||||||
|
specified type.
|
||||||
|
|
||||||
|
A Set with a class attribute is treated as a static set method invocation.
|
||||||
|
-->
|
||||||
|
<!ELEMENT Set ( %VALUE; )* >
|
||||||
|
<!ATTLIST Set %NAMEATTR; %TYPEATTR; %IMPLIEDCLASSATTR; >
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Get Element.
|
||||||
|
This element maps to a call to a getter method or field on the current object.
|
||||||
|
The name attribute is used to select the get method.
|
||||||
|
If the name given is xxx, then a getXxx method is used, or
|
||||||
|
the xxx field is used if getXxx cannot be found.
|
||||||
|
A Get element can contain other elements such as Set, Put, Call, etc.
|
||||||
|
which act on the object returned by the get call.
|
||||||
|
|
||||||
|
A Get with a class attribute is treated as a static get method or field.
|
||||||
|
-->
|
||||||
|
<!ELEMENT Get (%CONFIG;)*>
|
||||||
|
<!ATTLIST Get %NAMEATTR; %IMPLIEDCLASSATTR; %IDATTR; >
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Put Element.
|
||||||
|
This element maps to a call to a put method on the current object,
|
||||||
|
which must implement the Map interface. The name attribute is used
|
||||||
|
as the put key and the optional type attribute can force the type
|
||||||
|
of the value.
|
||||||
|
|
||||||
|
A Put element can contain value text and/or value elements such as Call,
|
||||||
|
New, SystemProperty, etc. If no value type is specified, then white
|
||||||
|
space is trimmed out of the value. If it contains multiple value
|
||||||
|
elements they are added as strings before being converted to any
|
||||||
|
specified type.
|
||||||
|
-->
|
||||||
|
<!ELEMENT Put ( %VALUE; )* >
|
||||||
|
<!ATTLIST Put %NAMEATTR; %TYPEATTR; >
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Call Element.
|
||||||
|
This element maps to an arbitrary call to a method on the current object,
|
||||||
|
The name attribute and Arg elements are used to select the method.
|
||||||
|
|
||||||
|
A Call element can contain a sequence of Arg elements followed by
|
||||||
|
a sequence of other elements such as Set, Put, Call, etc. which act on any object
|
||||||
|
returned by the original call:
|
||||||
|
|
||||||
|
<Call id="o2" name="test">
|
||||||
|
<Arg>value1</Arg>
|
||||||
|
<Set name="Test">Value2</Set>
|
||||||
|
</Call>
|
||||||
|
|
||||||
|
This is equivalent to:
|
||||||
|
|
||||||
|
Object o2 = o1.test("value1");
|
||||||
|
o2.setTest("value2");
|
||||||
|
|
||||||
|
A Call with a class attribute is treated as a static call.
|
||||||
|
-->
|
||||||
|
<!ELEMENT Call (Arg*,(%CONFIG;)*)>
|
||||||
|
<!ATTLIST Call %NAMEATTR; %IMPLIEDCLASSATTR; %IDATTR;>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Arg Element.
|
||||||
|
This element defines a positional argument for the Call element.
|
||||||
|
The optional type attribute can force the type of the value.
|
||||||
|
|
||||||
|
An Arg element can contain value text and/or value elements such as Call,
|
||||||
|
New, SystemProperty, etc. If no value type is specified, then white
|
||||||
|
space is trimmed out of the value. If it contains multiple value
|
||||||
|
elements they are added as strings before being converted to any
|
||||||
|
specified type.
|
||||||
|
-->
|
||||||
|
<!ELEMENT Arg ( %VALUE; )* >
|
||||||
|
<!ATTLIST Arg %TYPEATTR; %IMPLIEDNAMEATTR; >
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
New Element.
|
||||||
|
This element allows the creation of a new object as part of a
|
||||||
|
value for elements such as Set, Put, Arg, etc. The class attribute determines
|
||||||
|
the type of the new object and the contained Arg elements
|
||||||
|
are used to select the constructor for the new object.
|
||||||
|
|
||||||
|
A New element can contain a sequence of Arg elements followed by
|
||||||
|
a sequence of elements such as Set, Put, Call, etc. elements
|
||||||
|
which act on the new object:
|
||||||
|
|
||||||
|
<New id="o" class="com.acme.MyClass">
|
||||||
|
<Arg>value1</Arg>
|
||||||
|
<Set name="test">Value2</Set>
|
||||||
|
</New>
|
||||||
|
|
||||||
|
This is equivalent to:
|
||||||
|
|
||||||
|
Object o = new com.acme.MyClass("value1");
|
||||||
|
o.setTest("Value2");
|
||||||
|
-->
|
||||||
|
<!ELEMENT New (Arg*,(%CONFIG;)*)>
|
||||||
|
<!ATTLIST New %CLASSATTR; %IDATTR;>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Ref Element.
|
||||||
|
This element allows a previously created object to be referenced by id.
|
||||||
|
A Ref element can contain a sequence of elements such as Set, Put, Call, etc.
|
||||||
|
which act on the referenced object:
|
||||||
|
|
||||||
|
<Ref id="myobject">
|
||||||
|
<Set name="Test">Value2</Set>
|
||||||
|
</New>
|
||||||
|
-->
|
||||||
|
<!ELEMENT Ref ((%CONFIG;)*)>
|
||||||
|
<!ATTLIST Ref %REQUIREDIDATTR;>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Array Element.
|
||||||
|
This element allows the creation of a new array as part of a
|
||||||
|
value of elements such as Set, Put, Arg, etc. The type attribute determines
|
||||||
|
the type of the new array and the contained Item elements
|
||||||
|
are used for each element of the array:
|
||||||
|
|
||||||
|
<Array type="java.lang.String">
|
||||||
|
<Item>value0</Item>
|
||||||
|
<Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
|
||||||
|
</Array>
|
||||||
|
|
||||||
|
This is equivalent to:
|
||||||
|
String[] a = new String[] { "value0", new String("value1") };
|
||||||
|
-->
|
||||||
|
<!ELEMENT Array (Item*)>
|
||||||
|
<!ATTLIST Array %TYPEATTR; %IDATTR; >
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Map Element.
|
||||||
|
This element allows the creation of a new map as part of a
|
||||||
|
value of elements such as Set, Put, Arg, etc. The type attribute determines
|
||||||
|
the type of the new array and the contained Item elements
|
||||||
|
are used for each element of the array:
|
||||||
|
|
||||||
|
<Map>
|
||||||
|
<Entry>
|
||||||
|
<Item>keyName</Item>
|
||||||
|
<Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
|
||||||
|
</Entry>
|
||||||
|
</Map>
|
||||||
|
|
||||||
|
This is equivalent to:
|
||||||
|
Map m = new HashMap();
|
||||||
|
m.put("keyName", new String("value1"));
|
||||||
|
-->
|
||||||
|
<!ELEMENT Map (Entry*)>
|
||||||
|
<!ATTLIST Map %IDATTR; >
|
||||||
|
<!ELEMENT Entry (Item,Item)>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Item Element.
|
||||||
|
This element defines an entry for the Array or Map Entry elements.
|
||||||
|
The optional type attribute can force the type of the value.
|
||||||
|
|
||||||
|
An Item element can contain value text and/or the value object of
|
||||||
|
elements such as Call, New, SystemProperty, etc. If no value type
|
||||||
|
is specified, then white space is trimmed out of the value.
|
||||||
|
If it contains multiple value elements they are added as strings
|
||||||
|
before being converted to any specified type.
|
||||||
|
-->
|
||||||
|
<!ELEMENT Item ( %VALUE; )* >
|
||||||
|
<!ATTLIST Item %TYPEATTR; %IDATTR; >
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
System Property Element.
|
||||||
|
This element allows JVM System properties to be retrieved as
|
||||||
|
part of the value of elements such as Set, Put, Arg, etc.
|
||||||
|
The name attribute specifies the property name and the optional
|
||||||
|
default argument provides a default value.
|
||||||
|
|
||||||
|
<SystemProperty name="Test" default="value" />
|
||||||
|
|
||||||
|
This is equivalent to:
|
||||||
|
|
||||||
|
System.getProperty("Test","value");
|
||||||
|
-->
|
||||||
|
<!ELEMENT SystemProperty EMPTY>
|
||||||
|
<!ATTLIST SystemProperty %NAMEATTR; %DEFAULTATTR; %IDATTR;>
|
||||||
|
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Property Element.
|
||||||
|
This element allows arbitrary properties to be retrieved by name.
|
||||||
|
The name attribute specifies the property name and the optional
|
||||||
|
default argument provides a default value.
|
||||||
|
|
||||||
|
A Property element can contain a sequence of elements such as Set, Put, Call, etc.
|
||||||
|
which act on the retrieved object:
|
||||||
|
|
||||||
|
<Property name="Server">
|
||||||
|
<Call id="jdbcIdMgr" name="getAttribute">
|
||||||
|
<Arg>jdbcIdMgr</Arg>
|
||||||
|
</Call>
|
||||||
|
</Property>
|
||||||
|
-->
|
||||||
|
<!ELEMENT Property ((%CONFIG;)*)>
|
||||||
|
<!ATTLIST Property %NAMEATTR; %DEFAULTATTR; %IDATTR;>
|
|
@ -63,6 +63,8 @@ public class XmlConfigurationTest
|
||||||
assertEquals( "ObjectsWhiteString", "-1\n String",tc.get("ObjectsWhiteString"));
|
assertEquals( "ObjectsWhiteString", "-1\n String",tc.get("ObjectsWhiteString"));
|
||||||
|
|
||||||
assertEquals( "SystemProperty", System.getProperty("user.dir")+"/stuff",tc.get("SystemProperty"));
|
assertEquals( "SystemProperty", System.getProperty("user.dir")+"/stuff",tc.get("SystemProperty"));
|
||||||
|
assertEquals( "Env", System.getenv("HOME"),tc.get("Env"));
|
||||||
|
|
||||||
assertEquals( "Property", "xxx", tc.get("Property"));
|
assertEquals( "Property", "xxx", tc.get("Property"));
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@
|
||||||
<Put name="SomethingElse"><SystemProperty name="floople" default="xxx"/></Put>
|
<Put name="SomethingElse"><SystemProperty name="floople" default="xxx"/></Put>
|
||||||
<Put name="Boolean" type="Boolean">True</Put>
|
<Put name="Boolean" type="Boolean">True</Put>
|
||||||
<Put name="Float" type="Float">2.3</Put>
|
<Put name="Float" type="Float">2.3</Put>
|
||||||
|
<Put name="Env"><Env name="HOME"/></Put>
|
||||||
|
|
||||||
<Call name="call">
|
<Call name="call">
|
||||||
</Call>
|
</Call>
|
||||||
|
|
Loading…
Reference in New Issue