diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java index bf23762a14f..99b22b03089 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/SslBytesServerTest.java @@ -56,7 +56,7 @@ public class SslBytesServerTest extends SslBytesTest private final AtomicInteger sslFlushes = new AtomicInteger(); private final AtomicInteger httpParses = new AtomicInteger(); private final AtomicReference serverEndPoint = new AtomicReference(); - private final int idleTimeout = 5000; + private final int idleTimeout = 2000; private ExecutorService threadPool; private Server server; private SSLContext sslContext; @@ -1277,7 +1277,13 @@ public class SslBytesServerTest extends SslBytesTest } 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)); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java index d859d126e10..d9af5953ca4 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/nio/SslConnection.java @@ -249,7 +249,10 @@ public class SslConnection extends AbstractConnection implements AsyncConnection try { LOG.debug("onIdleExpired {}ms on {}",idleForMs,this); - _sslEndPoint.shutdownOutput(); + if (_endp.isOutputShutdown()) + _sslEndPoint.close(); + else + _sslEndPoint.shutdownOutput(); } catch (IOException e) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 8af31771943..5bab60df0e3 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -30,7 +30,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; - import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; import javax.servlet.ServletContext; @@ -70,13 +69,13 @@ import org.eclipse.jetty.util.resource.Resource; /* ------------------------------------------------------------ */ /** * ContextHandler. - * + * * This handler wraps a call to handle by setting the context and servlet path, plus setting the context classloader. - * + * *

* 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() @@ -244,7 +243,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 +264,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 +286,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. { currentVirtualHosts = new ArrayList(); } - + for (int i = 0; i < virtualHosts.length; i++) { String normVhost = normalizeHostname(virtualHosts[i]); @@ -303,7 +302,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 +320,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. else { List existingVirtualHosts = new ArrayList(Arrays.asList(_vhosts)); - + for (int i = 0; i < virtualHosts.length; i++) { String toRemoveVirtualHost = normalizeHostname(virtualHosts[i]); @@ -330,7 +329,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 +340,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 +370,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 +424,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 +520,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ /** * Set the context event listeners. - * + * * @param eventListeners * the event listeners * @see ServletContextListener @@ -559,7 +558,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. /* ------------------------------------------------------------ */ /** * Add a context event listeners. - * + * * @see ServletContextListener * @see ServletContextAttributeListener * @see ServletRequestListener @@ -586,7 +585,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 +693,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 +1115,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) @@ -1381,14 +1380,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 +1433,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 Locale value * @return a String representing the character encoding for the locale or null if none found. @@ -1495,7 +1497,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 +1559,8 @@ public class ContextHandler extends ScopedHandler implements Attributes, Server. *

* A partial implementation of {@link javax.servlet.ServletContext}. A complete implementation is provided by the derived {@link ContextHandler}. *

- * - * + * + * */ public class Context implements ServletContext { diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java index 7b2e2b5650e..0ffb90f5269 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorTimeoutTest.java @@ -13,44 +13,45 @@ 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.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.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 +64,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture long start = System.currentTimeMillis(); IO.toString(is); - + Thread.sleep(300); assertEquals(-1, is.read()); @@ -73,13 +74,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 +97,119 @@ 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 + { + configureServer(new HelloWorldHandler()); + 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(); + + String result=IO.toString(is); + Assert.assertThat("OK",result,containsString("200 OK")); + assertEquals(-1, is.read()); + + TimeUnit.MILLISECONDS.sleep(MAX_IDLE_TIME); + + // further writes will get broken pipe or similar + try + { + for (int i=0;i<100;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 + } + } + + @Test + public void testMaxIdleWithRequest11NoClientClose() 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(); + + 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(); + + IO.toString(is); + + assertEquals(-1, is.read()); + + TimeUnit.MILLISECONDS.sleep(MAX_IDLE_TIME); + + // further writes will get broken pipe or similar + try + { + for (int i=0;i<100;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 + } + } + @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 +219,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 +259,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 +271,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 +288,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 +300,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 +317,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 +330,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 +340,7 @@ public abstract class ConnectorTimeoutTest extends HttpServerTestFixture out.close(); } } - + protected static class WaitHandler extends AbstractHandler { public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java index cff7de04304..cc5db438838 100644 --- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java +++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClient.java @@ -332,6 +332,25 @@ public class WebSocketClient { if (!_factory.isStarted()) throw new IllegalStateException("Factory !started"); + + InetSocketAddress address = toSocketAddress(uri); + + SocketChannel channel = SocketChannel.open(); + if (_bindAddress != null) + channel.socket().bind(_bindAddress); + channel.socket().setTcpNoDelay(true); + + WebSocketFuture holder = new WebSocketFuture(websocket, uri, this, channel); + + channel.configureBlocking(false); + channel.connect(address); + _factory.getSelectorManager().register(channel, holder); + + return holder; + } + + public static final InetSocketAddress toSocketAddress(URI uri) + { String scheme = uri.getScheme(); if (!("ws".equalsIgnoreCase(scheme) || "wss".equalsIgnoreCase(scheme))) throw new IllegalArgumentException("Bad WebSocket scheme: " + scheme); @@ -341,20 +360,8 @@ public class WebSocketClient if (port < 0) port = "ws".equals(scheme) ? 80 : 443; - SocketChannel channel = SocketChannel.open(); - if (_bindAddress != null) - channel.socket().bind(_bindAddress); - channel.socket().setTcpNoDelay(true); - InetSocketAddress address = new InetSocketAddress(uri.getHost(), port); - - WebSocketFuture holder = new WebSocketFuture(websocket, uri, this, channel); - - channel.configureBlocking(false); - channel.connect(address); - _factory.getSelectorManager().register(channel, holder); - - return holder; + return address; } /* ------------------------------------------------------------ */ @@ -486,6 +493,7 @@ public class WebSocketClient return _maskGen; } + @Override public String toString() { return "[" + _uri + ","+_websocket+"]@"+hashCode(); diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java index 77639bf0c65..571a454e0da 100644 --- a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java +++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.ConnectException; +import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.URI; @@ -44,6 +45,7 @@ import org.junit.Before; import org.junit.Test; import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; public class WebSocketClientTest { @@ -711,44 +713,19 @@ public class WebSocketClientTest @Test public void testURIWithDefaultPort() throws Exception { - WebSocketClient client = new WebSocketClient(_factory); + URI uri = new URI("ws://localhost"); + InetSocketAddress addr = WebSocketClient.toSocketAddress(uri); + Assert.assertThat("URI (" + uri + ").host", addr.getHostName(), is("localhost")); + Assert.assertThat("URI (" + uri + ").port", addr.getPort(), is(80)); + } - try - { - client.open(new URI("ws://localhost"), new WebSocket() - { - public void onOpen(Connection connection) - { - } - - public void onClose(int closeCode, String message) - { - System.out.println("closeCode = " + closeCode); - } - }).get(5, TimeUnit.SECONDS); - } - catch (ExecutionException x) - { - Assert.assertTrue(x.getCause() instanceof ConnectException); - } - - try - { - client.open(new URI("wss://localhost"), new WebSocket() - { - public void onOpen(Connection connection) - { - } - - public void onClose(int closeCode, String message) - { - } - }).get(5, TimeUnit.SECONDS); - } - catch (ExecutionException x) - { - Assert.assertTrue(x.getCause() instanceof ConnectException); - } + @Test + public void testURIWithDefaultWSSPort() throws Exception + { + URI uri = new URI("wss://localhost"); + InetSocketAddress addr = WebSocketClient.toSocketAddress(uri); + Assert.assertThat("URI (" + uri + ").host", addr.getHostName(), is("localhost")); + Assert.assertThat("URI (" + uri + ").port", addr.getPort(), is(443)); } private void respondToClient(Socket connection, String serverResponse) throws IOException