successful web socket Igtimi connection test with timestamp and heartbeat

This commit is contained in:
Axel Uhl committed 2013-11-29 11:07:41 +01:00
1 parent 1edba6922c
commit c5c86e91d4
5 files changed
+67 -24

No files matched your search

@@ -2,6 +2,7 @@ package com.sap.sailing.domain.igtimiadapter.websocket;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.net.URI; import java.net.URI;
@@ -13,12 +14,14 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.eclipse.jetty.websocket.api.Session; import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.StatusCode; import org.eclipse.jetty.websocket.api.StatusCode;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage; import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket; import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest; import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
@@ -79,6 +82,11 @@ public class WebSocketTest {
session.close(StatusCode.NORMAL, "I'm done"); session.close(StatusCode.NORMAL, "I'm done");
} }
@OnWebSocketError
public void onError(Throwable cause) {
logger.log(Level.SEVERE, "Error with web socket connection", cause);
}
@OnWebSocketMessage @OnWebSocketMessage
public void onMessage(String msg) { public void onMessage(String msg) {
logger.info("Received "+msg); logger.info("Received "+msg);
@@ -96,7 +104,7 @@ public class WebSocketTest {
@Test @Test
public void simpleWebSockeEchoTest() throws Exception { public void simpleWebSockeEchoTest() throws Exception {
String destUri = "ws://echo.websocket.org"; String destUri = "ws://echo.websocket.org"; // wss currently doesn't seem to work with Jetty 9.0.4 WebSocket implementation
WebSocketClient client = new WebSocketClient(); WebSocketClient client = new WebSocketClient();
SimpleEchoTestSocket socket = new SimpleEchoTestSocket(); SimpleEchoTestSocket socket = new SimpleEchoTestSocket();
try { try {
@@ -137,7 +145,7 @@ public class WebSocketTest {
Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9"); Account account = connectionFactory.registerAccountForWhichClientIsAuthorized("3b6cbd0522423bb1ac274ddb9e7e579c4b3be6667622271086c4fdbf30634ba9");
WebSocketConnectionManager manager = new WebSocketConnectionManager(connectionFactory, Collections.singleton("GA-EN-AAEJ"), account); WebSocketConnectionManager manager = new WebSocketConnectionManager(connectionFactory, Collections.singleton("GA-EN-AAEJ"), account);
assertNotNull(manager); assertNotNull(manager);
Thread.sleep(1000000); assertTrue("Connection handshake not successful within 5s", manager.waitForConnection(5000));
manager.disconnect(); manager.disconnect();
} }
} }
@@ -80,9 +80,8 @@ public interface IgtimiConnectionFactory {
* *
* @param account * @param account
* represents this factory's client's permissions to access a user account's data * represents this factory's client's permissions to access a user account's data
* @param unitIds * @param deviceIds
* IDs of the transmitting units expected to be visible to the account's {@link Account#getUser() user's} * IDs of the transmitting units expected to be visible to the account's {@link Account#getUser() user's}
* .
*/ */
JSONObject getWebSocketConfigurationMessage(Account account, Iterable<String> unitIds); JSONObject getWebSocketConfigurationMessage(Account account, Iterable<String> deviceIds);
} }
@@ -34,7 +34,7 @@ public class FixFactory {
JSONArray timePointsMillis = (JSONArray) fixesJson.get("t"); JSONArray timePointsMillis = (JSONArray) fixesJson.get("t");
int fixIndex = 0; int fixIndex = 0;
for (Object timePointMillis : timePointsMillis) { for (Object timePointMillis : timePointsMillis) {
TimePoint timePoint = new MillisecondsTimePoint((Long) timePointMillis); TimePoint timePoint = new MillisecondsTimePoint(((Number) timePointMillis).longValue());
Map<Integer, Object> valuesPerSubindex = new HashMap<>(); Map<Integer, Object> valuesPerSubindex = new HashMap<>();
int i=1; int i=1;
JSONArray values; JSONArray values;
@@ -128,10 +128,10 @@ public class IgtimiConnectionFactoryImpl implements IgtimiConnectionFactory {
public JSONObject getWebSocketConfigurationMessage(Account account, Iterable<String> deviceIds) { public JSONObject getWebSocketConfigurationMessage(Account account, Iterable<String> deviceIds) {
JSONObject result = new JSONObject(); JSONObject result = new JSONObject();
result.put("access_token", getAccessTokenForAccount(account)); result.put("access_token", getAccessTokenForAccount(account));
JSONArray unitIdsJson = new JSONArray(); JSONArray deviceIdsJson = new JSONArray();
result.put("devices", unitIdsJson); result.put("devices", deviceIdsJson);
for (String unitId : deviceIds) { for (String deviceId : deviceIds) {
unitIdsJson.add(unitId); deviceIdsJson.add(deviceId);
} }
return result; return result;
} }
@@ -40,7 +40,7 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
private final ClientUpgradeRequest request; private final ClientUpgradeRequest request;
private final JSONObject configurationMessage; private final JSONObject configurationMessage;
private final Timer timer; private final Timer timer;
private final Iterable<String> unitIds; private final Iterable<String> deviceIds;
private final Account account; private final Account account;
private final FixFactory fixFactory; private final FixFactory fixFactory;
private boolean receivedServerHeartbeatInInterval; private boolean receivedServerHeartbeatInInterval;
@@ -48,20 +48,39 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
private TimePoint igtimiServerTimepoint; private TimePoint igtimiServerTimepoint;
private TimePoint localTimepointWhenServerTimepointWasReceived; private TimePoint localTimepointWhenServerTimepointWasReceived;
public WebSocketConnectionManager(IgtimiConnectionFactory connectionFactory, Iterable<String> unitIds, Account account) throws Exception { public WebSocketConnectionManager(IgtimiConnectionFactory connectionFactory, Iterable<String> deviceIds, Account account) throws Exception {
this.timer = new Timer("Timer for WebSocketConnectionManager for units "+unitIds+" and account "+account); this.timer = new Timer("Timer for WebSocketConnectionManager for units "+deviceIds+" and account "+account);
this.unitIds = unitIds; this.deviceIds = deviceIds;
this.account = account; this.account = account;
this.fixFactory = new FixFactory(); this.fixFactory = new FixFactory();
this.connectionFactory = connectionFactory; this.connectionFactory = connectionFactory;
this.liveDataListeners = new ConcurrentSkipListSet<>(); this.liveDataListeners = new ConcurrentSkipListSet<>();
client = new WebSocketClient(); client = new WebSocketClient();
configurationMessage = connectionFactory.getWebSocketConfigurationMessage(account, unitIds); configurationMessage = connectionFactory.getWebSocketConfigurationMessage(account, deviceIds);
request = new ClientUpgradeRequest(); request = new ClientUpgradeRequest();
client.start(); client.start();
reconnect(); reconnect();
} }
/**
* Waits until the web socket connection has been established and the initial configuration handshake has
* successfully completed. Technically, this means that the Igtimi server timestamp has successfully been received
* and parsed. {@link #getIgtimiServerTimePointAndWhenItWasReceived()} then holds a valid time point.
*
* @param timeoutInMillis
* use 0 to wait indefinitely
* @return <code>true</code> if the connection is established before the timeout occurred
*/
public boolean waitForConnection(long timeoutInMillis) throws InterruptedException {
long startedToWait = System.currentTimeMillis();
synchronized (this) {
while (igtimiServerTimepoint == null && System.currentTimeMillis() - startedToWait < timeoutInMillis) {
wait(timeoutInMillis);
}
return igtimiServerTimepoint != null;
}
}
public void addLiveDataListener(LiveDataListener listener) { public void addLiveDataListener(LiveDataListener listener) {
liveDataListeners.add(listener); liveDataListeners.add(listener);
} }
@@ -72,7 +91,9 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
} }
@Override @Override
public void onWebSocketConnect(Session sess) { public void onWebSocketConnect(Session session) {
super.onWebSocketConnect(session);
logger.info("received connection "+session+" for "+this);
try { try {
getRemote().sendString(configurationMessage.toString()); getRemote().sendString(configurationMessage.toString());
startClientHeartbeat(); startClientHeartbeat();
@@ -85,7 +106,11 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
@Override @Override
public void onWebSocketText(String message) { public void onWebSocketText(String message) {
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Received "+message+" in "+this);
}
if (message.equals("1")) { if (message.equals("1")) {
logger.fine("Received server heartbeat");
receivedServerHeartbeatInInterval = true; receivedServerHeartbeatInInterval = true;
} else if (message.startsWith("[")) { } else if (message.startsWith("[")) {
List<Fix> fixes = new ArrayList<>(); List<Fix> fixes = new ArrayList<>();
@@ -94,14 +119,21 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
for (Object o : jsonArray) { for (Object o : jsonArray) {
Util.addAll(fixFactory.createFixes((JSONObject) o), fixes); Util.addAll(fixFactory.createFixes((JSONObject) o), fixes);
} }
if (logger.isLoggable(Level.FINEST)) {
logger.finest("Received fixes"+fixes);
}
notifyListeners(fixes); notifyListeners(fixes);
} catch (ParseException e) { } catch (ParseException e) {
logger.log(Level.SEVERE, "Error trying to parse a web socket data package coming from Igtimi "+this, e); logger.log(Level.SEVERE, "Error trying to parse a web socket data package coming from Igtimi "+this, e);
} }
} else { } else {
// try to parse server time stamp in response to the configuration message // try to parse server time stamp in response to the configuration message
igtimiServerTimepoint = new MillisecondsTimePoint(Long.valueOf(message)); synchronized (this) {
localTimepointWhenServerTimepointWasReceived = MillisecondsTimePoint.now(); igtimiServerTimepoint = new MillisecondsTimePoint(Long.valueOf(message));
localTimepointWhenServerTimepointWasReceived = MillisecondsTimePoint.now();
logger.info("Received server timestamp "+igtimiServerTimepoint);
notifyAll();
}
} }
} }
@@ -133,7 +165,7 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
@Override @Override
public String toString() { public String toString() {
return "Web Socket Connection Manager for units "+unitIds+" and account "+account+" with web socket session "+getSession(); return "Web Socket Connection Manager for devices "+deviceIds+" and account "+account+" with web socket session "+getSession();
} }
private void startListeningForServerHeartbeat() { private void startListeningForServerHeartbeat() {
@@ -157,6 +189,7 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
@Override @Override
public void run() { public void run() {
try { try {
logger.fine("Sending client heartbeat");
getRemote().sendString("1"); getRemote().sendString("1");
} catch (IOException e) { } catch (IOException e) {
logger.log(Level.WARNING, "Couldn't send heartbeat to Igtimi web socket session in "+this, e); logger.log(Level.WARNING, "Couldn't send heartbeat to Igtimi web socket session in "+this, e);
@@ -169,11 +202,14 @@ public class WebSocketConnectionManager extends WebSocketAdapter {
IOException lastException = null; IOException lastException = null;
for (URI uri : connectionFactory.getWebsocketServers()) { for (URI uri : connectionFactory.getWebsocketServers()) {
try { try {
logger.log(Level.INFO, "Trying to connect to "+uri+" for "+this); if (uri.getScheme().equals("ws")) { // as Jetty 9.0.4 currently doesn't seem to support wss, explicitly
client.connect(this, uri, request); // look for ws connectivity
logger.log(Level.INFO, "Successfully connected to "+uri+" for "+this); logger.log(Level.INFO, "Trying to connect to " + uri + " for " + this);
lastException = null; client.connect(this, uri, request);
break; // successfully connected logger.log(Level.INFO, "Successfully connected to " + uri + " for " + this);
lastException = null;
break; // successfully connected
}
} catch (IOException e) { } catch (IOException e) {
logger.log(Level.INFO, "Couldn't connect to "+uri+" for "+this, e); logger.log(Level.INFO, "Couldn't connect to "+uri+" for "+this, e);
lastException = e; lastException = e;