Merge remote-tracking branch 'origin/master' into bug-359329

This commit is contained in:
Jan Bartel 2012-01-01 17:29:29 +11:00
commit 0ebd3d636b
49 changed files with 2488 additions and 627 deletions

View File

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

View File

@ -29,6 +29,7 @@ import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.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);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,11 +1349,31 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server.
}
/* ------------------------------------------------------------ */
/**
* Set the maximum size of a form post, to protect against DOS attacks from large forms.
* @param maxSize
*/
public void setMaxFormContentSize(int maxSize)
{
_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
{

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
{
Main main = new Main();
main.parseCommandLine(args);
}
public void parseCommandLine(String[] args)
public static void main(String[] args)
{
try
{
List<String> arguments = new ArrayList<String>();
// 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"))
{
ini=true;
if (arg.length()>6)
{
arguments.addAll(loadStartIni(arg.substring(6)));
continue;
}
}
else if (arg.startsWith("--config="))
{
_startConfig=arg.substring(9);
}
else
{
arguments.add(arg);
}
}
// if no non-option inis, add the start.ini
if (!ini)
{
arguments.addAll(0,loadStartIni(null));
}
// The XML Configuration Files to initialize with
List<String> xmls = new ArrayList<String>();
// Process the arguments
int startup=0;
for (String arg : arguments)
{
if ("--help".equals(arg) || "-?".equals(arg))
{
_showUsage = true;
continue;
}
if ("--stop".equals(arg))
{
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
String key = Config.getProperty("STOP.KEY",null);
stop(port,key);
return;
}
if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
{
_dumpVersions = true;
continue;
}
if ("--list-modes".equals(arg) || "--list-options".equals(arg))
{
_listOptions = true;
continue;
}
if ("--list-config".equals(arg))
{
_listConfig=true;
continue;
}
if ("--exec-print".equals(arg)||"--dry-run".equals(arg))
{
_dryRun = true;
continue;
}
if ("--exec".equals(arg))
{
_exec = true;
continue;
}
// Special internal indicator that jetty was started by the jetty.sh Daemon
if ("--daemon".equals(arg))
{
File startDir = new File(System.getProperty("jetty.logs","logs"));
if (!startDir.exists() || !startDir.canWrite() )
startDir = new File(".");
File startLog = new File(startDir,"start.log");
if (!startLog.exists() && !startLog.createNewFile())
{
// Output about error is lost in majority of cases.
System.err.println("Unable to create: " + startLog.getAbsolutePath());
// Toss a unique exit code indicating this failure.
usageExit(ERR_LOGGING);
}
if (!startLog.canWrite())
{
// Output about error is lost in majority of cases.
System.err.println("Unable to write to: " + startLog.getAbsolutePath());
// Toss a unique exit code indicating this failure.
usageExit(ERR_LOGGING);
}
PrintStream logger = new PrintStream(new FileOutputStream(startLog,false));
System.setOut(logger);
System.setErr(logger);
System.out.println("Establishing start.log on " + new Date());
continue;
}
if (arg.startsWith("--pre="))
{
xmls.add(startup++,arg.substring(6));
continue;
}
if (arg.startsWith("-D"))
{
String[] assign = arg.substring(2).split("=",2);
_sysProps.add(assign[0]);
switch(assign.length)
{
case 2:
System.setProperty(assign[0],assign[1]);
break;
case 1:
System.setProperty(assign[0],"");
break;
default:
break;
}
continue;
}
if (arg.startsWith("-"))
{
_jvmArgs.add(arg);
continue;
}
// Is this a Property?
if (arg.indexOf('=') >= 0)
{
String[] assign = arg.split("=",2);
switch(assign.length)
{
case 2:
if ("OPTIONS".equals(assign[0]))
{
String opts[] = assign[1].split(",");
for (String opt : opts)
_config.addActiveOption(opt);
}
else
{
this._config.setProperty(assign[0],assign[1]);
}
break;
case 1:
this._config.setProperty(assign[0],null);
break;
default:
break;
}
continue;
}
// Anything else is considered an XML file.
if (xmls.contains(arg))
{
System.out.println("WARN: Argument '"+arg+"' specified multiple times. Check start.ini?");
System.out.println("Use \"java -jar start.jar --help\" for more information.");
}
xmls.add(arg);
}
start(xmls);
Main main = new Main();
List<String> arguments = main.expandCommandLine(args);
List<String> xmls = main.processCommandLine(arguments);
if (xmls!=null)
main.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)
{
System.err.println("Warning - can't find ini file: " + ini);
}
// No start.ini found, skip load.
return Collections.emptyList();
}
List<String> args = new ArrayList<String>();
FileReader reader = null;
BufferedReader buf = null;
try
{
reader = new FileReader(startIniFile);
buf = new BufferedReader(reader);
String arg;
while ((arg = buf.readLine()) != null)
{
arg = arg.trim();
if (arg.length() == 0 || arg.startsWith("#"))
{
continue;
}
args.add(arg);
}
}
catch (IOException e)
catch (Throwable e)
{
usageExit(e,ERR_UNKNOWN);
}
finally
}
Main() throws IOException
{
_jettyHome = System.getProperty("jetty.home",".");
_jettyHome = new File(_jettyHome).getCanonicalPath();
}
public List<String> expandCommandLine(String[] args) throws Exception
{
List<String> arguments = new ArrayList<String>();
// add the command line args and look for start.ini args
boolean ini = false;
for (String arg : args)
{
close(buf);
close(reader);
if (arg.startsWith("--ini=") || arg.equals("--ini"))
{
ini = true;
if (arg.length() > 6)
{
arguments.addAll(loadStartIni(new File(arg.substring(6))));
continue;
}
}
else if (arg.startsWith("--config="))
{
_startConfig = arg.substring(9);
}
else
{
arguments.add(arg);
}
}
return args;
// if no non-option inis, add the start.ini and start.d
if (!ini)
{
List<String> ini_args=new ArrayList<String>();
File start_ini = new File(_jettyHome,"start.ini");
if (start_ini.exists())
ini_args.addAll(loadStartIni(start_ini));
File start_d = new File(_jettyHome,"start.d");
if (start_d.isDirectory())
{
File[] inis = start_d.listFiles(new FilenameFilter()
{
public boolean accept(File dir, String name)
{
return name.toLowerCase().endsWith(".ini");
}
});
Arrays.sort(inis);
for (File i : inis)
ini_args.addAll(loadStartIni(i));
}
arguments.addAll(0,ini_args);
}
return arguments;
}
public List<String> processCommandLine(List<String> arguments) throws Exception
{
// The XML Configuration Files to initialize with
List<String> xmls = new ArrayList<String>();
// Process the arguments
int startup = 0;
for (String arg : arguments)
{
if ("--help".equals(arg) || "-?".equals(arg))
{
_showUsage = true;
continue;
}
if ("--stop".equals(arg))
{
int port = Integer.parseInt(Config.getProperty("STOP.PORT","-1"));
String key = Config.getProperty("STOP.KEY",null);
stop(port,key);
return null;
}
if ("--version".equals(arg) || "-v".equals(arg) || "--info".equals(arg))
{
_dumpVersions = true;
continue;
}
if ("--list-modes".equals(arg) || "--list-options".equals(arg))
{
_listOptions = true;
continue;
}
if ("--list-config".equals(arg))
{
_listConfig = true;
continue;
}
if ("--exec-print".equals(arg) || "--dry-run".equals(arg))
{
_dryRun = true;
continue;
}
if ("--exec".equals(arg))
{
_exec = true;
continue;
}
// Special internal indicator that jetty was started by the jetty.sh Daemon
if ("--daemon".equals(arg))
{
File startDir = new File(System.getProperty("jetty.logs","logs"));
if (!startDir.exists() || !startDir.canWrite())
startDir = new File(".");
File startLog = new File(startDir,"start.log");
if (!startLog.exists() && !startLog.createNewFile())
{
// Output about error is lost in majority of cases.
System.err.println("Unable to create: " + startLog.getAbsolutePath());
// Toss a unique exit code indicating this failure.
usageExit(ERR_LOGGING);
}
if (!startLog.canWrite())
{
// Output about error is lost in majority of cases.
System.err.println("Unable to write to: " + startLog.getAbsolutePath());
// Toss a unique exit code indicating this failure.
usageExit(ERR_LOGGING);
}
PrintStream logger = new PrintStream(new FileOutputStream(startLog,false));
System.setOut(logger);
System.setErr(logger);
System.out.println("Establishing start.log on " + new Date());
continue;
}
if (arg.startsWith("--pre="))
{
xmls.add(startup++,arg.substring(6));
continue;
}
if (arg.startsWith("-D"))
{
String[] assign = arg.substring(2).split("=",2);
_sysProps.add(assign[0]);
switch (assign.length)
{
case 2:
System.setProperty(assign[0],assign[1]);
break;
case 1:
System.setProperty(assign[0],"");
break;
default:
break;
}
continue;
}
if (arg.startsWith("-"))
{
_jvmArgs.add(arg);
continue;
}
// Is this a Property?
if (arg.indexOf('=') >= 0)
{
String[] assign = arg.split("=",2);
switch (assign.length)
{
case 2:
if ("OPTIONS".equals(assign[0]))
{
String opts[] = assign[1].split(",");
for (String opt : opts)
_config.addActiveOption(opt);
}
else
{
this._config.setProperty(assign[0],assign[1]);
}
break;
case 1:
this._config.setProperty(assign[0],null);
break;
default:
break;
}
continue;
}
// Anything else is considered an XML file.
if (xmls.contains(arg))
{
System.out.println("WARN: Argument '" + arg + "' specified multiple times. Check start.ini?");
System.out.println("Use \"java -jar start.jar --help\" for more information.");
}
xmls.add(arg);
}
return xmls;
}
private void usage()
@ -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;
}
}

View File

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

View File

@ -0,0 +1,76 @@
// ========================================================================
// Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
package org.eclipse.jetty.start;
import static org.junit.Assert.assertEquals;
import java.io.File;
import java.net.URL;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
/* ------------------------------------------------------------ */
/**
*/
public class MainTest
{
/* ------------------------------------------------------------ */
/**
* @throws java.lang.Exception
*/
@Before
public void setUp() throws Exception
{
System.setProperty("jetty.home",this.getClass().getResource("/jetty.home").getFile());
}
/**
* Test method for {@link org.eclipse.jetty.start.StartIniParser#loadStartIni(java.lang.String)}.
*/
@Test
public void testLoadStartIni()
{
URL startIni = this.getClass().getResource("/jetty.home/start.ini");
String startIniFileName = startIni.getFile();
List<String> args = Main.loadStartIni(new File(startIniFileName));
assertEquals("Expected 5 uncommented lines in start.ini",5,args.size());
assertEquals("First uncommented line in start.ini doesn't match expected result","OPTIONS=Server,jsp,resources,websocket,ext",args.get(0));
}
@Test
public void testExpandCommandLine() throws Exception
{
Main main = new Main();
List<String> args = main.expandCommandLine(new String[]{});
assertEquals("start.ini OPTIONS","OPTIONS=Server,jsp,resources,websocket,ext",args.get(0));
assertEquals("start.d/jmx OPTIONS","OPTIONS=jmx",args.get(5));
assertEquals("start.d/jmx XML","--pre=etc/jetty-jmx.xml",args.get(6));
assertEquals("start.d/websocket OPTIONS","OPTIONS=websocket",args.get(7));
}
@Test
public void testProcessCommandLine() throws Exception
{
Main main = new Main();
List<String> args = main.expandCommandLine(new String[]{});
List<String> xmls = main.processCommandLine(args);
assertEquals("jmx --pre","etc/jetty-jmx.xml",xmls.get(0));
assertEquals("start.ini","etc/jetty.xml",xmls.get(1));
assertEquals("start.d","etc/jetty-testrealm.xml",xmls.get(5));
}
}

View File

@ -0,0 +1,22 @@
#===========================================================
# Additional Jetty start.jar arguments
# Each line of this file is prepended to the command line
# arguments # of a call to:
# java -jar start.jar [arg...]
#===========================================================
#===========================================================
#-----------------------------------------------------------
OPTIONS=jmx
#-----------------------------------------------------------
#===========================================================
# Configuration files.
# For a full list of available configuration files do
# java -jar start.jar --help
#-----------------------------------------------------------
--pre=etc/jetty-jmx.xml
#===========================================================

View File

@ -0,0 +1,13 @@
#===========================================================
# Additional Jetty start.jar arguments
# Each line of this file is prepended to the command line
# arguments # of a call to:
# java -jar start.jar [arg...]
#===========================================================
#===========================================================
#-----------------------------------------------------------
OPTIONS=websocket
#-----------------------------------------------------------

View File

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

View File

@ -0,0 +1,65 @@
#===========================================================
# Jetty start.jar arguments
# Each line of this file is prepended to the command line
# arguments # of a call to:
# java -jar start.jar [arg...]
#===========================================================
#===========================================================
# If the arguements in this file include JVM arguments
# (eg -Xmx512m) or JVM System properties (eg com.sun.???),
# then these will not take affect unless the --exec
# parameter is included or if the output from --dry-run
# is executed like:
# eval $(java -jar start.jar --dry-run)
#
# Below are some recommended options for Sun's JRE
#-----------------------------------------------------------
# --exec
# -Dorg.apache.jasper.compiler.disablejsr199=true
# -Dcom.sun.management.jmxremote
# -Dorg.eclipse.jetty.util.log.IGNORED=true
# -Dorg.eclipse.jetty.util.log.stderr.DEBUG=true
# -Dorg.eclipse.jetty.util.log.stderr.SOURCE=true
# -Xmx2000m
# -Xmn512m
# -verbose:gc
# -XX:+PrintGCDateStamps
# -XX:+PrintGCTimeStamps
# -XX:+PrintGCDetails
# -XX:+PrintTenuringDistribution
# -XX:+PrintCommandLineFlags
# -XX:+DisableExplicitGC
# -XX:+UseConcMarkSweepGC
# -XX:ParallelCMSThreads=2
# -XX:+CMSClassUnloadingEnabled
# -XX:+UseCMSCompactAtFullCollection
# -XX:CMSInitiatingOccupancyFraction=80
#-----------------------------------------------------------
#===========================================================
# Start classpath OPTIONS.
# These control what classes are on the classpath
# for a full listing do
# java -jar start.jar --list-options
#-----------------------------------------------------------
OPTIONS=Server,jsp,resources,websocket,ext
#-----------------------------------------------------------
#===========================================================
# Configuration files.
# For a full list of available configuration files do
# java -jar start.jar --help
#-----------------------------------------------------------
etc/jetty.xml
# etc/jetty-ssl.xml
# etc/jetty-requestlog.xml
etc/jetty-deploy.xml
#etc/jetty-overlay.xml
etc/jetty-webapps.xml
etc/jetty-contexts.xml
#===========================================================

View File

@ -16,6 +16,7 @@ package org.eclipse.jetty.util;
import java.io.IOException;
import java.io.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);
}
/* -------------------------------------------------------------- */
@ -177,6 +178,15 @@ public class UrlEncoded extends MultiMap implements Cloneable
* @param content the string containing the encoded parameters
*/
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;
}

View File

@ -178,7 +178,7 @@ public class URLEncodedTest
{
ByteArrayInputStream in = new ByteArrayInputStream("name\n=value+%30&name1=&name2&n\u00e3me3=value+3".getBytes(charsets[i][0]));
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"));
}

View File

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

View File

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

View File

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

View File

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

View File

@ -274,6 +274,13 @@ public class WebSocketConnectionD06 extends AbstractConnection implements WebSoc
}
}
public void shutdown()
{
final WebSocket.Connection connection = _connection;
if (connection != null)
connection.close(CLOSE_SHUTDOWN, null);
}
/* ------------------------------------------------------------ */
public void fillBuffersFrom(Buffer buffer)
{
@ -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);

View File

@ -370,6 +370,13 @@ public class WebSocketConnectionD08 extends AbstractConnection implements WebSoc
}
}
public void shutdown()
{
final WebSocket.Connection connection = _connection;
if (connection != null)
connection.close(CLOSE_SHUTDOWN, null);
}
/* ------------------------------------------------------------ */
public void fillBuffersFrom(Buffer buffer)
{

View File

@ -400,6 +400,13 @@ public class WebSocketConnectionRFC6455 extends AbstractConnection implements We
}
}
public void shutdown()
{
final WebSocket.Connection connection = _connection;
if (connection != null)
connection.close(CLOSE_SHUTDOWN, null);
}
/* ------------------------------------------------------------ */
public void fillBuffersFrom(Buffer buffer)
{
@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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());
}
private static class TestWebSocket implements WebSocket
@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
{
boolean onConnect=false;
private final CountDownLatch latch = new CountDownLatch(1);
private volatile Connection outbound;
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
Socket socket = new Socket("localhost", __connector.getLocalPort());
socket.setSoTimeout(60000);
OutputStream output = socket.getOutputStream();
InputStream input = socket.getInputStream();
output.write(
("GET /test HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Protocol: latch\r\n" +
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
"\r\n"+
"^n:ds[4U").getBytes("ISO-8859-1"));
output.flush();
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
skipTo("\r\n\r\n",input);
lookFor("8jKS'y:G*Co,Wxa-",input);
assertTrue(__serverWebSocket.awaitConnected(1000));
assertNotNull(__serverWebSocket.connection);
__serverWebSocket.connection.setMaxIdleTime(60000);
byte[] bytes="This is a long message of text that we will send again and again".getBytes(StringUtil.__ISO_8859_1);
byte[] mesg=new byte[bytes.length+2];
mesg[0]=(byte)0x00;
for (int i=0;i<bytes.length;i++)
mesg[i+1]=(byte)(bytes[i]);
mesg[mesg.length-1]=(byte)0xFF;
final int count = 100000;
// Send and receive 1 message
output.write(mesg);
output.flush();
while(__textCount.get()==0)
Thread.sleep(10);
// unblock the latch in 4s
new Thread()
{
@Override
public void run()
{
try
{
Thread.sleep(4000);
__latch.countDown();
//System.err.println("latched");
}
catch(Exception e)
{
e.printStackTrace();
}
}
}.start();
// Send enough messages to fill receive buffer
long max=0;
long start=System.currentTimeMillis();
for (int i=0;i<count;i++)
{
output.write(mesg);
if (i%100==0)
{
// System.err.println(">>> "+i);
output.flush();
long now=System.currentTimeMillis();
long duration=now-start;
start=now;
if (max<duration)
max=duration;
}
}
Thread.sleep(50);
while(__textCount.get()<count+1)
{
System.err.println(__textCount.get()+"<"+(count+1));
Thread.sleep(10);
}
assertEquals(count+1,__textCount.get()); // all messages
assertTrue(max>2000); // was blocked
}
@Test
public void testBlockedProducer() throws Exception
{
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
final Socket socket = new Socket("localhost", __connector.getLocalPort());
socket.setSoTimeout(60000);
OutputStream output = socket.getOutputStream();
InputStream input = socket.getInputStream();
output.write(
("GET /test HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Protocol: latch\r\n" +
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
"\r\n"+
"^n:ds[4U").getBytes("ISO-8859-1"));
output.flush();
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
skipTo("\r\n\r\n",input);
lookFor("8jKS'y:G*Co,Wxa-",input);
assertTrue(__serverWebSocket.awaitConnected(1000));
assertNotNull(__serverWebSocket.connection);
final int count = 100000;
__serverWebSocket.connection.setMaxIdleTime(60000);
__latch.countDown();
// wait 2s and then consume messages
final AtomicLong totalB=new AtomicLong();
new Thread()
{
@Override
public void run()
{
try
{
Thread.sleep(2000);
byte[] recv = new byte[32*1024];
int len=0;
while (len>=0)
{
totalB.addAndGet(len);
len=socket.getInputStream().read(recv,0,recv.length);
Thread.sleep(10);
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
}.start();
// Send enough messages to fill receive buffer
long max=0;
long start=System.currentTimeMillis();
String mesg="How Now Brown Cow";
for (int i=0;i<count;i++)
{
__serverWebSocket.connection.sendMessage(mesg);
if (i%100==0)
{
output.flush();
long now=System.currentTimeMillis();
long duration=now-start;
start=now;
if (max<duration)
max=duration;
}
}
while(totalB.get()<(count*(mesg.length()+2)))
Thread.sleep(100);
assertEquals(count*(mesg.length()+2),totalB.get()); // all messages
assertTrue(max>1000); // was blocked
}
@Test
public void testIdle() throws Exception
{
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
final Socket socket = new Socket("localhost", __connector.getLocalPort());
socket.setSoTimeout(10000);
OutputStream output = socket.getOutputStream();
InputStream input = socket.getInputStream();
output.write(
("GET /test HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Protocol: onConnect\r\n" +
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
"\r\n"+
"^n:ds[4U").getBytes("ISO-8859-1"));
output.flush();
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
skipTo("\r\n\r\n",input);
lookFor("8jKS'y:G*Co,Wxa-",input);
assertTrue(__serverWebSocket.awaitConnected(1000));
assertNotNull(__serverWebSocket.connection);
__serverWebSocket.connection.setMaxIdleTime(250);
assertEquals(0x00,input.read());
lookFor("sent on connect",input);
assertEquals(0xff,input.read());
assertEquals(-1,input.read());
socket.close();
assertTrue(__serverWebSocket.awaitDisconnected(100));
}
@Test
public void testIdleBadClient() throws Exception
{
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
final Socket socket = new Socket("localhost", __connector.getLocalPort());
socket.setSoTimeout(10000);
OutputStream output = socket.getOutputStream();
InputStream input = socket.getInputStream();
output.write(
("GET /test HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Protocol: onConnect\r\n" +
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
"\r\n"+
"^n:ds[4U").getBytes("ISO-8859-1"));
output.flush();
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
skipTo("\r\n\r\n",input);
lookFor("8jKS'y:G*Co,Wxa-",input);
assertTrue(__serverWebSocket.awaitConnected(1000));
assertNotNull(__serverWebSocket.connection);
__serverWebSocket.connection.setMaxIdleTime(250);
assertEquals(0x00,input.read());
lookFor("sent on connect",input);
assertEquals(0xff,input.read());
assertEquals(-1,input.read());
assertTrue(__serverWebSocket.disconnected.getCount()>0);
assertTrue(__serverWebSocket.awaitDisconnected(1000));
}
@Test
public void testTCPClose() throws Exception
{
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
final Socket socket = new Socket("localhost", __connector.getLocalPort());
socket.setSoTimeout(10000);
OutputStream output = socket.getOutputStream();
InputStream input = socket.getInputStream();
output.write(
("GET /test HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Protocol: onConnect\r\n" +
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
"\r\n"+
"^n:ds[4U").getBytes("ISO-8859-1"));
output.flush();
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
skipTo("\r\n\r\n",input);
lookFor("8jKS'y:G*Co,Wxa-",input);
assertTrue(__serverWebSocket.awaitConnected(1000));
assertNotNull(__serverWebSocket.connection);
__serverWebSocket.connection.setMaxIdleTime(250);
assertEquals(0x00,input.read());
lookFor("sent on connect",input);
assertEquals(0xff,input.read());
socket.close();
assertTrue(__serverWebSocket.awaitDisconnected(500));
try
{
__serverWebSocket.connection.sendMessage("Don't send");
assertTrue(false);
}
catch(IOException e)
{
assertTrue(true);
}
}
@Test
public void testTCPHalfClose() throws Exception
{
// Log.getLogger("org.eclipse.jetty.websocket").setDebugEnabled(true);
final Socket socket = new Socket("localhost", __connector.getLocalPort());
socket.setSoTimeout(10000);
OutputStream output = socket.getOutputStream();
InputStream input = socket.getInputStream();
output.write(
("GET /test HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Protocol: onConnect\r\n" +
"Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" +
"Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" +
"\r\n"+
"^n:ds[4U").getBytes("ISO-8859-1"));
output.flush();
lookFor("HTTP/1.1 101 WebSocket Protocol Handshake\r\n",input);
skipTo("\r\n\r\n",input);
lookFor("8jKS'y:G*Co,Wxa-",input);
assertTrue(__serverWebSocket.awaitConnected(1000));
assertNotNull(__serverWebSocket.connection);
__serverWebSocket.connection.setMaxIdleTime(250);
assertEquals(0x00,input.read());
lookFor("sent on connect",input);
assertEquals(0xff,input.read());
socket.shutdownOutput();
assertTrue(__serverWebSocket.awaitDisconnected(500));
assertEquals(-1,input.read());
// look for broken pipe
try
{
for (int i=0;i<1000;i++)
output.write(0);
Assert.fail();
}
catch(SocketException e)
{
// expected
}
}
private void lookFor(String string,InputStream in)
throws IOException
{
String orig=string;
Utf8StringBuilder scanned=new Utf8StringBuilder();
try
{
while(true)
{
int b = in.read();
if (b<0)
throw new EOFException();
scanned.append((byte)b);
assertEquals("looking for\""+orig+"\" in '"+scanned+"'",(int)string.charAt(0),b);
if (string.length()==1)
break;
string=string.substring(1);
}
}
catch(IOException e)
{
System.err.println("IOE while looking for \""+orig+"\" in '"+scanned+"'");
throw e;
}
}
private void skipTo(String string,InputStream in)
throws IOException
{
int state=0;
while(true)
{
int b = in.read();
if (b<0)
throw new EOFException();
if (b==string.charAt(state))
{
state++;
if (state==string.length())
break;
}
else
state=0;
}
}
private static class TestWebSocket implements WebSocket.OnFrame, WebSocket, WebSocket.OnTextMessage
{
protected boolean _latch;
boolean _echo=true;
boolean _onConnect=false;
private volatile Connection outbound;
private 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();
}
}
}
}
}

View File

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

View File

@ -0,0 +1,176 @@
/*******************************************************************************
* Copyright (c) 2011 Intalio, Inc.
* ======================================================================
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*******************************************************************************/
package org.eclipse.jetty.websocket;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
public class WebSocketRedeployTest
{
private Server server;
private ServletContextHandler context;
private String uri;
private WebSocketClientFactory wsFactory;
public void init(final WebSocket webSocket) throws Exception
{
server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
// connector.setPort(8080);
server.addConnector(connector);
HandlerCollection handlers = new HandlerCollection();
server.setHandler(handlers);
String contextPath = "/test_context";
context = new ServletContextHandler(handlers, contextPath, ServletContextHandler.SESSIONS);
WebSocketServlet servlet = new WebSocketServlet()
{
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
{
return webSocket;
}
};
String servletPath = "/websocket";
context.addServlet(new ServletHolder(servlet), servletPath);
server.start();
uri = "ws://localhost:" + connector.getLocalPort() + contextPath + servletPath;
wsFactory = new WebSocketClientFactory();
wsFactory.start();
}
@After
public void destroy() throws Exception
{
if (wsFactory != null)
wsFactory.stop();
if (server != null)
{
server.stop();
server.join();
}
}
@Test
public void testStoppingContextClosesConnections() throws Exception
{
final CountDownLatch openLatch = new CountDownLatch(2);
final CountDownLatch closeLatch = new CountDownLatch(2);
init(new WebSocket.OnTextMessage()
{
public void onOpen(Connection connection)
{
openLatch.countDown();
}
public void onMessage(String data)
{
}
public void onClose(int closeCode, String message)
{
closeLatch.countDown();
}
});
WebSocketClient client = wsFactory.newWebSocketClient();
client.open(new URI(uri), new WebSocket.OnTextMessage()
{
public void onOpen(Connection connection)
{
openLatch.countDown();
}
public void onMessage(String data)
{
}
public void onClose(int closeCode, String message)
{
closeLatch.countDown();
}
}, 5, TimeUnit.SECONDS);
Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS));
context.stop();
Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
@Test
public void testStoppingClientFactoryClosesConnections() throws Exception
{
final CountDownLatch openLatch = new CountDownLatch(2);
final CountDownLatch closeLatch = new CountDownLatch(2);
init(new WebSocket.OnTextMessage()
{
public void onOpen(Connection connection)
{
openLatch.countDown();
}
public void onMessage(String data)
{
}
public void onClose(int closeCode, String message)
{
closeLatch.countDown();
}
});
WebSocketClient client = wsFactory.newWebSocketClient();
client.open(new URI(uri), new WebSocket.OnTextMessage()
{
public void onOpen(Connection connection)
{
openLatch.countDown();
}
public void onMessage(String data)
{
}
public void onClose(int closeCode, String message)
{
closeLatch.countDown();
}
}, 5, TimeUnit.SECONDS);
Assert.assertTrue(openLatch.await(5, TimeUnit.SECONDS));
wsFactory.stop();
Assert.assertTrue(closeLatch.await(5, TimeUnit.SECONDS));
}
}

View File

@ -110,20 +110,22 @@ public class XmlConfiguration
__parser = new XmlParser();
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);
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",configURL);
__parser.redirectEntity("http://jetty.eclipse.org/configure.dtd",configURL);
__parser.redirectEntity("http://www.eclipse.org/jetty/configure.dtd",configURL);
__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",configURL);
__parser.redirectEntity("-//Jetty//Configure//EN",configURL);
__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;

View File

@ -19,7 +19,7 @@ my be specified if a match is not achieved.
-->
<!ENTITY % CONFIG "Set|Get|Put|Call|New|Ref|Array|Map|Property">
<!ENTITY % 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.

View File

@ -0,0 +1,265 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
This is the document type descriptor for the
org.eclipse.XmlConfiguration class. It allows a java object to be
configured by with a sequence of Set, Put and Call elements. These tags are
mapped to methods on the object to be configured as follows:
<Set name="Test">value</Set> == obj.setTest("value");
<Put name="Test">value</Put> == obj.put("Test","value");
<Call name="test"><Arg>value</Arg></Call> == obj.test("value");
Values themselves may be configured objects that are created with the
<New> tag or returned from a <Call> tag.
Values are matched to arguments on a best effort approach, but types
my be specified if a match is not achieved.
-->
<!ENTITY % CONFIG "Set|Get|Put|Call|New|Ref|Array|Map|Property">
<!ENTITY % VALUE "#PCDATA|Get|Call|New|Ref|Array|Map|SystemProperty|Property">
<!ENTITY % TYPEATTR "type CDATA #IMPLIED " > <!-- String|Character|Short|Byte|Integer|Long|Boolean|Float|Double|char|short|byte|int|long|boolean|float|double|URL|InetAddress|InetAddrPort| #classname -->
<!ENTITY % IMPLIEDCLASSATTR "class NMTOKEN #IMPLIED" >
<!ENTITY % CLASSATTR "class NMTOKEN #REQUIRED" >
<!ENTITY % NAMEATTR "name NMTOKEN #REQUIRED" >
<!ENTITY % IMPLIEDNAMEATTR "name NMTOKEN #IMPLIED" >
<!ENTITY % DEFAULTATTR "default CDATA #IMPLIED" >
<!ENTITY % IDATTR "id NMTOKEN #IMPLIED" >
<!ENTITY % REQUIREDIDATTR "id NMTOKEN #REQUIRED" >
<!--
Configure Element.
This is the root element that specifies the class of object that
can be configured:
<Configure class="com.acme.MyClass"> ... </Configure>
-->
<!ELEMENT Configure (%CONFIG;)* >
<!ATTLIST Configure %IMPLIEDCLASSATTR; %IDATTR; >
<!--
Set Element.
This element maps to a call to a setter method or field on the current object.
The name and optional type attributes are used to select the setter
method. If the name given is xxx, then a setXxx method is used, or
the xxx field is used of setXxx cannot be found.
A Set element can contain value text and/or the value objects returned
by other elements such as Call, New, SystemProperty, etc.
If no value type is specified, then white
space is trimmed out of the value. If it contains multiple value
elements they are added as strings before being converted to any
specified type.
A Set with a class attribute is treated as a static set method invocation.
-->
<!ELEMENT Set ( %VALUE; )* >
<!ATTLIST Set %NAMEATTR; %TYPEATTR; %IMPLIEDCLASSATTR; >
<!--
Get Element.
This element maps to a call to a getter method or field on the current object.
The name attribute is used to select the get method.
If the name given is xxx, then a getXxx method is used, or
the xxx field is used if getXxx cannot be found.
A Get element can contain other elements such as Set, Put, Call, etc.
which act on the object returned by the get call.
A Get with a class attribute is treated as a static get method or field.
-->
<!ELEMENT Get (%CONFIG;)*>
<!ATTLIST Get %NAMEATTR; %IMPLIEDCLASSATTR; %IDATTR; >
<!--
Put Element.
This element maps to a call to a put method on the current object,
which must implement the Map interface. The name attribute is used
as the put key and the optional type attribute can force the type
of the value.
A Put element can contain value text and/or value elements such as Call,
New, SystemProperty, etc. If no value type is specified, then white
space is trimmed out of the value. If it contains multiple value
elements they are added as strings before being converted to any
specified type.
-->
<!ELEMENT Put ( %VALUE; )* >
<!ATTLIST Put %NAMEATTR; %TYPEATTR; >
<!--
Call Element.
This element maps to an arbitrary call to a method on the current object,
The name attribute and Arg elements are used to select the method.
A Call element can contain a sequence of Arg elements followed by
a sequence of other elements such as Set, Put, Call, etc. which act on any object
returned by the original call:
<Call id="o2" name="test">
<Arg>value1</Arg>
<Set name="Test">Value2</Set>
</Call>
This is equivalent to:
Object o2 = o1.test("value1");
o2.setTest("value2");
A Call with a class attribute is treated as a static call.
-->
<!ELEMENT Call (Arg*,(%CONFIG;)*)>
<!ATTLIST Call %NAMEATTR; %IMPLIEDCLASSATTR; %IDATTR;>
<!--
Arg Element.
This element defines a positional argument for the Call element.
The optional type attribute can force the type of the value.
An Arg element can contain value text and/or value elements such as Call,
New, SystemProperty, etc. If no value type is specified, then white
space is trimmed out of the value. If it contains multiple value
elements they are added as strings before being converted to any
specified type.
-->
<!ELEMENT Arg ( %VALUE; )* >
<!ATTLIST Arg %TYPEATTR; %IMPLIEDNAMEATTR; >
<!--
New Element.
This element allows the creation of a new object as part of a
value for elements such as Set, Put, Arg, etc. The class attribute determines
the type of the new object and the contained Arg elements
are used to select the constructor for the new object.
A New element can contain a sequence of Arg elements followed by
a sequence of elements such as Set, Put, Call, etc. elements
which act on the new object:
<New id="o" class="com.acme.MyClass">
<Arg>value1</Arg>
<Set name="test">Value2</Set>
</New>
This is equivalent to:
Object o = new com.acme.MyClass("value1");
o.setTest("Value2");
-->
<!ELEMENT New (Arg*,(%CONFIG;)*)>
<!ATTLIST New %CLASSATTR; %IDATTR;>
<!--
Ref Element.
This element allows a previously created object to be referenced by id.
A Ref element can contain a sequence of elements such as Set, Put, Call, etc.
which act on the referenced object:
<Ref id="myobject">
<Set name="Test">Value2</Set>
</New>
-->
<!ELEMENT Ref ((%CONFIG;)*)>
<!ATTLIST Ref %REQUIREDIDATTR;>
<!--
Array Element.
This element allows the creation of a new array as part of a
value of elements such as Set, Put, Arg, etc. The type attribute determines
the type of the new array and the contained Item elements
are used for each element of the array:
<Array type="java.lang.String">
<Item>value0</Item>
<Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
</Array>
This is equivalent to:
String[] a = new String[] { "value0", new String("value1") };
-->
<!ELEMENT Array (Item*)>
<!ATTLIST Array %TYPEATTR; %IDATTR; >
<!--
Map Element.
This element allows the creation of a new map as part of a
value of elements such as Set, Put, Arg, etc. The type attribute determines
the type of the new array and the contained Item elements
are used for each element of the array:
<Map>
<Entry>
<Item>keyName</Item>
<Item><New class="java.lang.String"><Arg>value1</Arg></New></Item>
</Entry>
</Map>
This is equivalent to:
Map m = new HashMap();
m.put("keyName", new String("value1"));
-->
<!ELEMENT Map (Entry*)>
<!ATTLIST Map %IDATTR; >
<!ELEMENT Entry (Item,Item)>
<!--
Item Element.
This element defines an entry for the Array or Map Entry elements.
The optional type attribute can force the type of the value.
An Item element can contain value text and/or the value object of
elements such as Call, New, SystemProperty, etc. If no value type
is specified, then white space is trimmed out of the value.
If it contains multiple value elements they are added as strings
before being converted to any specified type.
-->
<!ELEMENT Item ( %VALUE; )* >
<!ATTLIST Item %TYPEATTR; %IDATTR; >
<!--
System Property Element.
This element allows JVM System properties to be retrieved as
part of the value of elements such as Set, Put, Arg, etc.
The name attribute specifies the property name and the optional
default argument provides a default value.
<SystemProperty name="Test" default="value" />
This is equivalent to:
System.getProperty("Test","value");
-->
<!ELEMENT SystemProperty EMPTY>
<!ATTLIST SystemProperty %NAMEATTR; %DEFAULTATTR; %IDATTR;>
<!--
Property Element.
This element allows arbitrary properties to be retrieved by name.
The name attribute specifies the property name and the optional
default argument provides a default value.
A Property element can contain a sequence of elements such as Set, Put, Call, etc.
which act on the retrieved object:
<Property name="Server">
<Call id="jdbcIdMgr" name="getAttribute">
<Arg>jdbcIdMgr</Arg>
</Call>
</Property>
-->
<!ELEMENT Property ((%CONFIG;)*)>
<!ATTLIST Property %NAMEATTR; %DEFAULTATTR; %IDATTR;>

View File

@ -63,6 +63,8 @@ public class XmlConfigurationTest
assertEquals( "ObjectsWhiteString", "-1\n String",tc.get("ObjectsWhiteString"));
assertEquals( "SystemProperty", System.getProperty("user.dir")+"/stuff",tc.get("SystemProperty"));
assertEquals( "Env", System.getenv("HOME"),tc.get("Env"));
assertEquals( "Property", "xxx", tc.get("Property"));

View File

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

View File

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