Adding the HubProtocol Layer to the Java Client (#2524)

This commit is contained in:
Mikael Mengistu 2018-06-26 14:38:21 -07:00 committed by GitHub
parent d318d733c4
commit b0ce55e6c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 234 additions and 70 deletions

View File

@ -1,7 +1,15 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import com.google.gson.Gson;
public class DefaultJsonProtocolHandShakeMessage {
String protocol = "json";
int version = 1;
private String protocol = "json";
private int version = 1;
private static final String RECORD_SEPARATOR = "\u001e";
public String createHandshakeMessage() {
Gson gson = new Gson();
return gson.toJson(this) + RECORD_SEPARATOR;
}
}

View File

@ -1,34 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.net.URISyntaxException;
import java.util.HashMap;
public class HubConnection {
private String _url;
private ITransport _transport;
private Transport _transport;
private OnReceiveCallBack callback;
private HashMap<String, Action> handlers = new HashMap<>();
private JsonParser jsonParser = new JsonParser();
private static final String RECORD_SEPARATOR = "\u001e";
private HubProtocol protocol;
public Boolean connected = false;
public HubConnection(String url) {
_url = url;
protocol = new JsonHubProtocol();
callback = (payload) -> {
String[] messages = payload.split(RECORD_SEPARATOR);
for (String splitMessage : messages) {
InvocationMessage[] messages = protocol.parseMessages(payload);
// Empty handshake response "{}". We can ignore it
if (splitMessage.length() == 2) {
continue;
// message will be null if we receive any message other than an invocation.
// Adding this to avoid getting error messages on pings for now.
for (InvocationMessage message : messages) {
if (message != null && handlers.containsKey(message.target)) {
handlers.get(message.target).invoke(message.arguments[0]);
}
processMessage(splitMessage);
}
};
@ -39,44 +36,6 @@ public class HubConnection {
}
}
private void processMessage(String message) {
JsonObject jsonMessage = jsonParser.parse(message).getAsJsonObject();
String messageType = jsonMessage.get("type").toString();
switch(messageType) {
case "1":
//Invocation Message
String target = jsonMessage.get("target").getAsString();
if (handlers.containsKey(target)) {
handlers.get(target).invoke(jsonMessage.get("arguments"));
}
break;
case "2":
//Stream item
//Don't care yet
break;
case "3":
//Completion
//Don't care yet
break;
case "4":
//Stream invocation
//Don't care yet;
break;
case "5":
//Cancel invocation
//Don't care yet
break;
case "6":
//Ping
//Don't care yet
break;
case "7":
// Close message
//Don't care yet;
break;
}
}
public void start() throws InterruptedException {
_transport.setOnReceive(this.callback);
_transport.start();
@ -88,12 +47,13 @@ public class HubConnection {
connected = false;
}
public void send(String method, Object arg1) {
InvocationMessage message = new InvocationMessage(method, new Object[]{ arg1 });
public void send(String method, Object... args) {
InvocationMessage invocationMessage = new InvocationMessage(method, args);
String message = protocol.writeMessage(invocationMessage);
_transport.send(message);
}
public void On(String target, Action callback) {
handlers.put(target, callback);
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
public interface HubProtocol {
String getName();
int getVersion();
TransferFormat getTransferFormat();
InvocationMessage[] parseMessages(String message);
String writeMessage(InvocationMessage message);
}

View File

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
public class InvocationMessage {
private int type = 1;
int type = 1;
String invocationId;
String target;
Object[] arguments;
@ -28,7 +28,7 @@ public class InvocationMessage {
this.target = target;
}
public Object getArguments() {
public Object[] getArguments() {
return arguments;
}

View File

@ -0,0 +1,84 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.util.ArrayList;
import java.util.List;
public class JsonHubProtocol implements HubProtocol {
private final JsonParser jsonParser = new JsonParser();
private final Gson gson = new Gson();
private static final String RECORD_SEPARATOR = "\u001e";
@Override
public String getName() {
return "json";
}
@Override
public int getVersion() {
return 1;
}
@Override
public TransferFormat getTransferFormat() {
return TransferFormat.Text;
}
@Override
public InvocationMessage[] parseMessages(String payload) {
String[] messages = payload.split(RECORD_SEPARATOR);
List<InvocationMessage> invocationMessages = new ArrayList<>();
for (String splitMessage : messages) {
// Empty handshake response "{}". We can ignore it
if (splitMessage.equals("{}")) {
continue;
}
JsonObject jsonMessage = jsonParser.parse(splitMessage).getAsJsonObject();
String messageType = jsonMessage.get("type").toString();
switch (messageType) {
case "1":
//Invocation Message
String target = jsonMessage.get("target").getAsString();
JsonElement args = jsonMessage.get("arguments");
invocationMessages.add(new InvocationMessage(target, new Object[] {args}));
break;
case "2":
//Stream item
//Don't care yet
break;
case "3":
//Completion
//Don't care yet
break;
case "4":
//Stream invocation
//Don't care yet;
break;
case "5":
//Cancel invocation
//Don't care yet
break;
case "6":
//Ping
//Don't care yet
break;
case "7":
// Close message
//Don't care yet;
break;
}
}
return invocationMessages.toArray(new InvocationMessage[invocationMessages.size()]);
}
@Override
public String writeMessage(InvocationMessage invocationMessage) {
return gson.toJson(invocationMessage) + RECORD_SEPARATOR;
}
}

View File

@ -0,0 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
public enum TransferFormat {
Text,
Binary
}

View File

@ -1,9 +1,9 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
public interface ITransport {
public interface Transport {
void start() throws InterruptedException;
void send(InvocationMessage invocationMessage);
void send(String message);
void setOnReceive(OnReceiveCallBack callback);
void onReceive(String message);
void stop();

View File

@ -8,8 +8,7 @@ import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.net.URISyntaxException;
public class WebSocketTransport implements ITransport {
private static final String RECORD_SEPARATOR = "\u001e";
public class WebSocketTransport implements Transport {
private WebSocketClient _webSocket;
private OnReceiveCallBack onReceiveCallBack;
private URI _url;
@ -23,13 +22,11 @@ public class WebSocketTransport implements ITransport {
@Override
public void start() throws InterruptedException {
_webSocket.connectBlocking();
_webSocket.send(createHandshakeMessage() + RECORD_SEPARATOR);
_webSocket.send((new DefaultJsonProtocolHandShakeMessage()).createHandshakeMessage());
}
@Override
public void send(InvocationMessage invocationMessage) {
Gson gson = new Gson();
String message = gson.toJson(invocationMessage) + RECORD_SEPARATOR;
public void send(String message) {
_webSocket.send(message);
}
@ -43,11 +40,6 @@ public class WebSocketTransport implements ITransport {
this.onReceiveCallBack.invoke(message);
}
private String createHandshakeMessage() {
Gson gson = new Gson();
return gson.toJson(new DefaultJsonProtocolHandShakeMessage());
}
@Override
public void stop() {
_webSocket.closeConnection(0, "HubConnection Stopped");

View File

@ -0,0 +1,102 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
import com.google.gson.JsonArray;
import org.junit.Test;
import static org.junit.Assert.*;
public class JsonHubProtocolTest {
private JsonHubProtocol jsonHubProtocol = new JsonHubProtocol();
private static final String RECORD_SEPARATOR = "\u001e";
@Test
public void checkProtocolName() {
assertEquals("json", jsonHubProtocol.getName());
}
@Test
public void checkVersionNumber() {
assertEquals(1, jsonHubProtocol.getVersion());
}
@Test
public void checkTransferFormat() {
assertEquals(TransferFormat.Text, jsonHubProtocol.getTransferFormat());
}
@Test
public void VerifyWriteMessage() {
InvocationMessage invocationMessage = new InvocationMessage("test", new Object[] {"42"});
String result = jsonHubProtocol.writeMessage(invocationMessage);
String expectedResult = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"42\"]}\u001E";
assertEquals(expectedResult, result);
}
@Test
public void ParseSingleMessage() {
String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E";
InvocationMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
//We know it's only one message
assertEquals(1, messages.length);
InvocationMessage message = messages[0];
assertEquals("test", message.target);
assertEquals(null, message.invocationId);
assertEquals(1, message.type);
JsonArray messageResult = (JsonArray) message.arguments[0];
assertEquals(42, messageResult.getAsInt());
}
@Test
public void ParseHandshakeResponsePlusMessage() {
String twoMessages = "{}\u001E{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E";
InvocationMessage[] messages = jsonHubProtocol.parseMessages(twoMessages);
//We ignore the Handshake response for now
InvocationMessage message = messages[0];
assertEquals("test", message.target);
assertEquals(null, message.invocationId);
assertEquals(1, message.type);
JsonArray messageResult = (JsonArray) message.arguments[0];
assertEquals(42, messageResult.getAsInt());
}
@Test
public void ParseTwoMessages() {
String twoMessages = "{\"type\":1,\"target\":\"one\",\"arguments\":[42]}\u001E{\"type\":1,\"target\":\"two\",\"arguments\":[43]}\u001E";
InvocationMessage[] messages = jsonHubProtocol.parseMessages(twoMessages);
assertEquals(2, messages.length);
// Check the first message
InvocationMessage message = messages[0];
assertEquals("one", message.target);
assertEquals(null, message.invocationId);
assertEquals(1, message.type);
JsonArray messageResult = (JsonArray) message.arguments[0];
assertEquals(42, messageResult.getAsInt());
// Check the second message
InvocationMessage secondMessage = messages[1];
assertEquals("two", secondMessage.target);
assertEquals(null, secondMessage.invocationId);
assertEquals(1, secondMessage.type);
JsonArray secondMessageResult = (JsonArray) secondMessage.arguments[0];
assertEquals(43, secondMessageResult.getAsInt());
}
@Test
public void ParseSingleMessageMutipleArgs() {
String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E";
InvocationMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage);
//We know it's only one message
InvocationMessage message = messages[0];
assertEquals("test", message.target);
assertEquals(null, message.invocationId);
assertEquals(1, message.type);
JsonArray messageResult = ((JsonArray) message.arguments[0]);
assertEquals(42, messageResult.get(0).getAsInt());
assertEquals(24, messageResult.get(1).getAsInt());
}
}