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

This commit is contained in:
Jan Bartel 2012-01-18 14:17:41 +11:00
commit 98684397fd
10 changed files with 265 additions and 86 deletions

View File

@ -10,6 +10,7 @@ import java.net.SocketTimeoutException;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
@ -47,6 +48,7 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import static org.hamcrest.Matchers.greaterThan;
@ -61,8 +63,10 @@ public class SslBytesServerTest extends SslBytesTest
private final int idleTimeout = 2000;
private ExecutorService threadPool;
private Server server;
private int serverPort;
private SSLContext sslContext;
private SimpleProxy proxy;
private Runnable idleHook;
@Before
public void init() throws Exception
@ -98,6 +102,15 @@ public class SslBytesServerTest extends SslBytesTest
}
};
}
@Override
public void onIdleExpired(long idleForMs)
{
final Runnable idleHook = SslBytesServerTest.this.idleHook;
if (idleHook != null)
idleHook.run();
super.onIdleExpired(idleForMs);
}
};
}
@ -166,7 +179,7 @@ public class SslBytesServerTest extends SslBytesTest
}
});
server.start();
int serverPort = connector.getLocalPort();
serverPort = connector.getLocalPort();
sslContext = cf.getSslContext();
@ -606,10 +619,10 @@ public class SslBytesServerTest extends SslBytesTest
}
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
TimeUnit.MILLISECONDS.sleep(1000);
Assert.assertThat(sslHandles.get(), lessThan(750));
Assert.assertThat(sslFlushes.get(), lessThan(750));
Assert.assertThat(httpParses.get(), lessThan(150));
Assert.assertThat(httpParses.get(), lessThan(1000));
client.close();
@ -1512,6 +1525,162 @@ public class SslBytesServerTest extends SslBytesTest
Assert.assertFalse(serverEndPoint.get().isOpen());
}
@Test
public void testPlainText() throws Exception
{
final SSLSocket client = newClient();
threadPool.submit(new Callable<Object>()
{
public Object call() throws Exception
{
client.startHandshake();
return null;
}
});
// Instead of passing the Client Hello, we simulate plain text was passed in
proxy.flushToServer(0, "GET / HTTP/1.1\r\n".getBytes("UTF-8"));
// We expect that the server closes the connection immediately
TLSRecord record = proxy.readFromServer();
Assert.assertNull(String.valueOf(record), record);
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
Assert.assertThat(sslHandles.get(), lessThan(20));
Assert.assertThat(sslFlushes.get(), lessThan(20));
Assert.assertThat(httpParses.get(), lessThan(50));
client.close();
}
@Test
public void testRequestConcurrentWithIdleExpiration() throws Exception
{
final SSLSocket client = newClient();
final OutputStream clientOutput = client.getOutputStream();
final CountDownLatch latch = new CountDownLatch(1);
idleHook = new Runnable()
{
public void run()
{
try
{
// Send request
clientOutput.write(("" +
"GET / HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"\r\n").getBytes("UTF-8"));
clientOutput.flush();
latch.countDown();
}
catch (Exception x)
{
// Latch won't trigger and test will
// fail, just print the stack trace
x.printStackTrace();
}
}
};
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
client.startHandshake();
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
Assert.assertTrue(latch.await(idleTimeout * 2, TimeUnit.MILLISECONDS));
// Be sure that the server sent a SSL close alert
TLSRecord record = proxy.readFromServer();
Assert.assertNotNull(record);
Assert.assertEquals(TLSRecord.Type.ALERT, record.getType());
// Write the request to the server, to simulate a request
// concurrent with the SSL close alert
record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
proxy.flushToServer(record, 0);
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
Assert.assertThat(sslHandles.get(), lessThan(20));
Assert.assertThat(sslFlushes.get(), lessThan(20));
Assert.assertThat(httpParses.get(), lessThan(50));
}
/*
@Test
public void testRequestWriteBlockedWithPipelinedRequest() throws Exception
{
final SSLSocket client = newClient();
final OutputStream clientOutput = client.getOutputStream();
SimpleProxy.AutomaticFlow automaticProxyFlow = proxy.startAutomaticFlow();
client.startHandshake();
Assert.assertTrue(automaticProxyFlow.stop(5, TimeUnit.SECONDS));
byte[] data = new byte[128 * 1024];
Arrays.fill(data, (byte)'X');
final String content = new String(data, "UTF-8");
Future<Object> request = threadPool.submit(new Callable<Object>()
{
public Object call() throws Exception
{
clientOutput.write(("" +
"POST /echo HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"Content-Length: " + content.length() + "\r\n" +
"\r\n" +
content).getBytes("UTF-8"));
clientOutput.flush();
return null;
}
});
// Nine TLSRecords will be generated for the request
for (int i = 0; i < 9; ++i)
{
// Application data
TLSRecord record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
proxy.flushToServer(record, 0);
}
Assert.assertNull(request.get(5, TimeUnit.SECONDS));
// We do not read the big request to cause a write blocked on the server
TimeUnit.MILLISECONDS.sleep(500);
// Now send the pipelined request
Future<Object> pipelined = threadPool.submit(new Callable<Object>()
{
public Object call() throws Exception
{
clientOutput.write(("" +
"GET /pipelined HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"\r\n").getBytes("UTF-8"));
clientOutput.flush();
return null;
}
});
TLSRecord record = proxy.readFromClient();
Assert.assertEquals(TLSRecord.Type.APPLICATION, record.getType());
proxy.flushToServer(record, 0);
Assert.assertNull(pipelined.get(5, TimeUnit.SECONDS));
// Check that we did not spin
TimeUnit.MILLISECONDS.sleep(500);
Assert.assertThat(sslHandles.get(), lessThan(20));
Assert.assertThat(sslFlushes.get(), lessThan(20));
Assert.assertThat(httpParses.get(), lessThan(50));
Thread.sleep(5000);
// closeClient(client);
}
*/
private void assumeJavaVersionSupportsTLSRenegotiations()
{
// Due to a security bug, TLS renegotiations were disabled in JDK 1.6.0_19-21

View File

@ -194,7 +194,7 @@ public class HttpParser implements Parser
public void setPersistent(boolean persistent)
{
_persistent = persistent;
if (_state==STATE_END)
if (!_persistent &&(_state==STATE_END || _state==STATE_START))
_state=STATE_SEEKING_EOF;
}

View File

@ -24,6 +24,7 @@ import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
@ -303,7 +304,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
}
});
if (!selecting)
throw new IllegalStateException("!Selecting");
}
@ -960,7 +961,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
{
LOG.ignore(e);
}
AggregateLifeCycle.dump(out,indent,dump);
}
}
@ -969,8 +970,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
public void dumpKeyState(List<Object> dumpto)
{
Selector selector=_selector;
dumpto.add(selector+" keys="+selector.keys().size());
for (SelectionKey key: selector.keys())
Set<SelectionKey> keys = selector.keys();
dumpto.add(selector + " keys=" + keys.size());
for (SelectionKey key: keys)
{
if (key.isValid())
dumpto.add(key.attachment()+" iOps="+key.interestOps()+" rOps="+key.readyOps());

View File

@ -182,22 +182,17 @@ public class SslConnection extends AbstractConnection implements AsyncConnection
while (progress)
{
progress=false;
// If we are handshook let the delegate connection
if (_engine.getHandshakeStatus()!=HandshakeStatus.NOT_HANDSHAKING)
{
progress=process(null,null);
}
else
// handle the delegate connection
AsyncConnection next = (AsyncConnection)_connection.handle();
if (next!=_connection && next!=null)
{
// handle the delegate connection
AsyncConnection next = (AsyncConnection)_connection.handle();
if (next!=_connection && next!=null)
{
_connection=next;
progress=true;
}
// TODO: consider moving here hasProgressed() - it's only used in SSL
_connection=next;
progress=true;
}
LOG.debug("{} handle {} progress={}", _session, this, progress);
@ -389,6 +384,11 @@ public class SslConnection extends AbstractConnection implements AsyncConnection
// The SSL needs to receive some handshake data from the other side
if (_handshook && !_allowRenegotiate)
_endp.close();
else if (!_inbound.hasContent()&&filled==-1)
{
// No more input coming
_endp.shutdownInput();
}
else if (unwrap(toFill))
progress=true;
}

View File

@ -103,14 +103,14 @@ public abstract class AbstractHttpConnection extends AbstractConnection
protected final Parser _parser;
protected final HttpFields _requestFields;
protected final Request _request;
protected ServletInputStream _in;
protected volatile ServletInputStream _in;
protected final Generator _generator;
protected final HttpFields _responseFields;
protected final Response _response;
protected Output _out;
protected OutputWriter _writer;
protected PrintWriter _printWriter;
protected volatile Output _out;
protected volatile OutputWriter _writer;
protected volatile PrintWriter _printWriter;
int _include;
@ -123,7 +123,7 @@ public abstract class AbstractHttpConnection extends AbstractConnection
private boolean _expect102Processing = false;
private boolean _head = false;
private boolean _host = false;
private boolean _delayedHandling=false;
private boolean _delayedHandling=false;
/* ------------------------------------------------------------ */
public static AbstractHttpConnection getCurrentConnection()

View File

@ -177,6 +177,10 @@ public class AsyncHttpConnection extends AbstractHttpConnection implements Async
// then no more can happen, so close.
_endp.close();
}
// Make idle parser seek EOF
if (_parser.isIdle())
_parser.setPersistent(false);
}
}

View File

@ -114,7 +114,7 @@ public class Dispatcher implements RequestDispatcher
public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException
{
Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest();
request.removeAttribute(__JSP_FILE); // TODO remove when glassfish 1044 is fixed
if (!(request instanceof HttpServletRequest))
request = new ServletRequestHttpWrapper(request);
@ -198,7 +198,7 @@ public class Dispatcher implements RequestDispatcher
Response base_response=baseRequest.getResponse();
response.resetBuffer();
base_response.fwdReset();
request.removeAttribute(__JSP_FILE); // TODO remove when glassfish 1044 is fixed
if (!(request instanceof HttpServletRequest))
request = new ServletRequestHttpWrapper(request);

View File

@ -82,7 +82,7 @@ public class Response implements HttpServletResponse
private String _characterEncoding;
private boolean _explicitEncoding;
private String _contentType;
private int _outputState;
private volatile int _outputState;
private PrintWriter _writer;
/* ------------------------------------------------------------ */
@ -109,8 +109,8 @@ public class Response implements HttpServletResponse
_characterEncoding=null;
_explicitEncoding=false;
_contentType=null;
_outputState=NONE;
_writer=null;
_outputState=NONE;
}
/* ------------------------------------------------------------ */
@ -656,8 +656,9 @@ public class Response implements HttpServletResponse
if (_outputState!=NONE && _outputState!=STREAM)
throw new IllegalStateException("WRITER");
ServletOutputStream out = _connection.getOutputStream();
_outputState=STREAM;
return _connection.getOutputStream();
return out;
}
/* ------------------------------------------------------------ */
@ -1075,8 +1076,8 @@ public class Response implements HttpServletResponse
{
resetBuffer();
_outputState=NONE;
_writer=null;
_outputState=NONE;
}
/* ------------------------------------------------------------ */

View File

@ -354,7 +354,7 @@ public class WebSocketClientFactory extends AggregateLifeCycle
private final HttpParser _parser;
private String _accept;
private String _error;
private boolean _handshaken;
private ByteArrayBuffer _handshake;
public HandshakeConnection(AsyncEndPoint endpoint, WebSocketClient.WebSocketFuture future)
{
@ -404,72 +404,75 @@ public class WebSocketClientFactory extends AggregateLifeCycle
});
}
private void handshake()
private boolean handshake()
{
String path = _future.getURI().getPath();
if (path == null || path.length() == 0)
path = "/";
if (_future.getURI().getRawQuery() != null)
path += "?" + _future.getURI().getRawQuery();
String origin = _future.getOrigin();
StringBuilder request = new StringBuilder(512);
request.append("GET ").append(path).append(" HTTP/1.1\r\n")
.append("Host: ").append(_future.getURI().getHost()).append(":")
.append(_future.getURI().getPort()).append("\r\n")
.append("Upgrade: websocket\r\n")
.append("Connection: Upgrade\r\n")
.append("Sec-WebSocket-Key: ")
.append(_key).append("\r\n");
if (origin != null)
request.append("Origin: ").append(origin).append("\r\n");
request.append("Sec-WebSocket-Version: ").append(WebSocketConnectionRFC6455.VERSION).append("\r\n");
if (_future.getProtocol() != null)
request.append("Sec-WebSocket-Protocol: ").append(_future.getProtocol()).append("\r\n");
Map<String, String> cookies = _future.getCookies();
if (cookies != null && cookies.size() > 0)
if (_handshake==null)
{
for (String cookie : cookies.keySet())
request.append("Cookie: ")
.append(QuotedStringTokenizer.quoteIfNeeded(cookie, HttpFields.__COOKIE_DELIM))
.append("=")
.append(QuotedStringTokenizer.quoteIfNeeded(cookies.get(cookie), HttpFields.__COOKIE_DELIM))
.append("\r\n");
String path = _future.getURI().getPath();
if (path == null || path.length() == 0)
path = "/";
if (_future.getURI().getRawQuery() != null)
path += "?" + _future.getURI().getRawQuery();
String origin = _future.getOrigin();
StringBuilder request = new StringBuilder(512);
request.append("GET ").append(path).append(" HTTP/1.1\r\n")
.append("Host: ").append(_future.getURI().getHost()).append(":")
.append(_future.getURI().getPort()).append("\r\n")
.append("Upgrade: websocket\r\n")
.append("Connection: Upgrade\r\n")
.append("Sec-WebSocket-Key: ")
.append(_key).append("\r\n");
if (origin != null)
request.append("Origin: ").append(origin).append("\r\n");
request.append("Sec-WebSocket-Version: ").append(WebSocketConnectionRFC6455.VERSION).append("\r\n");
if (_future.getProtocol() != null)
request.append("Sec-WebSocket-Protocol: ").append(_future.getProtocol()).append("\r\n");
Map<String, String> cookies = _future.getCookies();
if (cookies != null && cookies.size() > 0)
{
for (String cookie : cookies.keySet())
request.append("Cookie: ")
.append(QuotedStringTokenizer.quoteIfNeeded(cookie, HttpFields.__COOKIE_DELIM))
.append("=")
.append(QuotedStringTokenizer.quoteIfNeeded(cookies.get(cookie), HttpFields.__COOKIE_DELIM))
.append("\r\n");
}
request.append("\r\n");
_handshake=new ByteArrayBuffer(request.toString(), false);
}
request.append("\r\n");
// TODO extensions
try
{
Buffer handshake = new ByteArrayBuffer(request.toString(), false);
int len = handshake.length();
if (len != _endp.flush(handshake))
throw new IOException("incomplete");
int len = _handshake.length();
int flushed = _endp.flush(_handshake);
if (flushed<0)
throw new IOException("incomplete handshake");
}
catch (IOException e)
{
_future.handshakeFailed(e);
}
finally
{
_handshaken = true;
}
return _handshake.length()==0;
}
public Connection handle() throws IOException
{
while (_endp.isOpen() && !_parser.isComplete())
{
if (!_handshaken)
handshake();
if (_handshake==null || _handshake.length()>0)
if (!handshake())
return this;
if (!_parser.parseAvailable())
{

View File

@ -15,6 +15,9 @@
*******************************************************************************/
package org.eclipse.jetty.websocket;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
@ -28,9 +31,9 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.Deflater;
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;
@ -41,13 +44,10 @@ import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.Utf8StringBuilder;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class WebSocketMessageRFC6455Test
{
private static Server __server;
@ -606,7 +606,7 @@ public class WebSocketMessageRFC6455Test
Thread.sleep(100);
assertEquals(count*(mesg.length()+2),totalB.get()); // all messages
assertTrue(max>1000); // was blocked
Assert.assertThat("Was blocked (max time)", max, greaterThan(1000L)); // was blocked
}
@Test