Merge remote-tracking branch 'origin/master' into jetty-8

This commit is contained in:
Greg Wilkins 2011-12-30 20:19:39 +11:00
commit 3141b50fa0
37 changed files with 2029 additions and 498 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
{ {

View File

@ -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 '/'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
#===========================================================

View File

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

View File

@ -0,0 +1 @@
etc/jetty-testrealm.xml

View File

@ -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
#===========================================================

View File

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

View File

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

View File

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

View File

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

View File

@ -28,4 +28,6 @@ public interface WebSocketConnection extends AsyncConnection
List<Extension> getExtensions(); List<Extension> getExtensions();
WebSocket.Connection getConnection(); WebSocket.Connection getConnection();
void shutdown();
} }

View File

@ -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)
{ {

View File

@ -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)
{ {

View File

@ -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)
{ {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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