Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoakim Erdfelt2012-10-29 18:18:01 +0000
committerJoakim Erdfelt2012-10-29 18:18:01 +0000
commit6108d0df0c598227c960bc3fffa7efb531eff8d3 (patch)
tree211b11bc2fbf027df648406db8b27b8c641c29cf /jetty-websocket/src
parent8b90057b68baea9c0bd11178c6122215b0e3a5c7 (diff)
downloadorg.eclipse.jetty.project-6108d0df0c598227c960bc3fffa7efb531eff8d3.tar.gz
org.eclipse.jetty.project-6108d0df0c598227c960bc3fffa7efb531eff8d3.tar.xz
org.eclipse.jetty.project-6108d0df0c598227c960bc3fffa7efb531eff8d3.zip
393075 Jetty WebSocket client cannot connect to Tomcat WebSocket Server
* Adding testcase to repliate behavior as reported by bug.
Diffstat (limited to 'jetty-websocket/src')
-rw-r--r--jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java6
-rw-r--r--jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TomcatServerQuirksTest.java122
-rw-r--r--jetty-websocket/src/test/java/org/eclipse/jetty/websocket/WebSocketClientTest.java6
-rw-r--r--jetty-websocket/src/test/java/org/eclipse/jetty/websocket/dummy/DummyServer.java308
-rw-r--r--jetty-websocket/src/test/resources/jetty-logging.properties4
5 files changed, 441 insertions, 5 deletions
diff --git a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java
index d624428b72..b365ee6dfd 100644
--- a/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java
+++ b/jetty-websocket/src/main/java/org/eclipse/jetty/websocket/WebSocketClientFactory.java
@@ -28,6 +28,7 @@ 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;
@@ -389,7 +390,7 @@ public class WebSocketClientFactory extends AggregateLifeCycle
_accept = value.toString();
}
- @Override
+ @Override // TODO simone says shouldn't be needed
public void startRequest(Buffer method, Buffer url, Buffer version) throws IOException
{
if (_error == null)
@@ -397,7 +398,7 @@ public class WebSocketClientFactory extends AggregateLifeCycle
_endp.close();
}
- @Override
+ @Override // TODO simone says shouldn't be needed
public void content(Buffer ref) throws IOException
{
if (_error == null)
@@ -515,6 +516,7 @@ public class WebSocketClientFactory extends AggregateLifeCycle
private WebSocketConnection newWebSocketConnection() throws IOException
{
+ __log.debug("newWebSocketConnection()");
return new WebSocketClientConnection(
_future._client.getFactory(),
_future.getWebSocket(),
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TomcatServerQuirksTest.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TomcatServerQuirksTest.java
new file mode 100644
index 0000000000..754a4199db
--- /dev/null
+++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/TomcatServerQuirksTest.java
@@ -0,0 +1,122 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.websocket;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
+import org.eclipse.jetty.websocket.dummy.DummyServer;
+import org.eclipse.jetty.websocket.dummy.DummyServer.ServerConnection;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class TomcatServerQuirksTest
+{
+ /**
+ * Test for when encountering a "Transfer-Encoding: chunked" on a Upgrade Response header.
+ * <ul>
+ * <li><a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=393075">Eclipse Jetty Bug #393075</a></li>
+ * <li><a href="https://issues.apache.org/bugzilla/show_bug.cgi?id=54067">Apache Tomcat Bug #54067</a></li>
+ * </ul>
+ * @throws IOException
+ */
+ @Test
+ @Ignore("Bug with Transfer-Encoding")
+ public void testTomcat7_0_32_WithTransferEncoding() throws Exception {
+ DummyServer server = new DummyServer();
+ int bufferSize = 512;
+ QueuedThreadPool threadPool = new QueuedThreadPool();
+ WebSocketClientFactory factory = new WebSocketClientFactory(threadPool, new ZeroMaskGen(), bufferSize);
+
+ try {
+ server.start();
+
+ // Setup Client Factory
+ threadPool.start();
+ factory.start();
+
+ // Create Client
+ WebSocketClient client = new WebSocketClient(factory);
+
+ // Create End User WebSocket Class
+ final CountDownLatch openLatch = new CountDownLatch(1);
+ final CountDownLatch dataLatch = new CountDownLatch(1);
+ WebSocket.OnTextMessage websocket = new WebSocket.OnTextMessage()
+ {
+ public void onOpen(Connection connection)
+ {
+ openLatch.countDown();
+ }
+
+ public void onMessage(String data)
+ {
+ // System.out.println("data = " + data);
+ dataLatch.countDown();
+ }
+
+ public void onClose(int closeCode, String message)
+ {
+ }
+ };
+
+ // Open connection
+ URI wsURI = server.getWsUri();
+ client.open(wsURI, websocket);
+
+ // Accept incoming connection
+ ServerConnection socket = server.accept();
+ socket.setSoTimeout(2000); // timeout
+
+ // Issue upgrade
+ Map<String,String> extraResponseHeaders = new HashMap<String, String>();
+ extraResponseHeaders.put("Transfer-Encoding", "chunked"); // !! The problem !!
+ socket.upgrade(extraResponseHeaders);
+
+ // Wait for proper upgrade
+ Assert.assertTrue("Timed out waiting for Client side WebSocket open event", openLatch.await(1, TimeUnit.SECONDS));
+
+ // Have server write frame.
+ int length = bufferSize / 2;
+ ByteBuffer serverFrame = ByteBuffer.allocate(bufferSize);
+ serverFrame.put((byte)(0x80 | 0x01)); // FIN + TEXT
+ serverFrame.put((byte)0x7E); // No MASK and 2 bytes length
+ serverFrame.put((byte)(length >> 8)); // first length byte
+ serverFrame.put((byte)(length & 0xFF)); // second length byte
+ for (int i = 0; i < length; ++i)
+ serverFrame.put((byte)'x');
+ serverFrame.flip();
+ byte buf[] = serverFrame.array();
+ socket.write(buf,0,buf.length);
+ socket.flush();
+
+ Assert.assertTrue(dataLatch.await(1000, TimeUnit.SECONDS));
+ } finally {
+ factory.stop();
+ threadPool.stop();
+ server.stop();
+ }
+ }
+}
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 f70c43ca36..8cd1b467c1 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
@@ -18,6 +18,8 @@
package org.eclipse.jetty.websocket;
+import static org.hamcrest.Matchers.*;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -47,9 +49,6 @@ 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();
@@ -103,6 +102,7 @@ public class WebSocketClientTest
{
}
};
+
client.open(new URI("ws://127.0.0.1:" + _serverPort + "/"), websocket);
Socket socket = _server.accept();
diff --git a/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/dummy/DummyServer.java b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/dummy/DummyServer.java
new file mode 100644
index 0000000000..9b41e51e2f
--- /dev/null
+++ b/jetty-websocket/src/test/java/org/eclipse/jetty/websocket/dummy/DummyServer.java
@@ -0,0 +1,308 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2012 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.websocket.dummy;
+
+import static org.hamcrest.Matchers.*;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.IO;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.websocket.WebSocketConnectionRFC6455;
+import org.junit.Assert;
+
+/**
+ * Simple ServerSocket server used to test oddball server scenarios encountered in the real world.
+ */
+public class DummyServer
+{
+ public static class ServerConnection
+ {
+ private static final Logger LOG = Log.getLogger(ServerConnection.class);
+ private final Socket socket;
+ private InputStream in;
+ private OutputStream out;
+
+ public ServerConnection(Socket socket)
+ {
+ this.socket = socket;
+ }
+
+ public int read(ByteBuffer buf) throws IOException
+ {
+ int len = 0;
+ while ((in.available() > 0) && (buf.remaining() > 0))
+ {
+ buf.put((byte)in.read());
+ len++;
+ }
+ return len;
+ }
+
+ public void disconnect()
+ {
+ LOG.debug("disconnect");
+ IO.close(in);
+ IO.close(out);
+ if (socket != null)
+ {
+ try
+ {
+ socket.close();
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ }
+ }
+
+ public InputStream getInputStream() throws IOException
+ {
+ if (in == null)
+ {
+ in = socket.getInputStream();
+ }
+ return in;
+ }
+
+ public OutputStream getOutputStream() throws IOException
+ {
+ if (out == null)
+ {
+ out = socket.getOutputStream();
+ }
+ return out;
+ }
+
+ public void flush() throws IOException
+ {
+ LOG.debug("flush()");
+ getOutputStream().flush();
+ }
+
+ public String readRequest() throws IOException
+ {
+ LOG.debug("Reading client request");
+ StringBuilder request = new StringBuilder();
+ BufferedReader in = new BufferedReader(new InputStreamReader(getInputStream()));
+ for (String line = in.readLine(); line != null; line = in.readLine())
+ {
+ if (line.length() == 0)
+ {
+ break;
+ }
+ request.append(line).append("\r\n");
+ LOG.debug("read line: {}",line);
+ }
+
+ LOG.debug("Client Request:{}{}","\n",request);
+ return request.toString();
+ }
+
+ public void respond(String rawstr) throws IOException
+ {
+ LOG.debug("respond(){}{}","\n",rawstr);
+ getOutputStream().write(rawstr.getBytes());
+ flush();
+ }
+
+ public void setSoTimeout(int ms) throws SocketException
+ {
+ socket.setSoTimeout(ms);
+ }
+
+ public void upgrade(Map<String, String> extraResponseHeaders) throws IOException
+ {
+ @SuppressWarnings("unused")
+ Pattern patExts = Pattern.compile("^Sec-WebSocket-Extensions: (.*)$",Pattern.CASE_INSENSITIVE);
+ Pattern patKey = Pattern.compile("^Sec-WebSocket-Key: (.*)$",Pattern.CASE_INSENSITIVE);
+
+ LOG.debug("(Upgrade) Reading HTTP Request");
+ Matcher mat;
+ String key = "not sent";
+ BufferedReader in = new BufferedReader(new InputStreamReader(getInputStream()));
+ for (String line = in.readLine(); line != null; line = in.readLine())
+ {
+ if (line.length() == 0)
+ {
+ break;
+ }
+
+ // TODO: Check for extensions
+ // mat = patExts.matcher(line);
+ // if (mat.matches())
+
+ // Check for Key
+ mat = patKey.matcher(line);
+ if (mat.matches())
+ {
+ key = mat.group(1);
+ }
+ }
+
+ LOG.debug("(Upgrade) Writing HTTP Response");
+ // TODO: handle extensions?
+
+ // Setup Response
+ StringBuilder resp = new StringBuilder();
+ resp.append("HTTP/1.1 101 Upgrade\r\n");
+ resp.append("Upgrade: websocket\r\n");
+ resp.append("Connection: upgrade\r\n");
+ resp.append("Sec-WebSocket-Accept: ");
+ resp.append(WebSocketConnectionRFC6455.hashKey(key)).append("\r\n");
+ // extra response headers.
+ if (extraResponseHeaders != null)
+ {
+ for (Map.Entry<String,String> header : extraResponseHeaders.entrySet())
+ {
+ resp.append(header.getKey());
+ resp.append(": ");
+ resp.append(header.getValue());
+ resp.append("\r\n");
+ }
+ }
+ resp.append("\r\n");
+
+ // Write Response
+ getOutputStream().write(resp.toString().getBytes());
+ flush();
+ }
+
+ public void write(byte[] bytes) throws IOException
+ {
+ LOG.debug("Writing {} bytes", bytes.length);
+ getOutputStream().write(bytes);
+ }
+
+ public void write(byte[] buf, int offset, int length) throws IOException
+ {
+ LOG.debug("Writing bytes[{}], offset={}, length={}", buf.length, offset, length);
+ getOutputStream().write(buf,offset,length);
+ }
+
+ public void write(int b) throws IOException
+ {
+ LOG.debug("Writing int={}", b);
+ getOutputStream().write(b);
+ }
+ }
+
+ private static final Logger LOG = Log.getLogger(DummyServer.class);
+ private ServerSocket serverSocket;
+ private URI wsUri;
+
+ public ServerConnection accept() throws IOException
+ {
+ LOG.debug(".accept()");
+ assertIsStarted();
+ Socket socket = serverSocket.accept();
+ return new ServerConnection(socket);
+ }
+
+ private void assertIsStarted()
+ {
+ Assert.assertThat("ServerSocket",serverSocket,notNullValue());
+ Assert.assertThat("ServerSocket.isBound",serverSocket.isBound(),is(true));
+ Assert.assertThat("ServerSocket.isClosed",serverSocket.isClosed(),is(false));
+
+ Assert.assertThat("WsUri",wsUri,notNullValue());
+ }
+
+ public URI getWsUri()
+ {
+ return wsUri;
+ }
+
+ public void respondToClient(Socket connection, String serverResponse) throws IOException
+ {
+ InputStream in = null;
+ InputStreamReader isr = null;
+ BufferedReader buf = null;
+ OutputStream out = null;
+ try
+ {
+ in = connection.getInputStream();
+ isr = new InputStreamReader(in);
+ buf = new BufferedReader(isr);
+ String line;
+ while ((line = buf.readLine()) != null)
+ {
+ // System.err.println(line);
+ if (line.length() == 0)
+ {
+ // Got the "\r\n" line.
+ break;
+ }
+ }
+
+ // System.out.println("[Server-Out] " + serverResponse);
+ out = connection.getOutputStream();
+ out.write(serverResponse.getBytes());
+ out.flush();
+ }
+ finally
+ {
+ IO.close(buf);
+ IO.close(isr);
+ IO.close(in);
+ IO.close(out);
+ }
+ }
+
+ public void start() throws IOException
+ {
+ serverSocket = new ServerSocket();
+ InetAddress addr = InetAddress.getByName("localhost");
+ InetSocketAddress endpoint = new InetSocketAddress(addr,0);
+ serverSocket.bind(endpoint);
+ int port = serverSocket.getLocalPort();
+ String uri = String.format("ws://%s:%d/",addr.getHostAddress(),port);
+ wsUri = URI.create(uri);
+ LOG.debug("Server Started on {} -> {}",endpoint,wsUri);
+ }
+
+ public void stop()
+ {
+ LOG.debug("Stopping Server");
+ try
+ {
+ serverSocket.close();
+ }
+ catch (IOException ignore)
+ {
+ /* ignore */
+ }
+ }
+
+}
diff --git a/jetty-websocket/src/test/resources/jetty-logging.properties b/jetty-websocket/src/test/resources/jetty-logging.properties
new file mode 100644
index 0000000000..78d8a9d812
--- /dev/null
+++ b/jetty-websocket/src/test/resources/jetty-logging.properties
@@ -0,0 +1,4 @@
+# Setup default logging implementation for during testing
+org.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
+org.eclipse.jetty.LEVEL=INFO
+org.eclipse.jetty.websocket.LEVEL=DEBUG \ No newline at end of file

Back to the top