Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--VERSION.txt20
-rw-r--r--example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java8
-rw-r--r--jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpConnection.java8
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java39
-rw-r--r--jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java95
-rw-r--r--jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java16
-rw-r--r--jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestSPIServer.java71
-rw-r--r--jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java12
-rw-r--r--jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java8
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java10
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java7
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/Request.java25
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java85
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java16
-rw-r--r--jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java1
-rw-r--r--jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java218
-rw-r--r--jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java62
-rw-r--r--jetty-start/src/main/java/org/eclipse/jetty/start/Main.java566
-rw-r--r--jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt9
-rw-r--r--jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java76
-rw-r--r--jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini22
-rw-r--r--jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini13
-rw-r--r--jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini1
-rw-r--r--jetty-start/src/test/resources/jetty.home/start.ini65
-rw-r--r--jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java63
-rw-r--r--jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java4
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java42
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java76
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java8
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java7
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java11
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java7
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java9
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java72
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java69
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java14
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java12
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java16
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java16
-rw-r--r--jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java7
-rw-r--r--jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java596
-rw-r--r--jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageRFC6455Test.java64
-rw-r--r--jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketRedeployTest.java176
-rw-r--r--jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java41
-rw-r--r--jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd20
-rw-r--r--jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_7_6.dtd265
-rw-r--r--jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java2
-rw-r--r--jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml1
-rw-r--r--pom.xml2
49 files changed, 2457 insertions, 596 deletions
diff --git a/VERSION.txt b/VERSION.txt
index 97a124abfa..eb6e1de36f 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1,5 +1,25 @@
jetty-7.6.0-SNAPSHOT
+jetty-7.6.0.RC2 - 22 December 2011
+ + 364638 HttpParser closes if data received while seeking EOF. Tests fixed to
+ cope
+ + 364921 Made test less time sensitive for ssl
+ + 364936 use Resource for opening URL streams
+ + 365267 NullPointerException in bad Address
+ + 365375 ResourceHandler should be a HandlerWrapper
+ + 365750 Support WebSocket over SSL, aka wss://
+ + 365932 Produce jetty-websocket aggregate jar for android use
+ + 365947 Set headers for Auth failure and retry in http-spi
+ + 366316 Superfluous printStackTrace on 404
+ + 366342 Dont persist DosFilter trackers in http session
+ + 366730 pass the time idle to onIdleExpire
+ + 367048 test harness for guard on suspended requests
+ + 367175 SSL 100% CPU spin in case of blocked write and RST.
+ + 367219 WebSocketClient.open() fails when URI uses default ports.
+ + JETTY-1460 suppress PrintWriter exceptions
+ + JETTY-1463 websocket D0 parser should return progress even if no fill done
+ + JETTY-1465 NPE in ContextHandler.toString
+
jetty-7.6.0.RC1 - 04 December 2011
+ 352565 cookie httponly flag ignored
+ 353285 ServletSecurity annotation ignored
diff --git a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
index 899c682bc0..778c9b0b23 100644
--- a/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
+++ b/example-jetty-embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
@@ -29,6 +29,7 @@ import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
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.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.server.ssl.SslSocketConnector;
@@ -68,6 +69,13 @@ public class LikeJettyXml
server.setConnectors(new 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();
ssl_connector.setPort(8443);
diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpConnection.java b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpConnection.java
index 00571cad0b..a032b38b17 100644
--- a/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpConnection.java
+++ b/jetty-client/src/main/java/org/eclipse/jetty/client/AbstractHttpConnection.java
@@ -179,7 +179,7 @@ public abstract class AbstractHttpConnection extends AbstractConnection implemen
_generator.setVersion(_exchange.getVersion());
String method=_exchange.getMethod();
- String uri = _exchange.getURI();
+ String uri = _exchange.getRequestURI();
if (_destination.isProxied() && !HttpMethods.CONNECT.equals(method) && uri.startsWith("/"))
{
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()
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java
index 923d686766..c65525583d 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/AbstractHttpExchangeCancelTest.java
@@ -14,15 +14,8 @@
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.net.SocketTimeoutException;
-
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
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.nio.SelectChannelConnector;
import org.eclipse.jetty.util.log.Log;
-import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.log.StdErrLog;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
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
{
- private static final Logger LOG = Log.getLogger(AbstractHttpExchangeCancelTest.TestHttpExchange.class);
-
private Server server;
private Connector connector;
@@ -362,7 +359,7 @@ public abstract class AbstractHttpExchangeCancelTest
int status = exchange.waitForDone();
long end = System.currentTimeMillis();
-
+
assertTrue(HttpExchange.STATUS_EXPIRED==status||HttpExchange.STATUS_EXCEPTED==status);
assertFalse(exchange.isResponseCompleted());
assertTrue(end-start<4000);
@@ -371,6 +368,26 @@ public abstract class AbstractHttpExchangeCancelTest
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();
diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java
index f2f04f738f..abcdce4d00 100644
--- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java
+++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java
@@ -5,7 +5,6 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
-import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
@@ -40,6 +39,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
+import org.eclipse.jetty.toolchain.test.OS;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.junit.After;
import org.junit.Assert;
@@ -53,17 +53,16 @@ import static org.hamcrest.Matchers.lessThan;
public class SslBytesServerTest extends SslBytesTest
{
- private final static int MAX_IDLE_TIME=5000;
-
private final AtomicInteger sslHandles = new AtomicInteger();
private final AtomicInteger sslFlushes = new AtomicInteger();
private final AtomicInteger httpParses = new AtomicInteger();
+ private final AtomicReference<EndPoint> serverEndPoint = new AtomicReference<EndPoint>();
+ private final int idleTimeout = 2000;
private ExecutorService threadPool;
private Server server;
private SSLContext sslContext;
private SimpleProxy proxy;
- private final AtomicReference<SslConnection> sslConnection = new AtomicReference<SslConnection>();
-
+
@Before
public void init() throws Exception
{
@@ -75,7 +74,8 @@ public class SslBytesServerTest extends SslBytesTest
@Override
protected SslConnection newSslConnection(AsyncEndPoint endPoint, SSLEngine engine)
{
- SslConnection connection = new SslConnection(engine, endPoint)
+ serverEndPoint.set(endPoint);
+ return new SslConnection(engine, endPoint)
{
@Override
public Connection handle() throws IOException
@@ -98,8 +98,6 @@ public class SslBytesServerTest extends SslBytesTest
};
}
};
- sslConnection.set(connection);
- return connection;
}
@Override
@@ -123,7 +121,7 @@ public class SslBytesServerTest extends SslBytesTest
};
}
};
- connector.setMaxIdleTime(MAX_IDLE_TIME);
+ connector.setMaxIdleTime(idleTimeout);
// connector.setPort(5870);
connector.setPort(0);
@@ -571,6 +569,21 @@ public class SslBytesServerTest extends SslBytesTest
@Test
public void testRequestWithCloseAlert() throws Exception
{
+ if ( !OS.IS_LINUX )
+ {
+ // currently we are ignoring this test on anything other then linux
+
+ //http://tools.ietf.org/html/rfc2246#section-7.2.1
+
+ // TODO (react to this portion which seems to allow win/mac behavior)
+ //It is required that the other party respond with a close_notify alert of its own
+ //and close down the connection immediately, discarding any pending writes. It is not
+ //required for the initiator of the close to wait for the responding
+ //close_notify alert before closing the read side of the connection.
+ return;
+ }
+
+
final SSLSocket client = newClient();
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
@@ -746,6 +759,20 @@ public class SslBytesServerTest extends SslBytesTest
@Test
public void testRequestWithCloseAlertWithSplitBoundary() throws Exception
{
+ if ( !OS.IS_LINUX )
+ {
+ // currently we are ignoring this test on anything other then linux
+
+ //http://tools.ietf.org/html/rfc2246#section-7.2.1
+
+ // TODO (react to this portion which seems to allow win/mac behavior)
+ //It is required that the other party respond with a close_notify alert of its own
+ //and close down the connection immediately, discarding any pending writes. It is not
+ //required for the initiator of the close to wait for the responding
+ //close_notify alert before closing the read side of the connection.
+ return;
+ }
+
final SSLSocket client = newClient();
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
@@ -1245,7 +1272,7 @@ public class SslBytesServerTest extends SslBytesTest
}
@Test
- public void testServerCloseClientDoesNotClose() throws Exception
+ public void testServerShutdownOutputClientDoesNotCloseServerCloses() throws Exception
{
final SSLSocket client = newClient();
final OutputStream clientOutput = client.getOutputStream();
@@ -1267,7 +1294,6 @@ public class SslBytesServerTest extends SslBytesTest
"\r\n" +
content).getBytes("UTF-8"));
clientOutput.flush();
- Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream(), "UTF-8"));
String line = reader.readLine();
@@ -1278,52 +1304,25 @@ public class SslBytesServerTest extends SslBytesTest
if (line.trim().length() == 0)
break;
}
+ Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
// Check client is at EOF
Assert.assertEquals(-1,client.getInputStream().read());
-
+
// Client should close the socket, but let's hold it open.
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(100);
Assert.assertThat(sslHandles.get(), lessThan(20));
+ Assert.assertThat(sslFlushes.get(), lessThan(20));
Assert.assertThat(httpParses.get(), lessThan(50));
-
- // Check it is still half closed on the server.
- Assert.assertTrue(((AsyncEndPoint)sslConnection.get().getEndPoint()).isOpen());
- Assert.assertTrue(((AsyncEndPoint)sslConnection.get().getEndPoint()).isOutputShutdown());
- Assert.assertFalse(((AsyncEndPoint)sslConnection.get().getEndPoint()).isInputShutdown());
-
- // wait for a bit more than MAX_IDLE_TIME
- TimeUnit.MILLISECONDS.sleep(MAX_IDLE_TIME+1000);
-
- // Server should have closed the endpoint
- Assert.assertFalse(((AsyncEndPoint)sslConnection.get().getEndPoint()).isOpen());
- Assert.assertTrue(((AsyncEndPoint)sslConnection.get().getEndPoint()).isOutputShutdown());
- Assert.assertTrue(((AsyncEndPoint)sslConnection.get().getEndPoint()).isInputShutdown());
-
-
- // writing to client will eventually get broken pipe exception or similar
- try
- {
- for (int i=0;i<100;i++)
- {
- clientOutput.write(("" +
- "POST / HTTP/1.1\r\n" +
- "Host: localhost\r\n" +
- "Content-Type: text/plain\r\n" +
- "Content-Length: " + content.length() + "\r\n" +
- "Connection: close\r\n" +
- "\r\n" +
- content).getBytes("UTF-8"));
- clientOutput.flush();
- }
- Assert.fail("Client should have seen server close");
- }
- catch(SocketException e)
- {
- // this was expected.
- }
+
+ // The server has shutdown the output since the client sent a Connection: close
+ // but the client does not close, so the server must idle timeout the endPoint.
+
+ TimeUnit.MILLISECONDS.sleep(idleTimeout + idleTimeout/2);
+
+ Assert.assertFalse(serverEndPoint.get().isOpen());
}
private void assumeJavaVersionSupportsTLSRenegotiations()
diff --git a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java
index 62c30cac16..7cf8011240 100644
--- a/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java
+++ b/jetty-http-spi/src/main/java/org/eclipse/jetty/http/spi/HttpSpiContextHandler.java
@@ -15,6 +15,8 @@ package org.eclipse.jetty.http.spi;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.List;
+import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -25,6 +27,7 @@ import org.eclipse.jetty.server.handler.ContextHandler;
import com.sun.net.httpserver.Authenticator;
import com.sun.net.httpserver.Authenticator.Result;
+import com.sun.net.httpserver.BasicAuthenticator;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
@@ -108,12 +111,23 @@ public class HttpSpiContextHandler extends ContextHandler
if (result instanceof Authenticator.Failure)
{
int rc = ((Authenticator.Failure)result).getResponseCode();
+ for (Map.Entry<String,List<String>> header : httpExchange.getResponseHeaders().entrySet())
+ {
+ for (String value : header.getValue())
+ resp.addHeader(header.getKey(),value);
+ }
resp.sendError(rc);
}
else if (result instanceof Authenticator.Retry)
{
int rc = ((Authenticator.Retry)result).getResponseCode();
- resp.sendError(rc);
+ for (Map.Entry<String,List<String>> header : httpExchange.getResponseHeaders().entrySet())
+ {
+ for (String value : header.getValue())
+ resp.addHeader(header.getKey(),value);
+ }
+ resp.setStatus(rc);
+ resp.flushBuffer();
}
else if (result instanceof Authenticator.Success)
{
diff --git a/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestSPIServer.java b/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestSPIServer.java
new file mode 100644
index 0000000000..27110811a6
--- /dev/null
+++ b/jetty-http-spi/src/test/java/org/eclipse/jetty/http/spi/TestSPIServer.java
@@ -0,0 +1,71 @@
+package org.eclipse.jetty.http.spi;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import com.sun.net.httpserver.BasicAuthenticator;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+
+public class TestSPIServer
+{
+ public static void main(String[] args) throws Exception
+ {
+ String host="localhost";
+ int port = 8080;
+
+ HttpServer server = new JettyHttpServerProvider().createHttpServer(new
+ InetSocketAddress(host, port), 10);
+ server.start();
+
+ final HttpContext httpContext = server.createContext("/",
+ new HttpHandler()
+ {
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException
+ {
+ Headers responseHeaders = exchange.getResponseHeaders();
+ responseHeaders.set("Content-Type","text/plain");
+ exchange.sendResponseHeaders(200,0);
+
+ OutputStream responseBody = exchange.getResponseBody();
+ Headers requestHeaders = exchange.getRequestHeaders();
+ Set<String> keySet = requestHeaders.keySet();
+ Iterator<String> iter = keySet.iterator();
+ while (iter.hasNext())
+ {
+ String key = iter.next();
+ List values = requestHeaders.get(key);
+ String s = key + " = " + values.toString() + "\n";
+ responseBody.write(s.getBytes());
+ }
+ responseBody.close();
+
+ }
+ });
+
+ httpContext.setAuthenticator(new BasicAuthenticator("Test")
+ {
+ @Override
+ public boolean checkCredentials(String username, String password)
+ {
+ if ("username".equals(username) && password.equals("password"))
+ return true;
+ return false;
+ }
+ });
+
+
+ Thread.sleep(10000000);
+
+ }
+}
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
index 1f10532588..ab5d9ff75c 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SelectChannelEndPoint.java
@@ -257,15 +257,6 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
getSelectSet().scheduleTimeout(task,timeoutMs);
}
-
- /* ------------------------------------------------------------ */
- @Override
- public boolean isOutputShutdown()
- {
- setCheckForIdle(true);
- return super.isOutputShutdown();
- }
-
/* ------------------------------------------------------------ */
public void setCheckForIdle(boolean check)
{
@@ -289,10 +280,11 @@ public class SelectChannelEndPoint extends ChannelEndPoint implements AsyncEndPo
public void checkIdleTimestamp(long now)
{
long idleTimestamp=_idleTimestamp;
-
+
if (idleTimestamp!=0 && _maxIdleTime>0)
{
long idleForMs=now-idleTimestamp;
+
if (idleForMs>_maxIdleTime)
{
onIdleExpired(idleForMs);
diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java
index d9af5953ca..876db22112 100644
--- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java
+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java
@@ -96,6 +96,7 @@ public class SslConnection extends AbstractConnection implements AsyncConnection
_sslEndPoint = newSslEndPoint();
}
+ /* ------------------------------------------------------------ */
protected SslEndPoint newSslEndPoint()
{
return new SslEndPoint();
@@ -583,6 +584,7 @@ public class SslConnection extends AbstractConnection implements AsyncConnection
return _sslEndPoint;
}
+ /* ------------------------------------------------------------ */
public String toString()
{
return String.format("%s %s", super.toString(), _sslEndPoint);
@@ -596,6 +598,11 @@ public class SslConnection extends AbstractConnection implements AsyncConnection
{
return _engine;
}
+
+ public AsyncEndPoint getEndpoint()
+ {
+ return _aEndp;
+ }
public void shutdownOutput() throws IOException
{
@@ -822,5 +829,6 @@ public class SslConnection extends AbstractConnection implements AsyncConnection
_ishut, _oshut,
_connection);
}
+
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java
index 8ec0737e5a..1ddedf015a 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AsyncHttpConnection.java
@@ -54,6 +54,10 @@ public class AsyncHttpConnection extends AbstractHttpConnection implements Async
try
{
setCurrentConnection(this);
+
+ // don't check for idle while dispatched (unless blocking IO is done).
+ _asyncEndp.setCheckForIdle(false);
+
// While progress and the connection has not changed
while (progress && connection==this)
@@ -133,10 +137,16 @@ public class AsyncHttpConnection extends AbstractHttpConnection implements Async
finally
{
setCurrentConnection(null);
+
+ // If we are not suspended
if (!_request.isAsyncStarted())
{
+ // return buffers
_parser.returnBuffers();
_generator.returnBuffers();
+
+ // resuming checking for idle
+ _asyncEndp.setCheckForIdle(true);
}
// Safety net to catch spinning
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java
index ec15289f91..6b5c8284da 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/BlockingHttpConnection.java
@@ -110,6 +110,13 @@ public class BlockingHttpConnection extends AbstractHttpConnection
_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();
+ }
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
index 84ec0e2449..ca8789de20 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java
@@ -101,6 +101,14 @@ import org.eclipse.jetty.util.log.Logger;
* to avoid reparsing headers and cookies that are likely to be the same for
* 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
{
@@ -231,7 +239,7 @@ public class Request implements HttpServletRequest
if (content_type != null && content_type.length() > 0)
{
content_type = HttpFields.valueParameters(content_type, null);
-
+
if (MimeTypes.FORM_ENCODED.equalsIgnoreCase(content_type) && _inputState==__NONE &&
(HttpMethods.POST.equals(getMethod()) || HttpMethods.PUT.equals(getMethod())))
{
@@ -241,16 +249,21 @@ public class Request implements HttpServletRequest
try
{
int maxFormContentSize=-1;
+ int maxFormKeys=-1;
if (_context!=null)
+ {
maxFormContentSize=_context.getContextHandler().getMaxFormContentSize();
+ maxFormKeys=_context.getContextHandler().getMaxFormKeys();
+ }
else
{
- Integer size = (Integer)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
- if (size!=null)
- maxFormContentSize =size.intValue();
+ Number size = (Number)_connection.getConnector().getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize");
+ maxFormContentSize=size==null?200000: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)
{
throw new IllegalStateException("Form too large"+content_length+">"+maxFormContentSize);
@@ -258,7 +271,7 @@ public class Request implements HttpServletRequest
InputStream in = getInputStream();
// 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)
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
index 8af3177194..3538d75f96 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java
@@ -30,7 +30,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
-
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
@@ -70,13 +69,13 @@ import org.eclipse.jetty.util.resource.Resource;
/* ------------------------------------------------------------ */
/**
* ContextHandler.
- *
+ *
* This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader.
- *
+ *
* <p>
* If the context init parameter "org.eclipse.jetty.server.context.ManagedAttributes" is set to a comma separated list of names, then they are treated as
* context attribute names, which if set as attributes are passed to the servers Container so that they may be managed with JMX.
- *
+ *
* @org.apache.xbean.XBean description="Creates a basic HTTP context"
*/
public class ContextHandler extends ScopedHandler implements Attributes, Server.Graceful
@@ -95,7 +94,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/* ------------------------------------------------------------ */
/**
* Get the current ServletContext implementation.
- *
+ *
* @return ServletContext implementation
*/
public static Context getCurrentContext()
@@ -121,6 +120,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
private EventListener[] _eventListeners;
private Logger _logger;
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 boolean _compactPath = false;
private boolean _aliases = false;
@@ -244,7 +244,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
* Set the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
* virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
* matching virtual host name.
- *
+ *
* @param vhosts
* Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
* String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
@@ -265,7 +265,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/* ------------------------------------------------------------ */
/** Either set virtual hosts or add to an existing set of virtual hosts.
- *
+ *
* @param virtualHosts
* Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
* String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
@@ -287,7 +287,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
{
currentVirtualHosts = new ArrayList<String>();
}
-
+
for (int i = 0; i < virtualHosts.length; i++)
{
String normVhost = normalizeHostname(virtualHosts[i]);
@@ -303,7 +303,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/* ------------------------------------------------------------ */
/**
* Removes an array of virtual host entries, if this removes all entries the _vhosts will be set to null
- *
+ *
* @param virtualHosts
* Array of virtual hosts that this context responds to. A null host name or null/empty array means any hostname is acceptable. Host names may be
* String representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
@@ -321,7 +321,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
else
{
List<String> existingVirtualHosts = new ArrayList<String>(Arrays.asList(_vhosts));
-
+
for (int i = 0; i < virtualHosts.length; i++)
{
String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]);
@@ -330,7 +330,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
existingVirtualHosts.remove(toRemoveVirtualHost);
}
}
-
+
if (existingVirtualHosts.isEmpty())
{
_vhosts = null; // if we ended up removing them all, just null out _vhosts
@@ -341,13 +341,13 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
}
}
}
-
+
/* ------------------------------------------------------------ */
/**
* Get the virtual hosts for the context. Only requests that have a matching host header or fully qualified URL will be passed to that context with a
* virtual host name. A context with no virtual host names or a null virtual host name is available to all requests that are not served by a context with a
* matching virtual host name.
- *
+ *
* @return Array of virtual hosts that this context responds to. A null host name or empty array means any hostname is acceptable. Host names may be String
* representation of IP addresses. Host names may start with '*.' to wildcard one level of names.
*/
@@ -371,9 +371,9 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/* ------------------------------------------------------------ */
/**
* Set the names of accepted connectors.
- *
+ *
* Names are either "host:port" or a specific configured name for a connector.
- *
+ *
* @param connectors
* If non null, an array of connector names that this context will accept a request from.
*/
@@ -425,7 +425,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/* ------------------------------------------------------------ */
/**
* Make best effort to extract a file classpath from the context classloader
- *
+ *
* @return Returns the classLoader.
*/
public String getClassPath()
@@ -521,7 +521,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/* ------------------------------------------------------------ */
/**
* Set the context event listeners.
- *
+ *
* @param eventListeners
* the event listeners
* @see ServletContextListener
@@ -559,7 +559,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/* ------------------------------------------------------------ */
/**
* Add a context event listeners.
- *
+ *
* @see ServletContextListener
* @see ServletContextAttributeListener
* @see ServletRequestListener
@@ -586,7 +586,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/**
* Set shutdown status. This field allows for graceful shutdown of a context. A started context may be put into non accepting state so that existing
* requests can complete, but no new requests are accepted.
- *
+ *
* @param shutdown
* true if this context is (not?) accepting new requests
*/
@@ -694,7 +694,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/**
* Extensible startContext. this method is called from {@link ContextHandler#doStart()} instead of a call to super.doStart(). This allows derived classes to
* insert additional handling (Eg configuration) before the call to super.doStart by this method will start contained handlers.
- *
+ *
* @see org.eclipse.jetty.server.handler.ContextHandler.Context
*/
protected void startContext() throws Exception
@@ -1116,7 +1116,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/*
* Set a context attribute. Attributes set via this API cannot be overriden by the ServletContext.setAttribute API. Their lifecycle spans the stop/start of
* a context. No attribute listener events are triggered by this API.
- *
+ *
* @see javax.servlet.ServletContext#setAttribute(java.lang.String, java.lang.Object)
*/
public void setAttribute(String name, Object value)
@@ -1349,12 +1349,32 @@ 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)
{
_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 '/'
*/
@@ -1381,14 +1401,17 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
StringBuilder b = new StringBuilder();
- String p = getClass().getPackage().getName();
- if (p != null && p.length() > 0)
+ Package pkg = getClass().getPackage();
+ if (pkg != null)
{
- String[] ss = p.split("\\.");
- for (String s : ss)
- b.append(s.charAt(0)).append('.');
+ String p = pkg.getName();
+ if (p != null && p.length() > 0)
+ {
+ String[] ss = p.split("\\.");
+ for (String s : ss)
+ b.append(s.charAt(0)).append('.');
+ }
}
-
b.append(getClass().getSimpleName());
b.append('{').append(getContextPath()).append(',').append(getBaseResource());
@@ -1431,7 +1454,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/**
* Get the character encoding for a locale. The full locale name is first looked up in the map of encodings. If no encoding is found, then the locale
* language is looked up.
- *
+ *
* @param locale
* a <code>Locale</code> value
* @return a <code>String</code> representing the character encoding for the locale or null if none found.
@@ -1495,7 +1518,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
/* ------------------------------------------------------------ */
/**
* Convert a URL or path to a Resource. The default implementation is a wrapper for {@link Resource#newResource(String)}.
- *
+ *
* @param urlOrPath
* The URL or path to convert
* @return The Resource for the URL/path
@@ -1557,8 +1580,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
* <p>
* A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}.
* </p>
- *
- *
+ *
+ *
*/
public class Context implements ServletContext
{
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java
index 98716285b7..8e331142e6 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/BlockingChannelConnector.java
@@ -17,6 +17,7 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.ByteChannel;
+import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
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);
+ }
+
}
}
diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java
index 998dc76638..b6165d45fd 100644
--- a/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java
+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/nio/SelectChannelConnector.java
@@ -122,7 +122,6 @@ public class SelectChannelConnector extends AbstractNIOConnector
public void customize(EndPoint endpoint, Request request) throws IOException
{
AsyncEndPoint aEndp = ((AsyncEndPoint)endpoint);
- aEndp.setCheckForIdle(false);
request.setTimeStamp(System.currentTimeMillis());
endpoint.setMaxIdleTime(_maxIdleTime);
super.customize(endpoint, request);
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
index 7b2e2b5650..f01dfc831a 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java
@@ -13,44 +13,48 @@
package org.eclipse.jetty.server;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
-
+import java.net.SocketException;
+import java.util.concurrent.Exchanger;
+import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
-import junit.framework.Assert;
-
+import org.eclipse.jetty.io.EndPoint;
+import org.eclipse.jetty.io.nio.SslConnection;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO;
+import org.junit.Assert;
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.matchers.JUnitMatchers.containsString;
+
public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
{
protected static final int MAX_IDLE_TIME=250;
-
+
static
{
System.setProperty("org.eclipse.jetty.io.nio.IDLE_TICK","100");
}
-
-
+
+
@Test
public void testMaxIdleWithRequest10() throws Exception
- {
+ {
configureServer(new HelloWorldHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
assertFalse(client.isClosed());
-
+
OutputStream os=client.getOutputStream();
InputStream is=client.getInputStream();
@@ -63,7 +67,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
long start = System.currentTimeMillis();
IO.toString(is);
-
+
Thread.sleep(300);
assertEquals(-1, is.read());
@@ -73,13 +77,13 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
@Test
public void testMaxIdleWithRequest11() throws Exception
- {
+ {
configureServer(new EchoHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
assertFalse(client.isClosed());
-
+
OutputStream os=client.getOutputStream();
InputStream is=client.getInputStream();
@@ -96,24 +100,172 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
long start = System.currentTimeMillis();
IO.toString(is);
-
+
Thread.sleep(300);
assertEquals(-1, is.read());
Assert.assertTrue(System.currentTimeMillis()-start>200);
Assert.assertTrue(System.currentTimeMillis()-start<5000);
}
-
+
+ @Test
+ public void testMaxIdleWithRequest10NoClientClose() throws Exception
+ {
+ final Exchanger<EndPoint> endpoint = new Exchanger<EndPoint>();
+ configureServer(new HelloWorldHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
+ ServletException
+ {
+ try
+ {
+ endpoint.exchange(baseRequest.getConnection().getEndPoint());
+ }
+ catch(Exception e)
+ {}
+ super.handle(target,baseRequest,request,response);
+ }
+
+ });
+ Socket client=newSocket(HOST,_connector.getLocalPort());
+ client.setSoTimeout(10000);
+
+ assertFalse(client.isClosed());
+
+ OutputStream os=client.getOutputStream();
+ InputStream is=client.getInputStream();
+
+ os.write((
+ "GET / HTTP/1.0\r\n"+
+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+ "connection: close\r\n"+
+ "\r\n").getBytes("utf-8"));
+ os.flush();
+
+ // Get the server side endpoint
+ EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS);
+ if (endp instanceof SslConnection.SslEndPoint)
+ endp=((SslConnection.SslEndPoint)endp).getEndpoint();
+
+ // read the response
+ String result=IO.toString(is);
+ Assert.assertThat("OK",result,containsString("200 OK"));
+
+ // check client reads EOF
+ assertEquals(-1, is.read());
+
+ // wait for idle timeout
+ TimeUnit.MILLISECONDS.sleep(MAX_IDLE_TIME+MAX_IDLE_TIME/2);
+
+
+ // further writes will get broken pipe or similar
+ try
+ {
+ for (int i=0;i<1000;i++)
+ {
+ os.write((
+ "GET / HTTP/1.0\r\n"+
+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+ "connection: keep-alive\r\n"+
+ "\r\n").getBytes("utf-8"));
+ os.flush();
+ }
+ Assert.fail("half close should have timed out");
+ }
+ catch(SocketException e)
+ {
+ // expected
+ }
+ // check the server side is closed
+ Assert.assertFalse(endp.isOpen());
+ }
+
+ @Test
+ public void testMaxIdleWithRequest11NoClientClose() throws Exception
+ {
+ final Exchanger<EndPoint> endpoint = new Exchanger<EndPoint>();
+ configureServer(new EchoHandler()
+ {
+ @Override
+ public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException,
+ ServletException
+ {
+ try
+ {
+ endpoint.exchange(baseRequest.getConnection().getEndPoint());
+ }
+ catch(Exception e)
+ {}
+ super.handle(target,baseRequest,request,response);
+ }
+
+ });
+ Socket client=newSocket(HOST,_connector.getLocalPort());
+ client.setSoTimeout(10000);
+
+ assertFalse(client.isClosed());
+
+ OutputStream os=client.getOutputStream();
+ InputStream is=client.getInputStream();
+
+ String content="Wibble";
+ byte[] contentB=content.getBytes("utf-8");
+ os.write((
+ "POST /echo HTTP/1.1\r\n"+
+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+ "content-type: text/plain; charset=utf-8\r\n"+
+ "content-length: "+contentB.length+"\r\n"+
+ "connection: close\r\n"+
+ "\r\n").getBytes("utf-8"));
+ os.write(contentB);
+ os.flush();
+
+ // Get the server side endpoint
+ EndPoint endp = endpoint.exchange(null,10,TimeUnit.SECONDS);
+
+ // read the response
+ IO.toString(is);
+
+ // check client reads EOF
+ assertEquals(-1, is.read());
+
+ TimeUnit.MILLISECONDS.sleep(MAX_IDLE_TIME+MAX_IDLE_TIME/2);
+
+
+ // further writes will get broken pipe or similar
+ try
+ {
+ for (int i=0;i<1000;i++)
+ {
+ os.write((
+ "GET / HTTP/1.0\r\n"+
+ "host: "+HOST+":"+_connector.getLocalPort()+"\r\n"+
+ "connection: keep-alive\r\n"+
+ "\r\n").getBytes("utf-8"));
+ os.flush();
+ }
+ Assert.fail("half close should have timed out");
+ }
+ catch(SocketException e)
+ {
+ // expected
+ }
+
+ // check the server side is closed
+ Assert.assertFalse(endp.isOpen());
+ }
+
@Test
public void testMaxIdleNoRequest() throws Exception
- {
+ {
configureServer(new EchoHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
InputStream is=client.getInputStream();
assertFalse(client.isClosed());
-
+
Thread.sleep(500);
long start = System.currentTimeMillis();
try
@@ -123,25 +275,25 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
}
catch(SSLException e)
{
-
+
}
catch(Exception e)
{
e.printStackTrace();
}
Assert.assertTrue(System.currentTimeMillis()-start<5000);
-
- }
+
+ }
@Test
public void testMaxIdleWithSlowRequest() throws Exception
- {
+ {
configureServer(new EchoHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
assertFalse(client.isClosed());
-
+
OutputStream os=client.getOutputStream();
InputStream is=client.getInputStream();
@@ -163,7 +315,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
os.write(contentB);
os.flush();
}
-
+
String in = IO.toString(is);
int offset=0;
for (int i =0;i<20;i++)
@@ -175,13 +327,13 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
@Test
public void testMaxIdleWithSlowResponse() throws Exception
- {
+ {
configureServer(new SlowResponseHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
assertFalse(client.isClosed());
-
+
OutputStream os=client.getOutputStream();
InputStream is=client.getInputStream();
@@ -192,7 +344,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
"Connection: close\r\n"+
"\r\n").getBytes("utf-8"));
os.flush();
-
+
String in = IO.toString(is);
int offset=0;
for (int i =0;i<20;i++)
@@ -204,13 +356,13 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
@Test
public void testMaxIdleWithWait() throws Exception
- {
+ {
configureServer(new WaitHandler());
Socket client=newSocket(HOST,_connector.getLocalPort());
client.setSoTimeout(10000);
assertFalse(client.isClosed());
-
+
OutputStream os=client.getOutputStream();
InputStream is=client.getInputStream();
@@ -221,12 +373,12 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
"Connection: close\r\n"+
"\r\n").getBytes("utf-8"));
os.flush();
-
+
String in = IO.toString(is);
int offset=in.indexOf("Hello World");
Assert.assertTrue(offset>0);
}
-
+
protected static class SlowResponseHandler extends AbstractHandler
{
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
@@ -234,7 +386,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
baseRequest.setHandled(true);
response.setStatus(200);
OutputStream out = response.getOutputStream();
-
+
for (int i=0;i<20;i++)
{
out.write("Hello World\r\n".getBytes());
@@ -244,7 +396,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture
out.close();
}
}
-
+
protected static class WaitHandler extends AbstractHandler
{
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
index 71cfdcd236..bc4cbff2cd 100644
--- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java
@@ -19,12 +19,16 @@ import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
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.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
+import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
@@ -34,9 +38,11 @@ import javax.servlet.http.HttpServletResponse;
import junit.framework.Assert;
+import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.log.Log;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -740,6 +746,56 @@ public class RequestTest
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
{
boolean check(HttpServletRequest request,HttpServletResponse response) throws IOException;
@@ -754,13 +810,15 @@ public class RequestTest
{
((Request)request).setHandled(true);
- if (request.getContentLength()>0)
+ if (request.getContentLength()>0 && !MimeTypes.FORM_ENCODED.equals(request.getContentType()))
_content=IO.toString(request.getInputStream());
-
+
if (_checker!=null && _checker.check(request,response))
response.setStatus(200);
else
response.sendError(500);
+
+
}
}
}
diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
index 4e2d6115f9..c42b93d710 100644
--- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
+++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java
@@ -20,6 +20,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
+import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -30,7 +31,6 @@ import java.lang.reflect.Method;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
-import java.security.Policy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -40,17 +40,15 @@ import java.util.List;
import java.util.Properties;
import java.util.Set;
-
/*-------------------------------------------*/
/**
* <p>
- * 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 the command "java -jar start.jar".
+ * 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
+ * the command "java -jar start.jar".
* </p>
- *
+ *
* <p>
- * 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.
+ * 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.
* </p>
*/
public class Main
@@ -67,257 +65,240 @@ public class Main
private boolean _dryRun = false;
private boolean _exec = false;
private final Config _config = new Config();
- private Set<String> _sysProps = new HashSet<String>();
- private List<String> _jvmArgs = new ArrayList<String>();
+ private final Set<String> _sysProps = new HashSet<String>();
+ private final List<String> _jvmArgs = new ArrayList<String>();
private String _startConfig = null;
private String _jettyHome;
- public static void main(String[] args)
+ public static void main(String[] args)
+ {
+ try
+ {
+ Main main = new Main();
+ List<String> arguments = main.expandCommandLine(args);
+ List<String> xmls = main.processCommandLine(arguments);
+ if (xmls!=null)
+ main.start(xmls);
+ }
+ catch (Throwable e)
+ {
+ usageExit(e,ERR_UNKNOWN);
+ }
+ }
+
+ Main() throws IOException
{
- Main main = new Main();
- main.parseCommandLine(args);
+ _jettyHome = System.getProperty("jetty.home",".");
+ _jettyHome = new File(_jettyHome).getCanonicalPath();
}
- public void parseCommandLine(String[] args)
+ public List<String> expandCommandLine(String[] args) throws Exception
{
- try
- {
- List<String> arguments = new ArrayList<String>();
+ List<String> arguments = new ArrayList<String>();
- // add the command line args and look for start.ini args
- boolean ini=false;
- for (String arg : args)
+ // add the command line args and look for start.ini args
+ boolean ini = false;
+ for (String arg : args)
+ {
+ if (arg.startsWith("--ini=") || arg.equals("--ini"))
{
- 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="))
+ ini = true;
+ if (arg.length() > 6)
{
- _startConfig=arg.substring(9);
- }
- else
- {
- arguments.add(arg);
+ arguments.addAll(loadStartIni(new File(arg.substring(6))));
+ continue;
}
}
-
- // if no non-option inis, add the start.ini
- if (!ini)
+ else if (arg.startsWith("--config="))
{
- arguments.addAll(0,loadStartIni(null));
+ _startConfig = arg.substring(9);
}
-
- // The XML Configuration Files to initialize with
- List<String> xmls = new ArrayList<String>();
+ else
+ {
+ arguments.add(arg);
+ }
+ }
- // Process the arguments
- int startup=0;
- for (String arg : arguments)
+ // 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())
{
- if ("--help".equals(arg) || "-?".equals(arg))
+ File[] inis = start_d.listFiles(new FilenameFilter()
{
- _showUsage = true;
- continue;
- }
+ 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);
+ }
- if ("--stop".equals(arg))
- {
- int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
- String key = Config.getProperty("STOP.KEY",null);
- stop(port,key);
- return;
- }
+ return arguments;
+ }
+
+ public List<String> processCommandLine(List<String> arguments) throws Exception
+ {
+ // The XML Configuration Files to initialize with
+ List<String> xmls = new ArrayList<String>();
- if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
- {
- _dumpVersions = true;
- continue;
- }
+ // Process the arguments
+ int startup = 0;
+ for (String arg : arguments)
+ {
+ if ("--help".equals(arg) || "-?".equals(arg))
+ {
+ _showUsage = true;
+ continue;
+ }
- if ("--list-modes".equals(arg) || "--list-options".equals(arg))
- {
- _listOptions = 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 ("--list-config".equals(arg))
- {
- _listConfig=true;
- continue;
- }
+ if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
+ {
+ _dumpVersions = true;
+ continue;
+ }
- if ("--exec-print".equals(arg)||"--dry-run".equals(arg))
- {
- _dryRun = true;
- continue;
- }
+ if ("--list-modes".equals(arg) || "--list-options".equals(arg))
+ {
+ _listOptions = true;
+ continue;
+ }
- if ("--exec".equals(arg))
- {
- _exec = true;
- continue;
- }
+ if ("--list-config".equals(arg))
+ {
+ _listConfig = 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 ("--exec-print".equals(arg) || "--dry-run".equals(arg))
+ {
+ _dryRun = true;
+ continue;
+ }
- 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"))
+ 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())
{
- 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;
+ // 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 (arg.startsWith("-"))
+ if (!startLog.canWrite())
{
- _jvmArgs.add(arg);
- continue;
+ // 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;
+ }
- // Is this a Property?
- if (arg.indexOf('=') >= 0)
- {
- String[] assign = arg.split("=",2);
+ if (arg.startsWith("--pre="))
+ {
+ xmls.add(startup++,arg.substring(6));
+ continue;
+ }
- 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))
+ if (arg.startsWith("-D"))
+ {
+ String[] assign = arg.substring(2).split("=",2);
+ _sysProps.add(assign[0]);
+ switch (assign.length)
{
- System.out.println("WARN: Argument '"+arg+"' specified multiple times. Check start.ini?");
- System.out.println("Use \"java -jar start.jar --help\" for more information.");
+ case 2:
+ System.setProperty(assign[0],assign[1]);
+ break;
+ case 1:
+ System.setProperty(assign[0],"");
+ break;
+ default:
+ break;
}
- xmls.add(arg);
+ continue;
}
-
- start(xmls);
- }
- catch (Throwable t)
- {
- 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)
+ if (arg.startsWith("-"))
{
- System.err.println("Warning - can't find ini file: " + ini);
+ _jvmArgs.add(arg);
+ continue;
}
- // 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)
+ // Is this a Property?
+ if (arg.indexOf('=') >= 0)
{
- arg = arg.trim();
- if (arg.length() == 0 || arg.startsWith("#"))
+ String[] assign = arg.split("=",2);
+
+ switch (assign.length)
{
- continue;
+ 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;
}
- args.add(arg);
+
+ continue;
}
- }
- catch (IOException e)
- {
- usageExit(e,ERR_UNKNOWN);
- }
- finally
- {
- close(buf);
- close(reader);
+
+ // 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 args;
+ return xmls;
}
private void usage()
@@ -339,10 +320,10 @@ public class Main
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 info=line.substring(line.indexOf('@'),line.lastIndexOf('@'));
+ String indent = line.substring(0,line.indexOf("@"));
+ String info = line.substring(line.indexOf('@'),line.lastIndexOf('@'));
if (info.equals("@OPTIONS"))
{
@@ -352,7 +333,7 @@ public class Main
for (String option : sortedOptions)
{
- if ("*".equals(option) || option.trim().length()==0)
+ if ("*".equals(option) || option.trim().length() == 0)
continue;
System.out.print(indent);
System.out.println(option);
@@ -396,7 +377,7 @@ public class Main
else if (info.equals("@STARTINI"))
{
List<String> ini = loadStartIni(null);
- if (ini!=null && ini.size()>0)
+ if (ini != null && ini.size() > 0)
{
for (String a : ini)
{
@@ -429,7 +410,7 @@ public class Main
}
public void invokeMain(ClassLoader classloader, String classname, List<String> args) throws IllegalAccessException, InvocationTargetException,
- NoSuchMethodException, ClassNotFoundException
+ NoSuchMethodException, ClassNotFoundException
{
Class<?> invoked_class = null;
@@ -462,10 +443,12 @@ public class Main
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);
- Object[] method_params = new Object[] { argArray };
+ Object[] method_params = new Object[]
+ { argArray };
main.invoke(null,method_params);
}
@@ -492,13 +475,12 @@ public class Main
// Setup Start / Stop Monitoring
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
String key = Config.getProperty("STOP.KEY",null);
- Monitor monitor=new Monitor(port,key);
-
+ Monitor monitor = new Monitor(port,key);
// Load potential Config (start.config)
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())
{
throw new FileNotFoundException("No XML configuration files specified in start.config or command line.");
@@ -523,7 +505,7 @@ public class Main
System.err.println("classloader.parent=" + cl.getParent());
System.err.println("properties=" + Config.getProperties());
}
-
+
// Show the usage information and return
if (_showUsage)
{
@@ -544,7 +526,7 @@ public class Main
showAllOptionsWithVersions(classpath);
return;
}
-
+
if (_listConfig)
{
listConfig();
@@ -557,7 +539,7 @@ public class Main
System.out.println(buildCommandLine(classpath,configuredXmls));
return;
}
-
+
// execute Jetty in another JVM
if (_exec)
{
@@ -565,11 +547,12 @@ public class Main
final Process process = Runtime.getRuntime().exec(cmd);
Runtime.getRuntime().addShutdownHook(new Thread()
{
+ @Override
public void run()
{
Config.debug("Destroying " + process);
process.destroy();
- }
+ }
});
copyInThread(process.getErrorStream(),System.err);
copyInThread(process.getInputStream(),System.out);
@@ -578,7 +561,7 @@ public class Main
process.waitFor();
return;
}
-
+
if (_jvmArgs.size() > 0 || _sysProps.size() > 0)
{
System.err.println("WARNING: System properties and/or JVM args set. Consider using --dry-run or --exec");
@@ -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()
{
@@ -625,23 +608,23 @@ public class Main
{
try
{
- byte[] buf=new byte[1024];
- int len=in.read(buf);
- while(len>0)
+ byte[] buf = new byte[1024];
+ int len = in.read(buf);
+ while (len > 0)
{
out.write(buf,0,len);
- len=in.read(buf);
+ len = in.read(buf);
}
}
- catch(IOException e)
+ catch (IOException e)
{
// e.printStackTrace();
}
}
-
+
}).start();
}
-
+
private String resolveXmlConfig(String xmlFilename) throws FileNotFoundException
{
if (!xmlFilename.toLowerCase().endsWith(".xml"))
@@ -675,22 +658,22 @@ public class Main
{
StringBuilder cmd = new StringBuilder();
cmd.append(findJavaBin());
- for (String x:_jvmArgs)
+ for (String x : _jvmArgs)
cmd.append(' ').append(x);
cmd.append(" -Djetty.home=").append(_jettyHome);
- for (String p:_sysProps)
+ for (String p : _sysProps)
{
cmd.append(" -D").append(p);
- String v=System.getProperty(p);
- if (v!=null && v.length()>0)
+ String v = System.getProperty(p);
+ if (v != null && v.length() > 0)
cmd.append('=').append(v);
}
cmd.append(" -cp ").append(classpath.toString());
cmd.append(" ").append(_config.getMainClassname());
-
+
// Check if we need to pass properties as a file
Properties properties = Config.getProperties();
- if (properties.size()>0)
+ if (properties.size() > 0)
{
File prop_file = File.createTempFile("start",".properties");
if (!_dryRun)
@@ -698,7 +681,7 @@ public class Main
properties.store(new FileOutputStream(prop_file),"start.jar properties");
cmd.append(" ").append(prop_file.getAbsolutePath());
}
-
+
for (String xml : xmls)
{
cmd.append(' ').append(xml);
@@ -875,11 +858,10 @@ public class Main
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 "";
}
-
private List<String> resolveXmlConfigs(List<String> xmls) throws FileNotFoundException
{
List<String> ret = new ArrayList<String>();
@@ -896,15 +878,15 @@ public class Main
InputStream cfgstream = null;
try
{
- cfgstream=getConfigStream();
- byte[] buf=new byte[4096];
-
- int len=0;
-
- while (len>=0)
+ cfgstream = getConfigStream();
+ byte[] buf = new byte[4096];
+
+ int len = 0;
+
+ while (len >= 0)
{
- len=cfgstream.read(buf);
- if (len>0)
+ len = cfgstream.read(buf);
+ if (len > 0)
System.out.write(buf,0,len);
}
}
@@ -917,13 +899,13 @@ public class Main
close(cfgstream);
}
}
-
+
/**
* Load Configuration.
- *
- * No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to
- * execute the {@link Class} specified by {@link Config#getMainClassname()} is executed.
- *
+ *
+ * No specific configuration is real until a {@link Config#getCombinedClasspath(java.util.Collection)} is used to execute the {@link Class} specified by
+ * {@link Config#getMainClassname()} is executed.
+ *
* @param xmls
* the command line specified xml configuration options.
* @return the list of xml configurations arriving via command line and start.config choices.
@@ -935,13 +917,13 @@ public class Main
{
// Pass in xmls.size into Config so that conditions based on "nargs" work.
_config.setArgCount(xmls.size());
-
- cfgstream=getConfigStream();
-
+
+ cfgstream = getConfigStream();
+
// parse the config
_config.parse(cfgstream);
-
- _jettyHome = Config.getProperty("jetty.home");
+
+ _jettyHome = Config.getProperty("jetty.home",_jettyHome);
if (_jettyHome != null)
{
_jettyHome = new File(_jettyHome).getCanonicalPath();
@@ -975,12 +957,12 @@ public class Main
private InputStream getConfigStream() throws FileNotFoundException
{
- String config=_startConfig;
+ String config = _startConfig;
if (config == null || config.length() == 0)
{
config = System.getProperty("START","org/eclipse/jetty/start/start.config");
}
-
+
Config.debug("config=" + config);
// Look up config as resource first.
@@ -991,11 +973,10 @@ public class Main
{
cfgstream = new FileInputStream(config);
}
-
+
return cfgstream;
}
-
/**
* Stop a running jetty instance.
*/
@@ -1038,7 +1019,6 @@ public class Main
usageExit(e,ERR_UNKNOWN);
}
}
-
static void usageExit(Throwable t, int exit)
{
@@ -1048,6 +1028,7 @@ public class Main
System.err.println(" java -jar start.jar --help # for more information");
System.exit(exit);
}
+
static void usageExit(int exit)
{
System.err.println();
@@ -1055,4 +1036,53 @@ public class Main
System.err.println(" java -jar start.jar --help # for more information");
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;
+ }
}
diff --git a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
index 7371be3f99..c4c85c4523 100644
--- a/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
+++ b/jetty-start/src/main/resources/org/eclipse/jetty/start/usage.txt
@@ -116,8 +116,11 @@ Available Configurations:
Defaults:
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
- the defaults in the start.config file. If --ini options are provided on
- the command line, then start.ini will no be read. The current start.ini
- arguments are:
+ the defaults in the start.config file. If the directory start.d exists,
+ then multiple *.ini files will be read from that directory in alphabetical
+ 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@
diff --git a/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java
new file mode 100644
index 0000000000..246771b4da
--- /dev/null
+++ b/jetty-start/src/test/java/org/eclipse/jetty/start/MainTest.java
@@ -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));
+ }
+
+}
diff --git a/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini b/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini
new file mode 100644
index 0000000000..827e41bf43
--- /dev/null
+++ b/jetty-start/src/test/resources/jetty.home/start.d/10-jmx.ini
@@ -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
+#===========================================================
diff --git a/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini b/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini
new file mode 100644
index 0000000000..679a221dd7
--- /dev/null
+++ b/jetty-start/src/test/resources/jetty.home/start.d/20-websocket.ini
@@ -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
+#----------------------------------------------------------- \ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini b/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini
new file mode 100644
index 0000000000..59313d3b99
--- /dev/null
+++ b/jetty-start/src/test/resources/jetty.home/start.d/90-testrealm.ini
@@ -0,0 +1 @@
+etc/jetty-testrealm.xml \ No newline at end of file
diff --git a/jetty-start/src/test/resources/jetty.home/start.ini b/jetty-start/src/test/resources/jetty.home/start.ini
new file mode 100644
index 0000000000..a9b724988e
--- /dev/null
+++ b/jetty-start/src/test/resources/jetty.home/start.ini
@@ -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
+#===========================================================
diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
index e0ed56cbaf..06741c0a13 100644
--- a/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java
@@ -16,6 +16,7 @@ package org.eclipse.jetty.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.Map;
@@ -78,13 +79,13 @@ public class UrlEncoded extends MultiMap implements Cloneable
/* ----------------------------------------------------------------- */
public void decode(String query)
{
- decodeTo(query,this,ENCODING);
+ decodeTo(query,this,ENCODING,-1);
}
/* ----------------------------------------------------------------- */
public void decode(String query,String charset)
{
- decodeTo(query,this,charset);
+ decodeTo(query,this,charset,-1);
}
/* -------------------------------------------------------------- */
@@ -178,6 +179,15 @@ public class UrlEncoded extends MultiMap implements Cloneable
*/
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)
charset=ENCODING;
@@ -208,6 +218,11 @@ public class UrlEncoded extends MultiMap implements Cloneable
}
key = null;
value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ {
+ LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
+ return;
+ }
break;
case '=':
if (key!=null)
@@ -343,9 +358,10 @@ public class UrlEncoded extends MultiMap implements Cloneable
/** Decoded parameters to Map.
* @param in InputSteam to read
* @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
{
synchronized(map)
@@ -375,6 +391,11 @@ public class UrlEncoded extends MultiMap implements Cloneable
}
key = null;
value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ {
+ LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
+ return;
+ }
break;
case '=':
@@ -423,9 +444,10 @@ public class UrlEncoded extends MultiMap implements Cloneable
/** Decoded parameters to Map.
* @param in InputSteam to read
* @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
{
synchronized(map)
@@ -455,6 +477,11 @@ public class UrlEncoded extends MultiMap implements Cloneable
}
key = null;
value=null;
+ if (maxKeys>0 && map.size()>maxKeys)
+ {
+ LOG.warn("maxFormKeys limit exceeded keys>{}",maxKeys);
+ return;
+ }
break;
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);
- StringBuffer buf = new StringBuffer();
-
- int c;
- 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);
+ StringWriter buf = new StringWriter(8192);
+ IO.copy(input,buf,maxLength);
+
+ decodeTo(buf.getBuffer().toString(),map,ENCODING,maxKeys);
}
/* -------------------------------------------------------------- */
/** Decoded parameters to Map.
* @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
{
//no charset present, use the configured default
@@ -527,22 +549,21 @@ public class UrlEncoded extends MultiMap implements Cloneable
charset=ENCODING;
}
-
if (StringUtil.__UTF8.equalsIgnoreCase(charset))
{
- decodeUtf8To(in,map,maxLength);
+ decodeUtf8To(in,map,maxLength,maxKeys);
return;
}
if (StringUtil.__ISO_8859_1.equals(charset))
{
- decode88591To(in,map,maxLength);
+ decode88591To(in,map,maxLength,maxKeys);
return;
}
if (StringUtil.__UTF16.equalsIgnoreCase(charset)) // Should be all 2 byte encodings
{
- decodeUtf16To(in,map,maxLength);
+ decodeUtf16To(in,map,maxLength,maxKeys);
return;
}
diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java
index 5d4c22159a..42bba2b05b 100644
--- a/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java
+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/URLEncodedTest.java
@@ -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]));
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);
assertEquals(i+" stream length",4,m.size());
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());
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 name","\u30c6\u30b9\u30c8",m2.getString("name"));
}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java
index cc5db43883..743bdc38b6 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java
@@ -348,8 +348,8 @@ public class WebSocketClient
return holder;
}
-
- public static final InetSocketAddress toSocketAddress(URI uri)
+
+ public static InetSocketAddress toSocketAddress(URI uri)
{
String scheme = uri.getScheme();
if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme)))
@@ -360,8 +360,7 @@ public class WebSocketClient
if (port < 0)
port = "ws".equals(scheme) ? 80 : 443;
- InetSocketAddress address = new InetSocketAddress(uri.getHost(), port);
- return address;
+ return new InetSocketAddress(uri.getHost(), port);
}
/* ------------------------------------------------------------ */
@@ -371,16 +370,8 @@ public class WebSocketClient
{
final WebSocket _websocket;
final URI _uri;
- final String _protocol;
- 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 WebSocketClient _client;
final CountDownLatch _done = new CountDownLatch(1);
-
ByteChannel _channel;
WebSocketConnection _connection;
Throwable _exception;
@@ -389,14 +380,7 @@ public class WebSocketClient
{
_websocket=websocket;
_uri=uri;
- _protocol=client._protocol;
- _origin=client._origin;
- _maskGen=client._maskGen;
- _maxIdleTime=client._maxIdleTime;
- _maxTextMessageSize=client._maxTextMessageSize;
- _maxBinaryMessageSize=client._maxBinaryMessageSize;
- _cookies=client._cookies;
- _extensions=client._extensions;
+ _client=client;
_channel=channel;
}
@@ -404,8 +388,10 @@ public class WebSocketClient
{
try
{
- connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize);
- connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize);
+ _client.getFactory().addConnection(connection);
+
+ connection.getConnection().setMaxTextMessageSize(_client.getMaxTextMessageSize());
+ connection.getConnection().setMaxBinaryMessageSize(_client.getMaxBinaryMessageSize());
WebSocketConnection con;
synchronized (this)
@@ -460,12 +446,12 @@ public class WebSocketClient
public Map<String,String> getCookies()
{
- return _cookies;
+ return _client.getCookies();
}
public String getProtocol()
{
- return _protocol;
+ return _client.getProtocol();
}
public WebSocket getWebSocket()
@@ -480,17 +466,17 @@ public class WebSocketClient
public int getMaxIdleTime()
{
- return _maxIdleTime;
+ return _client.getMaxIdleTime();
}
public String getOrigin()
{
- return _origin;
+ return _client.getOrigin();
}
public MaskGen getMaskGen()
{
- return _maskGen;
+ return _client.getMaskGen();
}
@Override
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java
index 35a1f39479..4b250ab1b2 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java
@@ -20,8 +20,11 @@ import java.io.IOException;
import java.net.ProtocolException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
+import java.util.List;
import java.util.Map;
+import java.util.Queue;
import java.util.Random;
+import java.util.concurrent.ConcurrentLinkedQueue;
import javax.net.ssl.SSLEngine;
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.ConnectedEndPoint;
import org.eclipse.jetty.io.Connection;
+import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.io.SimpleBuffers;
import org.eclipse.jetty.io.nio.AsyncConnection;
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 ByteArrayBuffer __ACCEPT = new ByteArrayBuffer.CaseInsensitive("Sec-WebSocket-Accept");
-
- private SslContextFactory _sslContextFactory = new SslContextFactory();
+ private final Queue<WebSocketConnection> connections = new ConcurrentLinkedQueue<WebSocketConnection>();
+ private final SslContextFactory _sslContextFactory = new SslContextFactory();
private final ThreadPool _threadPool;
private final WebSocketClientSelector _selector;
private MaskGen _maskGen;
@@ -200,6 +204,12 @@ public class WebSocketClientFactory extends AggregateLifeCycle
return _buffers.getBufferSize();
}
+ @Override
+ protected void doStop() throws Exception
+ {
+ closeConnections();
+ }
+
/* ------------------------------------------------------------ */
/**
* <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;
}
+ 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
@@ -457,18 +483,9 @@ public class WebSocketClientFactory extends AggregateLifeCycle
}
else
{
- Buffer header = _parser.getHeaderBuffer();
- MaskGen maskGen = _future.getMaskGen();
- WebSocketConnectionRFC6455 connection =
- new WebSocketConnectionRFC6455(_future.getWebSocket(),
- _endp,
- _buffers, System.currentTimeMillis(),
- _future.getMaxIdleTime(),
- _future.getProtocol(),
- null,
- WebSocketConnectionRFC6455.VERSION,
- maskGen);
+ WebSocketConnection connection = newWebSocketConnection();
+ Buffer header = _parser.getHeaderBuffer();
if (header.hasContent())
connection.fillBuffersFrom(header);
_buffers.returnBuffer(header);
@@ -483,6 +500,21 @@ public class WebSocketClientFactory extends AggregateLifeCycle
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
{
_endp.close();
@@ -506,4 +538,22 @@ public class WebSocketClientFactory extends AggregateLifeCycle
_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);
+ }
+ }
}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java
index 6ff8991062..67bfb37e65 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnection.java
@@ -24,8 +24,10 @@ import org.eclipse.jetty.io.nio.AsyncConnection;
public interface WebSocketConnection extends AsyncConnection
{
void fillBuffersFrom(Buffer buffer);
-
+
List<Extension> getExtensions();
-
+
WebSocket.Connection getConnection();
-} \ No newline at end of file
+
+ void shutdown();
+}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java
index b634bba1f9..12ec110144 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD00.java
@@ -293,6 +293,11 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc
}
}
+ public void shutdown()
+ {
+ close();
+ }
+
/* ------------------------------------------------------------ */
public void fillBuffersFrom(Buffer buffer)
{
@@ -389,7 +394,7 @@ public class WebSocketConnectionD00 extends AbstractConnection implements WebSoc
{
return _protocol;
}
-
+
protected void onFrameHandshake()
{
if (_websocket instanceof OnFrame)
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java
index f9e699fa57..c7c5f2b0a7 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD06.java
@@ -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)
{
@@ -294,7 +301,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc
{
return Collections.emptyList();
}
-
+
protected void onFrameHandshake()
{
if (_onFrame!=null)
@@ -302,7 +309,7 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc
_onFrame.onHandshake(_connection);
}
}
-
+
protected void onWebSocketOpen()
{
_webSocket.onOpen(_connection);
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java
index dc342ab4e6..9b282d6f6d 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionD08.java
@@ -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)
{
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java
index 47c45a5922..7f693751be 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketConnectionRFC6455.java
@@ -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)
{
@@ -431,7 +438,7 @@ public class WebSocketConnectionRFC6455 extends AbstractConnection implements We
/* ------------------------------------------------------------ */
private class WSFrameConnection implements WebSocket.FrameConnection
{
- volatile boolean _disconnecting;
+ private volatile boolean _disconnecting;
/* ------------------------------------------------------------ */
public void sendMessage(String content) throws IOException
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java
index 25f58c1ef0..28dd23f296 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketFactory.java
@@ -30,12 +30,12 @@ package org.eclipse.jetty.websocket;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
import javax.servlet.http.HttpServletRequest;
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.BlockingHttpConnection;
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.Logger;
/**
* Factory to create WebSocket connections
*/
-public class WebSocketFactory
+public class WebSocketFactory extends AbstractLifeCycle
{
private static final Logger LOG = Log.getLogger(WebSocketFactory.class);
+ private final Queue<WebSocketServletConnection> connections = new ConcurrentLinkedQueue<WebSocketServletConnection>();
public interface Acceptor
{
@@ -87,7 +89,7 @@ public class WebSocketFactory
private final Acceptor _acceptor;
private WebSocketBuffers _buffers;
private int _maxIdleTime = 300000;
- private int _maxTextMessageSize = 16*1024;
+ private int _maxTextMessageSize = 16 * 1024;
private int _maxBinaryMessageSize = -1;
public WebSocketFactory(Acceptor acceptor)
@@ -101,7 +103,6 @@ public class WebSocketFactory
_acceptor = acceptor;
}
-
/**
* @return A modifiable map of extension name to extension class
*/
@@ -187,6 +188,12 @@ public class WebSocketFactory
_maxBinaryMessageSize = maxBinaryMessageSize;
}
+ @Override
+ protected void doStop() throws Exception
+ {
+ closeConnections();
+ }
+
/**
* Upgrade the request/response to a WebSocket Connection.
* <p>This method will not normally return, but will instead throw a
@@ -230,40 +237,49 @@ public class WebSocketFactory
}
final WebSocketServletConnection connection;
- final List<Extension> extensions;
switch (draft)
{
case -1: // unspecified 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;
+ }
case 1:
case 2:
case 3:
case 4:
case 5:
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;
+ }
case 7:
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;
+ }
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;
+ }
default:
- LOG.warn("Unsupported Websocket version: "+draft);
+ {
+ LOG.warn("Unsupported Websocket version: " + draft);
// Per RFC 6455 - 4.4 - Supporting Multiple Versions of WebSocket Protocol
// 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);
+ }
}
+ addConnection(connection);
+
// Set the defaults
connection.getConnection().setMaxBinaryMessageSize(_maxBinaryMessageSize);
connection.getConnection().setMaxTextMessageSize(_maxTextMessageSize);
@@ -281,8 +297,6 @@ public class WebSocketFactory
request.setAttribute("org.eclipse.jetty.io.Connection", connection);
}
- /**
- */
protected String[] parseProtocols(String protocol)
{
if (protocol == null)
@@ -296,8 +310,6 @@ public class WebSocketFactory
return protocols;
}
- /**
- */
public boolean acceptWebSocket(HttpServletRequest request, HttpServletResponse response)
throws IOException
{
@@ -353,8 +365,6 @@ public class WebSocketFactory
return false;
}
- /**
- */
public List<Extension> initExtensions(List<String> requested,int maxDataOpcodes,int maxControlOpcodes,int maxReservedBits)
{
List<Extension> extensions = new ArrayList<Extension>();
@@ -386,8 +396,6 @@ public class WebSocketFactory
return extensions;
}
- /**
- */
private Extension newExtension(String name)
{
try
@@ -403,4 +411,20 @@ public class WebSocketFactory
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();
+ }
}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java
index ce9edc5c8e..1e765263e6 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServlet.java
@@ -34,31 +34,33 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
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
- * <p>
+ * <p/>
* The request must have the correct upgrade headers, else it is
* handled as a normal servlet request.
- * <p>
+ * <p/>
* The initParameter "bufferSize" can be used to set the buffer size,
* which is also the max frame byte size (default 8192).
- * <p>
+ * <p/>
* The initParameter "maxIdleTime" can be used to set the time in ms
* that a websocket may be idle before closing.
- * <p>
+ * <p/>
* The initParameter "maxTextMessagesSize" can be used to set the size in characters
* that a websocket may be accept before closing.
- * <p>
+ * <p/>
* The initParameter "maxBinaryMessagesSize" can be used to set the size in bytes
* that a websocket may be accept before closing.
- *
*/
@SuppressWarnings("serial")
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
public void init() throws ServletException
{
- String bs=getInitParameter("bufferSize");
- _webSocketFactory = new WebSocketFactory(this,bs==null?8192:Integer.parseInt(bs));
- String max=getInitParameter("maxIdleTime");
- if (max!=null)
- _webSocketFactory.setMaxIdleTime(Integer.parseInt(max));
+ try
+ {
+ String bs = getInitParameter("bufferSize");
+ _webSocketFactory = new WebSocketFactory(this, bs == null ? 8192 : Integer.parseInt(bs));
+ _webSocketFactory.start();
- max=getInitParameter("maxTextMessageSize");
- if (max!=null)
- _webSocketFactory.setMaxTextMessageSize(Integer.parseInt(max));
+ String max = getInitParameter("maxIdleTime");
+ if (max != null)
+ _webSocketFactory.setMaxIdleTime(Integer.parseInt(max));
- max=getInitParameter("maxBinaryMessageSize");
- if (max!=null)
- _webSocketFactory.setMaxBinaryMessageSize(Integer.parseInt(max));
+ max = getInitParameter("maxTextMessageSize");
+ if (max != null)
+ _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
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;
- super.service(request,response);
+ super.service(request, response);
}
/* ------------------------------------------------------------ */
@@ -101,6 +115,17 @@ public abstract class WebSocketServlet extends HttpServlet implements WebSocketF
return true;
}
-
-
+ /* ------------------------------------------------------------ */
+ @Override
+ public void destroy()
+ {
+ try
+ {
+ _webSocketFactory.stop();
+ }
+ catch (Exception x)
+ {
+ LOG.ignore(x);
+ }
+ }
}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java
index aa2a65f745..72632beddb 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD00.java
@@ -25,10 +25,13 @@ import org.eclipse.jetty.util.QuotedStringTokenizer;
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
{
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol);
+ this.factory = factory;
}
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.sendError(101,"WebSocket Protocol Handshake");
+ response.sendError(101, "WebSocket Protocol Handshake");
}
else
{
@@ -89,4 +92,11 @@ public class WebSocketServletConnectionD00 extends WebSocketConnectionD00 implem
onWebsocketOpen();
}
}
+
+ @Override
+ public void onClose()
+ {
+ super.onClose();
+ factory.removeConnection(this);
+ }
}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java
index 741abe6967..d75e153f41 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD06.java
@@ -23,10 +23,13 @@ import org.eclipse.jetty.io.EndPoint;
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
{
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol);
+ this.factory = factory;
}
/* ------------------------------------------------------------ */
@@ -47,4 +50,11 @@ public class WebSocketServletConnectionD06 extends WebSocketConnectionD06 implem
onFrameHandshake();
onWebSocketOpen();
}
+
+ @Override
+ public void onClose()
+ {
+ super.onClose();
+ factory.removeConnection(this);
+ }
}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java
index ba2f97eb6a..daa54334c3 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionD08.java
@@ -24,16 +24,13 @@ import org.eclipse.jetty.io.EndPoint;
public class WebSocketServletConnectionD08 extends WebSocketConnectionD08 implements WebSocketServletConnection
{
- public WebSocketServletConnectionD08(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
- List<Extension> extensions, int draft, MaskGen maskgen) throws IOException
- {
- super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,maskgen);
- }
+ private final WebSocketFactory factory;
- 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
{
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft);
+ this.factory = factory;
}
/* ------------------------------------------------------------ */
@@ -59,4 +56,11 @@ public class WebSocketServletConnectionD08 extends WebSocketConnectionD08 implem
onFrameHandshake();
onWebSocketOpen();
}
+
+ @Override
+ public void onClose()
+ {
+ super.onClose();
+ factory.removeConnection(this);
+ }
}
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java
index c93b41c267..b6e44fcbec 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketServletConnectionRFC6455.java
@@ -24,16 +24,13 @@ import org.eclipse.jetty.io.EndPoint;
public class WebSocketServletConnectionRFC6455 extends WebSocketConnectionRFC6455 implements WebSocketServletConnection
{
- public WebSocketServletConnectionRFC6455(WebSocket websocket, EndPoint endpoint, WebSocketBuffers buffers, long timestamp, int maxIdleTime, String protocol,
- List<Extension> extensions, int draft, MaskGen maskgen) throws IOException
- {
- super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft,maskgen);
- }
+ private final WebSocketFactory factory;
- 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
{
super(websocket,endpoint,buffers,timestamp,maxIdleTime,protocol,extensions,draft);
+ this.factory = factory;
}
/* ------------------------------------------------------------ */
@@ -59,4 +56,11 @@ public class WebSocketServletConnectionRFC6455 extends WebSocketConnectionRFC645
onFrameHandshake();
onWebSocketOpen();
}
+
+ @Override
+ public void onClose()
+ {
+ super.onClose();
+ factory.removeConnection(this);
+ }
}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java
index 2ab060ac23..571a454e0d 100644
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java
+++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java
@@ -15,8 +15,6 @@
*******************************************************************************/
package org.eclipse.jetty.websocket;
-import static org.hamcrest.Matchers.*;
-
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -46,6 +44,9 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import static org.hamcrest.Matchers.greaterThan;
+import static org.hamcrest.Matchers.is;
+
public class WebSocketClientTest
{
private WebSocketClientFactory _factory = new WebSocketClientFactory();
@@ -717,7 +718,7 @@ public class WebSocketClientTest
Assert.assertThat("URI (" + uri + ").host", addr.getHostName(), is("localhost"));
Assert.assertThat("URI (" + uri + ").port", addr.getPort(), is(80));
}
-
+
@Test
public void testURIWithDefaultWSSPort() throws Exception
{
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java
index 0bcd6ce4b7..fd2c02cbf2 100644
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java
+++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageD00Test.java
@@ -25,57 +25,80 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
+import java.net.SocketException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.http.HttpServletRequest;
+import junit.framework.Assert;
+
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.util.StringUtil;
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.Utf8StringBuilder;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.StdErrLog;
import org.junit.AfterClass;
+import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class WebSocketMessageD00Test
{
- private static Server _server;
- private static Connector _connector;
- private static TestWebSocket _serverWebSocket;
+ private static Server __server;
+ private static Connector __connector;
+ private static TestWebSocket __serverWebSocket;
+ private static CountDownLatch __latch;
+ private static AtomicInteger __textCount = new AtomicInteger(0);
+
@BeforeClass
public static void startServer() throws Exception
{
- _server = new Server();
- _connector = new SelectChannelConnector();
- _server.addConnector(_connector);
+ __server = new Server();
+ __connector = new SelectChannelConnector();
+ __server.addConnector(__connector);
WebSocketHandler wsHandler = new WebSocketHandler()
{
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
{
- _serverWebSocket = new TestWebSocket();
- _serverWebSocket.onConnect=("onConnect".equals(protocol));
- return _serverWebSocket;
+ __serverWebSocket = new TestWebSocket();
+ __serverWebSocket._onConnect=("onConnect".equals(protocol));
+ __serverWebSocket._echo=("echo".equals(protocol));
+ __serverWebSocket._latch=("latch".equals(protocol));
+ if (__serverWebSocket._latch)
+ __latch=new CountDownLatch(1);
+ return __serverWebSocket;
}
};
wsHandler.setHandler(new DefaultHandler());
- _server.setHandler(wsHandler);
- _server.start();
+ __server.setHandler(wsHandler);
+ __server.start();
}
@AfterClass
public static void stopServer() throws Exception
{
- _server.stop();
- _server.join();
+ __server.stop();
+ __server.join();
}
+ @Before
+ public void reset()
+ {
+ __textCount.set(0);
+ }
+
@Test
public void testServerSendBigStringMessage() throws Exception
{
- Socket socket = new Socket("localhost", _connector.getLocalPort());
+ Socket socket = new Socket("localhost", __connector.getLocalPort());
OutputStream output = socket.getOutputStream();
output.write(
("GET /test HTTP/1.1\r\n" +
@@ -94,7 +117,6 @@ public class WebSocketMessageD00Test
InputStream input = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input, "ISO-8859-1"));
String responseLine = reader.readLine();
- System.err.println(responseLine);
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
String line;
@@ -102,8 +124,8 @@ public class WebSocketMessageD00Test
if (line.length() == 0)
break;
- assertTrue(_serverWebSocket.awaitConnected(1000));
- assertNotNull(_serverWebSocket.outbound);
+ assertTrue(__serverWebSocket.awaitConnected(1000));
+ assertNotNull(__serverWebSocket.outbound);
// read the hixie bytes
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";
for (int i = 0; i < 64 * 1024 / text.length(); ++i)
message.append(text);
- _serverWebSocket.outbound.sendMessage(message.toString());
+ __serverWebSocket.outbound.sendMessage(message.toString());
// Read until we get 0xFF
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -147,7 +169,7 @@ public class WebSocketMessageD00Test
@Test
public void testServerSendOnConnect() throws Exception
{
- Socket socket = new Socket("localhost", _connector.getLocalPort());
+ Socket socket = new Socket("localhost", __connector.getLocalPort());
OutputStream output = socket.getOutputStream();
output.write(
("GET /test HTTP/1.1\r\n" +
@@ -200,8 +222,8 @@ public class WebSocketMessageD00Test
}
- assertTrue(_serverWebSocket.awaitConnected(1000));
- assertNotNull(_serverWebSocket.outbound);
+ assertTrue(__serverWebSocket.awaitConnected(1000));
+ assertNotNull(__serverWebSocket.outbound);
looking_for="8jKS'y:G*Co,Wxa-";
while(true)
@@ -232,17 +254,498 @@ public class WebSocketMessageD00Test
assertEquals(0xff,input.read());
}
+
+
+ @Test
+ public void testServerEcho() throws Exception
+ {
+ // Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
+
+ 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();
- private static class TestWebSocket implements WebSocket
+ // 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
{
- boolean onConnect=false;
- private final CountDownLatch latch = new CountDownLatch(1);
+ // 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 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)
{
this.outbound = outbound;
- if (onConnect)
+ if (_onConnect)
{
try
{
@@ -253,16 +756,55 @@ public class WebSocketMessageD00Test
e.printStackTrace();
}
}
- latch.countDown();
+ connected.countDown();
}
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)
{
+ 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();
+ }
+ }
}
}
}
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageRFC6455Test.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageRFC6455Test.java
index 113fc83012..6c017e9317 100644
--- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageRFC6455Test.java
+++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketMessageRFC6455Test.java
@@ -22,6 +22,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
+import java.net.SocketException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -32,6 +33,8 @@ import java.util.zip.Inflater;
import javax.servlet.http.HttpServletRequest;
+import junit.framework.Assert;
+
import org.eclipse.jetty.io.Buffer;
import org.eclipse.jetty.io.ByteArrayEndPoint;
import org.eclipse.jetty.server.Connector;
@@ -1314,7 +1317,7 @@ public class WebSocketMessageRFC6455Test
}
@Test
- public void testClose() throws Exception
+ public void testTCPClose() throws Exception
{
Socket socket = new Socket("localhost", __connector.getLocalPort());
OutputStream output = socket.getOutputStream();
@@ -1350,7 +1353,6 @@ public class WebSocketMessageRFC6455Test
socket.close();
assertTrue(__serverWebSocket.awaitDisconnected(500));
-
try
{
@@ -1362,6 +1364,64 @@ public class WebSocketMessageRFC6455Test
assertTrue(true);
}
}
+
+ @Test
+ public void testTCPHalfClose() throws Exception
+ {
+ Socket socket = new Socket("localhost", __connector.getLocalPort());
+ OutputStream output = socket.getOutputStream();
+ output.write(
+ ("GET /chat HTTP/1.1\r\n"+
+ "Host: server.example.com\r\n"+
+ "Upgrade: websocket\r\n"+
+ "Connection: Upgrade\r\n"+
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"+
+ "Sec-WebSocket-Origin: http://example.com\r\n"+
+ "Sec-WebSocket-Protocol: onConnect\r\n" +
+ "Sec-WebSocket-Version: "+WebSocketConnectionRFC6455.VERSION+"\r\n"+
+ "\r\n").getBytes("ISO-8859-1"));
+ output.flush();
+
+ // Make sure the read times out if there are problems with the implementation
+ socket.setSoTimeout(1000);
+
+ InputStream input = socket.getInputStream();
+
+ lookFor("HTTP/1.1 101 Switching Protocols\r\n",input);
+ skipTo("Sec-WebSocket-Accept: ",input);
+ lookFor("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=",input);
+ skipTo("\r\n\r\n",input);
+
+
+ assertTrue(__serverWebSocket.awaitConnected(1000));
+ assertNotNull(__serverWebSocket.connection);
+
+ assertEquals(0x81,input.read());
+ assertEquals(0x0f,input.read());
+ lookFor("sent on connect",input);
+
+ socket.shutdownOutput();
+
+ assertTrue(__serverWebSocket.awaitDisconnected(500));
+
+ assertEquals(0x88,input.read());
+ assertEquals(0x00,input.read());
+ 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
+ }
+ }
+
+
@Test
public void testParserAndGenerator() throws Exception
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketRedeployTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketRedeployTest.java
new file mode 100644
index 0000000000..39f218085d
--- /dev/null
+++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketRedeployTest.java
@@ -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));
+ }
+}
diff --git a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
index 6da0355acf..d5f4df3915 100644
--- a/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
+++ b/jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java
@@ -110,20 +110,22 @@ public class XmlConfiguration
__parser = new XmlParser();
try
{
- URL configURL = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true);
- __parser.redirectEntity("configure.dtd",configURL);
- __parser.redirectEntity("configure_1_0.dtd",configURL);
- __parser.redirectEntity("configure_1_1.dtd",configURL);
- __parser.redirectEntity("configure_1_2.dtd",configURL);
- __parser.redirectEntity("configure_1_3.dtd",configURL);
- __parser.redirectEntity("configure_6_0.dtd",configURL);
-
- __parser.redirectEntity("http://jetty.mortbay.org/configure.dtd",configURL);
- __parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",configURL);
- __parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",configURL);
-
- __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",configURL);
- __parser.redirectEntity("-//Jetty//Configure//EN",configURL);
+ URL config60 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_6_0.dtd",true);
+ URL config71 = Loader.getResource(XmlConfiguration.class,"org/eclipse/jetty/xml/configure_7_1.dtd",true);
+ __parser.redirectEntity("configure.dtd",config71);
+ __parser.redirectEntity("configure_1_0.dtd",config60);
+ __parser.redirectEntity("configure_1_1.dtd",config60);
+ __parser.redirectEntity("configure_1_2.dtd",config60);
+ __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",config71);
+ __parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",config71);
+ __parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",config71);
+
+ __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN",config71);
+ __parser.redirectEntity("-//Jetty//Configure//EN",config71);
}
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
*/
@@ -922,6 +924,7 @@ public class XmlConfiguration
configure(prop,node,0);
return prop;
}
+
/* ------------------------------------------------------------ */
/*
@@ -1091,6 +1094,14 @@ public class XmlConfiguration
String defaultValue = node.getAttribute("default");
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());
return null;
diff --git a/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd b/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd
index f79d4b7f00..e2139c162e 100644
--- a/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd
+++ b/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_6_0.dtd
@@ -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 % 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 % IMPLIEDCLASSATTR "class NMTOKEN #IMPLIED" >
@@ -245,6 +245,24 @@ This is equivalent to:
<!ELEMENT SystemProperty EMPTY>
<!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.
diff --git a/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_7_6.dtd b/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_7_6.dtd
new file mode 100644
index 0000000000..f79d4b7f00
--- /dev/null
+++ b/jetty-xml/src/main/resources/org/eclipse/jetty/xml/configure_7_6.dtd
@@ -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;>
diff --git a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java
index 6608b1639c..700d750880 100644
--- a/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java
+++ b/jetty-xml/src/test/java/org/eclipse/jetty/xml/XmlConfigurationTest.java
@@ -63,6 +63,8 @@ public class XmlConfigurationTest
assertEquals( "ObjectsWhiteString", "-1\n String",tc.get("ObjectsWhiteString"));
assertEquals( "SystemProperty", System.getProperty("user.dir")+"/stuff",tc.get("SystemProperty"));
+ assertEquals( "Env", System.getenv("HOME"),tc.get("Env"));
+
assertEquals( "Property", "xxx", tc.get("Property"));
diff --git a/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml
index ec26b3ea94..fd0316bff6 100644
--- a/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml
+++ b/jetty-xml/src/test/resources/org/eclipse/jetty/xml/configure.xml
@@ -81,6 +81,7 @@
<Put name="SomethingElse"><SystemProperty name="floople" default="xxx"/></Put>
<Put name="Boolean" type="Boolean">True</Put>
<Put name="Float" type="Float">2.3</Put>
+ <Put name="Env"><Env name="HOME"/></Put>
<Call name="call">
</Call>
diff --git a/pom.xml b/pom.xml
index d72b94869a..63315d4c21 100644
--- a/pom.xml
+++ b/pom.xml
@@ -311,6 +311,7 @@
</plugin>
</plugins>
</reporting>
+<!--
<repositories>
<repository>
<snapshots>
@@ -321,6 +322,7 @@
<url>http://oss.sonatype.org/content/groups/jetty</url>
</repository>
</repositories>
+-->
<modules>
<module>jetty-util</module>
<module>jetty-jmx</module>

Back to the top