JETTY-1463 - WebSockets with Safari gets messages stuck as if in a buffer that needs to be flushed.
+ Implementation of Safari WebSocket Draft-0 behavior in a unit test. (Test fails, and is currently set as @Ignore)
This commit is contained in:
parent
42816041c0
commit
6b42a1c45d
|
@ -0,0 +1,110 @@
|
|||
package org.eclipse.jetty.websocket;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.util.log.StdErrLog;
|
||||
import org.eclipse.jetty.websocket.helper.CaptureSocket;
|
||||
import org.eclipse.jetty.websocket.helper.SafariD00;
|
||||
import org.eclipse.jetty.websocket.helper.WebSocketCaptureServlet;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SafariWebsocketDraft0Test
|
||||
{
|
||||
private Server server;
|
||||
private WebSocketCaptureServlet servlet;
|
||||
private URI serverUri;
|
||||
|
||||
@BeforeClass
|
||||
public static void initLogging()
|
||||
{
|
||||
// Configure Logging
|
||||
System.setProperty("org.eclipse.jetty.util.log.class",StdErrLog.class.getName());
|
||||
System.setProperty("org.eclipse.jetty.LEVEL","DEBUG");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void startServer() throws Exception
|
||||
{
|
||||
// Configure Server
|
||||
server = new Server(0);
|
||||
|
||||
ServletContextHandler context = new ServletContextHandler();
|
||||
context.setContextPath("/");
|
||||
server.setHandler(context);
|
||||
|
||||
// Serve capture servlet
|
||||
servlet = new WebSocketCaptureServlet();
|
||||
context.addServlet(new ServletHolder(servlet),"/");
|
||||
|
||||
// Start Server
|
||||
server.start();
|
||||
|
||||
Connector conn = server.getConnectors()[0];
|
||||
String host = conn.getHost();
|
||||
if (host == null)
|
||||
{
|
||||
host = "localhost";
|
||||
}
|
||||
int port = conn.getLocalPort();
|
||||
serverUri = new URI(String.format("ws://%s:%d/",host,port));
|
||||
System.out.printf("Server URI: %s%n",serverUri);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testSendTextMessages() throws Exception
|
||||
{
|
||||
SafariD00 safari = new SafariD00(serverUri);
|
||||
|
||||
try
|
||||
{
|
||||
safari.connect();
|
||||
safari.issueHandshake();
|
||||
|
||||
// Send 5 short messages, using technique seen in Safari.
|
||||
safari.sendMessage("aa-0"); // single msg
|
||||
safari.sendMessage("aa-1", "aa-2", "aa-3", "aa-4");
|
||||
|
||||
// Servlet should show only 1 connection.
|
||||
Assert.assertThat("Servlet.captureSockets.size",servlet.captures.size(),is(1));
|
||||
|
||||
CaptureSocket socket = servlet.captures.get(0);
|
||||
Assert.assertThat("CaptureSocket",socket,notNullValue());
|
||||
Assert.assertThat("CaptureSocket.isConnected", socket.isConnected(), is(true));
|
||||
|
||||
// Give servlet 500 millisecond to process messages
|
||||
threadSleep(1,TimeUnit.SECONDS);
|
||||
// Should have captured 5 messages.
|
||||
Assert.assertThat("CaptureSocket.messages.size",socket.messages.size(),is(5));
|
||||
}
|
||||
finally
|
||||
{
|
||||
System.out.println("Closing client socket");
|
||||
safari.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public static void threadSleep(int dur, TimeUnit unit) throws InterruptedException
|
||||
{
|
||||
long ms = TimeUnit.MILLISECONDS.convert(dur,unit);
|
||||
Thread.sleep(ms);
|
||||
}
|
||||
|
||||
@After
|
||||
public void stopServer() throws Exception
|
||||
{
|
||||
server.stop();
|
||||
}
|
||||
}
|
|
@ -2,22 +2,17 @@ package org.eclipse.jetty.websocket;
|
|||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.eclipse.jetty.util.log.StdErrLog;
|
||||
import org.eclipse.jetty.websocket.helper.CaptureSocket;
|
||||
import org.eclipse.jetty.websocket.helper.MessageSender;
|
||||
import org.eclipse.jetty.websocket.helper.WebSocketCaptureServlet;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
|
@ -29,106 +24,6 @@ import org.junit.Test;
|
|||
*/
|
||||
public class WebSocketCommTest
|
||||
{
|
||||
@SuppressWarnings("serial")
|
||||
private static class WebSocketCaptureServlet extends WebSocketServlet
|
||||
{
|
||||
public List<CaptureSocket> captures = new ArrayList<CaptureSocket>();;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
resp.sendError(404);
|
||||
}
|
||||
|
||||
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
|
||||
{
|
||||
CaptureSocket capture = new CaptureSocket();
|
||||
captures.add(capture);
|
||||
return capture;
|
||||
}
|
||||
}
|
||||
|
||||
private static class CaptureSocket implements WebSocket, WebSocket.OnTextMessage
|
||||
{
|
||||
private Connection conn;
|
||||
public List<String> messages;
|
||||
|
||||
public CaptureSocket()
|
||||
{
|
||||
messages = new ArrayList<String>();
|
||||
}
|
||||
|
||||
public boolean isConnected()
|
||||
{
|
||||
if (conn == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return conn.isOpen();
|
||||
}
|
||||
|
||||
public void onMessage(String data)
|
||||
{
|
||||
System.out.printf("Received Message \"%s\" [size %d]%n", data, data.length());
|
||||
messages.add(data);
|
||||
}
|
||||
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
this.conn = connection;
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
this.conn = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MessageSender implements WebSocket
|
||||
{
|
||||
private Connection conn;
|
||||
private CountDownLatch connectLatch = new CountDownLatch(1);
|
||||
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
this.conn = connection;
|
||||
connectLatch.countDown();
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
this.conn = null;
|
||||
}
|
||||
|
||||
public boolean isConnected()
|
||||
{
|
||||
if (this.conn == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return this.conn.isOpen();
|
||||
}
|
||||
|
||||
public void sendMessage(String format, Object... args) throws IOException
|
||||
{
|
||||
this.conn.sendMessage(String.format(format,args));
|
||||
}
|
||||
|
||||
public void awaitConnect() throws InterruptedException
|
||||
{
|
||||
connectLatch.await(1,TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
if (this.conn == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.conn.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Server server;
|
||||
private WebSocketCaptureServlet servlet;
|
||||
private URI serverUri;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package org.eclipse.jetty.websocket.helper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jetty.websocket.WebSocket;
|
||||
|
||||
public class CaptureSocket implements WebSocket, WebSocket.OnTextMessage
|
||||
{
|
||||
private Connection conn;
|
||||
public List<String> messages;
|
||||
|
||||
public CaptureSocket()
|
||||
{
|
||||
messages = new ArrayList<String>();
|
||||
}
|
||||
|
||||
public boolean isConnected()
|
||||
{
|
||||
if (conn == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return conn.isOpen();
|
||||
}
|
||||
|
||||
public void onMessage(String data)
|
||||
{
|
||||
System.out.printf("Received Message \"%s\" [size %d]%n", data, data.length());
|
||||
messages.add(data);
|
||||
}
|
||||
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
this.conn = connection;
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
this.conn = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package org.eclipse.jetty.websocket.helper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.websocket.WebSocket;
|
||||
|
||||
public class MessageSender implements WebSocket
|
||||
{
|
||||
private Connection conn;
|
||||
private CountDownLatch connectLatch = new CountDownLatch(1);
|
||||
|
||||
public void onOpen(Connection connection)
|
||||
{
|
||||
this.conn = connection;
|
||||
connectLatch.countDown();
|
||||
}
|
||||
|
||||
public void onClose(int closeCode, String message)
|
||||
{
|
||||
this.conn = null;
|
||||
}
|
||||
|
||||
public boolean isConnected()
|
||||
{
|
||||
if (this.conn == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return this.conn.isOpen();
|
||||
}
|
||||
|
||||
public void sendMessage(String format, Object... args) throws IOException
|
||||
{
|
||||
this.conn.sendMessage(String.format(format,args));
|
||||
}
|
||||
|
||||
public void awaitConnect() throws InterruptedException
|
||||
{
|
||||
connectLatch.await(1,TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void close()
|
||||
{
|
||||
if (this.conn == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.conn.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
package org.eclipse.jetty.websocket.helper;
|
||||
|
||||
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.io.UnsupportedEncodingException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.net.URI;
|
||||
|
||||
import org.eclipse.jetty.io.ByteArrayBuffer;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.junit.Assert;
|
||||
|
||||
public class SafariD00
|
||||
{
|
||||
private URI uri;
|
||||
private SocketAddress endpoint;
|
||||
private Socket socket;
|
||||
private OutputStream out;
|
||||
private InputStream in;
|
||||
|
||||
public SafariD00(URI uri)
|
||||
{
|
||||
this.uri = uri;
|
||||
this.endpoint = new InetSocketAddress(uri.getHost(),uri.getPort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the Socket to the destination endpoint and
|
||||
*
|
||||
* @return the open java Socket.
|
||||
* @throws IOException
|
||||
*/
|
||||
public Socket connect() throws IOException
|
||||
{
|
||||
socket = new Socket();
|
||||
socket.connect(endpoint,1000);
|
||||
|
||||
out = socket.getOutputStream();
|
||||
in = socket.getInputStream();
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue an Http websocket (Draft-0) upgrade request using the Safari particulars.
|
||||
*
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public void issueHandshake() throws IOException
|
||||
{
|
||||
StringBuilder req = new StringBuilder();
|
||||
req.append("GET ").append(uri.getPath()).append(" HTTP/1.1\r\n");
|
||||
req.append("Upgrade: WebSocket\r\n");
|
||||
req.append("Connection: Upgrade\r\n");
|
||||
req.append("Host: ").append(uri.getHost()).append(":").append(uri.getPort()).append("\r\n");
|
||||
req.append("Origin: http://www.google.com/\r\n");
|
||||
req.append("Sec-WebSocket-Key1: 15{ft :6@87 0 M 5 c901\r\n");
|
||||
req.append("Sec-WebSocket-Key2: 3? C;7~0 8 \" 3 2105 6 `_ {\r\n");
|
||||
req.append("\r\n");
|
||||
|
||||
System.out.printf("--- Request ---%n%s",req);
|
||||
|
||||
byte reqBytes[] = req.toString().getBytes("UTF-8");
|
||||
byte hixieBytes[] = TypeUtil.fromHexString("e739617916c9daf3");
|
||||
byte buf[] = new byte[reqBytes.length + hixieBytes.length];
|
||||
System.arraycopy(reqBytes,0,buf,0,reqBytes.length);
|
||||
System.arraycopy(hixieBytes,0,buf,reqBytes.length,hixieBytes.length);
|
||||
|
||||
// Send HTTP GET Request (with hixie bytes)
|
||||
out.write(buf,0,buf.length);
|
||||
out.flush();
|
||||
|
||||
// Read HTTP 101 Upgrade / Handshake Response
|
||||
InputStreamReader reader = new InputStreamReader(in);
|
||||
BufferedReader br = new BufferedReader(reader);
|
||||
|
||||
boolean foundEnd = false;
|
||||
String line;
|
||||
while (!foundEnd)
|
||||
{
|
||||
line = br.readLine();
|
||||
System.out.printf("RESP: %s%n",line);
|
||||
if (line.length() == 0)
|
||||
{
|
||||
foundEnd = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Read expected handshake hixie bytes
|
||||
byte hixieHandshakeExpected[] = TypeUtil.fromHexString("c7438d956cf611a6af70603e6fa54809");
|
||||
byte hixieHandshake[] = new byte[hixieHandshakeExpected.length];
|
||||
|
||||
int readLen = in.read(hixieHandshake,0,hixieHandshake.length);
|
||||
Assert.assertThat("Read hixie handshake bytes",readLen,is(hixieHandshake.length));
|
||||
}
|
||||
|
||||
public void sendMessage(String... msgs) throws IOException
|
||||
{
|
||||
int len = 0;
|
||||
for (String msg : msgs)
|
||||
{
|
||||
len += (msg.length() + 2);
|
||||
}
|
||||
|
||||
ByteArrayBuffer buf = new ByteArrayBuffer(len);
|
||||
|
||||
for (String msg : msgs)
|
||||
{
|
||||
buf.put((byte)0x00);
|
||||
buf.put(msg.getBytes("UTF-8"));
|
||||
buf.put((byte)0xFF);
|
||||
}
|
||||
|
||||
out.write(buf.array());
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public void disconnect() throws IOException
|
||||
{
|
||||
socket.close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package org.eclipse.jetty.websocket.helper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.eclipse.jetty.websocket.WebSocket;
|
||||
import org.eclipse.jetty.websocket.WebSocketServlet;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class WebSocketCaptureServlet extends WebSocketServlet
|
||||
{
|
||||
public List<CaptureSocket> captures = new ArrayList<CaptureSocket>();;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
|
||||
{
|
||||
resp.sendError(404);
|
||||
}
|
||||
|
||||
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
|
||||
{
|
||||
CaptureSocket capture = new CaptureSocket();
|
||||
captures.add(capture);
|
||||
return capture;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue