From 58739e09657592b55033579f65946ce0c704db51 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 26 Jun 2020 17:06:14 -0700 Subject: [PATCH 01/64] Implement ParseMessages for java messagePack client --- src/SignalR/clients/java/signalr/build.gradle | 1 + .../signalr/CancelInvocationMessage.java | 14 + .../com/microsoft/signalr/CloseMessage.java | 18 +- .../microsoft/signalr/CompletionMessage.java | 14 + .../com/microsoft/signalr/HubMessageType.java | 3 +- .../com/microsoft/signalr/HubProtocol.java | 2 +- .../microsoft/signalr/InvocationMessage.java | 19 +- .../signalr/MessagePackHubProtocol.java | 305 ++++++++++++++++++ .../signalr/StreamBindingFailureMessage.java | 27 ++ .../signalr/StreamInvocationMessage.java | 11 + .../com/microsoft/signalr/StreamItem.java | 14 + 11 files changed, 423 insertions(+), 5 deletions(-) create mode 100644 src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java create mode 100644 src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamBindingFailureMessage.java diff --git a/src/SignalR/clients/java/signalr/build.gradle b/src/SignalR/clients/java/signalr/build.gradle index b845d839497d..bc8d76657e24 100644 --- a/src/SignalR/clients/java/signalr/build.gradle +++ b/src/SignalR/clients/java/signalr/build.gradle @@ -39,6 +39,7 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.11.0' api 'io.reactivex.rxjava2:rxjava:2.2.3' implementation 'org.slf4j:slf4j-api:1.7.25' + compile 'org.msgpack:msgpack-core:0.8.20' } spotless { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java index 096c49faf025..f61a2ca43ee9 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java @@ -3,14 +3,28 @@ package com.microsoft.signalr; +import java.util.Map; + final class CancelInvocationMessage extends HubMessage { private final int type = HubMessageType.CANCEL_INVOCATION.value; + private Map headers; private final String invocationId; public CancelInvocationMessage(String invocationId) { + this(null, invocationId); + } + + public CancelInvocationMessage(Map headers, String invocationId) { + if (headers != null & !headers.isEmpty()) { + this.headers = headers; + } this.invocationId = invocationId; } + public Map getHeaders() { + return headers; + } + @Override public HubMessageType getMessageType() { return HubMessageType.CANCEL_INVOCATION; diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java index 2c0dd006442f..7cfc411ec46d 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java @@ -5,6 +5,7 @@ final class CloseMessage extends HubMessage { private final String error; + private final boolean allowReconnect; @Override public HubMessageType getMessageType() { @@ -12,14 +13,27 @@ public HubMessageType getMessageType() { } public CloseMessage() { - this(null); + this(null, false); } - + public CloseMessage(String error) { + this (error, false) + } + + public CloseMessage(boolean allowReconnect) { + this (null, allowReconnect) + } + + public CloseMessage(String error, boolean allowReconnect) { this.error = error; + this.allowReconnect = allowReconnect; } public String getError() { return this.error; } + + public boolean getAllowReconnect() { + return this.allowReconnect; + } } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java index 4cd5f68263ae..d23aecb6bf45 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java @@ -3,13 +3,23 @@ package com.microsoft.signalr; +import java.util.Map; + final class CompletionMessage extends HubMessage { private final int type = HubMessageType.COMPLETION.value; + private Map headers; private final String invocationId; private final Object result; private final String error; public CompletionMessage(String invocationId, Object result, String error) { + this(null, invocationId, result, error); + } + + public CompletionMessage(Map headers, String invocationId, Object result, String error) { + if (headers != null & !headers.isEmpty()) { + this.headers = headers; + } if (error != null && result != null) { throw new IllegalArgumentException("Expected either 'error' or 'result' to be provided, but not both."); } @@ -17,6 +27,10 @@ public CompletionMessage(String invocationId, Object result, String error) { this.result = result; this.error = error; } + + public Map getHeaders() { + return headers; + } public Object getResult() { return result; diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubMessageType.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubMessageType.java index 588a82e6d1a0..0f8381cea69d 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubMessageType.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubMessageType.java @@ -11,7 +11,8 @@ enum HubMessageType { CANCEL_INVOCATION(5), PING(6), CLOSE(7), - INVOCATION_BINDING_FAILURE(-1); + INVOCATION_BINDING_FAILURE(-1), + STREAM_BINDING_FAILURE(-2); public int value; HubMessageType(int id) { this.value = id; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java index 072fa6b5065e..02ddc0e36639 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java @@ -6,7 +6,7 @@ /** * A protocol abstraction for communicating with SignalR hubs. */ -interface HubProtocol { +public interface HubProtocol { String getName(); int getVersion(); TransferFormat getTransferFormat(); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java index ffb842c537c2..fb0e74ec3acf 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java @@ -4,19 +4,32 @@ package com.microsoft.signalr; import java.util.Collection; +import java.util.Map; class InvocationMessage extends HubMessage { int type = HubMessageType.INVOCATION.value; + private Map headers; private final String invocationId; private final String target; private final Object[] arguments; private Collection streamIds; public InvocationMessage(String invocationId, String target, Object[] args) { - this(invocationId, target, args, null); + this(null, invocationId, target, args, null); + } + + public InvocationMessage(Map headers, String invocationId, String target, Object[] args) { + this(headers, invocationId, target, args, null); } public InvocationMessage(String invocationId, String target, Object[] args, Collection streamIds) { + this(null, invocationId, target, args, streamIds); + } + + public InvocationMessage(Map headers, String invocationId, String target, Object[] args, Collection streamIds) { + if (headers != null & !headers.isEmpty()) { + this.headers = headers; + } this.invocationId = invocationId; this.target = target; this.arguments = args; @@ -24,6 +37,10 @@ public InvocationMessage(String invocationId, String target, Object[] args, Coll this.streamIds = streamIds; } } + + public Map getHeaders() { + return headers; + } public String getInvocationId() { return invocationId; diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java new file mode 100644 index 000000000000..b9b155cf0d01 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -0,0 +1,305 @@ +// 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. + +package com.microsoft.signalr; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.msgpack.core.ExtensionTypeHeader; +import org.msgpack.core.MessageFormat; +import org.msgpack.core.MessagePack; +import org.msgpack.core.MessagePackException; +import org.msgpack.core.MessageUnpacker; +import org.msgpack.value.ValueType; + +class MessagePackHubProtocol implements HubProtocol { + + private static final int ERROR_RESULT = 1; + private static final int VOID_RESULT = 2; + private static final int NON_VOID_RESULT = 3; + + @Override + public String getName() { + return "messagepack"; + } + + @Override + public int getVersion() { + return 1; + } + + @Override + public TransferFormat getTransferFormat() { + return TransferFormat.BINARY; + } + + @Override + public HubMessage[] parseMessages(String payload, InvocationBinder binder) { + if (payload.length() == 0) { + return new HubMessage[]{}; + } + + List hubMessages = new ArrayList<>(); + + try { + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payload.getBytes()); + while (unpacker.hasNext()) { + int itemCount = unpacker.unpackArrayHeader(); + HubMessageType messageType = HubMessageType.values()[unpacker.unpackInt() - 1]; + + switch (messageType) { + case INVOCATION: + hubMessages.add(createInvocationMessage(unpacker, binder, itemCount)); + break; + case STREAM_ITEM: + hubMessages.add(createStreamItemMessage(unpacker, binder)); + break; + case COMPLETION: + hubMessages.add(createCompletionMessage(unpacker, binder)); + break; + case STREAM_INVOCATION: + hubMessages.add(createStreamInvocationMessage(unpacker, binder, itemCount)); + break; + case CANCEL_INVOCATION: + hubMessages.add(createCancelInvocationMessage(unpacker)); + break; + case PING: + hubMessages.add(PingMessage.getInstance()); + break; + case CLOSE: + hubMessages.add(createCloseMessage(unpacker, itemCount)); + break; + default: + break; + } + } + } catch (MessagePackException | IOException ex) { + throw new RuntimeException("Error reading MessagePack data.", ex); + } + + return hubMessages.toArray(new HubMessage[hubMessages.size()]); + } + + @Override + public String writeMessage(HubMessage hubMessage) { + return null; + } + + private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationBinder binder, int itemCount) throws IOException { + Map headers = readHeaders(unpacker); + String invocationId = unpacker.unpackString(); + + // For MsgPack, we represent an empty invocation ID as an empty string, + // so we need to normalize that to "null", which is what indicates a non-blocking invocation. + if (invocationId == null || invocationId.isEmpty()) { + invocationId = null; + } + + String target = unpacker.unpackString(); + + Object[] arguments = null; + try { + List> types = binder.getParameterTypes(target); + arguments = bindArguments(unpacker, types); + } catch (Exception ex) { + return new InvocationBindingFailureMessage(invocationId, target, ex); + } + + // NOTE - C# client accounts for possibility of streamId array being absent, while spec claims it's required - which? + List streams = readStreamIds(unpacker); + + // NOTE - C# client applies headers by assigning Message.Headers directly, rather than adding new constructor + return new InvocationMessage(headers, invocationId, target, arguments, streams); + } + + private HubMessage createStreamItemMessage(MessageUnpacker unpacker, InvocationBinder binder) throws IOException { + Map headers = readHeaders(unpacker); + String invocationId = unpacker.unpackString(); + Object value; + try { + Class itemType = binder.getReturnType(invocationId); + value = readValue(unpacker, itemType); + } catch (Exception ex) { + return new StreamBindingFailureMessage(invocationId, ex); + } + + return new StreamItem(headers, invocationId, value); + } + + private HubMessage createCompletionMessage(MessageUnpacker unpacker, InvocationBinder binder) throws IOException { + Map headers = readHeaders(unpacker); + String invocationId = unpacker.unpackString(); + int resultKind = unpacker.unpackInt(); + + String error = null; + Object result = null; + + switch (resultKind) { + case ERROR_RESULT: + error = unpacker.unpackString(); + break; + case VOID_RESULT: + break; + case NON_VOID_RESULT: + Class itemType = binder.getReturnType(invocationId); + result = readValue(unpacker, itemType); + break; + default: + throw new RuntimeException("Invalid invocation result kind."); + } + + return new CompletionMessage(headers, invocationId, result, error); + } + + private HubMessage createStreamInvocationMessage(MessageUnpacker unpacker, InvocationBinder binder, int itemCount) throws IOException { + Map headers = readHeaders(unpacker); + String invocationId = unpacker.unpackString(); + String target = unpacker.unpackString(); + + Object[] arguments = null; + try { + List> types = binder.getParameterTypes(target); + arguments = bindArguments(unpacker, types); + } catch (Exception ex) { + return new InvocationBindingFailureMessage(invocationId, target, ex); + } + + List streams = readStreamIds(unpacker); + + return new StreamInvocationMessage(headers, invocationId, target, arguments, streams); + } + + private HubMessage createCancelInvocationMessage(MessageUnpacker unpacker) throws IOException { + Map headers = readHeaders(unpacker); + String invocationId = unpacker.unpackString(); + + return new CancelInvocationMessage(headers, invocationId); + } + + private HubMessage createCloseMessage(MessageUnpacker unpacker, int itemCount) throws IOException { + String error = unpacker.unpackString(); + boolean allowReconnect = false; + + if (itemCount > 2) { + allowReconnect = unpacker.unpackBoolean(); + } + + return new CloseMessage(error, allowReconnect); + } + + private Map readHeaders(MessageUnpacker unpacker) throws IOException { + int headerCount = unpacker.unpackMapHeader(); + if (headerCount > 0) { + Map headers = new HashMap(); + for (int i = 0; i < headerCount; i++) { + headers.put(unpacker.unpackString(), unpacker.unpackString()); + } + return headers; + } else { + return null; + } + } + + private List readStreamIds(MessageUnpacker unpacker) throws IOException { + int streamCount = unpacker.unpackArrayHeader(); + List streams = null; + + if (streamCount > 0) { + streams = new ArrayList(); + for (int i = 0; i < streamCount; i++) { + streams.add(unpacker.unpackString()); + } + } + + return streams; + } + + private Object[] bindArguments(MessageUnpacker unpacker, List> paramTypes) throws IOException { + int argumentCount = unpacker.unpackArrayHeader(); + + if (paramTypes.size() != argumentCount) { + throw new RuntimeException(String.format("Invocation provides %d argument(s) but target expects %d.", argumentCount, paramTypes.size())); + } + + Object[] arguments = new Object[argumentCount]; + + for (int i = 0; i < argumentCount; i++) { + arguments[i] = readValue(unpacker, paramTypes.get(i)); + } + + return arguments; + } + + private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOException { + MessageFormat messageFormat = unpacker.getNextFormat(); + ValueType valueType = messageFormat.getValueType(); + int length; + Object item = null; + + switch(valueType) { + case NIL: + unpacker.unpackNil(); + item = null; + break; + case BOOLEAN: + item = unpacker.unpackBoolean(); + break; + case INTEGER: + switch (messageFormat) { + case UINT64: + item = unpacker.unpackBigInteger(); + break; + case INT64: + case UINT32: + item = unpacker.unpackLong(); + break; + default: + item = unpacker.unpackInt(); + break; + } + case FLOAT: + item = unpacker.unpackDouble(); + break; + case STRING: + item = unpacker.unpackString(); + break; + case BINARY: + length = unpacker.unpackBinaryHeader(); + byte[] binaryValue = new byte[length]; + unpacker.readPayload(binaryValue); + item = binaryValue; + break; + case ARRAY: + length = unpacker.unpackArrayHeader(); + Object[] arrayValue = new Object[length]; + for (int i = 0; i < length; i++) { + arrayValue[i] = readValue(unpacker, new Object().getClass()); + } + item = arrayValue; + break; + case MAP: + length = unpacker.unpackMapHeader(); + Map mapValue = new HashMap(); + for (int i = 0; i < length; i++) { + Object key = readValue(unpacker, new Object().getClass()); + Object value = readValue(unpacker, new Object().getClass()); + mapValue.put(key, value); + } + item = mapValue; + break; + case EXTENSION: + ExtensionTypeHeader extension = unpacker.unpackExtensionTypeHeader(); + byte[] extensionValue = new byte[extension.getLength()]; + unpacker.readPayload(extensionValue); + item = extensionValue; + break; + default: + break; + } + return itemType.cast(item); + } +} diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamBindingFailureMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamBindingFailureMessage.java new file mode 100644 index 000000000000..d7b145fa3f71 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamBindingFailureMessage.java @@ -0,0 +1,27 @@ +// 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. + +package com.microsoft.signalr; + +class StreamBindingFailureMessage extends HubMessage { + private final String invocationId; + private final Exception exception; + + public StreamBindingFailureMessage(String invocationId, Exception exception) { + this.invocationId = invocationId; + this.exception = exception; + } + + public String getInvocationId() { + return invocationId; + } + + public Exception getException() { + return exception; + } + + @Override + public HubMessageType getMessageType() { + return HubMessageType.STREAM_BINDING_FAILURE; + } +} diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamInvocationMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamInvocationMessage.java index 046ec6003684..b13edfe16f20 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamInvocationMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamInvocationMessage.java @@ -4,6 +4,7 @@ package com.microsoft.signalr; import java.util.Collection; +import java.util.Map; final class StreamInvocationMessage extends InvocationMessage { @@ -11,11 +12,21 @@ public StreamInvocationMessage(String invocationId, String target, Object[] args super(invocationId, target, args); super.type = HubMessageType.STREAM_INVOCATION.value; } + + public StreamInvocationMessage(Map headers, String invocationId, String target, Object[] args) { + super(headers, invocationId, target, args); + super.type = HubMessageType.STREAM_INVOCATION.value; + } public StreamInvocationMessage(String invocationId, String target, Object[] args, Collection streamIds) { super(invocationId, target, args, streamIds); super.type = HubMessageType.STREAM_INVOCATION.value; } + + public StreamInvocationMessage(Map headers, String invocationId, String target, Object[] args, Collection streamIds) { + super(headers, invocationId, target, args, streamIds); + super.type = HubMessageType.STREAM_INVOCATION.value; + } @Override public HubMessageType getMessageType() { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java index 3b422daf4681..8263e6fc5f09 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java @@ -3,15 +3,29 @@ package com.microsoft.signalr; +import java.util.Map; + final class StreamItem extends HubMessage { private final int type = HubMessageType.STREAM_ITEM.value; + private Map headers; private final String invocationId; private final Object item; public StreamItem(String invocationId, Object item) { + this(null, invocationId, item); + } + + public StreamItem(Map headers, String invocationId, Object item) { + if (headers != null & !headers.isEmpty()) { + this.headers = headers; + } this.invocationId = invocationId; this.item = item; } + + public Map getHeaders() { + return headers; + } public String getInvocationId() { return invocationId; From 1bd2ae631f03c1fe5148ce7eec9619a67898e8e4 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 26 Jun 2020 17:11:50 -0700 Subject: [PATCH 02/64] Fix some spacing & syntax --- .../com/microsoft/signalr/CancelInvocationMessage.java | 8 ++++---- .../main/java/com/microsoft/signalr/CloseMessage.java | 4 ++-- .../java/com/microsoft/signalr/HubMessageType.java | 2 +- .../java/com/microsoft/signalr/InvocationMessage.java | 10 +++++----- .../main/java/com/microsoft/signalr/StreamItem.java | 8 ++++---- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java index f61a2ca43ee9..09968c47a733 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java @@ -15,14 +15,14 @@ public CancelInvocationMessage(String invocationId) { } public CancelInvocationMessage(Map headers, String invocationId) { - if (headers != null & !headers.isEmpty()) { - this.headers = headers; - } + if (headers != null & !headers.isEmpty()) { + this.headers = headers; + } this.invocationId = invocationId; } public Map getHeaders() { - return headers; + return headers; } @Override diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java index 7cfc411ec46d..45ce7aa5f6d6 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java @@ -17,11 +17,11 @@ public CloseMessage() { } public CloseMessage(String error) { - this (error, false) + this (error, false); } public CloseMessage(boolean allowReconnect) { - this (null, allowReconnect) + this (null, allowReconnect); } public CloseMessage(String error, boolean allowReconnect) { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubMessageType.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubMessageType.java index 0f8381cea69d..a1bfeebfcf3b 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubMessageType.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubMessageType.java @@ -12,7 +12,7 @@ enum HubMessageType { PING(6), CLOSE(7), INVOCATION_BINDING_FAILURE(-1), - STREAM_BINDING_FAILURE(-2); + STREAM_BINDING_FAILURE(-2); public int value; HubMessageType(int id) { this.value = id; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java index fb0e74ec3acf..cad440d69b16 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java @@ -23,13 +23,13 @@ public InvocationMessage(Map headers, String invocationId, Strin } public InvocationMessage(String invocationId, String target, Object[] args, Collection streamIds) { - this(null, invocationId, target, args, streamIds); + this(null, invocationId, target, args, streamIds); } public InvocationMessage(Map headers, String invocationId, String target, Object[] args, Collection streamIds) { - if (headers != null & !headers.isEmpty()) { - this.headers = headers; - } + if (headers != null & !headers.isEmpty()) { + this.headers = headers; + } this.invocationId = invocationId; this.target = target; this.arguments = args; @@ -39,7 +39,7 @@ public InvocationMessage(Map headers, String invocationId, Strin } public Map getHeaders() { - return headers; + return headers; } public String getInvocationId() { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java index 8263e6fc5f09..4c66c41ab001 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java @@ -16,15 +16,15 @@ public StreamItem(String invocationId, Object item) { } public StreamItem(Map headers, String invocationId, Object item) { - if (headers != null & !headers.isEmpty()) { - this.headers = headers; - } + if (headers != null & !headers.isEmpty()) { + this.headers = headers; + } this.invocationId = invocationId; this.item = item; } public Map getHeaders() { - return headers; + return headers; } public String getInvocationId() { From 417805bdf529f0766d7f368c729dcc2a5588ae95 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 30 Jun 2020 13:19:24 -0700 Subject: [PATCH 03/64] Implement write --- .../signalr/CancelInvocationMessage.java | 6 +- .../microsoft/signalr/CompletionMessage.java | 2 +- .../microsoft/signalr/InvocationMessage.java | 6 +- .../signalr/MessagePackHubProtocol.java | 293 +++++++++++++++++- .../com/microsoft/signalr/StreamItem.java | 2 +- 5 files changed, 296 insertions(+), 13 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java index 09968c47a733..2f0d8290aec5 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java @@ -15,7 +15,7 @@ public CancelInvocationMessage(String invocationId) { } public CancelInvocationMessage(Map headers, String invocationId) { - if (headers != null & !headers.isEmpty()) { + if (headers != null && !headers.isEmpty()) { this.headers = headers; } this.invocationId = invocationId; @@ -25,6 +25,10 @@ public Map getHeaders() { return headers; } + public String getInvocationId() { + return invocationId; + } + @Override public HubMessageType getMessageType() { return HubMessageType.CANCEL_INVOCATION; diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java index d23aecb6bf45..2e3bef1da8c6 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java @@ -17,7 +17,7 @@ public CompletionMessage(String invocationId, Object result, String error) { } public CompletionMessage(Map headers, String invocationId, Object result, String error) { - if (headers != null & !headers.isEmpty()) { + if (headers != null && !headers.isEmpty()) { this.headers = headers; } if (error != null && result != null) { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java index cad440d69b16..5b14ac6628c1 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java @@ -27,7 +27,7 @@ public InvocationMessage(String invocationId, String target, Object[] args, Coll } public InvocationMessage(Map headers, String invocationId, String target, Object[] args, Collection streamIds) { - if (headers != null & !headers.isEmpty()) { + if (headers != null && !headers.isEmpty()) { this.headers = headers; } this.invocationId = invocationId; @@ -53,6 +53,10 @@ public String getTarget() { public Object[] getArguments() { return arguments; } + + public Collection getStreamIds() { + return streamIds; + } @Override public HubMessageType getMessageType() { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index b9b155cf0d01..88003778c0f2 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -3,16 +3,23 @@ package com.microsoft.signalr; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.msgpack.core.ExtensionTypeHeader; import org.msgpack.core.MessageFormat; import org.msgpack.core.MessagePack; import org.msgpack.core.MessagePackException; +import org.msgpack.core.MessagePacker; import org.msgpack.core.MessageUnpacker; import org.msgpack.value.ValueType; @@ -46,7 +53,7 @@ public HubMessage[] parseMessages(String payload, InvocationBinder binder) { List hubMessages = new ArrayList<>(); try { - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payload.getBytes()); + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payload.getBytes(StandardCharsets.ISO_8859_1)); while (unpacker.hasNext()) { int itemCount = unpacker.unpackArrayHeader(); HubMessageType messageType = HubMessageType.values()[unpacker.unpackInt() - 1]; @@ -86,7 +93,30 @@ public HubMessage[] parseMessages(String payload, InvocationBinder binder) { @Override public String writeMessage(HubMessage hubMessage) { - return null; + HubMessageType messageType = hubMessage.getMessageType(); + + try { + switch (messageType) { + case INVOCATION: + return writeInvocationMessage((InvocationMessage) hubMessage); + case STREAM_ITEM: + return writeStreamItemMessage((StreamItem) hubMessage); + case COMPLETION: + return writeCompletionMessage((CompletionMessage) hubMessage); + case STREAM_INVOCATION: + return writeStreamInvocationMessage((StreamInvocationMessage) hubMessage); + case CANCEL_INVOCATION: + return writeCancelInvocationMessage((CancelInvocationMessage) hubMessage); + case PING: + return writePingMessage((PingMessage) hubMessage); + case CLOSE: + return writeCloseMessage((CloseMessage) hubMessage); + default: + throw new RuntimeException(String.format("Unexpected message type: %d", messageType.value)); + } + } catch (MessagePackException | IOException ex) { + throw new RuntimeException("Error writing MessagePack data.", ex); + } } private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationBinder binder, int itemCount) throws IOException { @@ -110,7 +140,7 @@ private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationB } // NOTE - C# client accounts for possibility of streamId array being absent, while spec claims it's required - which? - List streams = readStreamIds(unpacker); + Collection streams = readStreamIds(unpacker); // NOTE - C# client applies headers by assigning Message.Headers directly, rather than adding new constructor return new InvocationMessage(headers, invocationId, target, arguments, streams); @@ -168,7 +198,7 @@ private HubMessage createStreamInvocationMessage(MessageUnpacker unpacker, Invoc return new InvocationBindingFailureMessage(invocationId, target, ex); } - List streams = readStreamIds(unpacker); + Collection streams = readStreamIds(unpacker); return new StreamInvocationMessage(headers, invocationId, target, arguments, streams); } @@ -191,6 +221,167 @@ private HubMessage createCloseMessage(MessageUnpacker unpacker, int itemCount) t return new CloseMessage(error, allowReconnect); } + private String writeInvocationMessage(InvocationMessage message) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + + packer.packArrayHeader(6); + packer.packInt(message.getMessageType().value); + + writeHeaders(message.getHeaders(), packer); + + String invocationId = message.getInvocationId(); + if (invocationId != null && !invocationId.isEmpty()) { + packer.packString(invocationId); + } else { + packer.packNil(); + } + + packer.packString(message.getTarget()); + + Object[] arguments = message.getArguments(); + packer.packArrayHeader(arguments.length); + + for (Object o: arguments) { + writeValue(o, packer); + } + + writeStreamIds(message.getStreamIds(), packer); + + packer.flush(); + String content = out.toString(StandardCharsets.ISO_8859_1); + out.close(); + return content; + } + + private String writeStreamItemMessage(StreamItem message) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + + packer.packArrayHeader(4); + packer.packInt(message.getMessageType().value); + + writeHeaders(message.getHeaders(), packer); + + packer.packString(message.getInvocationId()); + + writeValue(message.getItem(), packer); + + packer.flush(); + String content = out.toString(StandardCharsets.ISO_8859_1); + out.close(); + return content; + } + + private String writeCompletionMessage(CompletionMessage message) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + int resultKind = + message.getError() != null ? ERROR_RESULT : + message.getResult() != null ? NON_VOID_RESULT : + VOID_RESULT; + + packer.packArrayHeader(4 + (resultKind != VOID_RESULT ? 1: 0)); + packer.packInt(message.getMessageType().value); + + writeHeaders(message.getHeaders(), packer); + + packer.packString(message.getInvocationId()); + packer.packInt(resultKind); + + switch (resultKind) { + case ERROR_RESULT: + packer.packString(message.getError()); + break; + case NON_VOID_RESULT: + writeValue(message.getResult(), packer); + break; + } + + String content = out.toString(StandardCharsets.ISO_8859_1); + out.close(); + return content; + } + + private String writeStreamInvocationMessage(StreamInvocationMessage message) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + + packer.packArrayHeader(6); + packer.packInt(message.getMessageType().value); + + writeHeaders(message.getHeaders(), packer); + + packer.packString(message.getInvocationId()); + packer.packString(message.getTarget()); + + Object[] arguments = message.getArguments(); + packer.packArrayHeader(arguments.length); + + for (Object o: arguments) { + writeValue(o, packer); + } + + writeStreamIds(message.getStreamIds(), packer); + + packer.flush(); + String content = out.toString(StandardCharsets.ISO_8859_1); + out.close(); + return content; + } + + private String writeCancelInvocationMessage(CancelInvocationMessage message) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + + packer.packArrayHeader(3); + packer.packInt(message.getMessageType().value); + + writeHeaders(message.getHeaders(), packer); + + packer.packString(message.getInvocationId()); + + packer.flush(); + String content = out.toString(StandardCharsets.ISO_8859_1); + out.close(); + return content; + } + + private String writePingMessage(PingMessage message) throws IOException { + OutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + + packer.packArrayHeader(1); + packer.packInt(message.getMessageType().value); + + packer.flush(); + String content = out.toString(); + out.close(); + return content; + } + + private String writeCloseMessage(CloseMessage message) throws IOException { + OutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + + packer.packArrayHeader(3); + packer.packInt(message.getMessageType().value); + + String error = message.getError(); + if (error != null && !error.isEmpty()) { + packer.packString(error); + } else { + packer.packNil(); + } + + packer.packBoolean(message.getAllowReconnect()); + + packer.flush(); + String content = out.toString(); + out.close(); + return content; + } + private Map readHeaders(MessageUnpacker unpacker) throws IOException { int headerCount = unpacker.unpackMapHeader(); if (headerCount > 0) { @@ -203,10 +394,22 @@ private Map readHeaders(MessageUnpacker unpacker) throws IOExcep return null; } } + + private void writeHeaders(Map headers, MessagePacker packer) throws IOException { + if (headers != null) { + packer.packMapHeader(headers.size()); + for (String k: headers.keySet()) { + packer.packString(k); + packer.packString(headers.get(k)); + } + } else { + packer.packMapHeader(0); + } + } - private List readStreamIds(MessageUnpacker unpacker) throws IOException { + private Collection readStreamIds(MessageUnpacker unpacker) throws IOException { int streamCount = unpacker.unpackArrayHeader(); - List streams = null; + Collection streams = null; if (streamCount > 0) { streams = new ArrayList(); @@ -218,6 +421,17 @@ private List readStreamIds(MessageUnpacker unpacker) throws IOException return streams; } + private void writeStreamIds(Collection streamIds, MessagePacker packer) throws IOException { + if (streamIds != null) { + packer.packArrayHeader(streamIds.size()); + for (String s: streamIds) { + packer.packString(s); + } + } else { + packer.packArrayHeader(0); + } + } + private Object[] bindArguments(MessageUnpacker unpacker, List> paramTypes) throws IOException { int argumentCount = unpacker.unpackArrayHeader(); @@ -235,11 +449,15 @@ private Object[] bindArguments(MessageUnpacker unpacker, List> paramTyp } private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOException { + // This shouldn't ever get called with itemType == null, but we return anyways to avoid NullPointerExceptions + if (itemType == null) { + return null; + } MessageFormat messageFormat = unpacker.getNextFormat(); ValueType valueType = messageFormat.getValueType(); int length; Object item = null; - + switch(valueType) { case NIL: unpacker.unpackNil(); @@ -261,6 +479,7 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE item = unpacker.unpackInt(); break; } + break; case FLOAT: item = unpacker.unpackDouble(); break; @@ -280,6 +499,10 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE arrayValue[i] = readValue(unpacker, new Object().getClass()); } item = arrayValue; + //If the itemType is an array, we return an array. Else we convert the array to a list. + if (!itemType.isArray()) { + item = new ArrayList(Arrays.asList(arrayValue)); + } break; case MAP: length = unpacker.unpackMapHeader(); @@ -292,14 +515,66 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE item = mapValue; break; case EXTENSION: + /* ExtensionTypeHeader extension = unpacker.unpackExtensionTypeHeader(); byte[] extensionValue = new byte[extension.getLength()]; unpacker.readPayload(extensionValue); + //Convert this to an object? item = extensionValue; - break; + */ + throw new RuntimeException("Extension types are not supported yet"); default: break; } return itemType.cast(item); } + + private void writeValue(Object o, MessagePacker packer) throws IOException { + + if (o == null) { + packer.packNil(); + } else if (o instanceof Boolean) { + packer.packBoolean((boolean) o); + } else if (o instanceof BigInteger) { + packer.packBigInteger((BigInteger) o); + } else if (o instanceof Long) { + packer.packLong((long) o); + } else if (o instanceof Short) { + packer.packShort((short) o); + } else if (o instanceof Integer) { + packer.packInt((int) o); + } else if (o instanceof Double) { + packer.packDouble((double) o); + } else if (o instanceof Float) { + packer.packFloat((float) o); + } else if (o instanceof String) { + packer.packString((String) o); + } else if (o instanceof Byte) { + packer.packByte((byte) o); + // Unsure about this + } else if (o instanceof Collection) { + @SuppressWarnings("unchecked") + Collection list = (Collection) o; + packer.packArrayHeader(list.size()); + for (Object item: list) { + writeValue(item, packer); + } + } else if (o.getClass().isArray()) { + Object[] array = (Object[]) o; + packer.packArrayHeader(array.length); + for (Object item: array) { + writeValue(item, packer); + } + } else if (o instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (HashMap) o; + packer.packMapHeader(map.size()); + for (Object k: map.keySet()) { + writeValue(k, packer); + writeValue(map.get(k), packer); + } + } else { + throw new RuntimeException("Only base MessagePack types are currently supported"); + } + } } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java index 4c66c41ab001..e170b41b1132 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java @@ -16,7 +16,7 @@ public StreamItem(String invocationId, Object item) { } public StreamItem(Map headers, String invocationId, Object item) { - if (headers != null & !headers.isEmpty()) { + if (headers != null && !headers.isEmpty()) { this.headers = headers; } this.invocationId = invocationId; From 77e2033ae73a83d7985c68190292e42c38c3492f Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 30 Jun 2020 13:21:24 -0700 Subject: [PATCH 04/64] Tab -> Spaces --- .../signalr/MessagePackHubProtocol.java | 704 +++++++++--------- 1 file changed, 352 insertions(+), 352 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 88003778c0f2..133c053dba44 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -24,7 +24,7 @@ import org.msgpack.value.ValueType; class MessagePackHubProtocol implements HubProtocol { - + private static final int ERROR_RESULT = 1; private static final int VOID_RESULT = 2; private static final int NON_VOID_RESULT = 3; @@ -53,285 +53,285 @@ public HubMessage[] parseMessages(String payload, InvocationBinder binder) { List hubMessages = new ArrayList<>(); try { - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payload.getBytes(StandardCharsets.ISO_8859_1)); - while (unpacker.hasNext()) { - int itemCount = unpacker.unpackArrayHeader(); - HubMessageType messageType = HubMessageType.values()[unpacker.unpackInt() - 1]; - - switch (messageType) { - case INVOCATION: - hubMessages.add(createInvocationMessage(unpacker, binder, itemCount)); - break; - case STREAM_ITEM: - hubMessages.add(createStreamItemMessage(unpacker, binder)); - break; - case COMPLETION: - hubMessages.add(createCompletionMessage(unpacker, binder)); - break; - case STREAM_INVOCATION: - hubMessages.add(createStreamInvocationMessage(unpacker, binder, itemCount)); - break; - case CANCEL_INVOCATION: - hubMessages.add(createCancelInvocationMessage(unpacker)); - break; - case PING: - hubMessages.add(PingMessage.getInstance()); - break; - case CLOSE: - hubMessages.add(createCloseMessage(unpacker, itemCount)); - break; - default: - break; - } - } - } catch (MessagePackException | IOException ex) { - throw new RuntimeException("Error reading MessagePack data.", ex); - } + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payload.getBytes(StandardCharsets.ISO_8859_1)); + while (unpacker.hasNext()) { + int itemCount = unpacker.unpackArrayHeader(); + HubMessageType messageType = HubMessageType.values()[unpacker.unpackInt() - 1]; + + switch (messageType) { + case INVOCATION: + hubMessages.add(createInvocationMessage(unpacker, binder, itemCount)); + break; + case STREAM_ITEM: + hubMessages.add(createStreamItemMessage(unpacker, binder)); + break; + case COMPLETION: + hubMessages.add(createCompletionMessage(unpacker, binder)); + break; + case STREAM_INVOCATION: + hubMessages.add(createStreamInvocationMessage(unpacker, binder, itemCount)); + break; + case CANCEL_INVOCATION: + hubMessages.add(createCancelInvocationMessage(unpacker)); + break; + case PING: + hubMessages.add(PingMessage.getInstance()); + break; + case CLOSE: + hubMessages.add(createCloseMessage(unpacker, itemCount)); + break; + default: + break; + } + } + } catch (MessagePackException | IOException ex) { + throw new RuntimeException("Error reading MessagePack data.", ex); + } return hubMessages.toArray(new HubMessage[hubMessages.size()]); } @Override public String writeMessage(HubMessage hubMessage) { - HubMessageType messageType = hubMessage.getMessageType(); - - try { - switch (messageType) { - case INVOCATION: - return writeInvocationMessage((InvocationMessage) hubMessage); - case STREAM_ITEM: - return writeStreamItemMessage((StreamItem) hubMessage); - case COMPLETION: - return writeCompletionMessage((CompletionMessage) hubMessage); - case STREAM_INVOCATION: - return writeStreamInvocationMessage((StreamInvocationMessage) hubMessage); - case CANCEL_INVOCATION: - return writeCancelInvocationMessage((CancelInvocationMessage) hubMessage); - case PING: - return writePingMessage((PingMessage) hubMessage); - case CLOSE: - return writeCloseMessage((CloseMessage) hubMessage); - default: - throw new RuntimeException(String.format("Unexpected message type: %d", messageType.value)); - } - } catch (MessagePackException | IOException ex) { - throw new RuntimeException("Error writing MessagePack data.", ex); - } + HubMessageType messageType = hubMessage.getMessageType(); + + try { + switch (messageType) { + case INVOCATION: + return writeInvocationMessage((InvocationMessage) hubMessage); + case STREAM_ITEM: + return writeStreamItemMessage((StreamItem) hubMessage); + case COMPLETION: + return writeCompletionMessage((CompletionMessage) hubMessage); + case STREAM_INVOCATION: + return writeStreamInvocationMessage((StreamInvocationMessage) hubMessage); + case CANCEL_INVOCATION: + return writeCancelInvocationMessage((CancelInvocationMessage) hubMessage); + case PING: + return writePingMessage((PingMessage) hubMessage); + case CLOSE: + return writeCloseMessage((CloseMessage) hubMessage); + default: + throw new RuntimeException(String.format("Unexpected message type: %d", messageType.value)); + } + } catch (MessagePackException | IOException ex) { + throw new RuntimeException("Error writing MessagePack data.", ex); + } } private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationBinder binder, int itemCount) throws IOException { - Map headers = readHeaders(unpacker); - String invocationId = unpacker.unpackString(); - - // For MsgPack, we represent an empty invocation ID as an empty string, + Map headers = readHeaders(unpacker); + String invocationId = unpacker.unpackString(); + + // For MsgPack, we represent an empty invocation ID as an empty string, // so we need to normalize that to "null", which is what indicates a non-blocking invocation. - if (invocationId == null || invocationId.isEmpty()) { - invocationId = null; - } - - String target = unpacker.unpackString(); - - Object[] arguments = null; - try { - List> types = binder.getParameterTypes(target); - arguments = bindArguments(unpacker, types); - } catch (Exception ex) { - return new InvocationBindingFailureMessage(invocationId, target, ex); - } - - // NOTE - C# client accounts for possibility of streamId array being absent, while spec claims it's required - which? - Collection streams = readStreamIds(unpacker); - - // NOTE - C# client applies headers by assigning Message.Headers directly, rather than adding new constructor - return new InvocationMessage(headers, invocationId, target, arguments, streams); + if (invocationId == null || invocationId.isEmpty()) { + invocationId = null; + } + + String target = unpacker.unpackString(); + + Object[] arguments = null; + try { + List> types = binder.getParameterTypes(target); + arguments = bindArguments(unpacker, types); + } catch (Exception ex) { + return new InvocationBindingFailureMessage(invocationId, target, ex); + } + + // NOTE - C# client accounts for possibility of streamId array being absent, while spec claims it's required - which? + Collection streams = readStreamIds(unpacker); + + // NOTE - C# client applies headers by assigning Message.Headers directly, rather than adding new constructor + return new InvocationMessage(headers, invocationId, target, arguments, streams); } private HubMessage createStreamItemMessage(MessageUnpacker unpacker, InvocationBinder binder) throws IOException { - Map headers = readHeaders(unpacker); - String invocationId = unpacker.unpackString(); - Object value; - try { - Class itemType = binder.getReturnType(invocationId); - value = readValue(unpacker, itemType); - } catch (Exception ex) { - return new StreamBindingFailureMessage(invocationId, ex); - } - - return new StreamItem(headers, invocationId, value); + Map headers = readHeaders(unpacker); + String invocationId = unpacker.unpackString(); + Object value; + try { + Class itemType = binder.getReturnType(invocationId); + value = readValue(unpacker, itemType); + } catch (Exception ex) { + return new StreamBindingFailureMessage(invocationId, ex); + } + + return new StreamItem(headers, invocationId, value); } private HubMessage createCompletionMessage(MessageUnpacker unpacker, InvocationBinder binder) throws IOException { - Map headers = readHeaders(unpacker); - String invocationId = unpacker.unpackString(); - int resultKind = unpacker.unpackInt(); - - String error = null; - Object result = null; - - switch (resultKind) { - case ERROR_RESULT: - error = unpacker.unpackString(); - break; - case VOID_RESULT: - break; - case NON_VOID_RESULT: - Class itemType = binder.getReturnType(invocationId); - result = readValue(unpacker, itemType); - break; - default: - throw new RuntimeException("Invalid invocation result kind."); - } - - return new CompletionMessage(headers, invocationId, result, error); + Map headers = readHeaders(unpacker); + String invocationId = unpacker.unpackString(); + int resultKind = unpacker.unpackInt(); + + String error = null; + Object result = null; + + switch (resultKind) { + case ERROR_RESULT: + error = unpacker.unpackString(); + break; + case VOID_RESULT: + break; + case NON_VOID_RESULT: + Class itemType = binder.getReturnType(invocationId); + result = readValue(unpacker, itemType); + break; + default: + throw new RuntimeException("Invalid invocation result kind."); + } + + return new CompletionMessage(headers, invocationId, result, error); } private HubMessage createStreamInvocationMessage(MessageUnpacker unpacker, InvocationBinder binder, int itemCount) throws IOException { - Map headers = readHeaders(unpacker); - String invocationId = unpacker.unpackString(); - String target = unpacker.unpackString(); - - Object[] arguments = null; - try { - List> types = binder.getParameterTypes(target); - arguments = bindArguments(unpacker, types); - } catch (Exception ex) { - return new InvocationBindingFailureMessage(invocationId, target, ex); - } - - Collection streams = readStreamIds(unpacker); - - return new StreamInvocationMessage(headers, invocationId, target, arguments, streams); + Map headers = readHeaders(unpacker); + String invocationId = unpacker.unpackString(); + String target = unpacker.unpackString(); + + Object[] arguments = null; + try { + List> types = binder.getParameterTypes(target); + arguments = bindArguments(unpacker, types); + } catch (Exception ex) { + return new InvocationBindingFailureMessage(invocationId, target, ex); + } + + Collection streams = readStreamIds(unpacker); + + return new StreamInvocationMessage(headers, invocationId, target, arguments, streams); } private HubMessage createCancelInvocationMessage(MessageUnpacker unpacker) throws IOException { - Map headers = readHeaders(unpacker); - String invocationId = unpacker.unpackString(); - - return new CancelInvocationMessage(headers, invocationId); + Map headers = readHeaders(unpacker); + String invocationId = unpacker.unpackString(); + + return new CancelInvocationMessage(headers, invocationId); } private HubMessage createCloseMessage(MessageUnpacker unpacker, int itemCount) throws IOException { - String error = unpacker.unpackString(); - boolean allowReconnect = false; - - if (itemCount > 2) { - allowReconnect = unpacker.unpackBoolean(); - } - - return new CloseMessage(error, allowReconnect); + String error = unpacker.unpackString(); + boolean allowReconnect = false; + + if (itemCount > 2) { + allowReconnect = unpacker.unpackBoolean(); + } + + return new CloseMessage(error, allowReconnect); } private String writeInvocationMessage(InvocationMessage message) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = MessagePack.newDefaultPacker(out); - - packer.packArrayHeader(6); - packer.packInt(message.getMessageType().value); - - writeHeaders(message.getHeaders(), packer); - - String invocationId = message.getInvocationId(); - if (invocationId != null && !invocationId.isEmpty()) { - packer.packString(invocationId); - } else { - packer.packNil(); - } - - packer.packString(message.getTarget()); - - Object[] arguments = message.getArguments(); - packer.packArrayHeader(arguments.length); - - for (Object o: arguments) { - writeValue(o, packer); - } - - writeStreamIds(message.getStreamIds(), packer); - - packer.flush(); - String content = out.toString(StandardCharsets.ISO_8859_1); - out.close(); - return content; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + + packer.packArrayHeader(6); + packer.packInt(message.getMessageType().value); + + writeHeaders(message.getHeaders(), packer); + + String invocationId = message.getInvocationId(); + if (invocationId != null && !invocationId.isEmpty()) { + packer.packString(invocationId); + } else { + packer.packNil(); + } + + packer.packString(message.getTarget()); + + Object[] arguments = message.getArguments(); + packer.packArrayHeader(arguments.length); + + for (Object o: arguments) { + writeValue(o, packer); + } + + writeStreamIds(message.getStreamIds(), packer); + + packer.flush(); + String content = out.toString(StandardCharsets.ISO_8859_1); + out.close(); + return content; } private String writeStreamItemMessage(StreamItem message) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = MessagePack.newDefaultPacker(out); - - packer.packArrayHeader(4); - packer.packInt(message.getMessageType().value); - - writeHeaders(message.getHeaders(), packer); - - packer.packString(message.getInvocationId()); - - writeValue(message.getItem(), packer); - - packer.flush(); - String content = out.toString(StandardCharsets.ISO_8859_1); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + + packer.packArrayHeader(4); + packer.packInt(message.getMessageType().value); + + writeHeaders(message.getHeaders(), packer); + + packer.packString(message.getInvocationId()); + + writeValue(message.getItem(), packer); + + packer.flush(); + String content = out.toString(StandardCharsets.ISO_8859_1); out.close(); - return content; + return content; } private String writeCompletionMessage(CompletionMessage message) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = MessagePack.newDefaultPacker(out); - int resultKind = - message.getError() != null ? ERROR_RESULT : - message.getResult() != null ? NON_VOID_RESULT : - VOID_RESULT; - - packer.packArrayHeader(4 + (resultKind != VOID_RESULT ? 1: 0)); - packer.packInt(message.getMessageType().value); - - writeHeaders(message.getHeaders(), packer); - - packer.packString(message.getInvocationId()); - packer.packInt(resultKind); - - switch (resultKind) { - case ERROR_RESULT: - packer.packString(message.getError()); - break; - case NON_VOID_RESULT: - writeValue(message.getResult(), packer); - break; - } - - String content = out.toString(StandardCharsets.ISO_8859_1); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + int resultKind = + message.getError() != null ? ERROR_RESULT : + message.getResult() != null ? NON_VOID_RESULT : + VOID_RESULT; + + packer.packArrayHeader(4 + (resultKind != VOID_RESULT ? 1: 0)); + packer.packInt(message.getMessageType().value); + + writeHeaders(message.getHeaders(), packer); + + packer.packString(message.getInvocationId()); + packer.packInt(resultKind); + + switch (resultKind) { + case ERROR_RESULT: + packer.packString(message.getError()); + break; + case NON_VOID_RESULT: + writeValue(message.getResult(), packer); + break; + } + + String content = out.toString(StandardCharsets.ISO_8859_1); out.close(); - return content; + return content; } private String writeStreamInvocationMessage(StreamInvocationMessage message) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = MessagePack.newDefaultPacker(out); - - packer.packArrayHeader(6); - packer.packInt(message.getMessageType().value); - - writeHeaders(message.getHeaders(), packer); - - packer.packString(message.getInvocationId()); - packer.packString(message.getTarget()); - - Object[] arguments = message.getArguments(); - packer.packArrayHeader(arguments.length); - - for (Object o: arguments) { - writeValue(o, packer); - } - - writeStreamIds(message.getStreamIds(), packer); - - packer.flush(); - String content = out.toString(StandardCharsets.ISO_8859_1); - out.close(); - return content; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + MessagePacker packer = MessagePack.newDefaultPacker(out); + + packer.packArrayHeader(6); + packer.packInt(message.getMessageType().value); + + writeHeaders(message.getHeaders(), packer); + + packer.packString(message.getInvocationId()); + packer.packString(message.getTarget()); + + Object[] arguments = message.getArguments(); + packer.packArrayHeader(arguments.length); + + for (Object o: arguments) { + writeValue(o, packer); + } + + writeStreamIds(message.getStreamIds(), packer); + + packer.flush(); + String content = out.toString(StandardCharsets.ISO_8859_1); + out.close(); + return content; } private String writeCancelInvocationMessage(CancelInvocationMessage message) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); MessagePacker packer = MessagePack.newDefaultPacker(out); packer.packArrayHeader(3); @@ -343,8 +343,8 @@ private String writeCancelInvocationMessage(CancelInvocationMessage message) thr packer.flush(); String content = out.toString(StandardCharsets.ISO_8859_1); - out.close(); - return content; + out.close(); + return content; } private String writePingMessage(PingMessage message) throws IOException { @@ -355,9 +355,9 @@ private String writePingMessage(PingMessage message) throws IOException { packer.packInt(message.getMessageType().value); packer.flush(); - String content = out.toString(); - out.close(); - return content; + String content = out.toString(); + out.close(); + return content; } private String writeCloseMessage(CloseMessage message) throws IOException { @@ -369,90 +369,90 @@ private String writeCloseMessage(CloseMessage message) throws IOException { String error = message.getError(); if (error != null && !error.isEmpty()) { - packer.packString(error); + packer.packString(error); } else { - packer.packNil(); + packer.packNil(); } packer.packBoolean(message.getAllowReconnect()); packer.flush(); - String content = out.toString(); - out.close(); - return content; + String content = out.toString(); + out.close(); + return content; } private Map readHeaders(MessageUnpacker unpacker) throws IOException { - int headerCount = unpacker.unpackMapHeader(); - if (headerCount > 0) { - Map headers = new HashMap(); - for (int i = 0; i < headerCount; i++) { - headers.put(unpacker.unpackString(), unpacker.unpackString()); - } - return headers; - } else { - return null; - } + int headerCount = unpacker.unpackMapHeader(); + if (headerCount > 0) { + Map headers = new HashMap(); + for (int i = 0; i < headerCount; i++) { + headers.put(unpacker.unpackString(), unpacker.unpackString()); + } + return headers; + } else { + return null; + } } private void writeHeaders(Map headers, MessagePacker packer) throws IOException { - if (headers != null) { - packer.packMapHeader(headers.size()); - for (String k: headers.keySet()) { - packer.packString(k); - packer.packString(headers.get(k)); - } - } else { - packer.packMapHeader(0); - } + if (headers != null) { + packer.packMapHeader(headers.size()); + for (String k: headers.keySet()) { + packer.packString(k); + packer.packString(headers.get(k)); + } + } else { + packer.packMapHeader(0); + } } private Collection readStreamIds(MessageUnpacker unpacker) throws IOException { - int streamCount = unpacker.unpackArrayHeader(); - Collection streams = null; - - if (streamCount > 0) { - streams = new ArrayList(); - for (int i = 0; i < streamCount; i++) { - streams.add(unpacker.unpackString()); - } - } - - return streams; + int streamCount = unpacker.unpackArrayHeader(); + Collection streams = null; + + if (streamCount > 0) { + streams = new ArrayList(); + for (int i = 0; i < streamCount; i++) { + streams.add(unpacker.unpackString()); + } + } + + return streams; } private void writeStreamIds(Collection streamIds, MessagePacker packer) throws IOException { - if (streamIds != null) { - packer.packArrayHeader(streamIds.size()); - for (String s: streamIds) { - packer.packString(s); - } - } else { - packer.packArrayHeader(0); - } + if (streamIds != null) { + packer.packArrayHeader(streamIds.size()); + for (String s: streamIds) { + packer.packString(s); + } + } else { + packer.packArrayHeader(0); + } } private Object[] bindArguments(MessageUnpacker unpacker, List> paramTypes) throws IOException { - int argumentCount = unpacker.unpackArrayHeader(); - - if (paramTypes.size() != argumentCount) { - throw new RuntimeException(String.format("Invocation provides %d argument(s) but target expects %d.", argumentCount, paramTypes.size())); - } - - Object[] arguments = new Object[argumentCount]; - - for (int i = 0; i < argumentCount; i++) { - arguments[i] = readValue(unpacker, paramTypes.get(i)); - } - - return arguments; + int argumentCount = unpacker.unpackArrayHeader(); + + if (paramTypes.size() != argumentCount) { + throw new RuntimeException(String.format("Invocation provides %d argument(s) but target expects %d.", argumentCount, paramTypes.size())); + } + + Object[] arguments = new Object[argumentCount]; + + for (int i = 0; i < argumentCount; i++) { + arguments[i] = readValue(unpacker, paramTypes.get(i)); + } + + return arguments; } private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOException { - // This shouldn't ever get called with itemType == null, but we return anyways to avoid NullPointerExceptions - if (itemType == null) { - return null; - } + // This shouldn't ever get called with itemType == null, but we return anyways to avoid NullPointerExceptions + if (itemType == null) { + return null; + } MessageFormat messageFormat = unpacker.getNextFormat(); ValueType valueType = messageFormat.getValueType(); int length; @@ -496,12 +496,12 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE length = unpacker.unpackArrayHeader(); Object[] arrayValue = new Object[length]; for (int i = 0; i < length; i++) { - arrayValue[i] = readValue(unpacker, new Object().getClass()); + arrayValue[i] = readValue(unpacker, new Object().getClass()); } item = arrayValue; //If the itemType is an array, we return an array. Else we convert the array to a list. if (!itemType.isArray()) { - item = new ArrayList(Arrays.asList(arrayValue)); + item = new ArrayList(Arrays.asList(arrayValue)); } break; case MAP: @@ -515,66 +515,66 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE item = mapValue; break; case EXTENSION: - /* + /* ExtensionTypeHeader extension = unpacker.unpackExtensionTypeHeader(); byte[] extensionValue = new byte[extension.getLength()]; unpacker.readPayload(extensionValue); //Convert this to an object? item = extensionValue; */ - throw new RuntimeException("Extension types are not supported yet"); - default: - break; + throw new RuntimeException("Extension types are not supported yet"); + default: + break; } return itemType.cast(item); } private void writeValue(Object o, MessagePacker packer) throws IOException { - if (o == null) { - packer.packNil(); - } else if (o instanceof Boolean) { - packer.packBoolean((boolean) o); - } else if (o instanceof BigInteger) { - packer.packBigInteger((BigInteger) o); - } else if (o instanceof Long) { - packer.packLong((long) o); - } else if (o instanceof Short) { - packer.packShort((short) o); - } else if (o instanceof Integer) { - packer.packInt((int) o); - } else if (o instanceof Double) { - packer.packDouble((double) o); - } else if (o instanceof Float) { - packer.packFloat((float) o); - } else if (o instanceof String) { - packer.packString((String) o); - } else if (o instanceof Byte) { - packer.packByte((byte) o); - // Unsure about this - } else if (o instanceof Collection) { - @SuppressWarnings("unchecked") - Collection list = (Collection) o; - packer.packArrayHeader(list.size()); - for (Object item: list) { - writeValue(item, packer); - } - } else if (o.getClass().isArray()) { - Object[] array = (Object[]) o; - packer.packArrayHeader(array.length); - for (Object item: array) { - writeValue(item, packer); - } - } else if (o instanceof Map) { - @SuppressWarnings("unchecked") - Map map = (HashMap) o; - packer.packMapHeader(map.size()); - for (Object k: map.keySet()) { - writeValue(k, packer); - writeValue(map.get(k), packer); - } + if (o == null) { + packer.packNil(); + } else if (o instanceof Boolean) { + packer.packBoolean((boolean) o); + } else if (o instanceof BigInteger) { + packer.packBigInteger((BigInteger) o); + } else if (o instanceof Long) { + packer.packLong((long) o); + } else if (o instanceof Short) { + packer.packShort((short) o); + } else if (o instanceof Integer) { + packer.packInt((int) o); + } else if (o instanceof Double) { + packer.packDouble((double) o); + } else if (o instanceof Float) { + packer.packFloat((float) o); + } else if (o instanceof String) { + packer.packString((String) o); + } else if (o instanceof Byte) { + packer.packByte((byte) o); + // Unsure about this + } else if (o instanceof Collection) { + @SuppressWarnings("unchecked") + Collection list = (Collection) o; + packer.packArrayHeader(list.size()); + for (Object item: list) { + writeValue(item, packer); + } + } else if (o.getClass().isArray()) { + Object[] array = (Object[]) o; + packer.packArrayHeader(array.length); + for (Object item: array) { + writeValue(item, packer); + } + } else if (o instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (HashMap) o; + packer.packMapHeader(map.size()); + for (Object k: map.keySet()) { + writeValue(k, packer); + writeValue(map.get(k), packer); + } } else { - throw new RuntimeException("Only base MessagePack types are currently supported"); + throw new RuntimeException("Only base MessagePack types are currently supported"); } } } From 3362173a74acd4dfa872050b900fdb0fd6624440 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 30 Jun 2020 13:50:38 -0700 Subject: [PATCH 05/64] MessagePacker -> MessageBufferPacker --- .../signalr/MessagePackHubProtocol.java | 54 ++++++++----------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 133c053dba44..0ac9562cbf4a 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -3,19 +3,17 @@ package com.microsoft.signalr; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.OutputStream; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; -import java.util.Base64; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.msgpack.core.MessageBufferPacker; import org.msgpack.core.MessageFormat; import org.msgpack.core.MessagePack; import org.msgpack.core.MessagePackException; @@ -222,8 +220,7 @@ private HubMessage createCloseMessage(MessageUnpacker unpacker, int itemCount) t } private String writeInvocationMessage(InvocationMessage message) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = MessagePack.newDefaultPacker(out); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(6); packer.packInt(message.getMessageType().value); @@ -249,14 +246,13 @@ private String writeInvocationMessage(InvocationMessage message) throws IOExcept writeStreamIds(message.getStreamIds(), packer); packer.flush(); - String content = out.toString(StandardCharsets.ISO_8859_1); - out.close(); + String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + packer.close(); return content; } private String writeStreamItemMessage(StreamItem message) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = MessagePack.newDefaultPacker(out); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(4); packer.packInt(message.getMessageType().value); @@ -268,14 +264,13 @@ private String writeStreamItemMessage(StreamItem message) throws IOException { writeValue(message.getItem(), packer); packer.flush(); - String content = out.toString(StandardCharsets.ISO_8859_1); - out.close(); + String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + packer.close(); return content; } private String writeCompletionMessage(CompletionMessage message) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = MessagePack.newDefaultPacker(out); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); int resultKind = message.getError() != null ? ERROR_RESULT : message.getResult() != null ? NON_VOID_RESULT : @@ -298,14 +293,14 @@ private String writeCompletionMessage(CompletionMessage message) throws IOExcept break; } - String content = out.toString(StandardCharsets.ISO_8859_1); - out.close(); + packer.flush(); + String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + packer.close(); return content; } private String writeStreamInvocationMessage(StreamInvocationMessage message) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = MessagePack.newDefaultPacker(out); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(6); packer.packInt(message.getMessageType().value); @@ -325,14 +320,13 @@ private String writeStreamInvocationMessage(StreamInvocationMessage message) thr writeStreamIds(message.getStreamIds(), packer); packer.flush(); - String content = out.toString(StandardCharsets.ISO_8859_1); - out.close(); + String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + packer.close(); return content; } private String writeCancelInvocationMessage(CancelInvocationMessage message) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = MessagePack.newDefaultPacker(out); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(3); packer.packInt(message.getMessageType().value); @@ -342,27 +336,25 @@ private String writeCancelInvocationMessage(CancelInvocationMessage message) thr packer.packString(message.getInvocationId()); packer.flush(); - String content = out.toString(StandardCharsets.ISO_8859_1); - out.close(); + String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + packer.close(); return content; } private String writePingMessage(PingMessage message) throws IOException { - OutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = MessagePack.newDefaultPacker(out); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(1); packer.packInt(message.getMessageType().value); packer.flush(); - String content = out.toString(); - out.close(); + String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + packer.close(); return content; } private String writeCloseMessage(CloseMessage message) throws IOException { - OutputStream out = new ByteArrayOutputStream(); - MessagePacker packer = MessagePack.newDefaultPacker(out); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(3); packer.packInt(message.getMessageType().value); @@ -377,8 +369,8 @@ private String writeCloseMessage(CloseMessage message) throws IOException { packer.packBoolean(message.getAllowReconnect()); packer.flush(); - String content = out.toString(); - out.close(); + String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + packer.close(); return content; } From 8b53f5b3814c77564c48ab9fa0e71f6c8bc07045 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 30 Jun 2020 13:55:38 -0700 Subject: [PATCH 06/64] Tabs -> Spaces --- .../com/microsoft/signalr/CompletionMessage.java | 10 +++++----- .../com/microsoft/signalr/InvocationMessage.java | 2 +- .../microsoft/signalr/MessagePackHubProtocol.java | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java index 2e3bef1da8c6..a3e68ec6886b 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java @@ -13,13 +13,13 @@ final class CompletionMessage extends HubMessage { private final String error; public CompletionMessage(String invocationId, Object result, String error) { - this(null, invocationId, result, error); + this(null, invocationId, result, error); } public CompletionMessage(Map headers, String invocationId, Object result, String error) { - if (headers != null && !headers.isEmpty()) { - this.headers = headers; - } + if (headers != null && !headers.isEmpty()) { + this.headers = headers; + } if (error != null && result != null) { throw new IllegalArgumentException("Expected either 'error' or 'result' to be provided, but not both."); } @@ -29,7 +29,7 @@ public CompletionMessage(Map headers, String invocationId, Objec } public Map getHeaders() { - return headers; + return headers; } public Object getResult() { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java index 5b14ac6628c1..920bc244bed3 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java @@ -55,7 +55,7 @@ public Object[] getArguments() { } public Collection getStreamIds() { - return streamIds; + return streamIds; } @Override diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 0ac9562cbf4a..8ca623c2a57b 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -252,7 +252,7 @@ private String writeInvocationMessage(InvocationMessage message) throws IOExcept } private String writeStreamItemMessage(StreamItem message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(4); packer.packInt(message.getMessageType().value); @@ -270,7 +270,7 @@ private String writeStreamItemMessage(StreamItem message) throws IOException { } private String writeCompletionMessage(CompletionMessage message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); int resultKind = message.getError() != null ? ERROR_RESULT : message.getResult() != null ? NON_VOID_RESULT : @@ -300,7 +300,7 @@ private String writeCompletionMessage(CompletionMessage message) throws IOExcept } private String writeStreamInvocationMessage(StreamInvocationMessage message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(6); packer.packInt(message.getMessageType().value); @@ -326,7 +326,7 @@ private String writeStreamInvocationMessage(StreamInvocationMessage message) thr } private String writeCancelInvocationMessage(CancelInvocationMessage message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(3); packer.packInt(message.getMessageType().value); @@ -342,7 +342,7 @@ private String writeCancelInvocationMessage(CancelInvocationMessage message) thr } private String writePingMessage(PingMessage message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(1); packer.packInt(message.getMessageType().value); @@ -354,7 +354,7 @@ private String writePingMessage(PingMessage message) throws IOException { } private String writeCloseMessage(CloseMessage message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(3); packer.packInt(message.getMessageType().value); From f13df5ad837290a3a4647407099894b2748c6732 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 30 Jun 2020 13:55:59 -0700 Subject: [PATCH 07/64] Tabs -> Spaces --- .../microsoft/signalr/MessagePackHubProtocol.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 8ca623c2a57b..923d265f893d 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -252,7 +252,7 @@ private String writeInvocationMessage(InvocationMessage message) throws IOExcept } private String writeStreamItemMessage(StreamItem message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(4); packer.packInt(message.getMessageType().value); @@ -270,7 +270,7 @@ private String writeStreamItemMessage(StreamItem message) throws IOException { } private String writeCompletionMessage(CompletionMessage message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); int resultKind = message.getError() != null ? ERROR_RESULT : message.getResult() != null ? NON_VOID_RESULT : @@ -300,7 +300,7 @@ private String writeCompletionMessage(CompletionMessage message) throws IOExcept } private String writeStreamInvocationMessage(StreamInvocationMessage message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(6); packer.packInt(message.getMessageType().value); @@ -326,7 +326,7 @@ private String writeStreamInvocationMessage(StreamInvocationMessage message) thr } private String writeCancelInvocationMessage(CancelInvocationMessage message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(3); packer.packInt(message.getMessageType().value); @@ -342,7 +342,7 @@ private String writeCancelInvocationMessage(CancelInvocationMessage message) thr } private String writePingMessage(PingMessage message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(1); packer.packInt(message.getMessageType().value); @@ -354,7 +354,7 @@ private String writePingMessage(PingMessage message) throws IOException { } private String writeCloseMessage(CloseMessage message) throws IOException { - MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); + MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(3); packer.packInt(message.getMessageType().value); From 27e4765a60edc639aff7c0680564e400f404f06c Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 9 Jul 2020 10:29:58 -0700 Subject: [PATCH 08/64] InvocationMessage may not include streamIDs --- .../com/microsoft/signalr/MessagePackHubProtocol.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 923d265f893d..95f08064355c 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -137,10 +137,12 @@ private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationB return new InvocationBindingFailureMessage(invocationId, target, ex); } - // NOTE - C# client accounts for possibility of streamId array being absent, while spec claims it's required - which? - Collection streams = readStreamIds(unpacker); + Collection streams = null; + // Older implementations may not send the streamID array + if (itemCount > 5) { + streams = readStreamIds(unpacker); + } - // NOTE - C# client applies headers by assigning Message.Headers directly, rather than adding new constructor return new InvocationMessage(headers, invocationId, target, arguments, streams); } From 2b59b772bb50287b5ae100c63f5ab09acbae9650 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 9 Jul 2020 10:43:01 -0700 Subject: [PATCH 09/64] Only 1 ctor per message type --- .../signalr/CancelInvocationMessage.java | 4 --- .../microsoft/signalr/CompletionMessage.java | 4 --- .../microsoft/signalr/InvocationMessage.java | 12 --------- .../microsoft/signalr/JsonHubProtocol.java | 8 +++--- .../signalr/StreamInvocationMessage.java | 15 ----------- .../com/microsoft/signalr/StreamItem.java | 4 --- .../signalr/JsonHubProtocolTest.java | 26 +++++++++---------- 7 files changed, 17 insertions(+), 56 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java index 2f0d8290aec5..9f375aea72c2 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CancelInvocationMessage.java @@ -9,10 +9,6 @@ final class CancelInvocationMessage extends HubMessage { private final int type = HubMessageType.CANCEL_INVOCATION.value; private Map headers; private final String invocationId; - - public CancelInvocationMessage(String invocationId) { - this(null, invocationId); - } public CancelInvocationMessage(Map headers, String invocationId) { if (headers != null && !headers.isEmpty()) { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java index a3e68ec6886b..7256c7dfd4de 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CompletionMessage.java @@ -11,10 +11,6 @@ final class CompletionMessage extends HubMessage { private final String invocationId; private final Object result; private final String error; - - public CompletionMessage(String invocationId, Object result, String error) { - this(null, invocationId, result, error); - } public CompletionMessage(Map headers, String invocationId, Object result, String error) { if (headers != null && !headers.isEmpty()) { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java index 920bc244bed3..ecc3650ffef5 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationMessage.java @@ -13,18 +13,6 @@ class InvocationMessage extends HubMessage { private final String target; private final Object[] arguments; private Collection streamIds; - - public InvocationMessage(String invocationId, String target, Object[] args) { - this(null, invocationId, target, args, null); - } - - public InvocationMessage(Map headers, String invocationId, String target, Object[] args) { - this(headers, invocationId, target, args, null); - } - - public InvocationMessage(String invocationId, String target, Object[] args, Collection streamIds) { - this(null, invocationId, target, args, streamIds); - } public InvocationMessage(Map headers, String invocationId, String target, Object[] args, Collection streamIds) { if (headers != null && !headers.isEmpty()) { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index fa4d3212739d..e2c9b0f92bc2 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -135,9 +135,9 @@ public HubMessage[] parseMessages(String payload, InvocationBinder binder) { hubMessages.add(new InvocationBindingFailureMessage(invocationId, target, argumentBindingException)); } else { if (arguments == null) { - hubMessages.add(new InvocationMessage(invocationId, target, new Object[0])); + hubMessages.add(new InvocationMessage(null, invocationId, target, new Object[0], null)); } else { - hubMessages.add(new InvocationMessage(invocationId, target, arguments.toArray())); + hubMessages.add(new InvocationMessage(null, invocationId, target, arguments.toArray(), null)); } } break; @@ -146,14 +146,14 @@ public HubMessage[] parseMessages(String payload, InvocationBinder binder) { Class returnType = binder.getReturnType(invocationId); result = gson.fromJson(resultToken, returnType != null ? returnType : Object.class); } - hubMessages.add(new CompletionMessage(invocationId, result, error)); + hubMessages.add(new CompletionMessage(null, invocationId, result, error)); break; case STREAM_ITEM: if (resultToken != null) { Class returnType = binder.getReturnType(invocationId); result = gson.fromJson(resultToken, returnType != null ? returnType : Object.class); } - hubMessages.add(new StreamItem(invocationId, result)); + hubMessages.add(new StreamItem(null, invocationId, result)); break; case STREAM_INVOCATION: case CANCEL_INVOCATION: diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamInvocationMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamInvocationMessage.java index b13edfe16f20..0a8c6211b6bb 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamInvocationMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamInvocationMessage.java @@ -7,21 +7,6 @@ import java.util.Map; final class StreamInvocationMessage extends InvocationMessage { - - public StreamInvocationMessage(String invocationId, String target, Object[] args) { - super(invocationId, target, args); - super.type = HubMessageType.STREAM_INVOCATION.value; - } - - public StreamInvocationMessage(Map headers, String invocationId, String target, Object[] args) { - super(headers, invocationId, target, args); - super.type = HubMessageType.STREAM_INVOCATION.value; - } - - public StreamInvocationMessage(String invocationId, String target, Object[] args, Collection streamIds) { - super(invocationId, target, args, streamIds); - super.type = HubMessageType.STREAM_INVOCATION.value; - } public StreamInvocationMessage(Map headers, String invocationId, String target, Object[] args, Collection streamIds) { super(headers, invocationId, target, args, streamIds); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java index e170b41b1132..9b18f0bdc3de 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/StreamItem.java @@ -10,10 +10,6 @@ final class StreamItem extends HubMessage { private Map headers; private final String invocationId; private final Object item; - - public StreamItem(String invocationId, Object item) { - this(null, invocationId, item); - } public StreamItem(Map headers, String invocationId, Object item) { if (headers != null && !headers.isEmpty()) { diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java index f4df7a24b673..c12e94752143 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java @@ -32,7 +32,7 @@ public void checkTransferFormat() { @Test public void verifyWriteMessage() { - InvocationMessage invocationMessage = new InvocationMessage(null, "test", new Object[] {"42"}); + InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] {"42"}, null); String result = jsonHubProtocol.writeMessage(invocationMessage); String expectedResult = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"42\"]}\u001E"; assertEquals(expectedResult, result); @@ -89,7 +89,7 @@ public void parseCloseMessageWithError() { @Test public void parseSingleMessage() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; - TestBinder binder = new TestBinder(new InvocationMessage("1", "test", new Object[] { 42 })); + TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42 }, null)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); @@ -111,7 +111,7 @@ public void parseSingleMessage() { @Test public void parseSingleUnsupportedStreamInvocationMessage() { String stringifiedMessage = "{\"type\":4,\"Id\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; - TestBinder binder = new TestBinder(new StreamInvocationMessage("1", "test", new Object[] { 42 })); + TestBinder binder = new TestBinder(new StreamInvocationMessage(null, "1", "test", new Object[] { 42 }, null)); Throwable exception = assertThrows(UnsupportedOperationException.class, () -> jsonHubProtocol.parseMessages(stringifiedMessage, binder)); assertEquals("The message type STREAM_INVOCATION is not supported yet.", exception.getMessage()); @@ -129,7 +129,7 @@ public void parseSingleUnsupportedCancelInvocationMessage() { @Test public void parseTwoMessages() { String twoMessages = "{\"type\":1,\"target\":\"one\",\"arguments\":[42]}\u001E{\"type\":1,\"target\":\"two\",\"arguments\":[43]}\u001E"; - TestBinder binder = new TestBinder(new InvocationMessage("1", "one", new Object[] { 42 })); + TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "one", new Object[] { 42 }, null)); HubMessage[] messages = jsonHubProtocol.parseMessages(twoMessages, binder); assertEquals(2, messages.length); @@ -160,7 +160,7 @@ public void parseTwoMessages() { @Test public void parseSingleMessageMutipleArgs() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; - TestBinder binder = new TestBinder(new InvocationMessage("1", "test", new Object[] { 42, 24 })); + TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42, 24 }, null)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); @@ -179,7 +179,7 @@ public void parseSingleMessageMutipleArgs() { @Test public void parseMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; - TestBinder binder = new TestBinder(new InvocationMessage("1", "test", new Object[] { 42, 24 })); + TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42, 24 }, null)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); @@ -198,7 +198,7 @@ public void parseMessageWithOutOfOrderProperties() { @Test public void parseCompletionMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"type\":3,\"result\":42,\"invocationId\":\"1\"}\u001E"; - TestBinder binder = new TestBinder(new CompletionMessage("1", 42, null)); + TestBinder binder = new TestBinder(new CompletionMessage(null, "1", 42, null)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); @@ -213,7 +213,7 @@ public void parseCompletionMessageWithOutOfOrderProperties() { @Test public void invocationBindingFailureWhileParsingTooManyArgumentsWithOutOfOrderProperties() { String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; - TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42 })); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); assertEquals(1, messages.length); @@ -225,7 +225,7 @@ public void invocationBindingFailureWhileParsingTooManyArgumentsWithOutOfOrderPr @Test public void invocationBindingFailureWhileParsingTooManyArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; - TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42 })); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); assertEquals(1, messages.length); @@ -237,7 +237,7 @@ public void invocationBindingFailureWhileParsingTooManyArguments() { @Test public void invocationBindingFailureWhileParsingTooFewArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; - TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42, 24 })); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); assertEquals(1, messages.length); @@ -249,7 +249,7 @@ public void invocationBindingFailureWhileParsingTooFewArguments() { @Test public void invocationBindingFailureWhenParsingIncorrectType() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"]}\u001E"; - TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42 })); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); assertEquals(1, messages.length); @@ -261,7 +261,7 @@ public void invocationBindingFailureWhenParsingIncorrectType() { @Test public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"],\"invocationId\":\"123\"}\u001E"; - TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42 })); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); assertEquals(1, messages.length); @@ -274,7 +274,7 @@ public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() { @Test public void errorWhileParsingIncompleteMessage() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":"; - TestBinder binder = new TestBinder(new InvocationMessage(null, "test", new Object[] { 42, 24 })); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); RuntimeException exception = assertThrows(RuntimeException.class, () -> jsonHubProtocol.parseMessages(stringifiedMessage, binder)); From 8ace703931991524fdee302068ef44ba823b3408 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 9 Jul 2020 13:11:16 -0700 Subject: [PATCH 10/64] Fixup HubConnection.java --- .../java/com/microsoft/signalr/HubConnection.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index addbd5c2f6ae..e7ecaa388ada 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -585,9 +585,9 @@ private void sendInvocationMessage(String method, Object[] args, String id, Bool args = checkUploadStream(args, streamIds); InvocationMessage invocationMessage; if (isStreamInvocation) { - invocationMessage = new StreamInvocationMessage(id, method, args, streamIds); + invocationMessage = new StreamInvocationMessage(null, id, method, args, streamIds); } else { - invocationMessage = new InvocationMessage(id, method, args, streamIds); + invocationMessage = new InvocationMessage(null, id, method, args, streamIds); } sendHubMessage(invocationMessage); @@ -602,13 +602,13 @@ void launchStreams(List streamIds) { for (String streamId: streamIds) { Observable observable = this.streamMap.get(streamId); observable.subscribe( - (item) -> sendHubMessage(new StreamItem(streamId, item)), + (item) -> sendHubMessage(new StreamItem(null, streamId, item)), (error) -> { - sendHubMessage(new CompletionMessage(streamId, null, error.toString())); + sendHubMessage(new CompletionMessage(null, streamId, null, error.toString())); this.streamMap.remove(streamId); }, () -> { - sendHubMessage(new CompletionMessage(streamId, null, null)); + sendHubMessage(new CompletionMessage(null, streamId, null, null)); this.streamMap.remove(streamId); }); } @@ -753,7 +753,7 @@ public Observable stream(Class returnType, String method, Object ... a sendInvocationMessage(method, args, invocationId, true); return observable.doOnDispose(() -> { if (subscriptionCount.decrementAndGet() == 0) { - CancelInvocationMessage cancelInvocationMessage = new CancelInvocationMessage(invocationId); + CancelInvocationMessage cancelInvocationMessage = new CancelInvocationMessage(null, invocationId); sendHubMessage(cancelInvocationMessage); if (connectionState != null) { connectionState.tryRemoveInvocation(invocationId); From 3da3d248cfa3e12ec8f77ce771edf6477aae526e Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 9 Jul 2020 13:26:54 -0700 Subject: [PATCH 11/64] Change return type of parseMessages to List --- .../com/microsoft/signalr/HubProtocol.java | 4 +- .../microsoft/signalr/JsonHubProtocol.java | 6 +- .../signalr/MessagePackHubProtocol.java | 6 +- .../signalr/JsonHubProtocolTest.java | 130 +++++++++++------- 4 files changed, 89 insertions(+), 57 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java index 02ddc0e36639..6521b94fb859 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java @@ -3,6 +3,8 @@ package com.microsoft.signalr; +import java.util.List; + /** * A protocol abstraction for communicating with SignalR hubs. */ @@ -16,7 +18,7 @@ public interface HubProtocol { * @param message A string representation of one or more {@link HubMessage}s. * @return A list of {@link HubMessage}s. */ - HubMessage[] parseMessages(String message, InvocationBinder binder); + List parseMessages(String message, InvocationBinder binder); /** * Writes the specified {@link HubMessage} to a String. diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index e2c9b0f92bc2..36d444ac233b 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -36,9 +36,9 @@ public TransferFormat getTransferFormat() { } @Override - public HubMessage[] parseMessages(String payload, InvocationBinder binder) { + public List parseMessages(String payload, InvocationBinder binder) { if (payload.length() == 0) { - return new HubMessage[]{}; + return null; } if (!(payload.substring(payload.length() - 1).equals(RECORD_SEPARATOR))) { throw new RuntimeException("Message is incomplete."); @@ -176,7 +176,7 @@ public HubMessage[] parseMessages(String payload, InvocationBinder binder) { throw new RuntimeException("Error reading JSON.", ex); } - return hubMessages.toArray(new HubMessage[hubMessages.size()]); + return hubMessages; } @Override diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 95f08064355c..a42cf6041bcf 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -43,9 +43,9 @@ public TransferFormat getTransferFormat() { } @Override - public HubMessage[] parseMessages(String payload, InvocationBinder binder) { + public List parseMessages(String payload, InvocationBinder binder) { if (payload.length() == 0) { - return new HubMessage[]{}; + return null; } List hubMessages = new ArrayList<>(); @@ -86,7 +86,7 @@ public HubMessage[] parseMessages(String payload, InvocationBinder binder) { throw new RuntimeException("Error reading MessagePack data.", ex); } - return hubMessages.toArray(new HubMessage[hubMessages.size()]); + return hubMessages; } @Override diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java index c12e94752143..c8c0d39d9cd2 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java @@ -43,11 +43,12 @@ public void parsePingMessage() { String stringifiedMessage = "{\"type\":6}\u001E"; TestBinder binder = new TestBinder(PingMessage.getInstance()); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); //We know it's only one message - assertEquals(1, messages.length); - assertEquals(HubMessageType.PING, messages[0].getMessageType()); + assertNotNull(messages); + assertEquals(1, messages.size()); + assertEquals(HubMessageType.PING, messages.get(0).getMessageType()); } @Test @@ -55,15 +56,16 @@ public void parseCloseMessage() { String stringifiedMessage = "{\"type\":7}\u001E"; TestBinder binder = new TestBinder(new CloseMessage()); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); //We know it's only one message - assertEquals(1, messages.length); + assertNotNull(messages); + assertEquals(1, messages.size()); - assertEquals(HubMessageType.CLOSE, messages[0].getMessageType()); + assertEquals(HubMessageType.CLOSE, messages.get(0).getMessageType()); //We can safely cast here because we know that it's a close message. - CloseMessage closeMessage = (CloseMessage) messages[0]; + CloseMessage closeMessage = (CloseMessage) messages.get(0); assertEquals(null, closeMessage.getError()); } @@ -73,15 +75,16 @@ public void parseCloseMessageWithError() { String stringifiedMessage = "{\"type\":7,\"error\": \"There was an error\"}\u001E"; TestBinder binder = new TestBinder(new CloseMessage("There was an error")); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); //We know it's only one message - assertEquals(1, messages.length); + assertNotNull(messages); + assertEquals(1, messages.size()); - assertEquals(HubMessageType.CLOSE, messages[0].getMessageType()); + assertEquals(HubMessageType.CLOSE, messages.get(0).getMessageType()); //We can safely cast here because we know that it's a close message. - CloseMessage closeMessage = (CloseMessage) messages[0]; + CloseMessage closeMessage = (CloseMessage) messages.get(0); assertEquals("There was an error", closeMessage.getError()); } @@ -91,15 +94,16 @@ public void parseSingleMessage() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42 }, null)); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); //We know it's only one message - assertEquals(1, messages.length); + assertNotNull(messages); + assertEquals(1, messages.size()); - assertEquals(HubMessageType.INVOCATION, messages[0].getMessageType()); + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); //We can safely cast here because we know that it's an invocation message. - InvocationMessage invocationMessage = (InvocationMessage) messages[0]; + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); assertEquals("test", invocationMessage.getTarget()); assertEquals(null, invocationMessage.getInvocationId()); @@ -131,14 +135,16 @@ public void parseTwoMessages() { String twoMessages = "{\"type\":1,\"target\":\"one\",\"arguments\":[42]}\u001E{\"type\":1,\"target\":\"two\",\"arguments\":[43]}\u001E"; TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "one", new Object[] { 42 }, null)); - HubMessage[] messages = jsonHubProtocol.parseMessages(twoMessages, binder); - assertEquals(2, messages.length); + List messages = jsonHubProtocol.parseMessages(twoMessages, binder); + + assertNotNull(messages); + assertEquals(2, messages.size()); // Check the first message - assertEquals(HubMessageType.INVOCATION, messages[0].getMessageType()); + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); //Now that we know we have an invocation message we can cast the hubMessage. - InvocationMessage invocationMessage = (InvocationMessage) messages[0]; + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); assertEquals("one", invocationMessage.getTarget()); assertEquals(null, invocationMessage.getInvocationId()); @@ -146,10 +152,10 @@ public void parseTwoMessages() { assertEquals(42, messageResult); // Check the second message - assertEquals(HubMessageType.INVOCATION, messages[1].getMessageType()); + assertEquals(HubMessageType.INVOCATION, messages.get(1).getMessageType()); //Now that we know we have an invocation message we can cast the hubMessage. - InvocationMessage invocationMessage2 = (InvocationMessage) messages[1]; + InvocationMessage invocationMessage2 = (InvocationMessage) messages.get(1); assertEquals("two", invocationMessage2.getTarget()); assertEquals(null, invocationMessage2.getInvocationId()); @@ -162,12 +168,15 @@ public void parseSingleMessageMutipleArgs() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42, 24 }, null)); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); //We know it's only one message - assertEquals(HubMessageType.INVOCATION, messages[0].getMessageType()); + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); - InvocationMessage message = (InvocationMessage)messages[0]; + InvocationMessage message = (InvocationMessage)messages.get(0); assertEquals("test", message.getTarget()); assertEquals(null, message.getInvocationId()); int messageResult = (int) message.getArguments()[0]; @@ -181,12 +190,15 @@ public void parseMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42, 24 }, null)); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); // We know it's only one message - assertEquals(HubMessageType.INVOCATION, messages[0].getMessageType()); + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); - InvocationMessage message = (InvocationMessage) messages[0]; + InvocationMessage message = (InvocationMessage) messages.get(0); assertEquals("test", message.getTarget()); assertEquals(null, message.getInvocationId()); int messageResult = (int) message.getArguments()[0]; @@ -200,12 +212,15 @@ public void parseCompletionMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"type\":3,\"result\":42,\"invocationId\":\"1\"}\u001E"; TestBinder binder = new TestBinder(new CompletionMessage(null, "1", 42, null)); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); // We know it's only one message - assertEquals(HubMessageType.COMPLETION, messages[0].getMessageType()); + assertEquals(HubMessageType.COMPLETION, messages.get(0).getMessageType()); - CompletionMessage message = (CompletionMessage) messages[0]; + CompletionMessage message = (CompletionMessage) messages.get(0); assertEquals(null, message.getError()); assertEquals(42 , message.getResult()); } @@ -215,10 +230,13 @@ public void invocationBindingFailureWhileParsingTooManyArgumentsWithOutOfOrderPr String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); - assertEquals(1, messages.length); - assertEquals(InvocationBindingFailureMessage.class, messages[0].getClass()); - InvocationBindingFailureMessage message = (InvocationBindingFailureMessage)messages[0]; + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); + InvocationBindingFailureMessage message = (InvocationBindingFailureMessage)messages.get(0); assertEquals("Invocation provides 2 argument(s) but target expects 1.", message.getException().getMessage()); } @@ -227,10 +245,13 @@ public void invocationBindingFailureWhileParsingTooManyArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); - assertEquals(1, messages.length); - assertEquals(InvocationBindingFailureMessage.class, messages[0].getClass()); - InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages[0]; + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); + InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages.get(0); assertEquals("Invocation provides 2 argument(s) but target expects 1.", message.getException().getMessage()); } @@ -239,10 +260,13 @@ public void invocationBindingFailureWhileParsingTooFewArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); - assertEquals(1, messages.length); - assertEquals(InvocationBindingFailureMessage.class, messages[0].getClass()); - InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages[0]; + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); + InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages.get(0); assertEquals("Invocation provides 1 argument(s) but target expects 2.", message.getException().getMessage()); } @@ -251,10 +275,13 @@ public void invocationBindingFailureWhenParsingIncorrectType() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"]}\u001E"; TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); - assertEquals(1, messages.length); - assertEquals(InvocationBindingFailureMessage.class, messages[0].getClass()); - InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages[0]; + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); + InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages.get(0); assertEquals("java.lang.NumberFormatException: For input string: \"true\"", message.getException().getMessage()); } @@ -263,10 +290,13 @@ public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"],\"invocationId\":\"123\"}\u001E"; TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); - HubMessage[] messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); - assertEquals(1, messages.length); - assertEquals(InvocationBindingFailureMessage.class, messages[0].getClass()); - InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages[0]; + List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); + InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages.get(0); assertEquals("java.lang.NumberFormatException: For input string: \"true\"", message.getException().getMessage()); assertEquals("123", message.getInvocationId()); } From 960180aaa707934f155375f31bb3e9f9d6d4eca8 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 9 Jul 2020 14:41:42 -0700 Subject: [PATCH 12/64] Fix HubConnection --- .../src/main/java/com/microsoft/signalr/HubConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index e7ecaa388ada..937683d3d09a 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -192,7 +192,7 @@ Transport getTransport() { } } - HubMessage[] messages = protocol.parseMessages(payload, connectionState); + List messages = protocol.parseMessages(payload, connectionState); for (HubMessage message : messages) { logger.debug("Received message of type {}.", message.getMessageType()); From 6bfdf7a9b95d711deb1f12db5bb5831555976e8b Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Mon, 27 Jul 2020 13:19:34 -0700 Subject: [PATCH 13/64] Check for primitive value before returning --- .../com/microsoft/signalr/MessagePackHubProtocol.java | 3 +++ .../src/main/java/com/microsoft/signalr/Utils.java | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index a42cf6041bcf..685a240b8871 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -520,6 +520,9 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE default: break; } + if (itemType.isPrimitive()) { + return Utils.toPrimitive(itemType, item); + } return itemType.cast(item); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java index d08c6fb91455..6ff7f3fc6196 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java @@ -11,4 +11,15 @@ public static String appendQueryString(String original, String queryStringValue) return original + "?" + queryStringValue; } } + + public static Object toPrimitive(Class c, Object value) { + if( Boolean.class == c) return ((Boolean) value).booleanValue(); + if( Byte.class == c) return ((Byte) value).byteValue(); + if( Short.class == c) return ((Short) value).shortValue(); + if( Integer.class == c) return ((Integer) value).intValue(); + if( Long.class == c) return ((Long) value).longValue(); + if( Float.class == c) return ((Float) value).floatValue(); + if( Double.class == c) return ((Double) value).doubleValue(); + return value; + } } \ No newline at end of file From 9f4b6066a920ce90f31766e676393b9fdd85b76d Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Mon, 27 Jul 2020 14:41:15 -0700 Subject: [PATCH 14/64] Implement length header prefix --- .../signalr/MessagePackHubProtocol.java | 69 +++++++++++++------ .../java/com/microsoft/signalr/Utils.java | 62 +++++++++++++++++ 2 files changed, 109 insertions(+), 22 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 685a240b8871..17bdcae27689 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -53,6 +53,9 @@ public List parseMessages(String payload, InvocationBinder binder) { try { MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payload.getBytes(StandardCharsets.ISO_8859_1)); while (unpacker.hasNext()) { + // Currently this length isn't used - would it be better to spin up a new unpacker on the next n bytes each time? + // Where n is length + int length = Utils.readLengthHeader(unpacker); int itemCount = unpacker.unpackArrayHeader(); HubMessageType messageType = HubMessageType.values()[unpacker.unpackInt() - 1]; @@ -94,24 +97,46 @@ public String writeMessage(HubMessage hubMessage) { HubMessageType messageType = hubMessage.getMessageType(); try { + byte[] message; switch (messageType) { case INVOCATION: - return writeInvocationMessage((InvocationMessage) hubMessage); + message = writeInvocationMessage((InvocationMessage) hubMessage); + break; case STREAM_ITEM: - return writeStreamItemMessage((StreamItem) hubMessage); + message = writeStreamItemMessage((StreamItem) hubMessage); + break; case COMPLETION: - return writeCompletionMessage((CompletionMessage) hubMessage); + message = writeCompletionMessage((CompletionMessage) hubMessage); + break; case STREAM_INVOCATION: - return writeStreamInvocationMessage((StreamInvocationMessage) hubMessage); + message = writeStreamInvocationMessage((StreamInvocationMessage) hubMessage); + break; case CANCEL_INVOCATION: - return writeCancelInvocationMessage((CancelInvocationMessage) hubMessage); + message = writeCancelInvocationMessage((CancelInvocationMessage) hubMessage); + break; case PING: - return writePingMessage((PingMessage) hubMessage); + message = writePingMessage((PingMessage) hubMessage); + break; case CLOSE: - return writeCloseMessage((CloseMessage) hubMessage); + message = writeCloseMessage((CloseMessage) hubMessage); + break; default: throw new RuntimeException(String.format("Unexpected message type: %d", messageType.value)); } + int length = message.length; + List header = Utils.writeLengthHeader(length); + byte[] messageWithHeader = new byte[header.size() + length]; + int headerSize = header.size(); + + // Write the length header, then all of the bytes of the original message + for (int i = 0; i < headerSize; i++) { + messageWithHeader[i] = header.get(i); + } + for (int i = 0; i < length; i++) { + messageWithHeader[i + headerSize] = message[i]; + } + + return new String(messageWithHeader, StandardCharsets.ISO_8859_1); } catch (MessagePackException | IOException ex) { throw new RuntimeException("Error writing MessagePack data.", ex); } @@ -221,7 +246,7 @@ private HubMessage createCloseMessage(MessageUnpacker unpacker, int itemCount) t return new CloseMessage(error, allowReconnect); } - private String writeInvocationMessage(InvocationMessage message) throws IOException { + private byte[] writeInvocationMessage(InvocationMessage message) throws IOException { MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(6); @@ -248,12 +273,12 @@ private String writeInvocationMessage(InvocationMessage message) throws IOExcept writeStreamIds(message.getStreamIds(), packer); packer.flush(); - String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + byte[] content = packer.toByteArray(); packer.close(); return content; } - private String writeStreamItemMessage(StreamItem message) throws IOException { + private byte[] writeStreamItemMessage(StreamItem message) throws IOException { MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(4); @@ -266,12 +291,12 @@ private String writeStreamItemMessage(StreamItem message) throws IOException { writeValue(message.getItem(), packer); packer.flush(); - String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + byte[] content = packer.toByteArray(); packer.close(); return content; } - private String writeCompletionMessage(CompletionMessage message) throws IOException { + private byte[] writeCompletionMessage(CompletionMessage message) throws IOException { MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); int resultKind = message.getError() != null ? ERROR_RESULT : @@ -296,12 +321,12 @@ private String writeCompletionMessage(CompletionMessage message) throws IOExcept } packer.flush(); - String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + byte[] content = packer.toByteArray(); packer.close(); return content; } - private String writeStreamInvocationMessage(StreamInvocationMessage message) throws IOException { + private byte[] writeStreamInvocationMessage(StreamInvocationMessage message) throws IOException { MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(6); @@ -322,12 +347,12 @@ private String writeStreamInvocationMessage(StreamInvocationMessage message) thr writeStreamIds(message.getStreamIds(), packer); packer.flush(); - String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + byte[] content = packer.toByteArray(); packer.close(); return content; } - private String writeCancelInvocationMessage(CancelInvocationMessage message) throws IOException { + private byte[] writeCancelInvocationMessage(CancelInvocationMessage message) throws IOException { MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(3); @@ -338,24 +363,24 @@ private String writeCancelInvocationMessage(CancelInvocationMessage message) thr packer.packString(message.getInvocationId()); packer.flush(); - String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + byte[] content = packer.toByteArray(); packer.close(); return content; } - private String writePingMessage(PingMessage message) throws IOException { + private byte[] writePingMessage(PingMessage message) throws IOException { MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(1); packer.packInt(message.getMessageType().value); packer.flush(); - String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + byte[] content = packer.toByteArray(); packer.close(); return content; } - private String writeCloseMessage(CloseMessage message) throws IOException { + private byte[] writeCloseMessage(CloseMessage message) throws IOException { MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer.packArrayHeader(3); @@ -371,7 +396,7 @@ private String writeCloseMessage(CloseMessage message) throws IOException { packer.packBoolean(message.getAllowReconnect()); packer.flush(); - String content = new String(packer.toByteArray(), StandardCharsets.ISO_8859_1); + byte[] content = packer.toByteArray(); packer.close(); return content; } @@ -521,7 +546,7 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE break; } if (itemType.isPrimitive()) { - return Utils.toPrimitive(itemType, item); + return Utils.toPrimitive(itemType, item); } return itemType.cast(item); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java index 6ff7f3fc6196..0854210238aa 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java @@ -3,6 +3,13 @@ package com.microsoft.signalr; +import java.awt.List; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; + +import org.msgpack.core.MessageUnpacker; + class Utils { public static String appendQueryString(String original, String queryStringValue) { if (original.contains("?")) { @@ -22,4 +29,59 @@ public static Object toPrimitive(Class c, Object value) { if( Double.class == c) return ((Double) value).doubleValue(); return value; } + + public static int readLengthHeader(MessageUnpacker unpacker) throws IOException { + // The payload starts with a length prefix encoded as a VarInt. VarInts use the most significant bit + // as a marker whether the byte is the last byte of the VarInt or if it spans to the next byte. Bytes + // appear in the reverse order - i.e. the first byte contains the least significant bits of the value + // Examples: + // VarInt: 0x35 - %00110101 - the most significant bit is 0 so the value is %x0110101 i.e. 0x35 (53) + // VarInt: 0x80 0x25 - %10000000 %00101001 - the most significant bit of the first byte is 1 so the + // remaining bits (%x0000000) are the lowest bits of the value. The most significant bit of the second + // byte is 0 meaning this is last byte of the VarInt. The actual value bits (%x0101001) need to be + // prepended to the bits we already read so the values is %01010010000000 i.e. 0x1480 (5248) + // We support payloads up to 2GB so the biggest number we support is 7fffffff which when encoded as + // VarInt is 0xFF 0xFF 0xFF 0xFF 0x07 - hence the maximum length prefix is 5 bytes. + + // We know the unpacker isn't empty + int length = 0; + int numBytes = 0; + int maxLength = 5; + byte curr; + + do { + // If we run out of bytes before we finish reading the length header, the message is malformed + if (unpacker.hasNext()) { + curr = unpacker.unpackByte(); + } else { + throw new RuntimeException("The length header was incomplete"); + } + length = length | (curr & (byte) 0x7f) << (numBytes * 7); + numBytes++; + } while (numBytes < maxLength && (curr & (byte) 0x80) != 0); + + // Max header length is 5, and the maximum value of the 5th byte is 0x07 + if ((curr & (byte) 0x80) != 0 || (numBytes == maxLength && curr > (byte) 0x07)) { + throw new RuntimeException("Messages over 2GB in size are not supported"); + } + + return length; + } + + public static ArrayList writeLengthHeader(int length) { + // This code writes length prefix of the message as a VarInt. Read the comment in + // the readLengthHeader for details. + + ArrayList header = new ArrayList(); + do { + byte curr = (byte) (length & 0x7f); + length >>= 7; + if (length > 0) { + curr |= 0x80; + } + header.add(curr); + } while (length > 0); + + return header; + } } \ No newline at end of file From 4693ac62ca15cce7ebaf9e4898bf74ebcebb6cbd Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 10:20:29 -0700 Subject: [PATCH 15/64] Minor fixes --- .../java/com/microsoft/signalr/MessagePackHubProtocol.java | 6 +++--- .../signalr/src/main/java/com/microsoft/signalr/Utils.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 17bdcae27689..f39f6d8f913d 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -124,16 +124,16 @@ public String writeMessage(HubMessage hubMessage) { throw new RuntimeException(String.format("Unexpected message type: %d", messageType.value)); } int length = message.length; - List header = Utils.writeLengthHeader(length); + List header = Utils.getLengthHeader(length); byte[] messageWithHeader = new byte[header.size() + length]; int headerSize = header.size(); // Write the length header, then all of the bytes of the original message for (int i = 0; i < headerSize; i++) { - messageWithHeader[i] = header.get(i); + messageWithHeader[i] = header.get(i); } for (int i = 0; i < length; i++) { - messageWithHeader[i + headerSize] = message[i]; + messageWithHeader[i + headerSize] = message[i]; } return new String(messageWithHeader, StandardCharsets.ISO_8859_1); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java index 0854210238aa..9e2c6208fc11 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java @@ -68,7 +68,7 @@ public static int readLengthHeader(MessageUnpacker unpacker) throws IOException return length; } - public static ArrayList writeLengthHeader(int length) { + public static ArrayList getLengthHeader(int length) { // This code writes length prefix of the message as a VarInt. Read the comment in // the readLengthHeader for details. From fa2e257d191d7c2840ec32a3dfca617a893f33fb Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 11:29:13 -0700 Subject: [PATCH 16/64] Use ByteBuffer to read length header --- .../signalr/MessagePackHubProtocol.java | 19 ++++++++++++++----- .../java/com/microsoft/signalr/Utils.java | 8 ++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index f39f6d8f913d..9d38423ec68b 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -51,11 +52,17 @@ public List parseMessages(String payload, InvocationBinder binder) { List hubMessages = new ArrayList<>(); try { - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payload.getBytes(StandardCharsets.ISO_8859_1)); - while (unpacker.hasNext()) { - // Currently this length isn't used - would it be better to spin up a new unpacker on the next n bytes each time? - // Where n is length - int length = Utils.readLengthHeader(unpacker); + byte[] payloadBytes = payload.getBytes(StandardCharsets.ISO_8859_1); + ByteBuffer bb = ByteBuffer.wrap(payloadBytes); + while (bb.hasRemaining()) { + int length = Utils.readLengthHeader(bb); + // Throw if remaining buffer is shorter than length header + if (bb.remaining() < length) { + throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", payloadBytes.length, length)); + } + // Instantiate MessageUnpacker w/ the next length bytes of payload, starting at bb's position + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payloadBytes, bb.position(), length); + int itemCount = unpacker.unpackArrayHeader(); HubMessageType messageType = HubMessageType.values()[unpacker.unpackInt() - 1]; @@ -84,6 +91,8 @@ public List parseMessages(String payload, InvocationBinder binder) { default: break; } + // Incrememnt buffer's position by the number of bytes we just read + bb = bb.position(bb.position() + length); } } catch (MessagePackException | IOException ex) { throw new RuntimeException("Error reading MessagePack data.", ex); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java index 9e2c6208fc11..e6ddff4fc8a2 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java @@ -5,6 +5,7 @@ import java.awt.List; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -30,7 +31,7 @@ public static Object toPrimitive(Class c, Object value) { return value; } - public static int readLengthHeader(MessageUnpacker unpacker) throws IOException { + public static int readLengthHeader(ByteBuffer bb) throws IOException { // The payload starts with a length prefix encoded as a VarInt. VarInts use the most significant bit // as a marker whether the byte is the last byte of the VarInt or if it spans to the next byte. Bytes // appear in the reverse order - i.e. the first byte contains the least significant bits of the value @@ -43,7 +44,6 @@ public static int readLengthHeader(MessageUnpacker unpacker) throws IOException // We support payloads up to 2GB so the biggest number we support is 7fffffff which when encoded as // VarInt is 0xFF 0xFF 0xFF 0xFF 0x07 - hence the maximum length prefix is 5 bytes. - // We know the unpacker isn't empty int length = 0; int numBytes = 0; int maxLength = 5; @@ -51,8 +51,8 @@ public static int readLengthHeader(MessageUnpacker unpacker) throws IOException do { // If we run out of bytes before we finish reading the length header, the message is malformed - if (unpacker.hasNext()) { - curr = unpacker.unpackByte(); + if (bb.hasRemaining()) { + curr = bb.get(); } else { throw new RuntimeException("The length header was incomplete"); } From 0a629ded1557df0adc79a973821c018c8d6b5379 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 11:30:07 -0700 Subject: [PATCH 17/64] Add case for Char --- .../main/java/com/microsoft/signalr/Utils.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java index e6ddff4fc8a2..4cd4130e7f94 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java @@ -21,13 +21,14 @@ public static String appendQueryString(String original, String queryStringValue) } public static Object toPrimitive(Class c, Object value) { - if( Boolean.class == c) return ((Boolean) value).booleanValue(); - if( Byte.class == c) return ((Byte) value).byteValue(); - if( Short.class == c) return ((Short) value).shortValue(); - if( Integer.class == c) return ((Integer) value).intValue(); - if( Long.class == c) return ((Long) value).longValue(); - if( Float.class == c) return ((Float) value).floatValue(); - if( Double.class == c) return ((Double) value).doubleValue(); + if (Boolean.class == c) return ((Boolean) value).booleanValue(); + if (Byte.class == c) return ((Byte) value).byteValue(); + if (Short.class == c) return ((Short) value).shortValue(); + if (Integer.class == c) return ((Integer) value).intValue(); + if (Long.class == c) return ((Long) value).longValue(); + if (Float.class == c) return ((Float) value).floatValue(); + if (Double.class == c) return ((Double) value).doubleValue(); + if (Character.class == c) return ((Character) value).charValue(); return value; } From ca2df7002c5c9c77e845fd26534316572246f704 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 11:31:39 -0700 Subject: [PATCH 18/64] Close unpacker --- .../main/java/com/microsoft/signalr/MessagePackHubProtocol.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 9d38423ec68b..0ace5ec3a019 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -93,6 +93,7 @@ public List parseMessages(String payload, InvocationBinder binder) { } // Incrememnt buffer's position by the number of bytes we just read bb = bb.position(bb.position() + length); + unpacker.close(); } } catch (MessagePackException | IOException ex) { throw new RuntimeException("Error reading MessagePack data.", ex); From 769b94e25e32f428da57de8c7e5fb65fc18522e2 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 12:29:48 -0700 Subject: [PATCH 19/64] Typo --- .../main/java/com/microsoft/signalr/MessagePackHubProtocol.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 0ace5ec3a019..4643426dd5c3 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -91,7 +91,7 @@ public List parseMessages(String payload, InvocationBinder binder) { default: break; } - // Incrememnt buffer's position by the number of bytes we just read + // Increment buffer's position by the number of bytes we just read bb = bb.position(bb.position() + length); unpacker.close(); } From 538cb0c1b1f7e9d55ab12792f138ca0e9ab9d133 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 12:33:34 -0700 Subject: [PATCH 20/64] Override onMessage w/ ByteString --- .../java/com/microsoft/signalr/OkHttpWebSocketWrapper.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java index b76f02fe2608..90b1525b63cd 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java @@ -17,6 +17,7 @@ import okhttp3.Response; import okhttp3.WebSocket; import okhttp3.WebSocketListener; +import okio.ByteString; class OkHttpWebSocketWrapper extends WebSocketWrapper { private WebSocket websocketClient; @@ -85,6 +86,11 @@ public void onOpen(WebSocket webSocket, Response response) { public void onMessage(WebSocket webSocket, String message) { onReceive.invoke(message); } + + @Override + public void onMessage(WebSocket webSocket, ByteString bytes) { + onReceive.invoke(bytes.utf8()); + } @Override public void onClosing(WebSocket webSocket, int code, String reason) { From 834bfc446c3a1e9da6fe8cc9104781008b6f08b5 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 14:58:38 -0700 Subject: [PATCH 21/64] Change OKHttpWebSocketWrapper --- .../java/com/microsoft/signalr/OkHttpWebSocketWrapper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java index 90b1525b63cd..6490d1831dfb 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java @@ -3,6 +3,7 @@ package com.microsoft.signalr; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; @@ -89,7 +90,7 @@ public void onMessage(WebSocket webSocket, String message) { @Override public void onMessage(WebSocket webSocket, ByteString bytes) { - onReceive.invoke(bytes.utf8()); + onReceive.invoke(new String(bytes.toByteArray(), StandardCharsets.ISO_8859_1)); } @Override From 65776fbfaeb6ec5f46aaf5df8b08188667781db1 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 15:01:41 -0700 Subject: [PATCH 22/64] Account for nil InvocationId --- .../java/com/microsoft/signalr/MessagePackHubProtocol.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 4643426dd5c3..8188effe0984 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -154,7 +154,12 @@ public String writeMessage(HubMessage hubMessage) { private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationBinder binder, int itemCount) throws IOException { Map headers = readHeaders(unpacker); - String invocationId = unpacker.unpackString(); + + // invocationId may be nil + String invocationId = null; + if (!unpacker.tryUnpackNil()) { + invocationId = unpacker.unpackString(); + } // For MsgPack, we represent an empty invocation ID as an empty string, // so we need to normalize that to "null", which is what indicates a non-blocking invocation. From c9df3457b0459e77896bd777ff88422a0864abdd Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 15:25:55 -0700 Subject: [PATCH 23/64] Change interface & MessagePack impl --- .../com/microsoft/signalr/HubProtocol.java | 5 ++-- .../signalr/MessagePackHubProtocol.java | 26 +++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java index 6521b94fb859..909cd1601157 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java @@ -3,6 +3,7 @@ package com.microsoft.signalr; +import java.nio.ByteBuffer; import java.util.List; /** @@ -18,12 +19,12 @@ public interface HubProtocol { * @param message A string representation of one or more {@link HubMessage}s. * @return A list of {@link HubMessage}s. */ - List parseMessages(String message, InvocationBinder binder); + List parseMessages(ByteBuffer message, InvocationBinder binder); /** * Writes the specified {@link HubMessage} to a String. * @param message The message to write. * @return A string representation of the message. */ - String writeMessage(HubMessage message); + ByteBuffer writeMessage(HubMessage message); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 8188effe0984..1d34b00b522e 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -44,24 +44,24 @@ public TransferFormat getTransferFormat() { } @Override - public List parseMessages(String payload, InvocationBinder binder) { - if (payload.length() == 0) { + public List parseMessages(ByteBuffer payload, InvocationBinder binder) { + if (payload.remaining() == 0) { return null; } List hubMessages = new ArrayList<>(); try { - byte[] payloadBytes = payload.getBytes(StandardCharsets.ISO_8859_1); - ByteBuffer bb = ByteBuffer.wrap(payloadBytes); - while (bb.hasRemaining()) { - int length = Utils.readLengthHeader(bb); + while (payload.hasRemaining()) { + int length = Utils.readLengthHeader(payload); // Throw if remaining buffer is shorter than length header - if (bb.remaining() < length) { - throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", payloadBytes.length, length)); + if (payload.remaining() < length) { + throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", payload.remaining(), length)); } - // Instantiate MessageUnpacker w/ the next length bytes of payload, starting at bb's position - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payloadBytes, bb.position(), length); + // Instantiate MessageUnpacker w/ the next length bytes of payload + byte[] messageBytes = new byte[length]; + payload = payload.get(messageBytes, 0, length); + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(messageBytes); int itemCount = unpacker.unpackArrayHeader(); HubMessageType messageType = HubMessageType.values()[unpacker.unpackInt() - 1]; @@ -91,8 +91,6 @@ public List parseMessages(String payload, InvocationBinder binder) { default: break; } - // Increment buffer's position by the number of bytes we just read - bb = bb.position(bb.position() + length); unpacker.close(); } } catch (MessagePackException | IOException ex) { @@ -103,7 +101,7 @@ public List parseMessages(String payload, InvocationBinder binder) { } @Override - public String writeMessage(HubMessage hubMessage) { + public ByteBuffer writeMessage(HubMessage hubMessage) { HubMessageType messageType = hubMessage.getMessageType(); try { @@ -146,7 +144,7 @@ public String writeMessage(HubMessage hubMessage) { messageWithHeader[i + headerSize] = message[i]; } - return new String(messageWithHeader, StandardCharsets.ISO_8859_1); + return ByteBuffer.wrap(messageWithHeader); } catch (MessagePackException | IOException ex) { throw new RuntimeException("Error writing MessagePack data.", ex); } From b1d3e9f41c1c2b77dc760fbb3f0bfb110c69642e Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 15:30:23 -0700 Subject: [PATCH 24/64] Update JsonHubProtocol --- .../com/microsoft/signalr/JsonHubProtocol.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index 36d444ac233b..5630db0d4508 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -5,6 +5,8 @@ import java.io.IOException; import java.io.StringReader; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -36,15 +38,16 @@ public TransferFormat getTransferFormat() { } @Override - public List parseMessages(String payload, InvocationBinder binder) { - if (payload.length() == 0) { + public List parseMessages(ByteBuffer payload, InvocationBinder binder) { + String payloadStr = new String(payload.array(), StandardCharsets.UTF_8); + if (payloadStr.length() == 0) { return null; } - if (!(payload.substring(payload.length() - 1).equals(RECORD_SEPARATOR))) { + if (!(payloadStr.substring(payloadStr.length() - 1).equals(RECORD_SEPARATOR))) { throw new RuntimeException("Message is incomplete."); } - String[] messages = payload.split(RECORD_SEPARATOR); + String[] messages = payloadStr.split(RECORD_SEPARATOR); List hubMessages = new ArrayList<>(); try { for (String str : messages) { @@ -180,8 +183,8 @@ public List parseMessages(String payload, InvocationBinder binder) { } @Override - public String writeMessage(HubMessage hubMessage) { - return gson.toJson(hubMessage) + RECORD_SEPARATOR; + public ByteBuffer writeMessage(HubMessage hubMessage) { + return ByteBuffer.wrap((gson.toJson(hubMessage) + RECORD_SEPARATOR).getBytes(StandardCharsets.UTF_8)); } private ArrayList bindArguments(JsonArray argumentsToken, List> paramTypes) { From a9c83e46cc290f5894d27cf508372f783755f92b Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 15:49:05 -0700 Subject: [PATCH 25/64] Use ByteBuffer --- .../microsoft/signalr/MessagePackHubProtocol.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 1d34b00b522e..6f518fd4e428 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -58,10 +58,8 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde if (payload.remaining() < length) { throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", payload.remaining(), length)); } - // Instantiate MessageUnpacker w/ the next length bytes of payload - byte[] messageBytes = new byte[length]; - payload = payload.get(messageBytes, 0, length); - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(messageBytes); + // Instantiate MessageUnpacker + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payload); int itemCount = unpacker.unpackArrayHeader(); HubMessageType messageType = HubMessageType.values()[unpacker.unpackInt() - 1]; @@ -91,7 +89,13 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde default: break; } + // Make sure that we actually read the right number of bytes + long readBytes = unpacker.getTotalReadBytes(); + if (readBytes != length) { + throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", readBytes, length)); + } unpacker.close(); + payload = payload.position(payload.position() + (int) readBytes); } } catch (MessagePackException | IOException ex) { throw new RuntimeException("Error reading MessagePack data.", ex); From d71da9db1b3287d2515c51144e7a7fd5b1515148 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 16:07:13 -0700 Subject: [PATCH 26/64] Fixup HubConnection --- .../main/java/com/microsoft/signalr/HubConnection.java | 8 +++++--- .../com/microsoft/signalr/OkHttpWebSocketWrapper.java | 6 ++++-- .../src/main/java/com/microsoft/signalr/Transport.java | 4 +++- .../main/java/com/microsoft/signalr/WebSocketWrapper.java | 4 +++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index 937683d3d09a..ad0f5b8658b7 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -4,6 +4,8 @@ package com.microsoft.signalr; import java.io.StringReader; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; @@ -192,7 +194,7 @@ Transport getTransport() { } } - List messages = protocol.parseMessages(payload, connectionState); + List messages = protocol.parseMessages(ByteBuffer.wrap(payload.getBytes(StandardCharsets.UTF_8)), connectionState); for (HubMessage message : messages) { logger.debug("Received message of type {}.", message.getMessageType()); @@ -377,7 +379,7 @@ public Completable start() { connectionState = new ConnectionState(this); - return transport.send(handshake).andThen(Completable.defer(() -> { + return transport.send(ByteBuffer.wrap(handshake.getBytes(StandardCharsets.UTF_8))).andThen(Completable.defer(() -> { timeoutHandshakeResponse(handshakeResponseTimeout, TimeUnit.MILLISECONDS); return handshakeResponseSubject.andThen(Completable.defer(() -> { hubConnectionStateLock.lock(); @@ -767,7 +769,7 @@ public Observable stream(Class returnType, String method, Object ... a } private void sendHubMessage(HubMessage message) { - String serializedMessage = protocol.writeMessage(message); + ByteBuffer serializedMessage = protocol.writeMessage(message); if (message.getMessageType() == HubMessageType.INVOCATION ) { logger.debug("Sending {} message '{}'.", message.getMessageType().name(), ((InvocationMessage)message).getInvocationId()); } else if (message.getMessageType() == HubMessageType.STREAM_INVOCATION) { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java index 6490d1831dfb..30516b8fbe00 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java @@ -3,6 +3,7 @@ package com.microsoft.signalr; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; @@ -62,8 +63,9 @@ public Completable stop() { } @Override - public Completable send(String message) { - websocketClient.send(message); + public Completable send(ByteBuffer message) { + ByteString bs = ByteString.of(message); + websocketClient.send(bs); return Completable.complete(); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Transport.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Transport.java index 97e3a81896ae..63c844452c55 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Transport.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Transport.java @@ -3,11 +3,13 @@ package com.microsoft.signalr; +import java.nio.ByteBuffer; + import io.reactivex.Completable; interface Transport { Completable start(String url); - Completable send(String message); + Completable send(ByteBuffer message); void setOnReceive(OnReceiveCallBack callback); void onReceive(String message); void setOnClose(TransportOnClosedCallback onCloseCallback); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketWrapper.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketWrapper.java index 06d57a159bd3..773f40ab0e07 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketWrapper.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketWrapper.java @@ -3,6 +3,8 @@ package com.microsoft.signalr; +import java.nio.ByteBuffer; + import io.reactivex.Completable; abstract class WebSocketWrapper { @@ -10,7 +12,7 @@ abstract class WebSocketWrapper { public abstract Completable stop(); - public abstract Completable send(String message); + public abstract Completable send(ByteBuffer message); public abstract void setOnReceive(OnReceiveCallBack onReceive); From 9dc60f169aebbc2824fefb1d560ce67cb801339c Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 30 Jul 2020 16:15:10 -0700 Subject: [PATCH 27/64] Fixup more stuff --- .../main/java/com/microsoft/signalr/DefaultHttpClient.java | 6 ++++-- .../src/main/java/com/microsoft/signalr/HttpClient.java | 5 +++-- .../java/com/microsoft/signalr/LongPollingTransport.java | 3 ++- .../main/java/com/microsoft/signalr/WebSocketTransport.java | 4 +++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/DefaultHttpClient.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/DefaultHttpClient.java index 8f4f4f0df91d..2eb1cc203069 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/DefaultHttpClient.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/DefaultHttpClient.java @@ -4,6 +4,7 @@ package com.microsoft.signalr; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -15,6 +16,7 @@ import io.reactivex.Single; import io.reactivex.subjects.SingleSubject; import okhttp3.*; +import okio.ByteString; final class DefaultHttpClient extends HttpClient { private OkHttpClient client = null; @@ -104,7 +106,7 @@ public Single send(HttpRequest httpRequest) { } @Override - public Single send(HttpRequest httpRequest, String bodyContent) { + public Single send(HttpRequest httpRequest, ByteBuffer bodyContent) { Request.Builder requestBuilder = new Request.Builder().url(httpRequest.getUrl()); switch (httpRequest.getMethod()) { @@ -114,7 +116,7 @@ public Single send(HttpRequest httpRequest, String bodyContent) { case "POST": RequestBody body; if (bodyContent != null) { - body = RequestBody.create(MediaType.parse("text/plain"), bodyContent); + body = RequestBody.create(MediaType.parse("text/plain"), ByteString.of(bodyContent)); } else { body = RequestBody.create(null, new byte[]{}); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpClient.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpClient.java index 97efaa980486..597c3d04011e 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpClient.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpClient.java @@ -3,6 +3,7 @@ package com.microsoft.signalr; +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; @@ -95,7 +96,7 @@ public Single post(String url) { return this.send(request); } - public Single post(String url, String body, HttpRequest options) { + public Single post(String url, ByteBuffer body, HttpRequest options) { options.setUrl(url); options.setMethod("POST"); return this.send(options, body); @@ -122,7 +123,7 @@ public Single delete(String url, HttpRequest options) { public abstract Single send(HttpRequest request); - public abstract Single send(HttpRequest request, String body); + public abstract Single send(HttpRequest request, ByteBuffer body); public abstract WebSocketWrapper createWebSocket(String url, Map headers); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java index 32eaac224419..179eeb652e3d 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java @@ -3,6 +3,7 @@ package com.microsoft.signalr; +import java.nio.ByteBuffer; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -121,7 +122,7 @@ private Completable poll(String url) { } @Override - public Completable send(String message) { + public Completable send(ByteBuffer message) { if (!this.active) { return Completable.error(new Exception("Cannot send unless the transport is active.")); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java index f15ffd4bcee6..c0d332e25eb9 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java @@ -3,12 +3,14 @@ package com.microsoft.signalr; +import java.nio.ByteBuffer; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.reactivex.Completable; +import okio.ByteString; class WebSocketTransport implements Transport { private WebSocketWrapper webSocketClient; @@ -59,7 +61,7 @@ public Completable start(String url) { } @Override - public Completable send(String message) { + public Completable send(ByteBuffer message) { return webSocketClient.send(message); } From f2b414ce2189a1e48893e5c05080d5df50ad98ae Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 4 Aug 2020 11:36:40 -0700 Subject: [PATCH 28/64] Convert more stuff to ByteBuffer --- .../com/microsoft/signalr/HubConnection.java | 19 ++++++++++++------- .../microsoft/signalr/JsonHubProtocol.java | 2 +- .../signalr/LongPollingTransport.java | 5 +++-- .../signalr/MessagePackHubProtocol.java | 8 ++++---- .../signalr/OkHttpWebSocketWrapper.java | 6 +++--- .../microsoft/signalr/OnReceiveCallBack.java | 4 +++- .../java/com/microsoft/signalr/Transport.java | 2 +- .../microsoft/signalr/WebSocketTransport.java | 2 +- 8 files changed, 28 insertions(+), 20 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index ad0f5b8658b7..73639f0f6f80 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -28,7 +28,7 @@ * A connection used to invoke hub methods on a SignalR Server. */ public class HubConnection implements AutoCloseable { - private static final String RECORD_SEPARATOR = "\u001e"; + private static final byte RECORD_SEPARATOR = 0x1e; private static final List> emptyArray = new ArrayList<>(); private static final int MAX_NEGOTIATE_ATTEMPTS = 100; @@ -136,7 +136,7 @@ Transport getTransport() { } this.baseUrl = url; - this.protocol = new JsonHubProtocol(); + this.protocol = new MessagePackHubProtocol(); if (accessTokenProvider != null) { this.accessTokenProvider = accessTokenProvider; @@ -167,8 +167,10 @@ Transport getTransport() { this.callback = (payload) -> { resetServerTimeout(); if (!handshakeReceived) { - int handshakeLength = payload.indexOf(RECORD_SEPARATOR) + 1; - String handshakeResponseString = payload.substring(0, handshakeLength - 1); + // The handshake will always be a UTF8 Json string, so we can convert the ByteBuffer to that to read the beginning of the payload + String payloadStr = new String(payload.array(), StandardCharsets.UTF_8); + int handshakeLength = payloadStr.indexOf(RECORD_SEPARATOR) + 1; + String handshakeResponseString = payloadStr.substring(0, handshakeLength - 1); HandshakeResponseMessage handshakeResponse; try { handshakeResponse = HandshakeProtocol.parseHandshakeResponse(handshakeResponseString); @@ -186,15 +188,18 @@ Transport getTransport() { } handshakeReceived = true; handshakeResponseSubject.onComplete(); + + // Increment the ByteBuffer payload by the byte length of the handshake + the byte length of the record separator (1) + int readBytes = handshakeResponseString.getBytes(StandardCharsets.UTF_8).length + 1; + payload = payload.position(payload.position() + readBytes); - payload = payload.substring(handshakeLength); // The payload only contained the handshake response so we can return. - if (payload.length() == 0) { + if (!payload.hasRemaining()) { return; } } - List messages = protocol.parseMessages(ByteBuffer.wrap(payload.getBytes(StandardCharsets.UTF_8)), connectionState); + List messages = protocol.parseMessages(payload, connectionState); for (HubMessage message : messages) { logger.debug("Received message of type {}.", message.getMessageType()); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index 5630db0d4508..380eabdac1a4 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -39,7 +39,7 @@ public TransferFormat getTransferFormat() { @Override public List parseMessages(ByteBuffer payload, InvocationBinder binder) { - String payloadStr = new String(payload.array(), StandardCharsets.UTF_8); + String payloadStr = new String(payload.array(), StandardCharsets.UTF_8); if (payloadStr.length() == 0) { return null; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java index 179eeb652e3d..316dc4a611df 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java @@ -4,6 +4,7 @@ package com.microsoft.signalr; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -101,7 +102,7 @@ private Completable poll(String url) { } else { if (response.getContent() != null) { logger.debug("Message received."); - onReceiveThread.submit(() ->this.onReceive(response.getContent())); + onReceiveThread.submit(() ->this.onReceive(ByteBuffer.wrap(response.getContent().getBytes(StandardCharsets.UTF_8)))); } else { logger.debug("Poll timed out, reissuing."); } @@ -139,7 +140,7 @@ public void setOnReceive(OnReceiveCallBack callback) { } @Override - public void onReceive(String message) { + public void onReceive(ByteBuffer message) { this.onReceiveCallBack.invoke(message); logger.debug("OnReceived callback has been invoked."); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 6f518fd4e428..738acc784f73 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -90,12 +90,12 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde break; } // Make sure that we actually read the right number of bytes - long readBytes = unpacker.getTotalReadBytes(); + int readBytes = (int) unpacker.getTotalReadBytes(); if (readBytes != length) { - throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", readBytes, length)); + throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", readBytes, length)); } unpacker.close(); - payload = payload.position(payload.position() + (int) readBytes); + payload = payload.position(payload.position() + readBytes); } } catch (MessagePackException | IOException ex) { throw new RuntimeException("Error reading MessagePack data.", ex); @@ -160,7 +160,7 @@ private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationB // invocationId may be nil String invocationId = null; if (!unpacker.tryUnpackNil()) { - invocationId = unpacker.unpackString(); + invocationId = unpacker.unpackString(); } // For MsgPack, we represent an empty invocation ID as an empty string, diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java index 30516b8fbe00..ae273637204d 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OkHttpWebSocketWrapper.java @@ -64,7 +64,7 @@ public Completable stop() { @Override public Completable send(ByteBuffer message) { - ByteString bs = ByteString.of(message); + ByteString bs = ByteString.of(message); websocketClient.send(bs); return Completable.complete(); } @@ -87,12 +87,12 @@ public void onOpen(WebSocket webSocket, Response response) { @Override public void onMessage(WebSocket webSocket, String message) { - onReceive.invoke(message); + onReceive.invoke(ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8))); } @Override public void onMessage(WebSocket webSocket, ByteString bytes) { - onReceive.invoke(new String(bytes.toByteArray(), StandardCharsets.ISO_8859_1)); + onReceive.invoke(bytes.asByteBuffer()); } @Override diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OnReceiveCallBack.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OnReceiveCallBack.java index 71faede3f000..4dde2204404f 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OnReceiveCallBack.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/OnReceiveCallBack.java @@ -3,6 +3,8 @@ package com.microsoft.signalr; +import java.nio.ByteBuffer; + interface OnReceiveCallBack { - void invoke(String message); + void invoke(ByteBuffer message); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Transport.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Transport.java index 63c844452c55..ab0d547898bc 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Transport.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Transport.java @@ -11,7 +11,7 @@ interface Transport { Completable start(String url); Completable send(ByteBuffer message); void setOnReceive(OnReceiveCallBack callback); - void onReceive(String message); + void onReceive(ByteBuffer message); void setOnClose(TransportOnClosedCallback onCloseCallback); Completable stop(); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java index c0d332e25eb9..113ced03f89b 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java @@ -72,7 +72,7 @@ public void setOnReceive(OnReceiveCallBack callback) { } @Override - public void onReceive(String message) { + public void onReceive(ByteBuffer message) { this.onReceiveCallBack.invoke(message); } From 7559cc41593b084a4a6323dc32fdc607d2a90d86 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 4 Aug 2020 14:00:08 -0700 Subject: [PATCH 29/64] Account for ReadOnly --- .../main/java/com/microsoft/signalr/HubConnection.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index 73639f0f6f80..f409e72e9f10 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -166,9 +166,15 @@ Transport getTransport() { this.callback = (payload) -> { resetServerTimeout(); + // MessagePack library can't handle read-only ByteBuffer - copy into an array-backed ByteBuffer if this is the case + if (payload.isReadOnly()) { + byte[] payloadBytes = new byte[payload.remaining()]; + payload.get(payloadBytes, 0, payloadBytes.length); + payload = ByteBuffer.wrap(payloadBytes); + } if (!handshakeReceived) { - // The handshake will always be a UTF8 Json string, so we can convert the ByteBuffer to that to read the beginning of the payload - String payloadStr = new String(payload.array(), StandardCharsets.UTF_8); + // The handshake will always be a UTF8 Json string, so we can convert the ByteBuffer to that to read the beginning of the payload + String payloadStr = new String(payload.array(), StandardCharsets.UTF_8); int handshakeLength = payloadStr.indexOf(RECORD_SEPARATOR) + 1; String handshakeResponseString = payloadStr.substring(0, handshakeLength - 1); HandshakeResponseMessage handshakeResponse; From 89b126eef252aaefa61f0c4a120221d75fa6da23 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 4 Aug 2020 14:04:39 -0700 Subject: [PATCH 30/64] Spacing --- .../main/java/com/microsoft/signalr/HubConnection.java | 8 ++++---- .../main/java/com/microsoft/signalr/JsonHubProtocol.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index f409e72e9f10..d485b0d488cf 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -166,11 +166,11 @@ Transport getTransport() { this.callback = (payload) -> { resetServerTimeout(); - // MessagePack library can't handle read-only ByteBuffer - copy into an array-backed ByteBuffer if this is the case + // MessagePack library can't handle read-only ByteBuffer - copy into an array-backed ByteBuffer if this is the case if (payload.isReadOnly()) { - byte[] payloadBytes = new byte[payload.remaining()]; - payload.get(payloadBytes, 0, payloadBytes.length); - payload = ByteBuffer.wrap(payloadBytes); + byte[] payloadBytes = new byte[payload.remaining()]; + payload.get(payloadBytes, 0, payloadBytes.length); + payload = ByteBuffer.wrap(payloadBytes); } if (!handshakeReceived) { // The handshake will always be a UTF8 Json string, so we can convert the ByteBuffer to that to read the beginning of the payload diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index 380eabdac1a4..e5ea550c70c1 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -39,7 +39,7 @@ public TransferFormat getTransferFormat() { @Override public List parseMessages(ByteBuffer payload, InvocationBinder binder) { - String payloadStr = new String(payload.array(), StandardCharsets.UTF_8); + String payloadStr = new String(payload.array(), StandardCharsets.UTF_8); if (payloadStr.length() == 0) { return null; } From 4895d6dff192b749aa31f926b3399a99214f1f87 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 5 Aug 2020 09:38:51 -0700 Subject: [PATCH 31/64] No need to reset ByteBuffer when setting position --- .../src/main/java/com/microsoft/signalr/HubConnection.java | 4 ++-- .../java/com/microsoft/signalr/MessagePackHubProtocol.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index d485b0d488cf..258983a62703 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -136,7 +136,7 @@ Transport getTransport() { } this.baseUrl = url; - this.protocol = new MessagePackHubProtocol(); + this.protocol = new JsonHubProtocol(); if (accessTokenProvider != null) { this.accessTokenProvider = accessTokenProvider; @@ -197,7 +197,7 @@ Transport getTransport() { // Increment the ByteBuffer payload by the byte length of the handshake + the byte length of the record separator (1) int readBytes = handshakeResponseString.getBytes(StandardCharsets.UTF_8).length + 1; - payload = payload.position(payload.position() + readBytes); + payload.position(payload.position() + readBytes); // The payload only contained the handshake response so we can return. if (!payload.hasRemaining()) { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 738acc784f73..d90350dc0987 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -95,7 +95,7 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", readBytes, length)); } unpacker.close(); - payload = payload.position(payload.position() + readBytes); + payload.position(payload.position() + readBytes); } } catch (MessagePackException | IOException ex) { throw new RuntimeException("Error reading MessagePack data.", ex); From 7c8a2fb318f1086b0f2a00d7af3f95bab1f881ac Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 5 Aug 2020 09:48:59 -0700 Subject: [PATCH 32/64] Add Protocol to HubConnection ctor --- .../signalr/HttpHubConnectionBuilder.java | 14 +++++++++++++- .../java/com/microsoft/signalr/HubConnection.java | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java index e1f38e6888a4..f4b2593ef292 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java @@ -16,6 +16,7 @@ public class HttpHubConnectionBuilder { private final String url; private Transport transport; private HttpClient httpClient; + private HubProtocol protocol; private boolean skipNegotiate; private Single accessTokenProvider; private long handshakeResponseTimeout = 0; @@ -54,6 +55,17 @@ HttpHubConnectionBuilder withHttpClient(HttpClient httpClient) { this.httpClient = httpClient; return this; } + + /** + * Sets the {@link HubProtocol} to be used by the {@link HubConnection}. + * + * @param protocol The {@link HubProtocol} to be used by the {@link HubConnection}. + * @return This instance of the HttpHubConnectionBuilder. + */ + HttpHubConnectionBuilder withProtocol(HubProtocol protocol) { + this.protocol = protocol; + return this; + } /** * Indicates to the {@link HubConnection} that it should skip the negotiate process. @@ -133,7 +145,7 @@ public HttpHubConnectionBuilder setHttpClientBuilderCallback(Action1 accessTokenProvider, long handshakeResponseTimeout, Map headers, TransportEnum transportEnum, Action1 configureBuilder) { if (url == null || url.isEmpty()) { @@ -136,7 +136,7 @@ Transport getTransport() { } this.baseUrl = url; - this.protocol = new JsonHubProtocol(); + this.protocol = protocol; if (accessTokenProvider != null) { this.accessTokenProvider = accessTokenProvider; From 63b49fccd0a5a28a461dbfc68276399cda76ce80 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 5 Aug 2020 10:11:37 -0700 Subject: [PATCH 33/64] Set default, make stuff public --- .../java/com/microsoft/signalr/HttpHubConnectionBuilder.java | 4 ++-- .../src/main/java/com/microsoft/signalr/JsonHubProtocol.java | 2 +- .../java/com/microsoft/signalr/MessagePackHubProtocol.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java index f4b2593ef292..435194bfce13 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java @@ -16,7 +16,7 @@ public class HttpHubConnectionBuilder { private final String url; private Transport transport; private HttpClient httpClient; - private HubProtocol protocol; + private HubProtocol protocol = new JsonHubProtocol(); private boolean skipNegotiate; private Single accessTokenProvider; private long handshakeResponseTimeout = 0; @@ -62,7 +62,7 @@ HttpHubConnectionBuilder withHttpClient(HttpClient httpClient) { * @param protocol The {@link HubProtocol} to be used by the {@link HubConnection}. * @return This instance of the HttpHubConnectionBuilder. */ - HttpHubConnectionBuilder withProtocol(HubProtocol protocol) { + public HttpHubConnectionBuilder withProtocol(HubProtocol protocol) { this.protocol = protocol; return this; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index e5ea550c70c1..911582cb19b5 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -17,7 +17,7 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; -class JsonHubProtocol implements HubProtocol { +public class JsonHubProtocol implements HubProtocol { private final JsonParser jsonParser = new JsonParser(); private final Gson gson = new Gson(); private static final String RECORD_SEPARATOR = "\u001e"; diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index d90350dc0987..9dd151febec1 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -22,7 +22,7 @@ import org.msgpack.core.MessageUnpacker; import org.msgpack.value.ValueType; -class MessagePackHubProtocol implements HubProtocol { +public class MessagePackHubProtocol implements HubProtocol { private static final int ERROR_RESULT = 1; private static final int VOID_RESULT = 2; From e9a2b6a9380b308c508177699ae67e910a7b67c1 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 5 Aug 2020 12:29:41 -0700 Subject: [PATCH 34/64] Fixup tests --- .../microsoft/signalr/HubConnectionTest.java | 8 +- .../signalr/JsonHubProtocolTest.java | 102 ++++++++++-------- .../signalr/LongPollingTransportTest.java | 19 ++-- .../com/microsoft/signalr/MockTransport.java | 17 +-- .../com/microsoft/signalr/TestHttpClient.java | 3 +- .../signalr/WebSocketTransportTest.java | 5 +- 6 files changed, 94 insertions(+), 60 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index ab43980dd9fe..906db62e8b0f 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -5,6 +5,8 @@ import static org.junit.jupiter.api.Assertions.*; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.List; import java.util.concurrent.CancellationException; @@ -2287,9 +2289,11 @@ public void connectionSendsPingsRegularly() throws InterruptedException { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet(); + ByteBuffer messageBuffer = mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet(); + String message = new String(messageBuffer.array(), StandardCharsets.UTF_8); assertEquals("{\"type\":6}" + RECORD_SEPARATOR, message); - message = mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet(); + messageBuffer = mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet(); + message = new String(messageBuffer.array(), StandardCharsets.UTF_8); assertEquals("{\"type\":6}" + RECORD_SEPARATOR, message); hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java index c8c0d39d9cd2..0cacd6825404 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java @@ -5,6 +5,8 @@ import static org.junit.jupiter.api.Assertions.*; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -33,7 +35,7 @@ public void checkTransferFormat() { @Test public void verifyWriteMessage() { InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] {"42"}, null); - String result = jsonHubProtocol.writeMessage(invocationMessage); + String result = new String(jsonHubProtocol.writeMessage(invocationMessage).array(), StandardCharsets.UTF_8); String expectedResult = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"42\"]}\u001E"; assertEquals(expectedResult, result); } @@ -41,9 +43,10 @@ public void verifyWriteMessage() { @Test public void parsePingMessage() { String stringifiedMessage = "{\"type\":6}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(PingMessage.getInstance()); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); //We know it's only one message assertNotNull(messages); @@ -54,9 +57,10 @@ public void parsePingMessage() { @Test public void parseCloseMessage() { String stringifiedMessage = "{\"type\":7}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new CloseMessage()); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); //We know it's only one message assertNotNull(messages); @@ -73,9 +77,10 @@ public void parseCloseMessage() { @Test public void parseCloseMessageWithError() { String stringifiedMessage = "{\"type\":7,\"error\": \"There was an error\"}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new CloseMessage("There was an error")); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); //We know it's only one message assertNotNull(messages); @@ -92,9 +97,10 @@ public void parseCloseMessageWithError() { @Test public void parseSingleMessage() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42 }, null)); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); //We know it's only one message assertNotNull(messages); @@ -115,27 +121,30 @@ public void parseSingleMessage() { @Test public void parseSingleUnsupportedStreamInvocationMessage() { String stringifiedMessage = "{\"type\":4,\"Id\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new StreamInvocationMessage(null, "1", "test", new Object[] { 42 }, null)); - Throwable exception = assertThrows(UnsupportedOperationException.class, () -> jsonHubProtocol.parseMessages(stringifiedMessage, binder)); + Throwable exception = assertThrows(UnsupportedOperationException.class, () -> jsonHubProtocol.parseMessages(message, binder)); assertEquals("The message type STREAM_INVOCATION is not supported yet.", exception.getMessage()); } @Test public void parseSingleUnsupportedCancelInvocationMessage() { String stringifiedMessage = "{\"type\":5,\"invocationId\":123}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(null); - Throwable exception = assertThrows(UnsupportedOperationException.class, () -> jsonHubProtocol.parseMessages(stringifiedMessage, binder)); + Throwable exception = assertThrows(UnsupportedOperationException.class, () -> jsonHubProtocol.parseMessages(message, binder)); assertEquals("The message type CANCEL_INVOCATION is not supported yet.", exception.getMessage()); } @Test public void parseTwoMessages() { - String twoMessages = "{\"type\":1,\"target\":\"one\",\"arguments\":[42]}\u001E{\"type\":1,\"target\":\"two\",\"arguments\":[43]}\u001E"; + String stringifiedMessage = "{\"type\":1,\"target\":\"one\",\"arguments\":[42]}\u001E{\"type\":1,\"target\":\"two\",\"arguments\":[43]}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "one", new Object[] { 42 }, null)); - List messages = jsonHubProtocol.parseMessages(twoMessages, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); assertNotNull(messages); assertEquals(2, messages.size()); @@ -166,9 +175,10 @@ public void parseTwoMessages() { @Test public void parseSingleMessageMutipleArgs() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42, 24 }, null)); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); assertNotNull(messages); assertEquals(1, messages.size()); @@ -176,11 +186,11 @@ public void parseSingleMessageMutipleArgs() { //We know it's only one message assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); - InvocationMessage message = (InvocationMessage)messages.get(0); - assertEquals("test", message.getTarget()); - assertEquals(null, message.getInvocationId()); - int messageResult = (int) message.getArguments()[0]; - int messageResult2 = (int) message.getArguments()[1]; + InvocationMessage invocationMessage = (InvocationMessage)messages.get(0); + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + int messageResult = (int) invocationMessage.getArguments()[0]; + int messageResult2 = (int) invocationMessage.getArguments()[1]; assertEquals(42, messageResult); assertEquals(24, messageResult2); } @@ -188,9 +198,10 @@ public void parseSingleMessageMutipleArgs() { @Test public void parseMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42, 24 }, null)); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); assertNotNull(messages); assertEquals(1, messages.size()); @@ -198,11 +209,11 @@ public void parseMessageWithOutOfOrderProperties() { // We know it's only one message assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); - InvocationMessage message = (InvocationMessage) messages.get(0); - assertEquals("test", message.getTarget()); - assertEquals(null, message.getInvocationId()); - int messageResult = (int) message.getArguments()[0]; - int messageResult2 = (int) message.getArguments()[1]; + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + int messageResult = (int) invocationMessage.getArguments()[0]; + int messageResult2 = (int) invocationMessage.getArguments()[1]; assertEquals(42, messageResult); assertEquals(24, messageResult2); } @@ -210,9 +221,10 @@ public void parseMessageWithOutOfOrderProperties() { @Test public void parseCompletionMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"type\":3,\"result\":42,\"invocationId\":\"1\"}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new CompletionMessage(null, "1", 42, null)); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); assertNotNull(messages); assertEquals(1, messages.size()); @@ -220,94 +232,100 @@ public void parseCompletionMessageWithOutOfOrderProperties() { // We know it's only one message assertEquals(HubMessageType.COMPLETION, messages.get(0).getMessageType()); - CompletionMessage message = (CompletionMessage) messages.get(0); - assertEquals(null, message.getError()); - assertEquals(42 , message.getResult()); + CompletionMessage completionMessage = (CompletionMessage) messages.get(0); + assertEquals(null, completionMessage.getError()); + assertEquals(42 , completionMessage.getResult()); } @Test public void invocationBindingFailureWhileParsingTooManyArgumentsWithOutOfOrderProperties() { String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); assertNotNull(messages); assertEquals(1, messages.size()); assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); - InvocationBindingFailureMessage message = (InvocationBindingFailureMessage)messages.get(0); - assertEquals("Invocation provides 2 argument(s) but target expects 1.", message.getException().getMessage()); + InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage)messages.get(0); + assertEquals("Invocation provides 2 argument(s) but target expects 1.", invocationBindingFailureMessage.getException().getMessage()); } @Test public void invocationBindingFailureWhileParsingTooManyArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); assertNotNull(messages); assertEquals(1, messages.size()); assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); - InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages.get(0); - assertEquals("Invocation provides 2 argument(s) but target expects 1.", message.getException().getMessage()); + InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); + assertEquals("Invocation provides 2 argument(s) but target expects 1.", invocationBindingFailureMessage.getException().getMessage()); } @Test public void invocationBindingFailureWhileParsingTooFewArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); assertNotNull(messages); assertEquals(1, messages.size()); assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); - InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages.get(0); - assertEquals("Invocation provides 1 argument(s) but target expects 2.", message.getException().getMessage()); + InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); + assertEquals("Invocation provides 1 argument(s) but target expects 2.", invocationBindingFailureMessage.getException().getMessage()); } @Test public void invocationBindingFailureWhenParsingIncorrectType() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"]}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); assertNotNull(messages); assertEquals(1, messages.size()); assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); - InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages.get(0); - assertEquals("java.lang.NumberFormatException: For input string: \"true\"", message.getException().getMessage()); + InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); + assertEquals("java.lang.NumberFormatException: For input string: \"true\"", invocationBindingFailureMessage.getException().getMessage()); } @Test public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"],\"invocationId\":\"123\"}\u001E"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); - List messages = jsonHubProtocol.parseMessages(stringifiedMessage, binder); + List messages = jsonHubProtocol.parseMessages(message, binder); assertNotNull(messages); assertEquals(1, messages.size()); assertEquals(InvocationBindingFailureMessage.class, messages.get(0).getClass()); - InvocationBindingFailureMessage message = (InvocationBindingFailureMessage) messages.get(0); - assertEquals("java.lang.NumberFormatException: For input string: \"true\"", message.getException().getMessage()); - assertEquals("123", message.getInvocationId()); + InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); + assertEquals("java.lang.NumberFormatException: For input string: \"true\"", invocationBindingFailureMessage.getException().getMessage()); + assertEquals("123", invocationBindingFailureMessage.getInvocationId()); } @Test public void errorWhileParsingIncompleteMessage() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":"; + ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); RuntimeException exception = assertThrows(RuntimeException.class, - () -> jsonHubProtocol.parseMessages(stringifiedMessage, binder)); + () -> jsonHubProtocol.parseMessages(message, binder)); assertEquals("Message is incomplete.", exception.getMessage()); } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java index 5a9408387e81..b8655467d792 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java @@ -5,6 +5,8 @@ import static org.junit.jupiter.api.Assertions.*; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -39,7 +41,8 @@ public void LongPollingTransportCantSendBeforeStart() { Map headers = new HashMap<>(); LongPollingTransport transport = new LongPollingTransport(headers, client, Single.just("")); - Throwable exception = assertThrows(RuntimeException.class, () -> transport.send("First").timeout(1, TimeUnit.SECONDS).blockingAwait()); + ByteBuffer sendBuffer = ByteBuffer.wrap("First".getBytes(StandardCharsets.UTF_8)); + Throwable exception = assertThrows(RuntimeException.class, () -> transport.send(sendBuffer).timeout(1, TimeUnit.SECONDS).blockingAwait()); assertEquals(Exception.class, exception.getCause().getClass()); assertEquals("Cannot send unless the transport is active.", exception.getCause().getMessage()); assertFalse(transport.isActive()); @@ -117,7 +120,8 @@ public void CanSetAndTriggerOnReceive() { // The transport doesn't need to be active to trigger onReceive for the case // when we are handling the last outstanding poll. - transport.onReceive("TEST"); + ByteBuffer onReceiveBuffer = ByteBuffer.wrap("TEST".getBytes(StandardCharsets.UTF_8)); + transport.onReceive(onReceiveBuffer); assertTrue(onReceivedRan.get()); } @@ -145,7 +149,7 @@ public void LongPollingTransportOnReceiveGetsCalled() { AtomicReference message = new AtomicReference<>(); transport.setOnReceive((msg -> { onReceiveCalled.set(true); - message.set(msg); + message.set(new String(msg.array(), StandardCharsets.UTF_8)); block.onComplete(); }) ); @@ -227,7 +231,8 @@ public void LongPollingTransportSendsHeaders() { transport.setOnClose((error) -> {}); transport.start("http://example.com").timeout(1, TimeUnit.SECONDS).blockingAwait(); - assertTrue(transport.send("TEST").blockingAwait(1, TimeUnit.SECONDS)); + ByteBuffer sendBuffer = ByteBuffer.wrap("TEST".getBytes(StandardCharsets.UTF_8)); + assertTrue(transport.send(sendBuffer).blockingAwait(1, TimeUnit.SECONDS)); close.onComplete(); assertEquals(headerValue.get(), "VALUE"); } @@ -258,7 +263,8 @@ public void LongPollingTransportSetsAuthorizationHeader() { transport.setOnClose((error) -> {}); transport.start("http://example.com").timeout(1, TimeUnit.SECONDS).blockingAwait(); - assertTrue(transport.send("TEST").blockingAwait(1, TimeUnit.SECONDS)); + ByteBuffer sendBuffer = ByteBuffer.wrap("TEST".getBytes(StandardCharsets.UTF_8)); + assertTrue(transport.send(sendBuffer).blockingAwait(1, TimeUnit.SECONDS)); assertEquals(headerValue.get(), "Bearer TOKEN"); close.onComplete(); } @@ -294,7 +300,8 @@ public void LongPollingTransportRunsAccessTokenProviderEveryRequest() { transport.start("http://example.com").timeout(1, TimeUnit.SECONDS).blockingAwait(); secondGet.blockingAwait(1, TimeUnit.SECONDS); - assertTrue(transport.send("TEST").blockingAwait(1, TimeUnit.SECONDS)); + ByteBuffer sendBuffer = ByteBuffer.wrap("TEST".getBytes(StandardCharsets.UTF_8)); + assertTrue(transport.send(sendBuffer).blockingAwait(1, TimeUnit.SECONDS)); assertEquals("Bearer TOKEN2", headerValue.get()); close.onComplete(); } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java index 6b65067c621b..f02804cd600e 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java @@ -3,6 +3,8 @@ package com.microsoft.signalr; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import io.reactivex.Completable; @@ -11,14 +13,14 @@ class MockTransport implements Transport { private OnReceiveCallBack onReceiveCallBack; - private ArrayList sentMessages = new ArrayList<>(); + private ArrayList sentMessages = new ArrayList<>(); private String url; private TransportOnClosedCallback onClose; final private boolean ignorePings; final private boolean autoHandshake; final private CompletableSubject startSubject = CompletableSubject.create(); final private CompletableSubject stopSubject = CompletableSubject.create(); - private SingleSubject sendSubject = SingleSubject.create(); + private SingleSubject sendSubject = SingleSubject.create(); private static final String RECORD_SEPARATOR = "\u001e"; @@ -40,7 +42,8 @@ public Completable start(String url) { this.url = url; if (autoHandshake) { try { - onReceiveCallBack.invoke("{}" + RECORD_SEPARATOR); + ByteBuffer invokeBuffer = ByteBuffer.wrap(("{}" + RECORD_SEPARATOR).getBytes(StandardCharsets.UTF_8)); + onReceiveCallBack.invoke(invokeBuffer); } catch (Exception e) { throw new RuntimeException(e); } @@ -50,7 +53,7 @@ public Completable start(String url) { } @Override - public Completable send(String message) { + public Completable send(ByteBuffer message) { if (!(ignorePings && message.equals("{\"type\":6}" + RECORD_SEPARATOR))) { sentMessages.add(message); sendSubject.onSuccess(message); @@ -65,7 +68,7 @@ public void setOnReceive(OnReceiveCallBack callback) { } @Override - public void onReceive(String message) { + public void onReceive(ByteBuffer message) { this.onReceiveCallBack.invoke(message); } @@ -86,14 +89,14 @@ public void stopWithError(String errorMessage) { } public void receiveMessage(String message) { - this.onReceive(message); + this.onReceive(ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8))); } public String[] getSentMessages() { return sentMessages.toArray(new String[sentMessages.size()]); } - public SingleSubject getNextSentMessage() { + public SingleSubject getNextSentMessage() { return sendSubject; } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestHttpClient.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestHttpClient.java index ef3c27989f5e..b257076e915c 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestHttpClient.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestHttpClient.java @@ -3,6 +3,7 @@ package com.microsoft.signalr; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -27,7 +28,7 @@ public Single send(HttpRequest request) { } @Override - public Single send(HttpRequest request, String body) { + public Single send(HttpRequest request, ByteBuffer body) { this.sentRequests.add(request); return this.handler.invoke(request); } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/WebSocketTransportTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/WebSocketTransportTest.java index 4aeec16836ee..62ec1e95d7bc 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/WebSocketTransportTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/WebSocketTransportTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.*; +import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -35,7 +36,7 @@ public Single send(HttpRequest request) { } @Override - public Single send(HttpRequest request, String body) { + public Single send(HttpRequest request, ByteBuffer body) { return null; } @@ -71,7 +72,7 @@ public Completable stop() { } @Override - public Completable send(String message) { + public Completable send(ByteBuffer message) { return null; } From 897a15fffa8f6eb72326cb808327521b85cdac2f Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 5 Aug 2020 13:45:53 -0700 Subject: [PATCH 35/64] More test cleanup --- .../microsoft/signalr/HubConnectionTest.java | 225 ++++++++++-------- .../signalr/JsonHubProtocolTest.java | 34 +-- .../signalr/LongPollingTransportTest.java | 17 +- .../com/microsoft/signalr/MockTransport.java | 9 +- .../java/com/microsoft/signalr/TestUtils.java | 11 + 5 files changed, 163 insertions(+), 133 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index 906db62e8b0f..23e665af5d4e 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.*; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.List; import java.util.concurrent.CancellationException; @@ -204,7 +203,8 @@ public void hubConnectionReceiveHandshakeResponseWithError() { hubConnection.start(); mockTransport.getStartTask().timeout(1, TimeUnit.SECONDS).blockingAwait(); - Throwable exception = assertThrows(RuntimeException.class, () -> mockTransport.receiveMessage("{\"error\":\"Requested protocol 'messagepack' is not available.\"}" + RECORD_SEPARATOR)); + Throwable exception = assertThrows(RuntimeException.class, () -> + mockTransport.receiveMessage("{\"error\":\"Requested protocol 'messagepack' is not available.\"}" + RECORD_SEPARATOR)); assertEquals("Error in handshake Requested protocol 'messagepack' is not available.", exception.getMessage()); } @@ -222,7 +222,7 @@ public void registeringMultipleHandlersAndBothGetTriggered() { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = mockTransport.getSentMessages()[0]; + String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -245,7 +245,7 @@ public void removeHandlerByName() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = mockTransport.getSentMessages()[0]; + String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -272,7 +272,7 @@ public void addAndRemoveHandlerImmediately() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = mockTransport.getSentMessages()[0]; + String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -297,7 +297,7 @@ public void removingMultipleHandlersWithOneCallToRemove() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = mockTransport.getSentMessages()[0]; + String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -326,7 +326,7 @@ public void removeHandlerWithUnsubscribe() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = mockTransport.getSentMessages()[0]; + String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -358,7 +358,7 @@ public void unsubscribeTwice() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = mockTransport.getSentMessages()[0]; + String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -393,7 +393,7 @@ public void removeSingleHandlerWithUnsubscribe() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = mockTransport.getSentMessages()[0]; + String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -462,8 +462,8 @@ public void checkStreamUploadSingleItemThroughSend() { hubConnection.send("UploadStream", stream); stream.onNext("FirstItem"); - String[] messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", messages[2]); + ByteBuffer[] messages = mockTransport.getSentMessages(); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); stream.onComplete(); messages = mockTransport.getSentMessages(); @@ -484,17 +484,17 @@ public void checkStreamUploadMultipleStreamsThroughSend() { firstStream.onNext("First Stream 1"); secondStream.onNext("Second Stream 1"); - String[] messages = mockTransport.getSentMessages(); + ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First Stream 1\"}\u001E", messages[2]); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Second Stream 1\"}\u001E", messages[3]); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First Stream 1\"}\u001E", TestUtils.ByteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Second Stream 1\"}\u001E", TestUtils.ByteBufferToString(messages[3])); firstStream.onComplete(); secondStream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(6, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", messages[4]); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", messages[5]); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[4])); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[5])); } @Test @@ -508,13 +508,13 @@ public void checkStreamUploadThroughSendWithArgs() { hubConnection.send("UploadStream", stream, 12); stream.onNext("FirstItem"); - String[] messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", messages[1]); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", messages[2]); + ByteBuffer[] messages = mockTransport.getSentMessages(); + assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); stream.onComplete(); messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", messages[3]); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[3])); } @Test @@ -528,13 +528,13 @@ public void streamMapIsClearedOnClose() { hubConnection.send("UploadStream", stream, 12); stream.onNext("FirstItem"); - String[] messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", messages[1]); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", messages[2]); + ByteBuffer[] messages = mockTransport.getSentMessages(); + assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); stream.onComplete(); messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", messages[3]); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[3])); hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -557,11 +557,11 @@ public void streamMapEntriesRemovedOnStreamClose() { stream.onNext("FirstItem"); secondStream.onNext("SecondItem"); - String[] messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", messages[1]); - assertEquals("{\"type\":1,\"target\":\"SecondUploadStream\",\"arguments\":[13],\"streamIds\":[\"2\"]}\u001E", messages[2]); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", messages[3]); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"SecondItem\"}\u001E", messages[4]); + ByteBuffer[] messages = mockTransport.getSentMessages(); + assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); + assertEquals("{\"type\":1,\"target\":\"SecondUploadStream\",\"arguments\":[13],\"streamIds\":[\"2\"]}\u001E", TestUtils.ByteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[3])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"SecondItem\"}\u001E", TestUtils.ByteBufferToString(messages[4])); assertEquals(2, hubConnection.getStreamMap().size()); @@ -596,17 +596,17 @@ public void useSameSubjectMultipleTimes() { hubConnection.send("UploadStream", stream, stream); stream.onNext("FirstItem"); - String[] messages = mockTransport.getSentMessages(); + ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"1\",\"2\"]}\u001E", messages[1]); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", messages[2]); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", messages[3]); + assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"1\",\"2\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[3])); stream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(6, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", messages[4]); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", messages[5]); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[4])); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[5])); } @Test @@ -620,14 +620,15 @@ public void checkStreamUploadSingleItemThroughInvoke() { hubConnection.invoke(String.class, "UploadStream", stream); stream.onNext("FirstItem"); - String[] messages = mockTransport.getSentMessages(); + ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(3, messages.length); - assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"2\"]}\u001E", messages[1]); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", messages[2]); + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"2\"]}\u001E", + TestUtils.ByteBufferToString(messages[1])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); stream.onComplete(); messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", messages[3]); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[3])); } @Test @@ -642,15 +643,16 @@ public void checkStreamUploadSingleItemThroughStream() { stream.onNext("FirstItem"); - String[] messages = mockTransport.getSentMessages(); + ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(3, messages.length); - assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"2\"]}\u001E", messages[1]); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", messages[2]); + assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"2\"]}\u001E", + TestUtils.ByteBufferToString(messages[1])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); stream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", messages[3]); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[3])); } @Test @@ -665,26 +667,28 @@ public void useSameSubjectInMutlipleStreamsFromDifferentMethods() { hubConnection.invoke(String.class, "UploadStream", stream); hubConnection.stream(String.class, "UploadStream", stream); - String[] messages = mockTransport.getSentMessages(); + ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"1\"]}\u001E", messages[1]); - assertEquals("{\"type\":1,\"invocationId\":\"2\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"3\"]}\u001E", messages[2]); - assertEquals("{\"type\":4,\"invocationId\":\"4\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"5\"]}\u001E", messages[3]); + assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"1\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); + assertEquals("{\"type\":1,\"invocationId\":\"2\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"3\"]}\u001E", + TestUtils.ByteBufferToString(messages[2])); + assertEquals("{\"type\":4,\"invocationId\":\"4\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"5\"]}\u001E", + TestUtils.ByteBufferToString(messages[3])); stream.onNext("FirstItem"); messages = mockTransport.getSentMessages(); assertEquals(7, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", messages[4]); - assertEquals("{\"type\":2,\"invocationId\":\"3\",\"item\":\"FirstItem\"}\u001E", messages[5]); - assertEquals("{\"type\":2,\"invocationId\":\"5\",\"item\":\"FirstItem\"}\u001E", messages[6]); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[4])); + assertEquals("{\"type\":2,\"invocationId\":\"3\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[5])); + assertEquals("{\"type\":2,\"invocationId\":\"5\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[6])); stream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(10, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", messages[7]); - assertEquals("{\"type\":3,\"invocationId\":\"3\"}\u001E", messages[8]); - assertEquals("{\"type\":3,\"invocationId\":\"5\"}\u001E", messages[9]); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[7])); + assertEquals("{\"type\":3,\"invocationId\":\"3\"}\u001E", TestUtils.ByteBufferToString(messages[8])); + assertEquals("{\"type\":3,\"invocationId\":\"5\"}\u001E", TestUtils.ByteBufferToString(messages[9])); } @Test @@ -699,10 +703,11 @@ public void streamUploadCallOnError() { stream.onNext("FirstItem"); stream.onError(new RuntimeException("onError called")); - String[] messages = mockTransport.getSentMessages(); + ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", messages[2]); - assertEquals("{\"type\":3,\"invocationId\":\"1\",\"error\":\"java.lang.RuntimeException: onError called\"}\u001E", messages[3]); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); + assertEquals("{\"type\":3,\"invocationId\":\"1\",\"error\":\"java.lang.RuntimeException: onError called\"}\u001E", + TestUtils.ByteBufferToString(messages[3])); // onComplete doesn't send a completion message after onError. stream.onComplete(); @@ -724,16 +729,16 @@ public void checkStreamUploadMultipleItemsThroughSend() { stream.onNext("SecondItem"); stream.onNext("ThirdItem"); - String[] messages = mockTransport.getSentMessages(); + ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(5, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", messages[2]); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"SecondItem\"}\u001E", messages[3]); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"ThirdItem\"}\u001E", messages[4]); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"SecondItem\"}\u001E", TestUtils.ByteBufferToString(messages[3])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"ThirdItem\"}\u001E", TestUtils.ByteBufferToString(messages[4])); stream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(6, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", messages[5]); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[5])); } @Test @@ -749,15 +754,15 @@ public void checkStreamUploadMultipleItemsThroughInvoke() { stream.onNext("FirstItem"); stream.onNext("SecondItem"); - String[] messages = mockTransport.getSentMessages(); + ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", messages[2]); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"SecondItem\"}\u001E", messages[3]); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"SecondItem\"}\u001E", TestUtils.ByteBufferToString(messages[3])); stream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(5, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", messages[4]); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[4])); } @Test @@ -780,16 +785,16 @@ public void canStartAndStopMultipleStreams() { streamOne.onComplete(); streamTwo.onComplete(); - String[] messages = mockTransport.getSentMessages(); + ByteBuffer[] messages = mockTransport.getSentMessages(); // Handshake message + 2 calls to send + 4 calls to onNext + 2 calls to onComplete = 9 assertEquals(9, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"Stream One First Item\"}\u001E", messages[3]); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Stream Two First Item\"}\u001E", messages[4]); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"Stream One Second Item\"}\u001E", messages[5]); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Stream Two Second Item\"}\u001E", messages[6]); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", messages[7]); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", messages[8]); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"Stream One First Item\"}\u001E", TestUtils.ByteBufferToString(messages[3])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Stream Two First Item\"}\u001E", TestUtils.ByteBufferToString(messages[4])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"Stream One Second Item\"}\u001E", TestUtils.ByteBufferToString(messages[5])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Stream Two Second Item\"}\u001E", TestUtils.ByteBufferToString(messages[6])); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[7])); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[8])); } @Test @@ -806,7 +811,8 @@ public void checkStreamSingleItem() { (error) -> {}, () -> completed.set(true)); - assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); assertFalse(onNextCalled.get()); @@ -834,7 +840,8 @@ public void checkStreamCompletionResult() { (error) -> {}, () -> completed.set(true)); - assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); assertFalse(onNextCalled.get()); @@ -863,7 +870,8 @@ public void checkStreamCompletionError() { (error) -> onErrorCalled.set(true), () -> {}); - assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(onErrorCalled.get()); assertFalse(onNextCalled.get()); @@ -892,7 +900,8 @@ public void checkStreamMultipleItems() { (error) -> {/*OnError*/}, () -> {/*OnCompleted*/completed.set(true);}); - assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); mockTransport.receiveMessage("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First\"}" + RECORD_SEPARATOR); @@ -918,11 +927,12 @@ public void checkCancelIsSentAfterDispose() { (error) -> {/*OnError*/}, () -> {/*OnCompleted*/completed.set(true);}); - assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); subscription.dispose(); - assertEquals("{\"type\":5,\"invocationId\":\"1\"}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[2]); + assertEquals("{\"type\":5,\"invocationId\":\"1\"}" + RECORD_SEPARATOR, TestUtils.ByteBufferToString(mockTransport.getSentMessages()[2])); } @Test @@ -944,12 +954,12 @@ public void checkCancelIsSentAfterAllSubscriptionsAreDisposed() { subscription.dispose(); assertEquals(2, mockTransport.getSentMessages().length); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1]); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); secondSubscription.dispose(); assertEquals(3, mockTransport.getSentMessages().length); assertEquals("{\"type\":5,\"invocationId\":\"1\"}" + RECORD_SEPARATOR, - mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1]); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); } @Test @@ -964,7 +974,8 @@ public void checkStreamWithDispose() { (error) -> {/*OnError*/}, () -> {/*OnCompleted*/}); - assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); mockTransport.receiveMessage("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First\"}" + RECORD_SEPARATOR); @@ -991,7 +1002,8 @@ public void checkStreamWithDisposeWithMultipleSubscriptions() { (error) -> {/*OnError*/}, () -> {/*OnCompleted*/completed.set(true);}); - assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); mockTransport.receiveMessage("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First\"}" + RECORD_SEPARATOR); @@ -1017,7 +1029,8 @@ public void invokeWaitsForCompletionMessage() { AtomicBoolean done = new AtomicBoolean(); Single result = hubConnection.invoke(Integer.class, "echo", "message"); result.doOnSuccess(value -> done.set(true)).subscribe(); - assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); @@ -1037,7 +1050,8 @@ public void invokeNoReturnValueWaitsForCompletion() { Completable result = hubConnection.invoke("test", "message"); result.doOnComplete(() -> done.set(true)).subscribe(); - assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\"}" + RECORD_SEPARATOR); @@ -1057,7 +1071,8 @@ public void invokeCompletedByCompletionMessageWithResult() { Completable result = hubConnection.invoke("test", "message"); result.doOnComplete(() -> done.set(true)).subscribe(); - assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); @@ -1077,10 +1092,12 @@ public void completionWithResultAndErrorHandlesError() { Completable result = hubConnection.invoke("test", "message"); result.doOnComplete(() -> done.set(true)).subscribe(() -> {}, (error) -> {}); - assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); - Throwable exception = assertThrows(IllegalArgumentException.class, () -> mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42,\"error\":\"There was an error\"}" + RECORD_SEPARATOR)); + Throwable exception = assertThrows(IllegalArgumentException.class, () -> + mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42,\"error\":\"There was an error\"}" + RECORD_SEPARATOR)); assertEquals("Expected either 'error' or 'result' to be provided, but not both.", exception.getMessage()); } @@ -1095,7 +1112,8 @@ public void invokeNoReturnValueHandlesError() { Completable result = hubConnection.invoke("test", "message"); result.doOnComplete(() -> done.set(true)).subscribe(() -> {}, (error) -> {}); - assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"error\":\"There was an error\"}" + RECORD_SEPARATOR); @@ -1120,7 +1138,8 @@ public void canSendNullArgInInvocation() { AtomicBoolean done = new AtomicBoolean(); Single result = hubConnection.invoke(String.class, "fixedMessage", (Object)null); result.doOnSuccess(value -> done.set(true)).subscribe(); - assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":\"Hello World\"}" + RECORD_SEPARATOR); @@ -1139,7 +1158,8 @@ public void canSendMultipleNullArgsInInvocation() { AtomicBoolean done = new AtomicBoolean(); Single result = hubConnection.invoke(String.class, "fixedMessage", null, null); result.doOnSuccess(value -> done.set(true)).subscribe(); - assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null,null]}"+ RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null,null]}"+ RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":\"Hello World\"}" + RECORD_SEPARATOR); @@ -1161,8 +1181,10 @@ public void multipleInvokesWaitForOwnCompletionMessage() { Single result2 = hubConnection.invoke(String.class, "echo", "message"); result.doOnSuccess(value -> doneFirst.set(true)).subscribe(); result2.doOnSuccess(value -> doneSecond.set(true)).subscribe(); - assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[1]); - assertEquals("{\"type\":1,\"invocationId\":\"2\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, mockTransport.getSentMessages()[2]); + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + assertEquals("{\"type\":1,\"invocationId\":\"2\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[2])); assertFalse(doneFirst.get()); assertFalse(doneSecond.get()); @@ -1582,7 +1604,7 @@ public void receiveHandshakeResponseAndMessage() { hubConnection.start(); mockTransport.getStartTask().timeout(1, TimeUnit.SECONDS).blockingAwait(); String expectedSentMessage = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; - assertEquals(expectedSentMessage, mockTransport.getSentMessages()[0]); + assertEquals(expectedSentMessage, TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0])); mockTransport.receiveMessage("{}" + RECORD_SEPARATOR + "{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR); @@ -1872,9 +1894,9 @@ public void afterSuccessfulNegotiateConnectsWithWebsocketsTransport() { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String[] sentMessages = transport.getSentMessages(); + ByteBuffer[] sentMessages = transport.getSentMessages(); assertEquals(1, sentMessages.length); - assertEquals("{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR, sentMessages[0]); + assertEquals("{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR, TestUtils.ByteBufferToString(sentMessages[0])); } @Test @@ -1893,9 +1915,9 @@ public void afterSuccessfulNegotiateConnectsWithLongPollingTransport() { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String[] sentMessages = transport.getSentMessages(); + ByteBuffer[] sentMessages = transport.getSentMessages(); assertEquals(1, sentMessages.length); - assertEquals("{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR, sentMessages[0]); + assertEquals("{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR, TestUtils.ByteBufferToString(sentMessages[0])); } @Test @@ -2155,7 +2177,8 @@ public void AccessTokenProviderCanProvideDifferentValues() { public void accessTokenProviderIsOverriddenFromRedirectNegotiate() { AtomicReference token = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))) + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", + "{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," @@ -2289,11 +2312,9 @@ public void connectionSendsPingsRegularly() throws InterruptedException { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - ByteBuffer messageBuffer = mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet(); - String message = new String(messageBuffer.array(), StandardCharsets.UTF_8); + String message = TestUtils.ByteBufferToString(mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet()); assertEquals("{\"type\":6}" + RECORD_SEPARATOR, message); - messageBuffer = mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet(); - message = new String(messageBuffer.array(), StandardCharsets.UTF_8); + message = TestUtils.ByteBufferToString(mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet()); assertEquals("{\"type\":6}" + RECORD_SEPARATOR, message); hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java index 0cacd6825404..f4ac71fe0416 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java @@ -35,7 +35,7 @@ public void checkTransferFormat() { @Test public void verifyWriteMessage() { InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] {"42"}, null); - String result = new String(jsonHubProtocol.writeMessage(invocationMessage).array(), StandardCharsets.UTF_8); + String result = TestUtils.ByteBufferToString(jsonHubProtocol.writeMessage(invocationMessage)); String expectedResult = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"42\"]}\u001E"; assertEquals(expectedResult, result); } @@ -43,7 +43,7 @@ public void verifyWriteMessage() { @Test public void parsePingMessage() { String stringifiedMessage = "{\"type\":6}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(PingMessage.getInstance()); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -57,7 +57,7 @@ public void parsePingMessage() { @Test public void parseCloseMessage() { String stringifiedMessage = "{\"type\":7}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new CloseMessage()); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -77,7 +77,7 @@ public void parseCloseMessage() { @Test public void parseCloseMessageWithError() { String stringifiedMessage = "{\"type\":7,\"error\": \"There was an error\"}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new CloseMessage("There was an error")); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -97,7 +97,7 @@ public void parseCloseMessageWithError() { @Test public void parseSingleMessage() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42 }, null)); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -121,7 +121,7 @@ public void parseSingleMessage() { @Test public void parseSingleUnsupportedStreamInvocationMessage() { String stringifiedMessage = "{\"type\":4,\"Id\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new StreamInvocationMessage(null, "1", "test", new Object[] { 42 }, null)); Throwable exception = assertThrows(UnsupportedOperationException.class, () -> jsonHubProtocol.parseMessages(message, binder)); @@ -131,7 +131,7 @@ public void parseSingleUnsupportedStreamInvocationMessage() { @Test public void parseSingleUnsupportedCancelInvocationMessage() { String stringifiedMessage = "{\"type\":5,\"invocationId\":123}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(null); Throwable exception = assertThrows(UnsupportedOperationException.class, () -> jsonHubProtocol.parseMessages(message, binder)); @@ -141,7 +141,7 @@ public void parseSingleUnsupportedCancelInvocationMessage() { @Test public void parseTwoMessages() { String stringifiedMessage = "{\"type\":1,\"target\":\"one\",\"arguments\":[42]}\u001E{\"type\":1,\"target\":\"two\",\"arguments\":[43]}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "one", new Object[] { 42 }, null)); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -175,7 +175,7 @@ public void parseTwoMessages() { @Test public void parseSingleMessageMutipleArgs() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42, 24 }, null)); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -198,7 +198,7 @@ public void parseSingleMessageMutipleArgs() { @Test public void parseMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42, 24 }, null)); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -221,7 +221,7 @@ public void parseMessageWithOutOfOrderProperties() { @Test public void parseCompletionMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"type\":3,\"result\":42,\"invocationId\":\"1\"}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new CompletionMessage(null, "1", 42, null)); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -240,7 +240,7 @@ public void parseCompletionMessageWithOutOfOrderProperties() { @Test public void invocationBindingFailureWhileParsingTooManyArgumentsWithOutOfOrderProperties() { String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -256,7 +256,7 @@ public void invocationBindingFailureWhileParsingTooManyArgumentsWithOutOfOrderPr @Test public void invocationBindingFailureWhileParsingTooManyArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -272,7 +272,7 @@ public void invocationBindingFailureWhileParsingTooManyArguments() { @Test public void invocationBindingFailureWhileParsingTooFewArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -288,7 +288,7 @@ public void invocationBindingFailureWhileParsingTooFewArguments() { @Test public void invocationBindingFailureWhenParsingIncorrectType() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"]}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -304,7 +304,7 @@ public void invocationBindingFailureWhenParsingIncorrectType() { @Test public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"],\"invocationId\":\"123\"}\u001E"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -321,7 +321,7 @@ public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() { @Test public void errorWhileParsingIncompleteMessage() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":"; - ByteBuffer message = ByteBuffer.wrap(stringifiedMessage.getBytes(StandardCharsets.UTF_8)); + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); RuntimeException exception = assertThrows(RuntimeException.class, diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java index b8655467d792..54c2936f16d0 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.*; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -41,7 +40,7 @@ public void LongPollingTransportCantSendBeforeStart() { Map headers = new HashMap<>(); LongPollingTransport transport = new LongPollingTransport(headers, client, Single.just("")); - ByteBuffer sendBuffer = ByteBuffer.wrap("First".getBytes(StandardCharsets.UTF_8)); + ByteBuffer sendBuffer = TestUtils.StringToByteBuffer("First"); Throwable exception = assertThrows(RuntimeException.class, () -> transport.send(sendBuffer).timeout(1, TimeUnit.SECONDS).blockingAwait()); assertEquals(Exception.class, exception.getCause().getClass()); assertEquals("Cannot send unless the transport is active.", exception.getCause().getMessage()); @@ -115,12 +114,12 @@ public void CanSetAndTriggerOnReceive() { AtomicBoolean onReceivedRan = new AtomicBoolean(false); transport.setOnReceive((message) -> { onReceivedRan.set(true); - assertEquals("TEST", message); + assertEquals("TEST", TestUtils.ByteBufferToString(message)); }); // The transport doesn't need to be active to trigger onReceive for the case // when we are handling the last outstanding poll. - ByteBuffer onReceiveBuffer = ByteBuffer.wrap("TEST".getBytes(StandardCharsets.UTF_8)); + ByteBuffer onReceiveBuffer = TestUtils.StringToByteBuffer("TEST"); transport.onReceive(onReceiveBuffer); assertTrue(onReceivedRan.get()); } @@ -149,7 +148,7 @@ public void LongPollingTransportOnReceiveGetsCalled() { AtomicReference message = new AtomicReference<>(); transport.setOnReceive((msg -> { onReceiveCalled.set(true); - message.set(new String(msg.array(), StandardCharsets.UTF_8)); + message.set(TestUtils.ByteBufferToString(msg)); block.onComplete(); }) ); @@ -192,7 +191,7 @@ public void LongPollingTransportOnReceiveGetsCalledMultipleTimes() { AtomicInteger messageCount = new AtomicInteger(); transport.setOnReceive((msg) -> { onReceiveCalled.set(true); - message.set(message.get() + msg); + message.set(message.get() + TestUtils.ByteBufferToString(msg)); if (messageCount.incrementAndGet() == 3) { blocker.onComplete(); } @@ -231,7 +230,7 @@ public void LongPollingTransportSendsHeaders() { transport.setOnClose((error) -> {}); transport.start("http://example.com").timeout(1, TimeUnit.SECONDS).blockingAwait(); - ByteBuffer sendBuffer = ByteBuffer.wrap("TEST".getBytes(StandardCharsets.UTF_8)); + ByteBuffer sendBuffer = TestUtils.StringToByteBuffer("TEST"); assertTrue(transport.send(sendBuffer).blockingAwait(1, TimeUnit.SECONDS)); close.onComplete(); assertEquals(headerValue.get(), "VALUE"); @@ -263,7 +262,7 @@ public void LongPollingTransportSetsAuthorizationHeader() { transport.setOnClose((error) -> {}); transport.start("http://example.com").timeout(1, TimeUnit.SECONDS).blockingAwait(); - ByteBuffer sendBuffer = ByteBuffer.wrap("TEST".getBytes(StandardCharsets.UTF_8)); + ByteBuffer sendBuffer = TestUtils.StringToByteBuffer("TEST"); assertTrue(transport.send(sendBuffer).blockingAwait(1, TimeUnit.SECONDS)); assertEquals(headerValue.get(), "Bearer TOKEN"); close.onComplete(); @@ -300,7 +299,7 @@ public void LongPollingTransportRunsAccessTokenProviderEveryRequest() { transport.start("http://example.com").timeout(1, TimeUnit.SECONDS).blockingAwait(); secondGet.blockingAwait(1, TimeUnit.SECONDS); - ByteBuffer sendBuffer = ByteBuffer.wrap("TEST".getBytes(StandardCharsets.UTF_8)); + ByteBuffer sendBuffer = TestUtils.StringToByteBuffer("TEST"); assertTrue(transport.send(sendBuffer).blockingAwait(1, TimeUnit.SECONDS)); assertEquals("Bearer TOKEN2", headerValue.get()); close.onComplete(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java index f02804cd600e..d2670141b82d 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java @@ -42,8 +42,7 @@ public Completable start(String url) { this.url = url; if (autoHandshake) { try { - ByteBuffer invokeBuffer = ByteBuffer.wrap(("{}" + RECORD_SEPARATOR).getBytes(StandardCharsets.UTF_8)); - onReceiveCallBack.invoke(invokeBuffer); + onReceiveCallBack.invoke(TestUtils.StringToByteBuffer("{}" + RECORD_SEPARATOR)); } catch (Exception e) { throw new RuntimeException(e); } @@ -89,11 +88,11 @@ public void stopWithError(String errorMessage) { } public void receiveMessage(String message) { - this.onReceive(ByteBuffer.wrap(message.getBytes(StandardCharsets.UTF_8))); + this.onReceive(TestUtils.StringToByteBuffer(message)); } - public String[] getSentMessages() { - return sentMessages.toArray(new String[sentMessages.size()]); + public ByteBuffer[] getSentMessages() { + return sentMessages.toArray(new ByteBuffer[sentMessages.size()]); } public SingleSubject getNextSentMessage() { diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java index 795df060e448..55dfa6041c21 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java @@ -3,6 +3,9 @@ package com.microsoft.signalr; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + class TestUtils { static HubConnection createHubConnection(String url) { return createHubConnection(url, new MockTransport(true), true, new TestHttpClient()); @@ -20,4 +23,12 @@ static HubConnection createHubConnection(String url, Transport transport, boolea return builder.build(); } + + static String ByteBufferToString(ByteBuffer buffer) { + return new String(buffer.array(), StandardCharsets.UTF_8); + } + + static ByteBuffer StringToByteBuffer(String s) { + return ByteBuffer.wrap(s.getBytes(StandardCharsets.UTF_8)); + } } From 28eadc563e3d8385037b1916bb1b8564f47c53e6 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 5 Aug 2020 13:48:04 -0700 Subject: [PATCH 36/64] Spacing --- .../microsoft/signalr/HubConnectionTest.java | 52 +++++++++---------- .../java/com/microsoft/signalr/TestUtils.java | 4 +- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index 23e665af5d4e..ce543272cb6f 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -204,7 +204,7 @@ public void hubConnectionReceiveHandshakeResponseWithError() { hubConnection.start(); mockTransport.getStartTask().timeout(1, TimeUnit.SECONDS).blockingAwait(); Throwable exception = assertThrows(RuntimeException.class, () -> - mockTransport.receiveMessage("{\"error\":\"Requested protocol 'messagepack' is not available.\"}" + RECORD_SEPARATOR)); + mockTransport.receiveMessage("{\"error\":\"Requested protocol 'messagepack' is not available.\"}" + RECORD_SEPARATOR)); assertEquals("Error in handshake Requested protocol 'messagepack' is not available.", exception.getMessage()); } @@ -623,7 +623,7 @@ public void checkStreamUploadSingleItemThroughInvoke() { ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(3, messages.length); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"2\"]}\u001E", - TestUtils.ByteBufferToString(messages[1])); + TestUtils.ByteBufferToString(messages[1])); assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); stream.onComplete(); @@ -646,7 +646,7 @@ public void checkStreamUploadSingleItemThroughStream() { ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(3, messages.length); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"2\"]}\u001E", - TestUtils.ByteBufferToString(messages[1])); + TestUtils.ByteBufferToString(messages[1])); assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); stream.onComplete(); @@ -671,9 +671,9 @@ public void useSameSubjectInMutlipleStreamsFromDifferentMethods() { assertEquals(4, messages.length); assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"1\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); assertEquals("{\"type\":1,\"invocationId\":\"2\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"3\"]}\u001E", - TestUtils.ByteBufferToString(messages[2])); + TestUtils.ByteBufferToString(messages[2])); assertEquals("{\"type\":4,\"invocationId\":\"4\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"5\"]}\u001E", - TestUtils.ByteBufferToString(messages[3])); + TestUtils.ByteBufferToString(messages[3])); stream.onNext("FirstItem"); @@ -707,7 +707,7 @@ public void streamUploadCallOnError() { assertEquals(4, messages.length); assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); assertEquals("{\"type\":3,\"invocationId\":\"1\",\"error\":\"java.lang.RuntimeException: onError called\"}\u001E", - TestUtils.ByteBufferToString(messages[3])); + TestUtils.ByteBufferToString(messages[3])); // onComplete doesn't send a completion message after onError. stream.onComplete(); @@ -812,7 +812,7 @@ public void checkStreamSingleItem() { () -> completed.set(true)); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); assertFalse(onNextCalled.get()); @@ -841,7 +841,7 @@ public void checkStreamCompletionResult() { () -> completed.set(true)); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); assertFalse(onNextCalled.get()); @@ -871,7 +871,7 @@ public void checkStreamCompletionError() { () -> {}); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(onErrorCalled.get()); assertFalse(onNextCalled.get()); @@ -901,7 +901,7 @@ public void checkStreamMultipleItems() { () -> {/*OnCompleted*/completed.set(true);}); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); mockTransport.receiveMessage("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First\"}" + RECORD_SEPARATOR); @@ -928,7 +928,7 @@ public void checkCancelIsSentAfterDispose() { () -> {/*OnCompleted*/completed.set(true);}); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); subscription.dispose(); @@ -954,12 +954,12 @@ public void checkCancelIsSentAfterAllSubscriptionsAreDisposed() { subscription.dispose(); assertEquals(2, mockTransport.getSentMessages().length); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); secondSubscription.dispose(); assertEquals(3, mockTransport.getSentMessages().length); assertEquals("{\"type\":5,\"invocationId\":\"1\"}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); } @Test @@ -975,7 +975,7 @@ public void checkStreamWithDispose() { () -> {/*OnCompleted*/}); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); mockTransport.receiveMessage("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First\"}" + RECORD_SEPARATOR); @@ -1003,7 +1003,7 @@ public void checkStreamWithDisposeWithMultipleSubscriptions() { () -> {/*OnCompleted*/completed.set(true);}); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); mockTransport.receiveMessage("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First\"}" + RECORD_SEPARATOR); @@ -1030,7 +1030,7 @@ public void invokeWaitsForCompletionMessage() { Single result = hubConnection.invoke(Integer.class, "echo", "message"); result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); @@ -1051,7 +1051,7 @@ public void invokeNoReturnValueWaitsForCompletion() { result.doOnComplete(() -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\"}" + RECORD_SEPARATOR); @@ -1072,7 +1072,7 @@ public void invokeCompletedByCompletionMessageWithResult() { result.doOnComplete(() -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); @@ -1093,11 +1093,11 @@ public void completionWithResultAndErrorHandlesError() { result.doOnComplete(() -> done.set(true)).subscribe(() -> {}, (error) -> {}); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); Throwable exception = assertThrows(IllegalArgumentException.class, () -> - mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42,\"error\":\"There was an error\"}" + RECORD_SEPARATOR)); + mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42,\"error\":\"There was an error\"}" + RECORD_SEPARATOR)); assertEquals("Expected either 'error' or 'result' to be provided, but not both.", exception.getMessage()); } @@ -1113,7 +1113,7 @@ public void invokeNoReturnValueHandlesError() { result.doOnComplete(() -> done.set(true)).subscribe(() -> {}, (error) -> {}); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"error\":\"There was an error\"}" + RECORD_SEPARATOR); @@ -1139,7 +1139,7 @@ public void canSendNullArgInInvocation() { Single result = hubConnection.invoke(String.class, "fixedMessage", (Object)null); result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":\"Hello World\"}" + RECORD_SEPARATOR); @@ -1159,7 +1159,7 @@ public void canSendMultipleNullArgsInInvocation() { Single result = hubConnection.invoke(String.class, "fixedMessage", null, null); result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null,null]}"+ RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":\"Hello World\"}" + RECORD_SEPARATOR); @@ -1182,9 +1182,9 @@ public void multipleInvokesWaitForOwnCompletionMessage() { result.doOnSuccess(value -> doneFirst.set(true)).subscribe(); result2.doOnSuccess(value -> doneSecond.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); assertEquals("{\"type\":1,\"invocationId\":\"2\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[2])); + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[2])); assertFalse(doneFirst.get()); assertFalse(doneSecond.get()); @@ -2178,7 +2178,7 @@ public void accessTokenProviderIsOverriddenFromRedirectNegotiate() { AtomicReference token = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - "{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))) + "{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java index 55dfa6041c21..8163a5ace284 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java @@ -25,10 +25,10 @@ static HubConnection createHubConnection(String url, Transport transport, boolea } static String ByteBufferToString(ByteBuffer buffer) { - return new String(buffer.array(), StandardCharsets.UTF_8); + return new String(buffer.array(), StandardCharsets.UTF_8); } static ByteBuffer StringToByteBuffer(String s) { - return ByteBuffer.wrap(s.getBytes(StandardCharsets.UTF_8)); + return ByteBuffer.wrap(s.getBytes(StandardCharsets.UTF_8)); } } From 303832ef3f58d8c114aa37a35eab8cb4f155db47 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 5 Aug 2020 14:33:27 -0700 Subject: [PATCH 37/64] only grab remaining buffer bytes in json --- .../src/main/java/com/microsoft/signalr/JsonHubProtocol.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index 911582cb19b5..01ece61afcad 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -8,6 +8,7 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import com.google.gson.Gson; @@ -39,7 +40,9 @@ public TransferFormat getTransferFormat() { @Override public List parseMessages(ByteBuffer payload, InvocationBinder binder) { - String payloadStr = new String(payload.array(), StandardCharsets.UTF_8); + // The position of the ByteBuffer may have been incremented - make sure we only grab the remaining bytes + byte[] payloadBytes = Arrays.copyOfRange(payload.array(), payload.position(), payload.capacity()); + String payloadStr = new String(payloadBytes, StandardCharsets.UTF_8); if (payloadStr.length() == 0) { return null; } From 5fa92c6ecbfffe8f26f53a1077ea25cde85b9c15 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 5 Aug 2020 14:44:37 -0700 Subject: [PATCH 38/64] Last test fixes --- .../test/java/com/microsoft/signalr/HubConnectionTest.java | 6 +++--- .../src/test/java/com/microsoft/signalr/MockTransport.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index ce543272cb6f..611d4c02963c 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -467,7 +467,7 @@ public void checkStreamUploadSingleItemThroughSend() { stream.onComplete(); messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", messages[3]); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[3])); } @Test @@ -578,8 +578,8 @@ public void streamMapEntriesRemovedOnStreamClose() { assertTrue(hubConnection.getStreamMap().isEmpty()); messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", messages[5]); - assertEquals("{\"type\":3,\"invocationId\":\"2\",\"error\":\"java.lang.Exception: Exception\"}\u001E", messages[6]); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[5])); + assertEquals("{\"type\":3,\"invocationId\":\"2\",\"error\":\"java.lang.Exception: Exception\"}\u001E", TestUtils.ByteBufferToString(messages[6])); hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); assertTrue(hubConnection.getStreamMap().isEmpty()); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java index d2670141b82d..c0c103268662 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java @@ -53,7 +53,7 @@ public Completable start(String url) { @Override public Completable send(ByteBuffer message) { - if (!(ignorePings && message.equals("{\"type\":6}" + RECORD_SEPARATOR))) { + if (!(ignorePings && TestUtils.ByteBufferToString(message).equals("{\"type\":6}" + RECORD_SEPARATOR))) { sentMessages.add(message); sendSubject.onSuccess(message); sendSubject = SingleSubject.create(); From a8c640e06e6141d8626acbd36275803f5c03968c Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 5 Aug 2020 14:58:11 -0700 Subject: [PATCH 39/64] Get rid of some unused imports --- .../src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java | 1 - .../src/test/java/com/microsoft/signalr/MockTransport.java | 1 - 2 files changed, 2 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java index f4ac71fe0416..397cd66dc082 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java @@ -6,7 +6,6 @@ import static org.junit.jupiter.api.Assertions.*; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java index c0c103268662..9b7762b28f9f 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java @@ -4,7 +4,6 @@ package com.microsoft.signalr; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import io.reactivex.Completable; From 125cd7f87ab9d74887302a90b224e7e6afc5ca8d Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 6 Aug 2020 09:27:34 -0700 Subject: [PATCH 40/64] First round of msgpack tests --- .../signalr/MessagePackHubProtocol.java | 14 +- .../signalr/JsonHubProtocolTest.java | 51 --- .../signalr/MessagePackHubProtocolTest.java | 383 ++++++++++++++++++ .../com/microsoft/signalr/TestBinder.java | 53 +++ 4 files changed, 448 insertions(+), 53 deletions(-) create mode 100644 src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java create mode 100644 src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 9dd151febec1..407a25107721 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -92,7 +92,13 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde // Make sure that we actually read the right number of bytes int readBytes = (int) unpacker.getTotalReadBytes(); if (readBytes != length) { - throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", readBytes, length)); + // Check what the last message was + if (hubMessages.get(hubMessages.size() - 1).getMessageType() != HubMessageType.INVOCATION_BINDING_FAILURE) { + throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", readBytes, length)); + // If it was an invocation binding failure, we have to correct the position of the buffer + } else { + payload.position(payload.position() + (length - readBytes)); + } } unpacker.close(); payload.position(payload.position() + readBytes); @@ -253,7 +259,11 @@ private HubMessage createCancelInvocationMessage(MessageUnpacker unpacker) throw } private HubMessage createCloseMessage(MessageUnpacker unpacker, int itemCount) throws IOException { - String error = unpacker.unpackString(); + // error may be nil + String error = null; + if (!unpacker.tryUnpackNil()) { + error = unpacker.unpackString(); + } boolean allowReconnect = false; if (itemCount > 2) { diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java index 397cd66dc082..73ff89210077 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java @@ -6,13 +6,10 @@ import static org.junit.jupiter.api.Assertions.*; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Test; - class JsonHubProtocolTest { private JsonHubProtocol jsonHubProtocol = new JsonHubProtocol(); @@ -327,52 +324,4 @@ public void errorWhileParsingIncompleteMessage() { () -> jsonHubProtocol.parseMessages(message, binder)); assertEquals("Message is incomplete.", exception.getMessage()); } - - private class TestBinder implements InvocationBinder { - private Class[] paramTypes = null; - private Class returnType = null; - - public TestBinder(HubMessage expectedMessage) { - if (expectedMessage == null) { - return; - } - - switch (expectedMessage.getMessageType()) { - case STREAM_INVOCATION: - ArrayList> streamTypes = new ArrayList<>(); - for (Object obj : ((StreamInvocationMessage) expectedMessage).getArguments()) { - streamTypes.add(obj.getClass()); - } - paramTypes = streamTypes.toArray(new Class[streamTypes.size()]); - break; - case INVOCATION: - ArrayList> types = new ArrayList<>(); - for (Object obj : ((InvocationMessage) expectedMessage).getArguments()) { - types.add(obj.getClass()); - } - paramTypes = types.toArray(new Class[types.size()]); - break; - case STREAM_ITEM: - break; - case COMPLETION: - returnType = ((CompletionMessage)expectedMessage).getResult().getClass(); - break; - default: - break; - } - } - - @Override - public Class getReturnType(String invocationId) { - return returnType; - } - - @Override - public List> getParameterTypes(String methodName) { - if (paramTypes == null) { - return new ArrayList<>(); - } - return new ArrayList>(Arrays.asList(paramTypes)); - } - } } \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java new file mode 100644 index 000000000000..8bdd1da0d3be --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -0,0 +1,383 @@ +package com.microsoft.signalr; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.nio.ByteBuffer; +import java.util.List; +import okio.ByteString; + +import org.junit.jupiter.api.Test; + +class MessagePackHubProtocolTest { + private MessagePackHubProtocol messagePackHubProtocol = new MessagePackHubProtocol(); + + @Test + public void checkProtocolName() { + assertEquals("messagepack", messagePackHubProtocol.getName()); + } + + @Test + public void checkVersionNumber() { + assertEquals(1, messagePackHubProtocol.getVersion()); + } + + @Test + public void checkTransferFormat() { + assertEquals(TransferFormat.BINARY, messagePackHubProtocol.getTransferFormat()); + } + + @Test + public void verifyWriteMessage() { + InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] { 42 }, null); + ByteBuffer result = messagePackHubProtocol.writeMessage(invocationMessage); + byte[] expectedBytes = {0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, + 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void parsePingMessage() { + byte[] messageBytes = {0x02, (byte) 0x91, 0x06}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(PingMessage.getInstance()); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + assertEquals(HubMessageType.PING, messages.get(0).getMessageType()); + } + + @Test + public void parseCloseMessage() { + byte[] messageBytes = {0x04, (byte) 0x93, 0x07, (byte) 0xC0, (byte) 0xC2}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new CloseMessage()); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.CLOSE, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's a close message. + CloseMessage closeMessage = (CloseMessage) messages.get(0); + + assertEquals(null, closeMessage.getError()); + } + + @Test + public void parseCloseMessageWithError() { + byte[] messageBytes = {0x09, (byte) 0x93, 0x07, (byte) 0xA5, 0x45, 0x72, 0x72, 0x6F, 0x72, (byte) 0xC2}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new CloseMessage("Error")); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.CLOSE, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's a close message. + CloseMessage closeMessage = (CloseMessage) messages.get(0); + + assertEquals("Error", closeMessage.getError()); + } + + @Test + public void parseSingleInvocationMessage() { + byte[] messageBytes = {0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, + 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42 }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's an invocation message. + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); + + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + assertEquals(null, invocationMessage.getHeaders()); + assertEquals(null, invocationMessage.getStreamIds()); + + int messageResult = (int)invocationMessage.getArguments()[0]; + assertEquals(42, messageResult); + } + + @Test + public void parseSingleStreamInvocationMessage() { + byte[] messageBytes = {0x12, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA6, 0x6D, 0x65, 0x74, 0x68, 0x6F, 0x64, + (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new StreamInvocationMessage(null, "method", "test", new Object[] { 42 }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.STREAM_INVOCATION, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's a streaminvocation message. + StreamInvocationMessage streamInvocationMessage = (StreamInvocationMessage) messages.get(0); + + assertEquals("test", streamInvocationMessage.getTarget()); + assertEquals("method", streamInvocationMessage.getInvocationId()); + assertEquals(null, streamInvocationMessage.getHeaders()); + assertEquals(null, streamInvocationMessage.getStreamIds()); + + int messageResult = (int)streamInvocationMessage.getArguments()[0]; + assertEquals(42, messageResult); + } + + @Test + public void parseSingleCancelInvocationMessage() { + byte[] messageBytes = {0x0A, (byte) 0x93, 0x05, (byte) 0x80, (byte) 0xA6, 0x6D, 0x65, 0x74, 0x68, 0x6F, 0x64}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new CancelInvocationMessage(null, "method")); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.CANCEL_INVOCATION, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's a cancelinvocation message. + CancelInvocationMessage cancelInvocationMessage = (CancelInvocationMessage) messages.get(0); + + assertEquals("method", cancelInvocationMessage.getInvocationId()); + assertEquals(null, cancelInvocationMessage.getHeaders()); + } + + @Test + public void parseTwoMessages() { + byte[] messageBytes = {0x0B, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x6F, 0x6E, 0x65, (byte) 0x91, 0x2A, + (byte) 0x90, 0x0B, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x74, 0x77, 0x6F, (byte) 0x91, 0x2B, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "one", new Object[] { 42 }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + assertNotNull(messages); + assertEquals(2, messages.size()); + + // Check the first message + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); + + //Now that we know we have an invocation message we can cast the hubMessage. + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); + + assertEquals("one", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + assertEquals(null, invocationMessage.getHeaders()); + assertEquals(null, invocationMessage.getStreamIds()); + + int messageResult = (int)invocationMessage.getArguments()[0]; + assertEquals(42, messageResult); + + // Check the second message + assertEquals(HubMessageType.INVOCATION, messages.get(1).getMessageType()); + + //Now that we know we have an invocation message we can cast the hubMessage. + InvocationMessage invocationMessage2 = (InvocationMessage) messages.get(1); + + assertEquals("two", invocationMessage2.getTarget()); + assertEquals(null, invocationMessage2.getInvocationId()); + assertEquals(null, invocationMessage2.getHeaders()); + assertEquals(null, invocationMessage2.getStreamIds()); + + int secondMessageResult = (int)invocationMessage2.getArguments()[0]; + assertEquals(43, secondMessageResult); + } + + @Test + public void parseSingleMessageMutipleArgs() { + byte[] messageBytes = {0x0F, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x92, + 0x2A, (byte) 0xA2, 0x34, 0x32, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, "42" }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); + + //We know it's only one message + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); + + InvocationMessage invocationMessage = (InvocationMessage)messages.get(0); + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + int messageResult = (int) invocationMessage.getArguments()[0]; + String messageResult2 = (String) invocationMessage.getArguments()[1]; + assertEquals(42, messageResult); + assertEquals("42", messageResult2); + } + + @Test + public void invocationBindingFailureWhileParsingTooManyArguments() { + byte[] messageBytes = {0x0F, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x92, + 0x2A, (byte) 0xA2, 0x34, 0x32, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); + InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage)messages.get(0); + assertEquals("Invocation provides 2 argument(s) but target expects 1.", invocationBindingFailureMessage.getException().getMessage()); + } + + @Test + public void invocationBindingFailureWhileParsingTooFewArguments() { + byte[] messageBytes = {0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, + (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); + InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); + assertEquals("Invocation provides 1 argument(s) but target expects 2.", invocationBindingFailureMessage.getException().getMessage()); + } + + @Test + public void invocationBindingFailureWhenParsingIncorrectType() { + byte[] messageBytes = {0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, + (byte) 0xC3, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); + InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); + assertEquals("Cannot cast java.lang.Boolean to java.lang.Integer", invocationBindingFailureMessage.getException().getMessage()); + } + + @Test + public void invocationBindingFailureReadsNextMessageAfterTooManyArguments() { + byte[] messageBytes = {0x0F, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x92, + 0x2A, (byte) 0xA2, 0x34, 0x32, (byte) 0x90, 0x0B, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x74, + 0x77, 0x6F, (byte) 0x91, 0x2B, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + assertNotNull(messages); + assertEquals(2, messages.size()); + + assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); + InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); + assertEquals("Invocation provides 2 argument(s) but target expects 1.", invocationBindingFailureMessage.getException().getMessage()); + + // Check the second message + assertEquals(HubMessageType.INVOCATION, messages.get(1).getMessageType()); + + //Now that we know we have an invocation message we can cast the hubMessage. + InvocationMessage invocationMessage2 = (InvocationMessage) messages.get(1); + + assertEquals("two", invocationMessage2.getTarget()); + assertEquals(null, invocationMessage2.getInvocationId()); + assertEquals(null, invocationMessage2.getHeaders()); + assertEquals(null, invocationMessage2.getStreamIds()); + + int secondMessageResult = (int)invocationMessage2.getArguments()[0]; + assertEquals(43, secondMessageResult); + } + + @Test + public void invocationBindingFailureReadsNextMessageAfterTooFewArguments() { + byte[] messageBytes = {0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, + (byte) 0x90, 0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x74, 0x77, 0x6F, (byte) 0x92, 0x2A, 0x2B, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + assertNotNull(messages); + assertEquals(2, messages.size()); + + assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); + InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); + assertEquals("Invocation provides 1 argument(s) but target expects 2.", invocationBindingFailureMessage.getException().getMessage()); + + // Check the second message + assertEquals(HubMessageType.INVOCATION, messages.get(1).getMessageType()); + + //Now that we know we have an invocation message we can cast the hubMessage. + InvocationMessage invocationMessage2 = (InvocationMessage) messages.get(1); + + assertEquals("two", invocationMessage2.getTarget()); + assertEquals(null, invocationMessage2.getInvocationId()); + assertEquals(null, invocationMessage2.getHeaders()); + assertEquals(null, invocationMessage2.getStreamIds()); + + int secondMessageResult1 = (int)invocationMessage2.getArguments()[0]; + int secondMessageResult2 = (int)invocationMessage2.getArguments()[1]; + assertEquals(42, secondMessageResult1); + assertEquals(43, secondMessageResult2); + } + + @Test + public void invocationBindingFailureReadsNextMessageAfterIncorrectArgument() { + byte[] messageBytes = {0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, + (byte) 0xC3, (byte) 0x90, 0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, + (byte) 0x91, 0x2A, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + assertNotNull(messages); + assertEquals(2, messages.size()); + + assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); + InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); + assertEquals("Cannot cast java.lang.Boolean to java.lang.Integer", invocationBindingFailureMessage.getException().getMessage()); + + // Check the second message + assertEquals(HubMessageType.INVOCATION, messages.get(1).getMessageType()); + + //Now that we know we have an invocation message we can cast the hubMessage. + InvocationMessage invocationMessage2 = (InvocationMessage) messages.get(1); + + assertEquals("test", invocationMessage2.getTarget()); + assertEquals(null, invocationMessage2.getInvocationId()); + assertEquals(null, invocationMessage2.getHeaders()); + assertEquals(null, invocationMessage2.getStreamIds()); + + int secondMessageResult = (int)invocationMessage2.getArguments()[0]; + assertEquals(42, secondMessageResult); + } + +} diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java new file mode 100644 index 000000000000..6ad42e1f22d5 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java @@ -0,0 +1,53 @@ +package com.microsoft.signalr; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class TestBinder implements InvocationBinder { + private Class[] paramTypes = null; + private Class returnType = null; + + public TestBinder(HubMessage expectedMessage) { + if (expectedMessage == null) { + return; + } + + switch (expectedMessage.getMessageType()) { + case STREAM_INVOCATION: + ArrayList> streamTypes = new ArrayList<>(); + for (Object obj : ((StreamInvocationMessage) expectedMessage).getArguments()) { + streamTypes.add(obj.getClass()); + } + paramTypes = streamTypes.toArray(new Class[streamTypes.size()]); + break; + case INVOCATION: + ArrayList> types = new ArrayList<>(); + for (Object obj : ((InvocationMessage) expectedMessage).getArguments()) { + types.add(obj.getClass()); + } + paramTypes = types.toArray(new Class[types.size()]); + break; + case STREAM_ITEM: + break; + case COMPLETION: + returnType = ((CompletionMessage)expectedMessage).getResult().getClass(); + break; + default: + break; + } + } + + @Override + public Class getReturnType(String invocationId) { + return returnType; + } + + @Override + public List> getParameterTypes(String methodName) { + if (paramTypes == null) { + return new ArrayList<>(); + } + return new ArrayList>(Arrays.asList(paramTypes)); + } +} \ No newline at end of file From 4771aa2f5f75fc6c187e026e76d4d262e36fe7a0 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 6 Aug 2020 09:29:58 -0700 Subject: [PATCH 41/64] Flip condition --- .../java/com/microsoft/signalr/MessagePackHubProtocol.java | 6 +++--- .../src/test/java/com/microsoft/signalr/TestUtils.java | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 407a25107721..6fefd20ac7bf 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -93,11 +93,11 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde int readBytes = (int) unpacker.getTotalReadBytes(); if (readBytes != length) { // Check what the last message was - if (hubMessages.get(hubMessages.size() - 1).getMessageType() != HubMessageType.INVOCATION_BINDING_FAILURE) { - throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", readBytes, length)); // If it was an invocation binding failure, we have to correct the position of the buffer - } else { + if (hubMessages.get(hubMessages.size() - 1).getMessageType() == HubMessageType.INVOCATION_BINDING_FAILURE) { payload.position(payload.position() + (length - readBytes)); + } else { + throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", readBytes, length)); } } unpacker.close(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java index 8163a5ace284..7f9dd5ca87e2 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java @@ -17,9 +17,9 @@ static HubConnection createHubConnection(String url, Transport transport) { static HubConnection createHubConnection(String url, Transport transport, boolean skipNegotiate, HttpClient client) { HttpHubConnectionBuilder builder = HubConnectionBuilder.create(url) - .withTransportImplementation(transport) - .withHttpClient(client) - .shouldSkipNegotiate(skipNegotiate); + .withTransportImplementation(transport) + .withHttpClient(client) + .shouldSkipNegotiate(skipNegotiate); return builder.build(); } From 3f85c558b1c3dcf10013bd0c201150f002810bb4 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 6 Aug 2020 10:03:53 -0700 Subject: [PATCH 42/64] Respond to feedback --- .../microsoft/signalr/HandshakeProtocol.java | 7 +++- .../com/microsoft/signalr/HubConnection.java | 39 +++++++++++-------- .../microsoft/signalr/InvocationBinder.java | 2 +- .../microsoft/signalr/JsonHubProtocol.java | 3 +- .../com/microsoft/signalr/TransferFormat.java | 2 +- .../java/com/microsoft/signalr/Utils.java | 10 ++--- .../signalr/HandshakeProtocolTest.java | 5 ++- 7 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HandshakeProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HandshakeProtocol.java index 4c1d2ad8962b..c7df51fd0337 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HandshakeProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HandshakeProtocol.java @@ -3,15 +3,18 @@ package com.microsoft.signalr; +import java.nio.charset.StandardCharsets; +import java.nio.ByteBuffer; + import com.google.gson.Gson; final class HandshakeProtocol { private static final Gson gson = new Gson(); private static final String RECORD_SEPARATOR = "\u001e"; - public static String createHandshakeRequestMessage(HandshakeRequestMessage message) { + public static ByteBuffer createHandshakeRequestMessage(HandshakeRequestMessage message) { // The handshake request is always in the JSON format - return gson.toJson(message) + RECORD_SEPARATOR; + return ByteBuffer.wrap((gson.toJson(message) + RECORD_SEPARATOR).getBytes(StandardCharsets.UTF_8)); } public static HandshakeResponseMessage parseHandshakeResponse(String message) { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index 8da34f74d3e1..4275ec539a6d 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -166,17 +166,21 @@ Transport getTransport() { this.callback = (payload) -> { resetServerTimeout(); - // MessagePack library can't handle read-only ByteBuffer - copy into an array-backed ByteBuffer if this is the case - if (payload.isReadOnly()) { - byte[] payloadBytes = new byte[payload.remaining()]; - payload.get(payloadBytes, 0, payloadBytes.length); - payload = ByteBuffer.wrap(payloadBytes); - } if (!handshakeReceived) { - // The handshake will always be a UTF8 Json string, so we can convert the ByteBuffer to that to read the beginning of the payload - String payloadStr = new String(payload.array(), StandardCharsets.UTF_8); - int handshakeLength = payloadStr.indexOf(RECORD_SEPARATOR) + 1; - String handshakeResponseString = payloadStr.substring(0, handshakeLength - 1); + List handshakeByteList = new ArrayList(); + byte curr = payload.get(); + // Add the handshake to handshakeBytes, but not the record separator + while (curr != RECORD_SEPARATOR) { + handshakeByteList.add(curr); + curr = payload.get(); + } + int handshakeLength = handshakeByteList.size() + 1; + byte[] handshakeBytes = new byte[handshakeLength - 1]; + for (int i = 0; i < handshakeLength - 1; i++) { + handshakeBytes[i] = handshakeByteList.get(i); + } + // The handshake will always be a UTF8 Json string + String handshakeResponseString = new String(handshakeBytes, StandardCharsets.UTF_8); HandshakeResponseMessage handshakeResponse; try { handshakeResponse = HandshakeProtocol.parseHandshakeResponse(handshakeResponseString); @@ -194,10 +198,6 @@ Transport getTransport() { } handshakeReceived = true; handshakeResponseSubject.onComplete(); - - // Increment the ByteBuffer payload by the byte length of the handshake + the byte length of the record separator (1) - int readBytes = handshakeResponseString.getBytes(StandardCharsets.UTF_8).length + 1; - payload.position(payload.position() + readBytes); // The payload only contained the handshake response so we can return. if (!payload.hasRemaining()) { @@ -205,6 +205,13 @@ Transport getTransport() { } } + // MessagePack library can't handle read-only ByteBuffer - copy into an array-backed ByteBuffer if this is the case + if (payload.isReadOnly()) { + byte[] payloadBytes = new byte[payload.remaining()]; + payload.get(payloadBytes, 0, payloadBytes.length); + payload = ByteBuffer.wrap(payloadBytes); + } + List messages = protocol.parseMessages(payload, connectionState); for (HubMessage message : messages) { @@ -385,12 +392,12 @@ public Completable start() { transport.setOnClose((message) -> stopConnection(message)); return transport.start(negotiateResponse.getFinalUrl()).andThen(Completable.defer(() -> { - String handshake = HandshakeProtocol.createHandshakeRequestMessage( + ByteBuffer handshake = HandshakeProtocol.createHandshakeRequestMessage( new HandshakeRequestMessage(protocol.getName(), protocol.getVersion())); connectionState = new ConnectionState(this); - return transport.send(ByteBuffer.wrap(handshake.getBytes(StandardCharsets.UTF_8))).andThen(Completable.defer(() -> { + return transport.send(handshake).andThen(Completable.defer(() -> { timeoutHandshakeResponse(handshakeResponseTimeout, TimeUnit.MILLISECONDS); return handshakeResponseSubject.andThen(Completable.defer(() -> { hubConnectionStateLock.lock(); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java index 3f3457f730dd..07248cd27299 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java @@ -5,7 +5,7 @@ import java.util.List; -interface InvocationBinder { +public interface InvocationBinder { Class getReturnType(String invocationId); List> getParameterTypes(String methodName); } \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index 01ece61afcad..054244471899 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -41,8 +41,7 @@ public TransferFormat getTransferFormat() { @Override public List parseMessages(ByteBuffer payload, InvocationBinder binder) { // The position of the ByteBuffer may have been incremented - make sure we only grab the remaining bytes - byte[] payloadBytes = Arrays.copyOfRange(payload.array(), payload.position(), payload.capacity()); - String payloadStr = new String(payloadBytes, StandardCharsets.UTF_8); + String payloadStr = new String(payload.array(), payload.position(), payload.remaining(), StandardCharsets.UTF_8); if (payloadStr.length() == 0) { return null; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TransferFormat.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TransferFormat.java index 59a9dd588967..413404bea612 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TransferFormat.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TransferFormat.java @@ -3,7 +3,7 @@ package com.microsoft.signalr; -enum TransferFormat { +public enum TransferFormat { TEXT, BINARY } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java index 4cd4130e7f94..bc916e886c0e 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java @@ -3,13 +3,9 @@ package com.microsoft.signalr; -import java.awt.List; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; - -import org.msgpack.core.MessageUnpacker; class Utils { public static String appendQueryString(String original, String queryStringValue) { @@ -32,7 +28,7 @@ public static Object toPrimitive(Class c, Object value) { return value; } - public static int readLengthHeader(ByteBuffer bb) throws IOException { + public static int readLengthHeader(ByteBuffer buffer) throws IOException { // The payload starts with a length prefix encoded as a VarInt. VarInts use the most significant bit // as a marker whether the byte is the last byte of the VarInt or if it spans to the next byte. Bytes // appear in the reverse order - i.e. the first byte contains the least significant bits of the value @@ -52,8 +48,8 @@ public static int readLengthHeader(ByteBuffer bb) throws IOException { do { // If we run out of bytes before we finish reading the length header, the message is malformed - if (bb.hasRemaining()) { - curr = bb.get(); + if (buffer.hasRemaining()) { + curr = buffer.get(); } else { throw new RuntimeException("The length header was incomplete"); } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HandshakeProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HandshakeProtocolTest.java index 811d39c5580e..352a5d5d1725 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HandshakeProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HandshakeProtocolTest.java @@ -5,15 +5,16 @@ import static org.junit.jupiter.api.Assertions.*; +import java.nio.ByteBuffer; import org.junit.jupiter.api.Test; class HandshakeProtocolTest { @Test public void VerifyCreateHandshakerequestMessage() { HandshakeRequestMessage handshakeRequest = new HandshakeRequestMessage("json", 1); - String result = HandshakeProtocol.createHandshakeRequestMessage(handshakeRequest); + ByteBuffer result = HandshakeProtocol.createHandshakeRequestMessage(handshakeRequest); String expectedResult = "{\"protocol\":\"json\",\"version\":1}\u001E"; - assertEquals(expectedResult, result); + assertEquals(expectedResult, TestUtils.ByteBufferToString(result)); } @Test From e46b2eea5415e09c3be81853bffeb22704894245 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 6 Aug 2020 10:05:27 -0700 Subject: [PATCH 43/64] Spacing --- .../com/microsoft/signalr/HubConnection.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index 4275ec539a6d..107358df3caf 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -167,18 +167,18 @@ Transport getTransport() { this.callback = (payload) -> { resetServerTimeout(); if (!handshakeReceived) { - List handshakeByteList = new ArrayList(); - byte curr = payload.get(); - // Add the handshake to handshakeBytes, but not the record separator - while (curr != RECORD_SEPARATOR) { - handshakeByteList.add(curr); - curr = payload.get(); - } - int handshakeLength = handshakeByteList.size() + 1; - byte[] handshakeBytes = new byte[handshakeLength - 1]; - for (int i = 0; i < handshakeLength - 1; i++) { - handshakeBytes[i] = handshakeByteList.get(i); - } + List handshakeByteList = new ArrayList(); + byte curr = payload.get(); + // Add the handshake to handshakeBytes, but not the record separator + while (curr != RECORD_SEPARATOR) { + handshakeByteList.add(curr); + curr = payload.get(); + } + int handshakeLength = handshakeByteList.size() + 1; + byte[] handshakeBytes = new byte[handshakeLength - 1]; + for (int i = 0; i < handshakeLength - 1; i++) { + handshakeBytes[i] = handshakeByteList.get(i); + } // The handshake will always be a UTF8 Json string String handshakeResponseString = new String(handshakeBytes, StandardCharsets.UTF_8); HandshakeResponseMessage handshakeResponse; From 745b0dc45fb9e0eb1a1de9b5136702704e724078 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 6 Aug 2020 10:54:08 -0700 Subject: [PATCH 44/64] More tests --- .../signalr/MessagePackHubProtocolTest.java | 159 +++++++++++++++++- 1 file changed, 157 insertions(+), 2 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index 8bdd1da0d3be..9221697c88c4 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -5,7 +5,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import okio.ByteString; import org.junit.jupiter.api.Test; @@ -27,9 +30,9 @@ public void checkVersionNumber() { public void checkTransferFormat() { assertEquals(TransferFormat.BINARY, messagePackHubProtocol.getTransferFormat()); } - + @Test - public void verifyWriteMessage() { + public void verifyWriteInvocationMessage() { InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] { 42 }, null); ByteBuffer result = messagePackHubProtocol.writeMessage(invocationMessage); byte[] expectedBytes = {0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, @@ -37,6 +40,102 @@ public void verifyWriteMessage() { ByteString expectedResult = ByteString.of(expectedBytes); assertEquals(expectedResult, ByteString.of(result)); } + + @Test + public void verifyWriteInvocationMessageWithHeaders() { + Map headers = new HashMap(); + headers.put("a", "b"); + headers.put("c", "d"); + InvocationMessage invocationMessage = new InvocationMessage(headers, null, "test", new Object[] { 42 }, null); + ByteBuffer result = messagePackHubProtocol.writeMessage(invocationMessage); + byte[] expectedBytes = {0x14, (byte) 0x96, 0x01, (byte) 0x82, (byte) 0xA1, 0x61, (byte) 0xA1, 0x62, (byte) 0xA1, 0x63, + (byte) 0xA1, 0x64, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void verifyWriteStreamItem() { + StreamItem streamItem = new StreamItem(null, "id", 42); + ByteBuffer result = messagePackHubProtocol.writeMessage(streamItem); + byte[] expectedBytes = {0x07, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA2, 0x69, 0x64, 0x2A}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void verifyWriteCompletionMessageNonVoid() { + CompletionMessage completionMessage = new CompletionMessage(null, "id", 42, null); + ByteBuffer result = messagePackHubProtocol.writeMessage(completionMessage); + byte[] expectedBytes = {0x08, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA2, 0x69, 0x64, 0x03, 0x2A}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void verifyWriteCompletionMessageVoid() { + CompletionMessage completionMessage = new CompletionMessage(null, "id", null, null); + ByteBuffer result = messagePackHubProtocol.writeMessage(completionMessage); + byte[] expectedBytes = {0x07, (byte) 0x94, 0x03, (byte) 0x80, (byte) 0xA2, 0x69, 0x64, 0x02}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void verifyWriteCompletionMessageError() { + CompletionMessage completionMessage = new CompletionMessage(null, "id", null, "error"); + ByteBuffer result = messagePackHubProtocol.writeMessage(completionMessage); + byte[] expectedBytes = {0x0D, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA2, 0x69, 0x64, 0x01, (byte) 0xA5, 0x65, 0x72, 0x72, 0x6F, 0x72}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void verifyWriteStreamInvocationMessage() { + List streamIds = new ArrayList(); + streamIds.add("stream"); + StreamInvocationMessage streamInvocationMessage = new StreamInvocationMessage(null, "id", "test", new Object[] {42}, streamIds); + ByteBuffer result = messagePackHubProtocol.writeMessage(streamInvocationMessage); + byte[] expectedBytes = {0x15, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA2, 0x69, 0x64, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, + 0x2A, (byte) 0x91, (byte) 0xA6, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void verifyWriteCancelInvocationMessage() { + CancelInvocationMessage cancelInvocationMessage = new CancelInvocationMessage(null, "id"); + ByteBuffer result = messagePackHubProtocol.writeMessage(cancelInvocationMessage); + byte[] expectedBytes = {0x06, (byte) 0x93, 0x05, (byte) 0x80, (byte) 0xA2, 0x69, 0x64}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void verifyWritePingMessage() { + ByteBuffer result = messagePackHubProtocol.writeMessage(PingMessage.getInstance()); + byte[] expectedBytes = {0x02, (byte) 0x91, 0x06}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void verifyWriteCloseMessage() { + CloseMessage closeMessage = new CloseMessage(); + ByteBuffer result = messagePackHubProtocol.writeMessage(closeMessage); + byte[] expectedBytes = {0x04, (byte) 0x93, 0x07, (byte) 0xC0, (byte) 0xC2}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void verifyWriteCloseMessageWithError() { + CloseMessage closeMessage = new CloseMessage("Error"); + ByteBuffer result = messagePackHubProtocol.writeMessage(closeMessage); + byte[] expectedBytes = {0x09, (byte) 0x93, 0x07, (byte) 0xA5, 0x45, 0x72, 0x72, 0x6F, 0x72, (byte) 0xC2}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } @Test public void parsePingMessage() { @@ -118,6 +217,38 @@ public void parseSingleInvocationMessage() { int messageResult = (int)invocationMessage.getArguments()[0]; assertEquals(42, messageResult); } + + @Test + public void parseSingleInvocationMessageWithHeaders() { + byte[] messageBytes = {0x14, (byte) 0x96, 0x01, (byte) 0x82, (byte) 0xA1, 0x61, (byte) 0xA1, 0x62, (byte) 0xA1, 0x63, + (byte) 0xA1, 0x64, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's an invocation message. + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); + + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + + Map headers = invocationMessage.getHeaders(); + assertEquals(2, headers.size()); + assertEquals("b", headers.get("a")); + assertEquals("d", headers.get("c")); + + assertEquals(null, invocationMessage.getStreamIds()); + + int messageResult = (int)invocationMessage.getArguments()[0]; + assertEquals(42, messageResult); + } @Test public void parseSingleStreamInvocationMessage() { @@ -379,5 +510,29 @@ public void invocationBindingFailureReadsNextMessageAfterIncorrectArgument() { int secondMessageResult = (int)invocationMessage2.getArguments()[0]; assertEquals(42, secondMessageResult); } + + @Test + public void errorWhenLengthHeaderTooLong() { + byte[] messageBytes = {0x0D, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, + 0x2A, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> messagePackHubProtocol.parseMessages(message, binder)); + assertEquals("MessagePack message was length 12 but claimed to be length 13.", exception.getMessage()); + } + + @Test + public void errorWhenLengthHeaderTooShort() { + byte[] messageBytes = {0x0B, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, + 0x2A, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> messagePackHubProtocol.parseMessages(message, binder)); + assertEquals("MessagePack message was length 12 but claimed to be length 11.", exception.getMessage()); + } } From 5ee83018a82c2b30e2c8495d77ae80404a748be2 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 6 Aug 2020 14:05:19 -0700 Subject: [PATCH 45/64] Add test for primitives --- .../signalr/MessagePackHubProtocol.java | 15 ++- .../java/com/microsoft/signalr/Utils.java | 12 -- .../signalr/MessagePackHubProtocolTest.java | 125 +++++++++++++++--- 3 files changed, 119 insertions(+), 33 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 6fefd20ac7bf..da6ef7ae560c 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -523,6 +523,14 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE break; default: item = unpacker.unpackInt(); + // unpackInt could correspond to an int, short, char, or byte - cast those literally here + if (itemType.equals(Short.class)) { + item = ((Integer) item).shortValue(); + } else if (itemType.equals(Character.class)) { + item = (char) ((Integer) item).shortValue(); + } else if (itemType.equals(Byte.class)) { + item = ((Integer) item).byteValue(); + } break; } break; @@ -572,9 +580,6 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE default: break; } - if (itemType.isPrimitive()) { - return Utils.toPrimitive(itemType, item); - } return itemType.cast(item); } @@ -590,6 +595,9 @@ private void writeValue(Object o, MessagePacker packer) throws IOException { packer.packLong((long) o); } else if (o instanceof Short) { packer.packShort((short) o); + // Pack char as short + } else if (o instanceof Character) { + packer.packShort((short) ((Character) o).charValue()); } else if (o instanceof Integer) { packer.packInt((int) o); } else if (o instanceof Double) { @@ -600,7 +608,6 @@ private void writeValue(Object o, MessagePacker packer) throws IOException { packer.packString((String) o); } else if (o instanceof Byte) { packer.packByte((byte) o); - // Unsure about this } else if (o instanceof Collection) { @SuppressWarnings("unchecked") Collection list = (Collection) o; diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java index bc916e886c0e..0a23c3746bae 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java @@ -16,18 +16,6 @@ public static String appendQueryString(String original, String queryStringValue) } } - public static Object toPrimitive(Class c, Object value) { - if (Boolean.class == c) return ((Boolean) value).booleanValue(); - if (Byte.class == c) return ((Byte) value).byteValue(); - if (Short.class == c) return ((Short) value).shortValue(); - if (Integer.class == c) return ((Integer) value).intValue(); - if (Long.class == c) return ((Long) value).longValue(); - if (Float.class == c) return ((Float) value).floatValue(); - if (Double.class == c) return ((Double) value).doubleValue(); - if (Character.class == c) return ((Character) value).charValue(); - return value; - } - public static int readLengthHeader(ByteBuffer buffer) throws IOException { // The payload starts with a length prefix encoded as a VarInt. VarInts use the most significant bit // as a marker whether the byte is the last byte of the VarInt or if it spans to the next byte. Bytes diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index 9221697c88c4..313d19eb7ffe 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -43,9 +43,9 @@ public void verifyWriteInvocationMessage() { @Test public void verifyWriteInvocationMessageWithHeaders() { - Map headers = new HashMap(); - headers.put("a", "b"); - headers.put("c", "d"); + Map headers = new HashMap(); + headers.put("a", "b"); + headers.put("c", "d"); InvocationMessage invocationMessage = new InvocationMessage(headers, null, "test", new Object[] { 42 }, null); ByteBuffer result = messagePackHubProtocol.writeMessage(invocationMessage); byte[] expectedBytes = {0x14, (byte) 0x96, 0x01, (byte) 0x82, (byte) 0xA1, 0x61, (byte) 0xA1, 0x62, (byte) 0xA1, 0x63, @@ -56,7 +56,7 @@ public void verifyWriteInvocationMessageWithHeaders() { @Test public void verifyWriteStreamItem() { - StreamItem streamItem = new StreamItem(null, "id", 42); + StreamItem streamItem = new StreamItem(null, "id", 42); ByteBuffer result = messagePackHubProtocol.writeMessage(streamItem); byte[] expectedBytes = {0x07, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA2, 0x69, 0x64, 0x2A}; ByteString expectedResult = ByteString.of(expectedBytes); @@ -65,7 +65,7 @@ public void verifyWriteStreamItem() { @Test public void verifyWriteCompletionMessageNonVoid() { - CompletionMessage completionMessage = new CompletionMessage(null, "id", 42, null); + CompletionMessage completionMessage = new CompletionMessage(null, "id", 42, null); ByteBuffer result = messagePackHubProtocol.writeMessage(completionMessage); byte[] expectedBytes = {0x08, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA2, 0x69, 0x64, 0x03, 0x2A}; ByteString expectedResult = ByteString.of(expectedBytes); @@ -74,7 +74,7 @@ public void verifyWriteCompletionMessageNonVoid() { @Test public void verifyWriteCompletionMessageVoid() { - CompletionMessage completionMessage = new CompletionMessage(null, "id", null, null); + CompletionMessage completionMessage = new CompletionMessage(null, "id", null, null); ByteBuffer result = messagePackHubProtocol.writeMessage(completionMessage); byte[] expectedBytes = {0x07, (byte) 0x94, 0x03, (byte) 0x80, (byte) 0xA2, 0x69, 0x64, 0x02}; ByteString expectedResult = ByteString.of(expectedBytes); @@ -83,7 +83,7 @@ public void verifyWriteCompletionMessageVoid() { @Test public void verifyWriteCompletionMessageError() { - CompletionMessage completionMessage = new CompletionMessage(null, "id", null, "error"); + CompletionMessage completionMessage = new CompletionMessage(null, "id", null, "error"); ByteBuffer result = messagePackHubProtocol.writeMessage(completionMessage); byte[] expectedBytes = {0x0D, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA2, 0x69, 0x64, 0x01, (byte) 0xA5, 0x65, 0x72, 0x72, 0x6F, 0x72}; ByteString expectedResult = ByteString.of(expectedBytes); @@ -92,7 +92,7 @@ public void verifyWriteCompletionMessageError() { @Test public void verifyWriteStreamInvocationMessage() { - List streamIds = new ArrayList(); + List streamIds = new ArrayList(); streamIds.add("stream"); StreamInvocationMessage streamInvocationMessage = new StreamInvocationMessage(null, "id", "test", new Object[] {42}, streamIds); ByteBuffer result = messagePackHubProtocol.writeMessage(streamInvocationMessage); @@ -104,7 +104,7 @@ public void verifyWriteStreamInvocationMessage() { @Test public void verifyWriteCancelInvocationMessage() { - CancelInvocationMessage cancelInvocationMessage = new CancelInvocationMessage(null, "id"); + CancelInvocationMessage cancelInvocationMessage = new CancelInvocationMessage(null, "id"); ByteBuffer result = messagePackHubProtocol.writeMessage(cancelInvocationMessage); byte[] expectedBytes = {0x06, (byte) 0x93, 0x05, (byte) 0x80, (byte) 0xA2, 0x69, 0x64}; ByteString expectedResult = ByteString.of(expectedBytes); @@ -121,7 +121,7 @@ public void verifyWritePingMessage() { @Test public void verifyWriteCloseMessage() { - CloseMessage closeMessage = new CloseMessage(); + CloseMessage closeMessage = new CloseMessage(); ByteBuffer result = messagePackHubProtocol.writeMessage(closeMessage); byte[] expectedBytes = {0x04, (byte) 0x93, 0x07, (byte) 0xC0, (byte) 0xC2}; ByteString expectedResult = ByteString.of(expectedBytes); @@ -130,7 +130,7 @@ public void verifyWriteCloseMessage() { @Test public void verifyWriteCloseMessageWithError() { - CloseMessage closeMessage = new CloseMessage("Error"); + CloseMessage closeMessage = new CloseMessage("Error"); ByteBuffer result = messagePackHubProtocol.writeMessage(closeMessage); byte[] expectedBytes = {0x09, (byte) 0x93, 0x07, (byte) 0xA5, 0x45, 0x72, 0x72, 0x6F, 0x72, (byte) 0xC2}; ByteString expectedResult = ByteString.of(expectedBytes); @@ -513,9 +513,9 @@ public void invocationBindingFailureReadsNextMessageAfterIncorrectArgument() { @Test public void errorWhenLengthHeaderTooLong() { - byte[] messageBytes = {0x0D, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, - 0x2A, (byte) 0x90}; - ByteBuffer message = ByteBuffer.wrap(messageBytes); + byte[] messageBytes = {0x0D, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, + 0x2A, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); RuntimeException exception = assertThrows(RuntimeException.class, @@ -525,14 +525,105 @@ public void errorWhenLengthHeaderTooLong() { @Test public void errorWhenLengthHeaderTooShort() { - byte[] messageBytes = {0x0B, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, - 0x2A, (byte) 0x90}; - ByteBuffer message = ByteBuffer.wrap(messageBytes); + byte[] messageBytes = {0x0B, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, + 0x2A, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); RuntimeException exception = assertThrows(RuntimeException.class, () -> messagePackHubProtocol.parseMessages(message, binder)); assertEquals("MessagePack message was length 12 but claimed to be length 11.", exception.getMessage()); } + + @Test + public void parseMessageWithTwoByteLengthHeader() { + // Test that a long message w/ a 2-byte length header is still parsed correctly + byte[] messageBytes = {(byte) 0x87, 0x01, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, + (byte) 0x91, (byte) 0xD9, 0x7A, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x72, 0x65, 0x61, 0x6C, 0x6C, + 0x79, 0x20, 0x6C, 0x6F, 0x6E, 0x67, 0x20, 0x61, 0x72, 0x67, 0x75, 0x6D, 0x65, 0x6E, 0x74, 0x20, 0x74, 0x6F, 0x20, 0x6D, + 0x61, 0x6B, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x65, 0x6E, 0x67, 0x74, 0x68, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x6D, 0x6F, 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, + 0x20, 0x31, 0x32, 0x37, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2E, 0x20, 0x57, 0x65, 0x20, 0x6A, 0x75, 0x73, 0x74, 0x20, + 0x6E, 0x65, 0x65, 0x64, 0x20, 0x61, 0x20, 0x66, 0x65, 0x77, 0x20, 0x6D, 0x6F, 0x72, 0x65, 0x20, 0x63, 0x68, 0x61, 0x72, + 0x61, 0x63, 0x74, 0x65, 0x72, 0x73, 0x2E, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { "arg" }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's an invocation message. + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); + + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + assertEquals(null, invocationMessage.getHeaders()); + assertEquals(null, invocationMessage.getStreamIds()); + + String messageResult = (String)invocationMessage.getArguments()[0]; + assertEquals("This is a really long argument to make the length of this message more than " + + "127 bytes. We just need a few more characters.", messageResult); + } + + @Test + public void verifyWriteInvocationMessageWithTwoByteLengthHeader() { + InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] { "This is a really long argument to make " + + "the length of this message more than 127 bytes. We just need a few more characters." }, null); + ByteBuffer result = messagePackHubProtocol.writeMessage(invocationMessage); + byte[] expectedBytes = {(byte) 0x87, 0x01, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, + (byte) 0x91, (byte) 0xD9, 0x7A, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x72, 0x65, 0x61, 0x6C, 0x6C, + 0x79, 0x20, 0x6C, 0x6F, 0x6E, 0x67, 0x20, 0x61, 0x72, 0x67, 0x75, 0x6D, 0x65, 0x6E, 0x74, 0x20, 0x74, 0x6F, 0x20, 0x6D, + 0x61, 0x6B, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6C, 0x65, 0x6E, 0x67, 0x74, 0x68, 0x20, 0x6F, 0x66, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x6D, 0x6F, 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, + 0x20, 0x31, 0x32, 0x37, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2E, 0x20, 0x57, 0x65, 0x20, 0x6A, 0x75, 0x73, 0x74, 0x20, + 0x6E, 0x65, 0x65, 0x64, 0x20, 0x61, 0x20, 0x66, 0x65, 0x77, 0x20, 0x6D, 0x6F, 0x72, 0x65, 0x20, 0x63, 0x68, 0x61, 0x72, + 0x61, 0x63, 0x74, 0x65, 0x72, 0x73, 0x2E, (byte) 0x90}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void parseInvocationMessageWithPrimitiveArgs() { + byte[] messageBytes = {0x1D, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x96, 0x01, (byte) 0xCB, + 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xC3, 0x11, 0x63, (byte) 0xCE, (byte) 0xC6, (byte) 0xAE, (byte) 0xA1, 0x55, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + int i = 1; + double d = 2.5d; + boolean bool = true; + byte bite = 0x11; + char c = 'c'; + long l = 3333333333l; + + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { i, d, bool, bite, c, l }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's an invocation message. + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); + + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + assertEquals(null, invocationMessage.getHeaders()); + assertEquals(null, invocationMessage.getStreamIds()); + + Object[] args = invocationMessage.getArguments(); + assertEquals(6, args.length); + assertEquals(i, (int)args[0]); + assertEquals(d, (double)args[1]); + assertEquals(bool, (boolean)args[2]); + assertEquals(bite, (byte)args[3]); + assertEquals(c, (char)args[4]); + assertEquals(l, (long)args[5]); + } } From a4a0e8f18d184e47de713c2000c1d888888c4cdd Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 7 Aug 2020 13:34:38 -0700 Subject: [PATCH 46/64] Add more tests, start using msgpack-jackson --- src/SignalR/clients/java/signalr/build.gradle | 2 + .../signalr/MessagePackHubProtocol.java | 76 +++------- .../signalr/MessagePackHubProtocolTest.java | 131 +++++++++++++++++- 3 files changed, 148 insertions(+), 61 deletions(-) diff --git a/src/SignalR/clients/java/signalr/build.gradle b/src/SignalR/clients/java/signalr/build.gradle index bc8d76657e24..5befba42cf2f 100644 --- a/src/SignalR/clients/java/signalr/build.gradle +++ b/src/SignalR/clients/java/signalr/build.gradle @@ -40,6 +40,8 @@ dependencies { api 'io.reactivex.rxjava2:rxjava:2.2.3' implementation 'org.slf4j:slf4j-api:1.7.25' compile 'org.msgpack:msgpack-core:0.8.20' + compile 'org.msgpack:jackson-dataformat-msgpack:0.8.20' + compile 'com.squareup.okhttp3:okhttp:3.11.0' } spotless { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index da6ef7ae560c..6488670f0fef 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -4,11 +4,8 @@ package com.microsoft.signalr; import java.io.IOException; -import java.math.BigInteger; import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -20,8 +17,11 @@ import org.msgpack.core.MessagePackException; import org.msgpack.core.MessagePacker; import org.msgpack.core.MessageUnpacker; +import org.msgpack.jackson.dataformat.MessagePackFactory; import org.msgpack.value.ValueType; +import com.fasterxml.jackson.databind.ObjectMapper; + public class MessagePackHubProtocol implements HubProtocol { private static final int ERROR_RESULT = 1; @@ -523,11 +523,9 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE break; default: item = unpacker.unpackInt(); - // unpackInt could correspond to an int, short, char, or byte - cast those literally here + // unpackInt could correspond to an int, short, or byte - cast those literally here if (itemType.equals(Short.class)) { item = ((Integer) item).shortValue(); - } else if (itemType.equals(Character.class)) { - item = (char) ((Integer) item).shortValue(); } else if (itemType.equals(Byte.class)) { item = ((Integer) item).byteValue(); } @@ -539,6 +537,10 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE break; case STRING: item = unpacker.unpackString(); + // ObjectMapper packs chars as Strings - correct back to char while unpacking if necessary + if (itemType.equals(char.class) || itemType.equals(Character.class)) { + item = ((String) item).charAt(0); + } break; case BINARY: length = unpacker.unpackBinaryHeader(); @@ -547,18 +549,20 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE item = binaryValue; break; case ARRAY: + // All collections are returned as arrays + System.out.println(itemType); length = unpacker.unpackArrayHeader(); Object[] arrayValue = new Object[length]; for (int i = 0; i < length; i++) { arrayValue[i] = readValue(unpacker, new Object().getClass()); } - item = arrayValue; - //If the itemType is an array, we return an array. Else we convert the array to a list. - if (!itemType.isArray()) { - item = new ArrayList(Arrays.asList(arrayValue)); - } + // Round trip the array to correct it to a Collection if applicable + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + byte[] arrayBytes = objectMapper.writeValueAsBytes(arrayValue); + item = objectMapper.readValue(arrayBytes, itemType); break; case MAP: + System.out.println(itemType.getTypeName()); length = unpacker.unpackMapHeader(); Map mapValue = new HashMap(); for (int i = 0; i < length; i++) { @@ -584,53 +588,7 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE } private void writeValue(Object o, MessagePacker packer) throws IOException { - - if (o == null) { - packer.packNil(); - } else if (o instanceof Boolean) { - packer.packBoolean((boolean) o); - } else if (o instanceof BigInteger) { - packer.packBigInteger((BigInteger) o); - } else if (o instanceof Long) { - packer.packLong((long) o); - } else if (o instanceof Short) { - packer.packShort((short) o); - // Pack char as short - } else if (o instanceof Character) { - packer.packShort((short) ((Character) o).charValue()); - } else if (o instanceof Integer) { - packer.packInt((int) o); - } else if (o instanceof Double) { - packer.packDouble((double) o); - } else if (o instanceof Float) { - packer.packFloat((float) o); - } else if (o instanceof String) { - packer.packString((String) o); - } else if (o instanceof Byte) { - packer.packByte((byte) o); - } else if (o instanceof Collection) { - @SuppressWarnings("unchecked") - Collection list = (Collection) o; - packer.packArrayHeader(list.size()); - for (Object item: list) { - writeValue(item, packer); - } - } else if (o.getClass().isArray()) { - Object[] array = (Object[]) o; - packer.packArrayHeader(array.length); - for (Object item: array) { - writeValue(item, packer); - } - } else if (o instanceof Map) { - @SuppressWarnings("unchecked") - Map map = (HashMap) o; - packer.packMapHeader(map.size()); - for (Object k: map.keySet()) { - writeValue(k, packer); - writeValue(map.get(k), packer); - } - } else { - throw new RuntimeException("Only base MessagePack types are currently supported"); - } + ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + packer.addPayload(objectMapper.writeValueAsBytes(o)); } } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index 313d19eb7ffe..a81b2611bc70 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -9,6 +9,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + import okio.ByteString; import org.junit.jupiter.api.Test; @@ -589,8 +592,9 @@ public void verifyWriteInvocationMessageWithTwoByteLengthHeader() { @Test public void parseInvocationMessageWithPrimitiveArgs() { - byte[] messageBytes = {0x1D, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x96, 0x01, (byte) 0xCB, - 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xC3, 0x11, 0x63, (byte) 0xCE, (byte) 0xC6, (byte) 0xAE, (byte) 0xA1, 0x55, (byte) 0x90}; + byte[] messageBytes = {0x1E, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x96, 0x01, (byte) 0xCB, + 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xC3, 0x11, (byte) 0xA1, 0x63, (byte) 0xCE, (byte) 0xC6, (byte) 0xAE, (byte) 0xA1, + 0x55, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); int i = 1; double d = 2.5d; @@ -626,4 +630,127 @@ public void parseInvocationMessageWithPrimitiveArgs() { assertEquals(c, (char)args[4]); assertEquals(l, (long)args[5]); } + + @Test + public void verifyWriteInvocationMessageWithPrimitiveArgs() { + int i = 1; + double d = 2.5d; + boolean bool = true; + byte bite = 0x11; + char c = 'c'; + long l = 3333333333l; + InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] { i, d, bool, bite, c, l }, null); + ByteBuffer result = messagePackHubProtocol.writeMessage(invocationMessage); + byte[] expectedBytes = {0x1E, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x96, 0x01, + (byte) 0xCB, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xC3, 0x11, (byte) 0xA1, 0x63, (byte) 0xCE, (byte) 0xC6, (byte) 0xAE, + (byte) 0xA1, 0x55, (byte) 0x90}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void parseInvocationMessageWithArrayArg() { + // Make sure that the same bytes can be parsed as both an Array and a List + byte[] messageBytes = {0x10, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, (byte) 0x94, 0x01, + 0x02, 0x03, 0x04, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + + TestBinder arrayBinder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { new int[] {} }, null)); + TestBinder listBinder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { new ArrayList() }, null)); + + List arrayMessages = messagePackHubProtocol.parseMessages(message, arrayBinder); + message.flip(); + List listMessages = messagePackHubProtocol.parseMessages(message, listBinder); + + //We know it's only one message + assertNotNull(arrayMessages); + assertEquals(1, arrayMessages.size()); + + assertNotNull(listMessages); + assertEquals(1, listMessages.size()); + + assertEquals(HubMessageType.INVOCATION, arrayMessages.get(0).getMessageType()); + assertEquals(HubMessageType.INVOCATION, listMessages.get(0).getMessageType()); + + //We can safely cast here because we know that it's an invocation message. + InvocationMessage arrayInvocationMessage = (InvocationMessage) arrayMessages.get(0); + InvocationMessage listInvocationMessage = (InvocationMessage) listMessages.get(0); + + assertEquals("test", arrayInvocationMessage.getTarget()); + assertEquals(null, arrayInvocationMessage.getInvocationId()); + assertEquals(null, arrayInvocationMessage.getHeaders()); + assertEquals(null, arrayInvocationMessage.getStreamIds()); + + assertEquals("test", listInvocationMessage.getTarget()); + assertEquals(null, listInvocationMessage.getInvocationId()); + assertEquals(null, listInvocationMessage.getHeaders()); + assertEquals(null, listInvocationMessage.getStreamIds()); + + int[] arrayArg = (int[])arrayInvocationMessage.getArguments()[0]; + @SuppressWarnings("unchecked") + List listArg = (ArrayList)listInvocationMessage.getArguments()[0]; + + assertEquals(4, arrayArg.length); + assertEquals(4, listArg.size()); + for (int i = 0; i < arrayArg.length; i++) { + assertEquals(i + 1, arrayArg[i]); + assertEquals(i + 1, (int) listArg.get(i)); + } + } + + @Test + public void verifyWriteInvocationMessageWithArrayArg() { + InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] { new int[] { 1, 2, 3, 4 } }, null); + ByteBuffer result = messagePackHubProtocol.writeMessage(invocationMessage); + byte[] expectedBytes = {0x10, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, (byte) 0x94, 0x01, + 0x02, 0x03, 0x04, (byte) 0x90}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void parseInvocationMessageWithMapArg() { + byte[] messageBytes = {0x23, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, (byte) 0x82, (byte) 0xA5, + 0x61, 0x70, 0x70, 0x6C, 0x65, (byte) 0xA6, 0x62, 0x61, 0x6E, 0x61, 0x6E, 0x61, (byte) 0xA3, 0x6B, 0x65, 0x79, (byte) 0xA5, 0x76, 0x61, 0x6C, 0x75, + 0x65, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + + TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { new HashMap() }, null)); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's an invocation message. + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); + + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + assertEquals(null, invocationMessage.getHeaders()); + assertEquals(null, invocationMessage.getStreamIds()); + + @SuppressWarnings("unchecked") + Map result = (HashMap)invocationMessage.getArguments()[0]; + assertEquals(2, result.size()); + assertEquals("value", result.get("key")); + assertEquals("banana", result.get("apple")); + } + + @Test + public void verifyWriteInvocationMessageWithMapArg() { + SortedMap argument = new TreeMap(); + argument.put("apple", "banana"); + argument.put("key", "value"); + InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] { argument }, null); + ByteBuffer result = messagePackHubProtocol.writeMessage(invocationMessage); + byte[] expectedBytes = {0x23, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, (byte) 0x82, (byte) 0xA5, + 0x61, 0x70, 0x70, 0x6C, 0x65, (byte) 0xA6, 0x62, 0x61, 0x6E, 0x61, 0x6E, 0x61, (byte) 0xA3, 0x6B, 0x65, 0x79, (byte) 0xA5, 0x76, 0x61, 0x6C, 0x75, + 0x65, (byte) 0x90}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } } From 7f6fe1c1d678b0d7b8f473c81bf56400457fea06 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 7 Aug 2020 13:36:19 -0700 Subject: [PATCH 47/64] Fix build.gradle --- src/SignalR/clients/java/signalr/build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SignalR/clients/java/signalr/build.gradle b/src/SignalR/clients/java/signalr/build.gradle index 5befba42cf2f..d55927441423 100644 --- a/src/SignalR/clients/java/signalr/build.gradle +++ b/src/SignalR/clients/java/signalr/build.gradle @@ -41,7 +41,6 @@ dependencies { implementation 'org.slf4j:slf4j-api:1.7.25' compile 'org.msgpack:msgpack-core:0.8.20' compile 'org.msgpack:jackson-dataformat-msgpack:0.8.20' - compile 'com.squareup.okhttp3:okhttp:3.11.0' } spotless { From 1f3034b0c5a0a17c46e809f7e7cf0fdca38d7968 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 7 Aug 2020 13:37:12 -0700 Subject: [PATCH 48/64] Remove debug prints --- .../main/java/com/microsoft/signalr/MessagePackHubProtocol.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 6488670f0fef..94fd17f4793a 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -550,7 +550,6 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE break; case ARRAY: // All collections are returned as arrays - System.out.println(itemType); length = unpacker.unpackArrayHeader(); Object[] arrayValue = new Object[length]; for (int i = 0; i < length; i++) { @@ -562,7 +561,6 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE item = objectMapper.readValue(arrayBytes, itemType); break; case MAP: - System.out.println(itemType.getTypeName()); length = unpacker.unpackMapHeader(); Map mapValue = new HashMap(); for (int i = 0; i < length; i++) { From 58bbad308bc7aba1e94e6d0e3227f574c04cd984 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Mon, 10 Aug 2020 16:43:53 -0700 Subject: [PATCH 49/64] Start using Type instead of Class --- .../com/microsoft/signalr/CallbackMap.java | 4 +- .../com/microsoft/signalr/HubConnection.java | 131 +++++++++++++++--- .../microsoft/signalr/InvocationBinder.java | 4 +- .../microsoft/signalr/InvocationHandler.java | 10 +- .../microsoft/signalr/InvocationRequest.java | 7 +- .../microsoft/signalr/JsonHubProtocol.java | 32 +++-- .../signalr/MessagePackHubProtocol.java | 55 ++++---- .../com/microsoft/signalr/TypeAndClass.java | 22 +++ .../com/microsoft/signalr/TypeReference.java | 28 ++++ .../java/com/microsoft/signalr/Utils.java | 5 + .../signalr/MessagePackHubProtocolTest.java | 9 +- .../com/microsoft/signalr/TestBinder.java | 33 +++-- 12 files changed, 258 insertions(+), 82 deletions(-) create mode 100644 src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeAndClass.java create mode 100644 src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CallbackMap.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CallbackMap.java index a6298aed52cf..42034f13711a 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CallbackMap.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CallbackMap.java @@ -13,10 +13,10 @@ class CallbackMap { private final Map> handlers = new HashMap<>(); private final ReentrantLock lock = new ReentrantLock(); - public InvocationHandler put(String target, ActionBase action, Class... classes) { + public InvocationHandler put(String target, ActionBase action, TypeAndClass... types) { try { lock.lock(); - InvocationHandler handler = new InvocationHandler(action, classes); + InvocationHandler handler = new InvocationHandler(action, types); if (!handlers.containsKey(target)) { handlers.put(target, new ArrayList<>()); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index 107358df3caf..da04742bf5a5 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -4,6 +4,8 @@ package com.microsoft.signalr; import java.io.StringReader; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.*; @@ -29,7 +31,7 @@ */ public class HubConnection implements AutoCloseable { private static final byte RECORD_SEPARATOR = 0x1e; - private static final List> emptyArray = new ArrayList<>(); + private static final List emptyArray = new ArrayList<>(); private static final int MAX_NEGOTIATE_ATTEMPTS = 100; private String baseUrl; @@ -706,8 +708,9 @@ public Single invoke(Class returnType, String method, Object... args) throw new RuntimeException("The 'invoke' method cannot be called if the connection is not active."); } + TypeAndClass returnTac = new TypeAndClass(Utils.getType(returnType), returnType); String id = connectionState.getNextInvocationId(); - InvocationRequest irq = new InvocationRequest(returnType, id); + InvocationRequest irq = new InvocationRequest(returnTac, id); connectionState.addInvocation(irq); SingleSubject subject = SingleSubject.create(); @@ -752,8 +755,9 @@ public Observable stream(Class returnType, String method, Object ... a throw new RuntimeException("The 'stream' method cannot be called if the connection is not active."); } + TypeAndClass returnTac = new TypeAndClass(Utils.getType(returnType), returnType); invocationId = connectionState.getNextInvocationId(); - irq = new InvocationRequest(returnType, invocationId); + irq = new InvocationRequest(returnTac, invocationId); connectionState.addInvocation(irq); AtomicInteger subscriptionCount = new AtomicInteger(); @@ -853,9 +857,12 @@ public Subscription on(String target, Action callback) { * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. */ public Subscription on(String target, Action1 callback, Class param1) { - ActionBase action = params -> callback.invoke(param1.cast(params[0])); - return registerHandler(target, action, param1); - + TypeVariable type1 = (TypeVariable) (new TypeReference() {}).getType(); + + TypeAndClass tac1 = new TypeAndClass(type1, param1); + + ActionBase action = params -> callback.invoke(param1.cast(params[0])); + return registerHandler(target, action, tac1); } /** @@ -870,10 +877,16 @@ public Subscription on(String target, Action1 callback, Class param * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. */ public Subscription on(String target, Action2 callback, Class param1, Class param2) { + Type type1 = Utils.getType(param1); + Type type2 = Utils.getType(param2); + + TypeAndClass tac1 = new TypeAndClass(type1, param1); + TypeAndClass tac2 = new TypeAndClass(type2, param2); + ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1])); }; - return registerHandler(target, action, param1, param2); + return registerHandler(target, action, tac1, tac2); } /** @@ -891,10 +904,18 @@ public Subscription on(String target, Action2 callback, Class Subscription on(String target, Action3 callback, Class param1, Class param2, Class param3) { + Type type1 = Utils.getType(param1); + Type type2 = Utils.getType(param2); + Type type3 = Utils.getType(param3); + + TypeAndClass tac1 = new TypeAndClass(type1, param1); + TypeAndClass tac2 = new TypeAndClass(type2, param2); + TypeAndClass tac3 = new TypeAndClass(type3, param3); + ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2])); }; - return registerHandler(target, action, param1, param2, param3); + return registerHandler(target, action, tac1, tac2, tac3); } /** @@ -914,10 +935,20 @@ public Subscription on(String target, Action3 callback, */ public Subscription on(String target, Action4 callback, Class param1, Class param2, Class param3, Class param4) { + Type type1 = Utils.getType(param1); + Type type2 = Utils.getType(param2); + Type type3 = Utils.getType(param3); + Type type4 = Utils.getType(param4); + + TypeAndClass tac1 = new TypeAndClass(type1, param1); + TypeAndClass tac2 = new TypeAndClass(type2, param2); + TypeAndClass tac3 = new TypeAndClass(type3, param3); + TypeAndClass tac4 = new TypeAndClass(type4, param4); + ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3])); }; - return registerHandler(target, action, param1, param2, param3, param4); + return registerHandler(target, action, tac1, tac2, tac3, tac4); } /** @@ -939,11 +970,23 @@ public Subscription on(String target, Action4 c */ public Subscription on(String target, Action5 callback, Class param1, Class param2, Class param3, Class param4, Class param5) { + Type type1 = Utils.getType(param1); + Type type2 = Utils.getType(param2); + Type type3 = Utils.getType(param3); + Type type4 = Utils.getType(param4); + Type type5 = Utils.getType(param5); + + TypeAndClass tac1 = new TypeAndClass(type1, param1); + TypeAndClass tac2 = new TypeAndClass(type2, param2); + TypeAndClass tac3 = new TypeAndClass(type3, param3); + TypeAndClass tac4 = new TypeAndClass(type4, param4); + TypeAndClass tac5 = new TypeAndClass(type5, param5); + ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4])); }; - return registerHandler(target, action, param1, param2, param3, param4, param5); + return registerHandler(target, action, tac1, tac2, tac3, tac4, tac5); } /** @@ -967,11 +1010,25 @@ public Subscription on(String target, Action5 Subscription on(String target, Action6 callback, Class param1, Class param2, Class param3, Class param4, Class param5, Class param6) { - ActionBase action = params -> { + Type type1 = Utils.getType(param1); + Type type2 = Utils.getType(param2); + Type type3 = Utils.getType(param3); + Type type4 = Utils.getType(param4); + Type type5 = Utils.getType(param5); + Type type6 = Utils.getType(param6); + + TypeAndClass tac1 = new TypeAndClass(type1, param1); + TypeAndClass tac2 = new TypeAndClass(type2, param2); + TypeAndClass tac3 = new TypeAndClass(type3, param3); + TypeAndClass tac4 = new TypeAndClass(type4, param4); + TypeAndClass tac5 = new TypeAndClass(type5, param5); + TypeAndClass tac6 = new TypeAndClass(type6, param6); + + ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4]), param6.cast(params[5])); }; - return registerHandler(target, action, param1, param2, param3, param4, param5, param6); + return registerHandler(target, action, tac1, tac2, tac3, tac4, tac5, tac6); } /** @@ -997,11 +1054,27 @@ public Subscription on(String target, Action6 Subscription on(String target, Action7 callback, Class param1, Class param2, Class param3, Class param4, Class param5, Class param6, Class param7) { - ActionBase action = params -> { + Type type1 = Utils.getType(param1); + Type type2 = Utils.getType(param2); + Type type3 = Utils.getType(param3); + Type type4 = Utils.getType(param4); + Type type5 = Utils.getType(param5); + Type type6 = Utils.getType(param6); + Type type7 = Utils.getType(param7); + + TypeAndClass tac1 = new TypeAndClass(type1, param1); + TypeAndClass tac2 = new TypeAndClass(type2, param2); + TypeAndClass tac3 = new TypeAndClass(type3, param3); + TypeAndClass tac4 = new TypeAndClass(type4, param4); + TypeAndClass tac5 = new TypeAndClass(type5, param5); + TypeAndClass tac6 = new TypeAndClass(type6, param6); + TypeAndClass tac7 = new TypeAndClass(type7, param7); + + ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4]), param6.cast(params[5]), param7.cast(params[6])); }; - return registerHandler(target, action, param1, param2, param3, param4, param5, param6, param7); + return registerHandler(target, action, tac1, tac2, tac3, tac4, tac5, tac6, tac7); } /** @@ -1029,14 +1102,32 @@ public Subscription on(String target, Action7 Subscription on(String target, Action8 callback, Class param1, Class param2, Class param3, Class param4, Class param5, Class param6, Class param7, Class param8) { - ActionBase action = params -> { + Type type1 = Utils.getType(param1); + Type type2 = Utils.getType(param2); + Type type3 = Utils.getType(param3); + Type type4 = Utils.getType(param4); + Type type5 = Utils.getType(param5); + Type type6 = Utils.getType(param6); + Type type7 = Utils.getType(param7); + Type type8 = Utils.getType(param8); + + TypeAndClass tac1 = new TypeAndClass(type1, param1); + TypeAndClass tac2 = new TypeAndClass(type2, param2); + TypeAndClass tac3 = new TypeAndClass(type3, param3); + TypeAndClass tac4 = new TypeAndClass(type4, param4); + TypeAndClass tac5 = new TypeAndClass(type5, param5); + TypeAndClass tac6 = new TypeAndClass(type6, param6); + TypeAndClass tac7 = new TypeAndClass(type7, param7); + TypeAndClass tac8 = new TypeAndClass(type8, param8); + + ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4]), param6.cast(params[5]), param7.cast(params[6]), param8.cast(params[7])); }; - return registerHandler(target, action, param1, param2, param3, param4, param5, param6, param7, param8); + return registerHandler(target, action, tac1, tac2, tac3, tac4, tac5, tac6, tac7, tac8); } - private Subscription registerHandler(String target, ActionBase action, Class... types) { + private Subscription registerHandler(String target, ActionBase action, TypeAndClass... types) { InvocationHandler handler = handlers.put(target, action, types); logger.debug("Registering handler for client method: '{}'.", target); return new Subscription(handlers, handler, target); @@ -1107,7 +1198,7 @@ public InvocationRequest tryRemoveInvocation(String id) { } @Override - public Class getReturnType(String invocationId) { + public TypeAndClass getReturnType(String invocationId) { InvocationRequest irq = getInvocation(invocationId); if (irq == null) { return null; @@ -1117,7 +1208,7 @@ public Class getReturnType(String invocationId) { } @Override - public List> getParameterTypes(String methodName) { + public List getParameterTypes(String methodName) { List handlers = connection.handlers.get(methodName); if (handlers == null) { logger.warn("Failed to find handler for '{}' method.", methodName); @@ -1128,7 +1219,7 @@ public List> getParameterTypes(String methodName) { throw new RuntimeException(String.format("There are no callbacks registered for the method '%s'.", methodName)); } - return handlers.get(0).getClasses(); + return handlers.get(0).getTypes(); } } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java index 07248cd27299..790f2dae4be8 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java @@ -6,6 +6,6 @@ import java.util.List; public interface InvocationBinder { - Class getReturnType(String invocationId); - List> getParameterTypes(String methodName); + TypeAndClass getReturnType(String invocationId); + List getParameterTypes(String methodName); } \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationHandler.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationHandler.java index 64f8ed3a1d83..5478fb17057a 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationHandler.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationHandler.java @@ -7,16 +7,16 @@ import java.util.List; class InvocationHandler { - private final List> classes; + private final List types; private final ActionBase action; - InvocationHandler(ActionBase action, Class... classes) { + InvocationHandler(ActionBase action, TypeAndClass... types) { this.action = action; - this.classes = Arrays.asList(classes); + this.types = Arrays.asList(types); } - public List> getClasses() { - return classes; + public List getTypes() { + return types; } public ActionBase getAction() { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationRequest.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationRequest.java index 7de8890db73f..90b57e4b2f9f 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationRequest.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationRequest.java @@ -3,17 +3,18 @@ package com.microsoft.signalr; +import java.lang.reflect.Type; import java.util.concurrent.CancellationException; import io.reactivex.subjects.ReplaySubject; import io.reactivex.subjects.Subject; class InvocationRequest { - private final Class returnType; + private final TypeAndClass returnType; private final Subject pendingCall = ReplaySubject.create(); private final String invocationId; - InvocationRequest(Class returnType, String invocationId) { + InvocationRequest(TypeAndClass returnType, String invocationId) { this.returnType = returnType; this.invocationId = invocationId; } @@ -47,7 +48,7 @@ public Subject getPendingCall() { return pendingCall; } - public Class getReturnType() { + public TypeAndClass getReturnType() { return returnType; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index 054244471899..35db1261aac0 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -5,6 +5,7 @@ import java.io.IOException; import java.io.StringReader; +import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -85,14 +86,14 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde if (invocationId == null || binder.getReturnType(invocationId) == null) { resultToken = jsonParser.parse(reader); } else { - result = gson.fromJson(reader, binder.getReturnType(invocationId)); + result = gson.fromJson(reader, binder.getReturnType(invocationId).getClazz()); } break; case "arguments": if (target != null) { boolean startedArray = false; try { - List> types = binder.getParameterTypes(target); + List types = binder.getParameterTypes(target); startedArray = true; arguments = bindArguments(reader, types); } catch (Exception ex) { @@ -130,7 +131,7 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde case INVOCATION: if (argumentsToken != null) { try { - List> types = binder.getParameterTypes(target); + List types = binder.getParameterTypes(target); arguments = bindArguments(argumentsToken, types); } catch (Exception ex) { argumentBindingException = ex; @@ -148,15 +149,23 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde break; case COMPLETION: if (resultToken != null) { - Class returnType = binder.getReturnType(invocationId); - result = gson.fromJson(resultToken, returnType != null ? returnType : Object.class); + TypeAndClass returnType = binder.getReturnType(invocationId); + Class completionReturnType = Object.class; + if (returnType != null && returnType.getClazz() != null) { + completionReturnType = returnType.getClazz(); + } + result = gson.fromJson(resultToken, completionReturnType); } hubMessages.add(new CompletionMessage(null, invocationId, result, error)); break; case STREAM_ITEM: if (resultToken != null) { - Class returnType = binder.getReturnType(invocationId); - result = gson.fromJson(resultToken, returnType != null ? returnType : Object.class); + TypeAndClass returnType = binder.getReturnType(invocationId); + Class streamReturnType = Object.class; + if (returnType != null && returnType.getClazz() != null) { + streamReturnType = returnType.getClazz(); + } + result = gson.fromJson(resultToken, streamReturnType); } hubMessages.add(new StreamItem(null, invocationId, result)); break; @@ -189,7 +198,7 @@ public ByteBuffer writeMessage(HubMessage hubMessage) { return ByteBuffer.wrap((gson.toJson(hubMessage) + RECORD_SEPARATOR).getBytes(StandardCharsets.UTF_8)); } - private ArrayList bindArguments(JsonArray argumentsToken, List> paramTypes) { + private ArrayList bindArguments(JsonArray argumentsToken, List paramTypes) { if (argumentsToken.size() != paramTypes.size()) { throw new RuntimeException(String.format("Invocation provides %d argument(s) but target expects %d.", argumentsToken.size(), paramTypes.size())); } @@ -198,21 +207,22 @@ private ArrayList bindArguments(JsonArray argumentsToken, List> if (paramTypes.size() >= 1) { arguments = new ArrayList<>(); for (int i = 0; i < paramTypes.size(); i++) { - arguments.add(gson.fromJson(argumentsToken.get(i), paramTypes.get(i))); + arguments.add(gson.fromJson(argumentsToken.get(i), paramTypes.get(i).getClazz())); } } return arguments; } - private ArrayList bindArguments(JsonReader reader, List> paramTypes) throws IOException { + private ArrayList bindArguments(JsonReader reader, List paramTypes) throws IOException { reader.beginArray(); int paramCount = paramTypes.size(); int argCount = 0; ArrayList arguments = new ArrayList<>(); while (reader.peek() != JsonToken.END_ARRAY) { if (argCount < paramCount) { - arguments.add(gson.fromJson(reader, paramTypes.get(argCount))); + Object o = gson.fromJson(reader, paramTypes.get(argCount).getClazz()); + arguments.add(o); } else { reader.skipValue(); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 94fd17f4793a..aa984d584aa0 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -4,6 +4,7 @@ package com.microsoft.signalr; import java.io.IOException; +import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; @@ -21,12 +22,17 @@ import org.msgpack.value.ValueType; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.TypeFactory; public class MessagePackHubProtocol implements HubProtocol { private static final int ERROR_RESULT = 1; private static final int VOID_RESULT = 2; private static final int NON_VOID_RESULT = 3; + + private TypeAndClass emptyTac = new TypeAndClass(Object.class, Object.class); + private ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + private TypeFactory typeFactory = objectMapper.getTypeFactory(); @Override public String getName() { @@ -179,7 +185,7 @@ private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationB Object[] arguments = null; try { - List> types = binder.getParameterTypes(target); + List types = binder.getParameterTypes(target); arguments = bindArguments(unpacker, types); } catch (Exception ex) { return new InvocationBindingFailureMessage(invocationId, target, ex); @@ -199,7 +205,7 @@ private HubMessage createStreamItemMessage(MessageUnpacker unpacker, InvocationB String invocationId = unpacker.unpackString(); Object value; try { - Class itemType = binder.getReturnType(invocationId); + TypeAndClass itemType = binder.getReturnType(invocationId); value = readValue(unpacker, itemType); } catch (Exception ex) { return new StreamBindingFailureMessage(invocationId, ex); @@ -223,7 +229,7 @@ private HubMessage createCompletionMessage(MessageUnpacker unpacker, InvocationB case VOID_RESULT: break; case NON_VOID_RESULT: - Class itemType = binder.getReturnType(invocationId); + TypeAndClass itemType = binder.getReturnType(invocationId); result = readValue(unpacker, itemType); break; default: @@ -240,7 +246,7 @@ private HubMessage createStreamInvocationMessage(MessageUnpacker unpacker, Invoc Object[] arguments = null; try { - List> types = binder.getParameterTypes(target); + List types = binder.getParameterTypes(target); arguments = bindArguments(unpacker, types); } catch (Exception ex) { return new InvocationBindingFailureMessage(invocationId, target, ex); @@ -478,7 +484,7 @@ private void writeStreamIds(Collection streamIds, MessagePacker packer) } } - private Object[] bindArguments(MessageUnpacker unpacker, List> paramTypes) throws IOException { + private Object[] bindArguments(MessageUnpacker unpacker, List paramTypes) throws IOException { int argumentCount = unpacker.unpackArrayHeader(); if (paramTypes.size() != argumentCount) { @@ -494,7 +500,7 @@ private Object[] bindArguments(MessageUnpacker unpacker, List> paramTyp return arguments; } - private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOException { + private Object readValue(MessageUnpacker unpacker, TypeAndClass itemType) throws IOException { // This shouldn't ever get called with itemType == null, but we return anyways to avoid NullPointerExceptions if (itemType == null) { return null; @@ -507,8 +513,7 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE switch(valueType) { case NIL: unpacker.unpackNil(); - item = null; - break; + return null; case BOOLEAN: item = unpacker.unpackBoolean(); break; @@ -523,10 +528,12 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE break; default: item = unpacker.unpackInt(); - // unpackInt could correspond to an int, short, or byte - cast those literally here - if (itemType.equals(Short.class)) { + // unpackInt could correspond to an int, short, char, or byte - cast those literally here + if (itemType.getClazz().equals(Short.class)) { item = ((Integer) item).shortValue(); - } else if (itemType.equals(Byte.class)) { + } else if (itemType.getClazz().equals(Character.class)) { + item = (char) ((Integer) item).shortValue(); + } else if (itemType.getClazz().equals(Byte.class)) { item = ((Integer) item).byteValue(); } break; @@ -538,7 +545,7 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE case STRING: item = unpacker.unpackString(); // ObjectMapper packs chars as Strings - correct back to char while unpacking if necessary - if (itemType.equals(char.class) || itemType.equals(Character.class)) { + if (itemType.getClazz().equals(char.class) || itemType.getClazz().equals(Character.class)) { item = ((String) item).charAt(0); } break; @@ -549,27 +556,26 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE item = binaryValue; break; case ARRAY: - // All collections are returned as arrays length = unpacker.unpackArrayHeader(); Object[] arrayValue = new Object[length]; for (int i = 0; i < length; i++) { - arrayValue[i] = readValue(unpacker, new Object().getClass()); + arrayValue[i] = readValue(unpacker, emptyTac); } - // Round trip the array to correct it to a Collection if applicable - ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); + // Round trip the array through ObjectMapper, in case it needs to be converted to another collection, + // Or have the type of its contained elements corrected byte[] arrayBytes = objectMapper.writeValueAsBytes(arrayValue); - item = objectMapper.readValue(arrayBytes, itemType); - break; + return objectMapper.readValue(arrayBytes, typeFactory.constructType(itemType.getType())); case MAP: length = unpacker.unpackMapHeader(); Map mapValue = new HashMap(); for (int i = 0; i < length; i++) { - Object key = readValue(unpacker, new Object().getClass()); - Object value = readValue(unpacker, new Object().getClass()); + Object key = readValue(unpacker, emptyTac); + Object value = readValue(unpacker, emptyTac); mapValue.put(key, value); } - item = mapValue; - break; + // Round trip the map through ObjectMapper - it could be a POJO, or it could need its contained elements to be corrected + byte[] mapBytes = objectMapper.writeValueAsBytes(mapValue); + return objectMapper.readValue(mapBytes, typeFactory.constructType(itemType.getType())); case EXTENSION: /* ExtensionTypeHeader extension = unpacker.unpackExtensionTypeHeader(); @@ -580,9 +586,10 @@ private Object readValue(MessageUnpacker unpacker, Class itemType) throws IOE */ throw new RuntimeException("Extension types are not supported yet"); default: - break; + return null; } - return itemType.cast(item); + // If we get here, the item isn't a map or a collection/array, so we use the Class to cast it + return itemType.getClazz().cast(item); } private void writeValue(Object o, MessagePacker packer) throws IOException { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeAndClass.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeAndClass.java new file mode 100644 index 000000000000..22e584e363f7 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeAndClass.java @@ -0,0 +1,22 @@ +package com.microsoft.signalr; + +import java.lang.reflect.Type; + +class TypeAndClass { + + private Type type; + private Class clazz; + + public TypeAndClass(Type type, Class clazz) { + this.type = type; + this.clazz = clazz; + } + + public Type getType() { + return type; + } + + public Class getClazz() { + return clazz; + } +} \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java new file mode 100644 index 000000000000..840c884d00b5 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java @@ -0,0 +1,28 @@ +package com.microsoft.signalr; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.lang.reflect.ParameterizedType; + +public class TypeReference { + + private final Type type; + + protected TypeReference() { + Type superclass = getClass().getGenericSuperclass(); + if (superclass instanceof Class) { + throw new RuntimeException("Missing type parameter."); + } + this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; + } + + /** + * Gets the referenced type. + */ + public Type getType() { + return this.type; + } +} diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java index 0a23c3746bae..6a87c4c65a22 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java @@ -4,6 +4,7 @@ package com.microsoft.signalr; import java.io.IOException; +import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -69,4 +70,8 @@ public static ArrayList getLengthHeader(int length) { return header; } + + public static Type getType(Class param) { + return (new TypeReference() {}).getType(); + } } \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index a81b2611bc70..4c938754a335 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -414,7 +415,8 @@ public void invocationBindingFailureWhenParsingIncorrectType() { assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); - assertEquals("Cannot cast java.lang.Boolean to java.lang.Integer", invocationBindingFailureMessage.getException().getMessage()); + assertTrue(invocationBindingFailureMessage.getException().getMessage().startsWith + ("Cannot deserialize instance of `java.lang.Integer` out of VALUE_TRUE token")); } @Test @@ -497,7 +499,8 @@ public void invocationBindingFailureReadsNextMessageAfterIncorrectArgument() { assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); - assertEquals("Cannot cast java.lang.Boolean to java.lang.Integer", invocationBindingFailureMessage.getException().getMessage()); + assertTrue(invocationBindingFailureMessage.getException().getMessage().startsWith + ("Cannot deserialize instance of `java.lang.Integer` out of VALUE_TRUE token")); // Check the second message assertEquals(HubMessageType.INVOCATION, messages.get(1).getMessageType()); @@ -690,7 +693,7 @@ public void parseInvocationMessageWithArrayArg() { @SuppressWarnings("unchecked") List listArg = (ArrayList)listInvocationMessage.getArguments()[0]; - assertEquals(4, arrayArg.length); + //assertEquals(4, arrayArg.length); assertEquals(4, listArg.size()); for (int i = 0; i < arrayArg.length; i++) { assertEquals(i + 1, arrayArg[i]); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java index 6ad42e1f22d5..9dbb59dc1393 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java @@ -1,12 +1,13 @@ package com.microsoft.signalr; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class TestBinder implements InvocationBinder { - private Class[] paramTypes = null; - private Class returnType = null; + private TypeAndClass[] paramTypes = null; + private TypeAndClass returnType = null; public TestBinder(HubMessage expectedMessage) { if (expectedMessage == null) { @@ -15,39 +16,47 @@ public TestBinder(HubMessage expectedMessage) { switch (expectedMessage.getMessageType()) { case STREAM_INVOCATION: - ArrayList> streamTypes = new ArrayList<>(); + ArrayList streamTypes = new ArrayList<>(); for (Object obj : ((StreamInvocationMessage) expectedMessage).getArguments()) { - streamTypes.add(obj.getClass()); + Type type = getType(obj.getClass()); + streamTypes.add(new TypeAndClass(type, obj.getClass())); } - paramTypes = streamTypes.toArray(new Class[streamTypes.size()]); + paramTypes = streamTypes.toArray(new TypeAndClass[streamTypes.size()]); break; case INVOCATION: - ArrayList> types = new ArrayList<>(); + ArrayList types = new ArrayList<>(); for (Object obj : ((InvocationMessage) expectedMessage).getArguments()) { - types.add(obj.getClass()); + Type type = getType(obj.getClass()); + types.add(new TypeAndClass(type, obj.getClass())); } - paramTypes = types.toArray(new Class[types.size()]); + paramTypes = types.toArray(new TypeAndClass[types.size()]); break; case STREAM_ITEM: break; case COMPLETION: - returnType = ((CompletionMessage)expectedMessage).getResult().getClass(); + Object obj = ((CompletionMessage)expectedMessage).getResult().getClass(); + returnType = new TypeAndClass(getType(((CompletionMessage)expectedMessage).getResult().getClass()), + ((CompletionMessage)expectedMessage).getResult().getClass()); break; default: break; } } + + private Type getType(Class param) { + return (new TypeReference() {}).getType(); + } @Override - public Class getReturnType(String invocationId) { + public TypeAndClass getReturnType(String invocationId) { return returnType; } @Override - public List> getParameterTypes(String methodName) { + public List getParameterTypes(String methodName) { if (paramTypes == null) { return new ArrayList<>(); } - return new ArrayList>(Arrays.asList(paramTypes)); + return new ArrayList(Arrays.asList(paramTypes)); } } \ No newline at end of file From 7c3a18c29eab99514092f5afcc140fff4823521f Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 12 Aug 2020 13:57:26 -0700 Subject: [PATCH 50/64] Add overloads for Type, make messagePack readValue() more efficient --- .../com/microsoft/signalr/CallbackMap.java | 3 +- .../com/microsoft/signalr/HubConnection.java | 441 +++++++++++++----- .../microsoft/signalr/InvocationBinder.java | 5 +- .../microsoft/signalr/InvocationHandler.java | 7 +- .../microsoft/signalr/InvocationRequest.java | 6 +- .../microsoft/signalr/JsonHubProtocol.java | 30 +- .../signalr/MessagePackHubProtocol.java | 96 ++-- .../com/microsoft/signalr/TypeAndClass.java | 22 - .../com/microsoft/signalr/TypeReference.java | 8 +- .../java/com/microsoft/signalr/Utils.java | 36 +- .../microsoft/signalr/HubConnectionTest.java | 8 +- .../signalr/JsonHubProtocolTest.java | 33 +- .../signalr/MessagePackHubProtocolTest.java | 57 +-- .../com/microsoft/signalr/TestBinder.java | 49 +- .../java/com/microsoft/signalr/TestUtils.java | 1 + 15 files changed, 510 insertions(+), 292 deletions(-) delete mode 100644 src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeAndClass.java diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CallbackMap.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CallbackMap.java index 42034f13711a..5bb317cd1fb5 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CallbackMap.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CallbackMap.java @@ -3,6 +3,7 @@ package com.microsoft.signalr; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -13,7 +14,7 @@ class CallbackMap { private final Map> handlers = new HashMap<>(); private final ReentrantLock lock = new ReentrantLock(); - public InvocationHandler put(String target, ActionBase action, TypeAndClass... types) { + public InvocationHandler put(String target, ActionBase action, Type... types) { try { lock.lock(); InvocationHandler handler = new InvocationHandler(action, types); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index da04742bf5a5..bd7c7ea3712f 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -31,7 +31,7 @@ */ public class HubConnection implements AutoCloseable { private static final byte RECORD_SEPARATOR = 0x1e; - private static final List emptyArray = new ArrayList<>(); + private static final List emptyArray = new ArrayList<>(); private static final int MAX_NEGOTIATE_ATTEMPTS = 100; private String baseUrl; @@ -707,10 +707,9 @@ public Single invoke(Class returnType, String method, Object... args) if (hubConnectionState != HubConnectionState.CONNECTED) { throw new RuntimeException("The 'invoke' method cannot be called if the connection is not active."); } - - TypeAndClass returnTac = new TypeAndClass(Utils.getType(returnType), returnType); + String id = connectionState.getNextInvocationId(); - InvocationRequest irq = new InvocationRequest(returnTac, id); + InvocationRequest irq = new InvocationRequest(returnType, id); connectionState.addInvocation(irq); SingleSubject subject = SingleSubject.create(); @@ -735,6 +734,52 @@ public Single invoke(Class returnType, String method, Object... args) hubConnectionStateLock.unlock(); } } + + + /** + * Invokes a hub method on the server using the specified method name and arguments. + * + * @param returnType The expected return type. + * @param method The name of the server method to invoke. + * @param args The arguments used to invoke the server method. + * @param The expected return type. + * @return A Single that yields the return value when the invocation has completed. + */ + @SuppressWarnings("unchecked") + public Single invoke(Type returnType, String method, Object... args) { + hubConnectionStateLock.lock(); + try { + if (hubConnectionState != HubConnectionState.CONNECTED) { + throw new RuntimeException("The 'invoke' method cannot be called if the connection is not active."); + } + + Class returnClass = Utils.typeToClass(returnType); + String id = connectionState.getNextInvocationId(); + InvocationRequest irq = new InvocationRequest(returnType, id); + connectionState.addInvocation(irq); + + SingleSubject subject = SingleSubject.create(); + + // forward the invocation result or error to the user + // run continuations on a separate thread + Subject pendingCall = irq.getPendingCall(); + pendingCall.subscribe(result -> { + // Primitive types can't be cast with the Class cast function + if (returnClass.isPrimitive()) { + subject.onSuccess((T)result); + } else { + subject.onSuccess((T)returnClass.cast(result)); + } + }, error -> subject.onError(error)); + + // Make sure the actual send is after setting up the callbacks otherwise there is a race + // where the map doesn't have the callbacks yet when the response is returned + sendInvocationMessage(method, args, id, false); + return subject; + } finally { + hubConnectionStateLock.unlock(); + } + } /** * Invokes a streaming hub method on the server using the specified name and arguments. @@ -755,9 +800,8 @@ public Observable stream(Class returnType, String method, Object ... a throw new RuntimeException("The 'stream' method cannot be called if the connection is not active."); } - TypeAndClass returnTac = new TypeAndClass(Utils.getType(returnType), returnType); invocationId = connectionState.getNextInvocationId(); - irq = new InvocationRequest(returnTac, invocationId); + irq = new InvocationRequest(returnType, invocationId); connectionState.addInvocation(irq); AtomicInteger subscriptionCount = new AtomicInteger(); @@ -789,6 +833,60 @@ public Observable stream(Class returnType, String method, Object ... a hubConnectionStateLock.unlock(); } } + + /** + * Invokes a streaming hub method on the server using the specified name and arguments. + * + * @param returnType The expected return type of the stream items. + * @param method The name of the server method to invoke. + * @param args The arguments used to invoke the server method. + * @param The expected return type. + * @return An observable that yields the streaming results from the server. + */ + @SuppressWarnings("unchecked") + public Observable stream(Type returnType, String method, Object ... args) { + String invocationId; + InvocationRequest irq; + hubConnectionStateLock.lock(); + try { + if (hubConnectionState != HubConnectionState.CONNECTED) { + throw new RuntimeException("The 'stream' method cannot be called if the connection is not active."); + } + + Class returnClass = Utils.typeToClass(returnType); + invocationId = connectionState.getNextInvocationId(); + irq = new InvocationRequest(returnType, invocationId); + connectionState.addInvocation(irq); + + AtomicInteger subscriptionCount = new AtomicInteger(); + ReplaySubject subject = ReplaySubject.create(); + Subject pendingCall = irq.getPendingCall(); + pendingCall.subscribe(result -> { + // Primitive types can't be cast with the Class cast function + if (returnClass.isPrimitive()) { + subject.onNext((T)result); + } else { + subject.onNext((T)returnClass.cast(result)); + } + }, error -> subject.onError(error), + () -> subject.onComplete()); + + Observable observable = subject.doOnSubscribe((subscriber) -> subscriptionCount.incrementAndGet()); + sendInvocationMessage(method, args, invocationId, true); + return observable.doOnDispose(() -> { + if (subscriptionCount.decrementAndGet() == 0) { + CancelInvocationMessage cancelInvocationMessage = new CancelInvocationMessage(null, invocationId); + sendHubMessage(cancelInvocationMessage); + if (connectionState != null) { + connectionState.tryRemoveInvocation(invocationId); + } + subject.onComplete(); + } + }); + } finally { + hubConnectionStateLock.unlock(); + } + } private void sendHubMessage(HubMessage message) { ByteBuffer serializedMessage = protocol.writeMessage(message); @@ -846,7 +944,7 @@ public Subscription on(String target, Action callback) { ActionBase action = args -> callback.invoke(); return registerHandler(target, action); } - + /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. * @@ -857,12 +955,9 @@ public Subscription on(String target, Action callback) { * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. */ public Subscription on(String target, Action1 callback, Class param1) { - TypeVariable type1 = (TypeVariable) (new TypeReference() {}).getType(); - - TypeAndClass tac1 = new TypeAndClass(type1, param1); - - ActionBase action = params -> callback.invoke(param1.cast(params[0])); - return registerHandler(target, action, tac1); + ActionBase action = params -> callback.invoke(param1.cast(params[0])); + return registerHandler(target, action, param1); + } /** @@ -877,16 +972,10 @@ public Subscription on(String target, Action1 callback, Class param * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. */ public Subscription on(String target, Action2 callback, Class param1, Class param2) { - Type type1 = Utils.getType(param1); - Type type2 = Utils.getType(param2); - - TypeAndClass tac1 = new TypeAndClass(type1, param1); - TypeAndClass tac2 = new TypeAndClass(type2, param2); - ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1])); }; - return registerHandler(target, action, tac1, tac2); + return registerHandler(target, action, param1, param2); } /** @@ -904,18 +993,10 @@ public Subscription on(String target, Action2 callback, Class Subscription on(String target, Action3 callback, Class param1, Class param2, Class param3) { - Type type1 = Utils.getType(param1); - Type type2 = Utils.getType(param2); - Type type3 = Utils.getType(param3); - - TypeAndClass tac1 = new TypeAndClass(type1, param1); - TypeAndClass tac2 = new TypeAndClass(type2, param2); - TypeAndClass tac3 = new TypeAndClass(type3, param3); - ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2])); }; - return registerHandler(target, action, tac1, tac2, tac3); + return registerHandler(target, action, param1, param2, param3); } /** @@ -935,20 +1016,10 @@ public Subscription on(String target, Action3 callback, */ public Subscription on(String target, Action4 callback, Class param1, Class param2, Class param3, Class param4) { - Type type1 = Utils.getType(param1); - Type type2 = Utils.getType(param2); - Type type3 = Utils.getType(param3); - Type type4 = Utils.getType(param4); - - TypeAndClass tac1 = new TypeAndClass(type1, param1); - TypeAndClass tac2 = new TypeAndClass(type2, param2); - TypeAndClass tac3 = new TypeAndClass(type3, param3); - TypeAndClass tac4 = new TypeAndClass(type4, param4); - ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3])); }; - return registerHandler(target, action, tac1, tac2, tac3, tac4); + return registerHandler(target, action, param1, param2, param3, param4); } /** @@ -970,23 +1041,11 @@ public Subscription on(String target, Action4 c */ public Subscription on(String target, Action5 callback, Class param1, Class param2, Class param3, Class param4, Class param5) { - Type type1 = Utils.getType(param1); - Type type2 = Utils.getType(param2); - Type type3 = Utils.getType(param3); - Type type4 = Utils.getType(param4); - Type type5 = Utils.getType(param5); - - TypeAndClass tac1 = new TypeAndClass(type1, param1); - TypeAndClass tac2 = new TypeAndClass(type2, param2); - TypeAndClass tac3 = new TypeAndClass(type3, param3); - TypeAndClass tac4 = new TypeAndClass(type4, param4); - TypeAndClass tac5 = new TypeAndClass(type5, param5); - ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4])); }; - return registerHandler(target, action, tac1, tac2, tac3, tac4, tac5); + return registerHandler(target, action, param1, param2, param3, param4, param5); } /** @@ -1010,25 +1069,11 @@ public Subscription on(String target, Action5 Subscription on(String target, Action6 callback, Class param1, Class param2, Class param3, Class param4, Class param5, Class param6) { - Type type1 = Utils.getType(param1); - Type type2 = Utils.getType(param2); - Type type3 = Utils.getType(param3); - Type type4 = Utils.getType(param4); - Type type5 = Utils.getType(param5); - Type type6 = Utils.getType(param6); - - TypeAndClass tac1 = new TypeAndClass(type1, param1); - TypeAndClass tac2 = new TypeAndClass(type2, param2); - TypeAndClass tac3 = new TypeAndClass(type3, param3); - TypeAndClass tac4 = new TypeAndClass(type4, param4); - TypeAndClass tac5 = new TypeAndClass(type5, param5); - TypeAndClass tac6 = new TypeAndClass(type6, param6); - - ActionBase action = params -> { + ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4]), param6.cast(params[5])); }; - return registerHandler(target, action, tac1, tac2, tac3, tac4, tac5, tac6); + return registerHandler(target, action, param1, param2, param3, param4, param5, param6); } /** @@ -1054,27 +1099,11 @@ public Subscription on(String target, Action6 Subscription on(String target, Action7 callback, Class param1, Class param2, Class param3, Class param4, Class param5, Class param6, Class param7) { - Type type1 = Utils.getType(param1); - Type type2 = Utils.getType(param2); - Type type3 = Utils.getType(param3); - Type type4 = Utils.getType(param4); - Type type5 = Utils.getType(param5); - Type type6 = Utils.getType(param6); - Type type7 = Utils.getType(param7); - - TypeAndClass tac1 = new TypeAndClass(type1, param1); - TypeAndClass tac2 = new TypeAndClass(type2, param2); - TypeAndClass tac3 = new TypeAndClass(type3, param3); - TypeAndClass tac4 = new TypeAndClass(type4, param4); - TypeAndClass tac5 = new TypeAndClass(type5, param5); - TypeAndClass tac6 = new TypeAndClass(type6, param6); - TypeAndClass tac7 = new TypeAndClass(type7, param7); - - ActionBase action = params -> { + ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4]), param6.cast(params[5]), param7.cast(params[6])); }; - return registerHandler(target, action, tac1, tac2, tac3, tac4, tac5, tac6, tac7); + return registerHandler(target, action, param1, param2, param3, param4, param5, param6, param7); } /** @@ -1102,32 +1131,224 @@ public Subscription on(String target, Action7 Subscription on(String target, Action8 callback, Class param1, Class param2, Class param3, Class param4, Class param5, Class param6, Class param7, Class param8) { - Type type1 = Utils.getType(param1); - Type type2 = Utils.getType(param2); - Type type3 = Utils.getType(param3); - Type type4 = Utils.getType(param4); - Type type5 = Utils.getType(param5); - Type type6 = Utils.getType(param6); - Type type7 = Utils.getType(param7); - Type type8 = Utils.getType(param8); - - TypeAndClass tac1 = new TypeAndClass(type1, param1); - TypeAndClass tac2 = new TypeAndClass(type2, param2); - TypeAndClass tac3 = new TypeAndClass(type3, param3); - TypeAndClass tac4 = new TypeAndClass(type4, param4); - TypeAndClass tac5 = new TypeAndClass(type5, param5); - TypeAndClass tac6 = new TypeAndClass(type6, param6); - TypeAndClass tac7 = new TypeAndClass(type7, param7); - TypeAndClass tac8 = new TypeAndClass(type8, param8); - - ActionBase action = params -> { + ActionBase action = params -> { callback.invoke(param1.cast(params[0]), param2.cast(params[1]), param3.cast(params[2]), param4.cast(params[3]), param5.cast(params[4]), param6.cast(params[5]), param7.cast(params[6]), param8.cast(params[7])); }; - return registerHandler(target, action, tac1, tac2, tac3, tac4, tac5, tac6, tac7, tac8); + return registerHandler(target, action, param1, param2, param3, param4, param5, param6, param7, param8); + } + + /** + * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * + * @param target The name of the hub method to define. + * @param callback The handler that will be raised when the hub method is invoked. + * @param param1 The first parameter. + * @param The first argument type. + * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. + */ + @SuppressWarnings("unchecked") + public Subscription on(String target, Action1 callback, Type param1) { + Class class1 = Utils.typeToClass(param1); + ActionBase action = params -> callback.invoke((T1)Utils.typeToClass(param1).cast(params[0])); + return registerHandler(target, action, param1); + } + + /** + * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * + * @param target The name of the hub method to define. + * @param callback The handler that will be raised when the hub method is invoked. + * @param param1 The first parameter. + * @param param2 The second parameter. + * @param The first parameter type. + * @param The second parameter type. + * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. + */ + @SuppressWarnings("unchecked") + public Subscription on(String target, Action2 callback, Type param1, Type param2) { + ActionBase action = params -> { + callback.invoke((T1)Utils.typeToClass(param1).cast(params[0]), (T2)Utils.typeToClass(param2).cast(params[1])); + }; + return registerHandler(target, action, param1, param2); + } + + /** + * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * + * @param target The name of the hub method to define. + * @param callback The handler that will be raised when the hub method is invoked. + * @param param1 The first parameter. + * @param param2 The second parameter. + * @param param3 The third parameter. + * @param The first parameter type. + * @param The second parameter type. + * @param The third parameter type. + * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. + */ + @SuppressWarnings("unchecked") + public Subscription on(String target, Action3 callback, + Type param1, Type param2, Type param3) { + ActionBase action = params -> { + callback.invoke((T1)Utils.typeToClass(param1).cast(params[0]), (T2)Utils.typeToClass(param2).cast(params[1]), + (T3)Utils.typeToClass(param3).cast(params[2])); + }; + return registerHandler(target, action, param1, param2, param3); + } + + /** + * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * + * @param target The name of the hub method to define. + * @param callback The handler that will be raised when the hub method is invoked. + * @param param1 The first parameter. + * @param param2 The second parameter. + * @param param3 The third parameter. + * @param param4 The fourth parameter. + * @param The first parameter type. + * @param The second parameter type. + * @param The third parameter type. + * @param The fourth parameter type. + * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. + */ + @SuppressWarnings("unchecked") + public Subscription on(String target, Action4 callback, + Type param1, Type param2, Type param3, Type param4) { + ActionBase action = params -> { + callback.invoke((T1)Utils.typeToClass(param1).cast(params[0]), (T2)Utils.typeToClass(param2).cast(params[1]), + (T3)Utils.typeToClass(param3).cast(params[2]), (T4)Utils.typeToClass(param4).cast(params[3])); + }; + return registerHandler(target, action, param1, param2, param3, param4); + } + + /** + * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * + * @param target The name of the hub method to define. + * @param callback The handler that will be raised when the hub method is invoked. + * @param param1 The first parameter. + * @param param2 The second parameter. + * @param param3 The third parameter. + * @param param4 The fourth parameter. + * @param param5 The fifth parameter. + * @param The first parameter type. + * @param The second parameter type. + * @param The third parameter type. + * @param The fourth parameter type. + * @param The fifth parameter type. + * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. + */ + @SuppressWarnings("unchecked") + public Subscription on(String target, Action5 callback, + Type param1, Type param2, Type param3, Type param4, Type param5) { + ActionBase action = params -> { + callback.invoke((T1)Utils.typeToClass(param1).cast(params[0]), (T2)Utils.typeToClass(param2).cast(params[1]), + (T3)Utils.typeToClass(param3).cast(params[2]), (T4)Utils.typeToClass(param4).cast(params[3]), + (T5)Utils.typeToClass(param5).cast(params[4])); + }; + return registerHandler(target, action, param1, param2, param3, param4, param5); + } + + /** + * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * + * @param target The name of the hub method to define. + * @param callback The handler that will be raised when the hub method is invoked. + * @param param1 The first parameter. + * @param param2 The second parameter. + * @param param3 The third parameter. + * @param param4 The fourth parameter. + * @param param5 The fifth parameter. + * @param param6 The sixth parameter. + * @param The first parameter type. + * @param The second parameter type. + * @param The third parameter type. + * @param The fourth parameter type. + * @param The fifth parameter type. + * @param The sixth parameter type. + * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. + */ + @SuppressWarnings("unchecked") + public Subscription on(String target, Action6 callback, + Type param1, Type param2, Type param3, Type param4, Type param5, Type param6) { + ActionBase action = params -> { + callback.invoke((T1)Utils.typeToClass(param1).cast(params[0]), (T2)Utils.typeToClass(param2).cast(params[1]), + (T3)Utils.typeToClass(param3).cast(params[2]), (T4)Utils.typeToClass(param4).cast(params[3]), + (T5)Utils.typeToClass(param5).cast(params[4]), (T6)Utils.typeToClass(param6).cast(params[5])); + }; + return registerHandler(target, action, param1, param2, param3, param4, param5, param6); + } + + /** + * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * + * @param target The name of the hub method to define. + * @param callback The handler that will be raised when the hub method is invoked. + * @param param1 The first parameter. + * @param param2 The second parameter. + * @param param3 The third parameter. + * @param param4 The fourth parameter. + * @param param5 The fifth parameter. + * @param param6 The sixth parameter. + * @param param7 The seventh parameter. + * @param The first parameter type. + * @param The second parameter type. + * @param The third parameter type. + * @param The fourth parameter type. + * @param The fifth parameter type. + * @param The sixth parameter type. + * @param The seventh parameter type. + * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. + */ + @SuppressWarnings("unchecked") + public Subscription on(String target, Action7 callback, + Type param1, Type param2, Type param3, Type param4, Type param5, Type param6, Type param7) { + ActionBase action = params -> { + callback.invoke((T1)Utils.typeToClass(param1).cast(params[0]), (T2)Utils.typeToClass(param2).cast(params[1]), + (T3)Utils.typeToClass(param3).cast(params[2]), (T4)Utils.typeToClass(param4).cast(params[3]), + (T5)Utils.typeToClass(param5).cast(params[4]), (T6)Utils.typeToClass(param6).cast(params[5]), + (T7)Utils.typeToClass(param7).cast(params[6])); + }; + return registerHandler(target, action, param1, param2, param3, param4, param5, param6, param7); + } + + /** + * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * + * @param target The name of the hub method to define. + * @param callback The handler that will be raised when the hub method is invoked. + * @param param1 The first parameter. + * @param param2 The second parameter. + * @param param3 The third parameter. + * @param param4 The fourth parameter. + * @param param5 The fifth parameter. + * @param param6 The sixth parameter. + * @param param7 The seventh parameter. + * @param param8 The eighth parameter + * @param The first parameter type. + * @param The second parameter type. + * @param The third parameter type. + * @param The fourth parameter type. + * @param The fifth parameter type. + * @param The sixth parameter type. + * @param The seventh parameter type. + * @param The eighth parameter type. + * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. + */ + @SuppressWarnings("unchecked") + public Subscription on(String target, Action8 callback, + Type param1, Type param2, Type param3, Type param4, Type param5, Type param6, Type param7, + Type param8) { + ActionBase action = params -> { + callback.invoke((T1)Utils.typeToClass(param1).cast(params[0]), (T2)Utils.typeToClass(param2).cast(params[1]), + (T3)Utils.typeToClass(param3).cast(params[2]), (T4)Utils.typeToClass(param4).cast(params[3]), + (T5)Utils.typeToClass(param5).cast(params[4]), (T6)Utils.typeToClass(param6).cast(params[5]), + (T7)Utils.typeToClass(param7).cast(params[6]), (T8)Utils.typeToClass(param8).cast(params[7])); + }; + return registerHandler(target, action, param1, param2, param3, param4, param5, param6, param7, param8); } - private Subscription registerHandler(String target, ActionBase action, TypeAndClass... types) { + private Subscription registerHandler(String target, ActionBase action, Type... types) { InvocationHandler handler = handlers.put(target, action, types); logger.debug("Registering handler for client method: '{}'.", target); return new Subscription(handlers, handler, target); @@ -1198,7 +1419,7 @@ public InvocationRequest tryRemoveInvocation(String id) { } @Override - public TypeAndClass getReturnType(String invocationId) { + public Type getReturnType(String invocationId) { InvocationRequest irq = getInvocation(invocationId); if (irq == null) { return null; @@ -1208,7 +1429,7 @@ public TypeAndClass getReturnType(String invocationId) { } @Override - public List getParameterTypes(String methodName) { + public List getParameterTypes(String methodName) { List handlers = connection.handlers.get(methodName); if (handlers == null) { logger.warn("Failed to find handler for '{}' method.", methodName); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java index 790f2dae4be8..4fd2e3ecb1e4 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java @@ -3,9 +3,10 @@ package com.microsoft.signalr; +import java.lang.reflect.Type; import java.util.List; public interface InvocationBinder { - TypeAndClass getReturnType(String invocationId); - List getParameterTypes(String methodName); + Type getReturnType(String invocationId); + List getParameterTypes(String methodName); } \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationHandler.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationHandler.java index 5478fb17057a..0ee7b745e91c 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationHandler.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationHandler.java @@ -3,19 +3,20 @@ package com.microsoft.signalr; +import java.lang.reflect.Type; import java.util.Arrays; import java.util.List; class InvocationHandler { - private final List types; + private final List types; private final ActionBase action; - InvocationHandler(ActionBase action, TypeAndClass... types) { + InvocationHandler(ActionBase action, Type... types) { this.action = action; this.types = Arrays.asList(types); } - public List getTypes() { + public List getTypes() { return types; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationRequest.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationRequest.java index 90b57e4b2f9f..7c57d6177c1c 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationRequest.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationRequest.java @@ -10,11 +10,11 @@ import io.reactivex.subjects.Subject; class InvocationRequest { - private final TypeAndClass returnType; + private final Type returnType; private final Subject pendingCall = ReplaySubject.create(); private final String invocationId; - InvocationRequest(TypeAndClass returnType, String invocationId) { + InvocationRequest(Type returnType, String invocationId) { this.returnType = returnType; this.invocationId = invocationId; } @@ -48,7 +48,7 @@ public Subject getPendingCall() { return pendingCall; } - public TypeAndClass getReturnType() { + public Type getReturnType() { return returnType; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index 35db1261aac0..f111ede1fac5 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -86,14 +86,14 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde if (invocationId == null || binder.getReturnType(invocationId) == null) { resultToken = jsonParser.parse(reader); } else { - result = gson.fromJson(reader, binder.getReturnType(invocationId).getClazz()); + result = gson.fromJson(reader, binder.getReturnType(invocationId)); } break; case "arguments": if (target != null) { boolean startedArray = false; try { - List types = binder.getParameterTypes(target); + List types = binder.getParameterTypes(target); startedArray = true; arguments = bindArguments(reader, types); } catch (Exception ex) { @@ -131,7 +131,7 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde case INVOCATION: if (argumentsToken != null) { try { - List types = binder.getParameterTypes(target); + List types = binder.getParameterTypes(target); arguments = bindArguments(argumentsToken, types); } catch (Exception ex) { argumentBindingException = ex; @@ -149,10 +149,10 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde break; case COMPLETION: if (resultToken != null) { - TypeAndClass returnType = binder.getReturnType(invocationId); - Class completionReturnType = Object.class; - if (returnType != null && returnType.getClazz() != null) { - completionReturnType = returnType.getClazz(); + Type returnType = binder.getReturnType(invocationId); + Type completionReturnType = Object.class; + if (returnType != null) { + completionReturnType = returnType; } result = gson.fromJson(resultToken, completionReturnType); } @@ -160,10 +160,10 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde break; case STREAM_ITEM: if (resultToken != null) { - TypeAndClass returnType = binder.getReturnType(invocationId); - Class streamReturnType = Object.class; - if (returnType != null && returnType.getClazz() != null) { - streamReturnType = returnType.getClazz(); + Type returnType = binder.getReturnType(invocationId); + Type streamReturnType = Object.class; + if (returnType != null) { + streamReturnType = returnType; } result = gson.fromJson(resultToken, streamReturnType); } @@ -198,7 +198,7 @@ public ByteBuffer writeMessage(HubMessage hubMessage) { return ByteBuffer.wrap((gson.toJson(hubMessage) + RECORD_SEPARATOR).getBytes(StandardCharsets.UTF_8)); } - private ArrayList bindArguments(JsonArray argumentsToken, List paramTypes) { + private ArrayList bindArguments(JsonArray argumentsToken, List paramTypes) { if (argumentsToken.size() != paramTypes.size()) { throw new RuntimeException(String.format("Invocation provides %d argument(s) but target expects %d.", argumentsToken.size(), paramTypes.size())); } @@ -207,21 +207,21 @@ private ArrayList bindArguments(JsonArray argumentsToken, List= 1) { arguments = new ArrayList<>(); for (int i = 0; i < paramTypes.size(); i++) { - arguments.add(gson.fromJson(argumentsToken.get(i), paramTypes.get(i).getClazz())); + arguments.add(gson.fromJson(argumentsToken.get(i), paramTypes.get(i))); } } return arguments; } - private ArrayList bindArguments(JsonReader reader, List paramTypes) throws IOException { + private ArrayList bindArguments(JsonReader reader, List paramTypes) throws IOException { reader.beginArray(); int paramCount = paramTypes.size(); int argCount = 0; ArrayList arguments = new ArrayList<>(); while (reader.peek() != JsonToken.END_ARRAY) { if (argCount < paramCount) { - Object o = gson.fromJson(reader, paramTypes.get(argCount).getClazz()); + Object o = gson.fromJson(reader, paramTypes.get(argCount)); arguments.add(o); } else { reader.skipValue(); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index aa984d584aa0..8cd42448999c 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -7,6 +7,7 @@ import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -30,7 +31,6 @@ public class MessagePackHubProtocol implements HubProtocol { private static final int VOID_RESULT = 2; private static final int NON_VOID_RESULT = 3; - private TypeAndClass emptyTac = new TypeAndClass(Object.class, Object.class); private ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); private TypeFactory typeFactory = objectMapper.getTypeFactory(); @@ -72,16 +72,16 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde switch (messageType) { case INVOCATION: - hubMessages.add(createInvocationMessage(unpacker, binder, itemCount)); + hubMessages.add(createInvocationMessage(unpacker, binder, itemCount, payload)); break; case STREAM_ITEM: - hubMessages.add(createStreamItemMessage(unpacker, binder)); + hubMessages.add(createStreamItemMessage(unpacker, binder, payload)); break; case COMPLETION: - hubMessages.add(createCompletionMessage(unpacker, binder)); + hubMessages.add(createCompletionMessage(unpacker, binder, payload)); break; case STREAM_INVOCATION: - hubMessages.add(createStreamInvocationMessage(unpacker, binder, itemCount)); + hubMessages.add(createStreamInvocationMessage(unpacker, binder, itemCount, payload)); break; case CANCEL_INVOCATION: hubMessages.add(createCancelInvocationMessage(unpacker)); @@ -166,7 +166,7 @@ public ByteBuffer writeMessage(HubMessage hubMessage) { } } - private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationBinder binder, int itemCount) throws IOException { + private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationBinder binder, int itemCount, ByteBuffer payload) throws IOException { Map headers = readHeaders(unpacker); // invocationId may be nil @@ -185,8 +185,8 @@ private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationB Object[] arguments = null; try { - List types = binder.getParameterTypes(target); - arguments = bindArguments(unpacker, types); + List types = binder.getParameterTypes(target); + arguments = bindArguments(unpacker, types, payload); } catch (Exception ex) { return new InvocationBindingFailureMessage(invocationId, target, ex); } @@ -200,13 +200,13 @@ private HubMessage createInvocationMessage(MessageUnpacker unpacker, InvocationB return new InvocationMessage(headers, invocationId, target, arguments, streams); } - private HubMessage createStreamItemMessage(MessageUnpacker unpacker, InvocationBinder binder) throws IOException { + private HubMessage createStreamItemMessage(MessageUnpacker unpacker, InvocationBinder binder, ByteBuffer payload) throws IOException { Map headers = readHeaders(unpacker); String invocationId = unpacker.unpackString(); Object value; try { - TypeAndClass itemType = binder.getReturnType(invocationId); - value = readValue(unpacker, itemType); + Type itemType = binder.getReturnType(invocationId); + value = readValue(unpacker, itemType, payload, true); } catch (Exception ex) { return new StreamBindingFailureMessage(invocationId, ex); } @@ -214,7 +214,7 @@ private HubMessage createStreamItemMessage(MessageUnpacker unpacker, InvocationB return new StreamItem(headers, invocationId, value); } - private HubMessage createCompletionMessage(MessageUnpacker unpacker, InvocationBinder binder) throws IOException { + private HubMessage createCompletionMessage(MessageUnpacker unpacker, InvocationBinder binder, ByteBuffer payload) throws IOException { Map headers = readHeaders(unpacker); String invocationId = unpacker.unpackString(); int resultKind = unpacker.unpackInt(); @@ -229,8 +229,8 @@ private HubMessage createCompletionMessage(MessageUnpacker unpacker, InvocationB case VOID_RESULT: break; case NON_VOID_RESULT: - TypeAndClass itemType = binder.getReturnType(invocationId); - result = readValue(unpacker, itemType); + Type itemType = binder.getReturnType(invocationId); + result = readValue(unpacker, itemType, payload, true); break; default: throw new RuntimeException("Invalid invocation result kind."); @@ -239,15 +239,15 @@ private HubMessage createCompletionMessage(MessageUnpacker unpacker, InvocationB return new CompletionMessage(headers, invocationId, result, error); } - private HubMessage createStreamInvocationMessage(MessageUnpacker unpacker, InvocationBinder binder, int itemCount) throws IOException { + private HubMessage createStreamInvocationMessage(MessageUnpacker unpacker, InvocationBinder binder, int itemCount, ByteBuffer payload) throws IOException { Map headers = readHeaders(unpacker); String invocationId = unpacker.unpackString(); String target = unpacker.unpackString(); Object[] arguments = null; try { - List types = binder.getParameterTypes(target); - arguments = bindArguments(unpacker, types); + List types = binder.getParameterTypes(target); + arguments = bindArguments(unpacker, types, payload); } catch (Exception ex) { return new InvocationBindingFailureMessage(invocationId, target, ex); } @@ -484,7 +484,7 @@ private void writeStreamIds(Collection streamIds, MessagePacker packer) } } - private Object[] bindArguments(MessageUnpacker unpacker, List paramTypes) throws IOException { + private Object[] bindArguments(MessageUnpacker unpacker, List paramTypes, ByteBuffer payload) throws IOException { int argumentCount = unpacker.unpackArrayHeader(); if (paramTypes.size() != argumentCount) { @@ -494,20 +494,22 @@ private Object[] bindArguments(MessageUnpacker unpacker, List para Object[] arguments = new Object[argumentCount]; for (int i = 0; i < argumentCount; i++) { - arguments[i] = readValue(unpacker, paramTypes.get(i)); + arguments[i] = readValue(unpacker, paramTypes.get(i), payload, true); } return arguments; } - private Object readValue(MessageUnpacker unpacker, TypeAndClass itemType) throws IOException { + private Object readValue(MessageUnpacker unpacker, Type itemType, ByteBuffer payload, boolean outermostCall) throws IOException { // This shouldn't ever get called with itemType == null, but we return anyways to avoid NullPointerExceptions if (itemType == null) { return null; } + Class itemClass = Utils.typeToClass(itemType); MessageFormat messageFormat = unpacker.getNextFormat(); ValueType valueType = messageFormat.getValueType(); int length; + long readBytesStart; Object item = null; switch(valueType) { @@ -529,11 +531,11 @@ private Object readValue(MessageUnpacker unpacker, TypeAndClass itemType) throws default: item = unpacker.unpackInt(); // unpackInt could correspond to an int, short, char, or byte - cast those literally here - if (itemType.getClazz().equals(Short.class)) { + if (itemClass.equals(Short.class) || itemClass.equals(short.class)) { item = ((Integer) item).shortValue(); - } else if (itemType.getClazz().equals(Character.class)) { + } else if (itemClass.equals(Character.class) || itemClass.equals(char.class)) { item = (char) ((Integer) item).shortValue(); - } else if (itemType.getClazz().equals(Byte.class)) { + } else if (itemClass.equals(Byte.class) || itemClass.equals(byte.class)) { item = ((Integer) item).byteValue(); } break; @@ -545,7 +547,7 @@ private Object readValue(MessageUnpacker unpacker, TypeAndClass itemType) throws case STRING: item = unpacker.unpackString(); // ObjectMapper packs chars as Strings - correct back to char while unpacking if necessary - if (itemType.getClazz().equals(char.class) || itemType.getClazz().equals(Character.class)) { + if (itemClass.equals(char.class) || itemClass.equals(Character.class)) { item = ((String) item).charAt(0); } break; @@ -556,26 +558,40 @@ private Object readValue(MessageUnpacker unpacker, TypeAndClass itemType) throws item = binaryValue; break; case ARRAY: + readBytesStart = unpacker.getTotalReadBytes(); length = unpacker.unpackArrayHeader(); - Object[] arrayValue = new Object[length]; for (int i = 0; i < length; i++) { - arrayValue[i] = readValue(unpacker, emptyTac); + readValue(unpacker, Object.class, payload, false); + } + if (outermostCall) { + // Check how many bytes we've read, grab that from the payload, and deserialize with objectMapper + byte[] payloadBytes = payload.array(); + byte[] arrayBytes = Arrays.copyOfRange(payloadBytes, payload.position() + (int) readBytesStart, + payload.position() + (int) unpacker.getTotalReadBytes()); + return objectMapper.readValue(arrayBytes, typeFactory.constructType(itemType)); + } else { + // This is an inner call to readValue - we just need to read the right number of bytes + // We can return null, and the outermost call will know how many bytes to give to objectMapper. + return null; } - // Round trip the array through ObjectMapper, in case it needs to be converted to another collection, - // Or have the type of its contained elements corrected - byte[] arrayBytes = objectMapper.writeValueAsBytes(arrayValue); - return objectMapper.readValue(arrayBytes, typeFactory.constructType(itemType.getType())); case MAP: + readBytesStart = unpacker.getTotalReadBytes(); length = unpacker.unpackMapHeader(); - Map mapValue = new HashMap(); for (int i = 0; i < length; i++) { - Object key = readValue(unpacker, emptyTac); - Object value = readValue(unpacker, emptyTac); - mapValue.put(key, value); + readValue(unpacker, Object.class, payload, false); + readValue(unpacker, Object.class, payload, false); + } + if (outermostCall) { + // Check how many bytes we've read, grab that from the payload, and deserialize with objectMapper + byte[] payloadBytes = payload.array(); + byte[] mapBytes = Arrays.copyOfRange(payloadBytes, payload.position() + (int) readBytesStart, + payload.position() + (int) unpacker.getTotalReadBytes()); + return objectMapper.readValue(mapBytes, typeFactory.constructType(itemType)); + } else { + // This is an inner call to readValue - we just need to read the right number of bytes + // We can return null, and the outermost call will know how many bytes to give to objectMapper. + return null; } - // Round trip the map through ObjectMapper - it could be a POJO, or it could need its contained elements to be corrected - byte[] mapBytes = objectMapper.writeValueAsBytes(mapValue); - return objectMapper.readValue(mapBytes, typeFactory.constructType(itemType.getType())); case EXTENSION: /* ExtensionTypeHeader extension = unpacker.unpackExtensionTypeHeader(); @@ -589,11 +605,13 @@ private Object readValue(MessageUnpacker unpacker, TypeAndClass itemType) throws return null; } // If we get here, the item isn't a map or a collection/array, so we use the Class to cast it - return itemType.getClazz().cast(item); + if (itemClass.isPrimitive()) { + return Utils.toPrimitive(itemClass, item); + } + return itemClass.cast(item); } private void writeValue(Object o, MessagePacker packer) throws IOException { - ObjectMapper objectMapper = new ObjectMapper(new MessagePackFactory()); packer.addPayload(objectMapper.writeValueAsBytes(o)); } } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeAndClass.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeAndClass.java deleted file mode 100644 index 22e584e363f7..000000000000 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeAndClass.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.microsoft.signalr; - -import java.lang.reflect.Type; - -class TypeAndClass { - - private Type type; - private Class clazz; - - public TypeAndClass(Type type, Class clazz) { - this.type = type; - this.clazz = clazz; - } - - public Type getType() { - return type; - } - - public Class getClazz() { - return clazz; - } -} \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java index 840c884d00b5..4bebe9542cb2 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java @@ -1,16 +1,12 @@ package com.microsoft.signalr; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.List; import java.lang.reflect.ParameterizedType; public class TypeReference { private final Type type; - + protected TypeReference() { Type superclass = getClass().getGenericSuperclass(); if (superclass instanceof Class) { @@ -25,4 +21,4 @@ protected TypeReference() { public Type getType() { return this.type; } -} +} \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java index 6a87c4c65a22..f3747a5156c9 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java @@ -4,7 +4,12 @@ package com.microsoft.signalr; import java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -71,7 +76,34 @@ public static ArrayList getLengthHeader(int length) { return header; } - public static Type getType(Class param) { - return (new TypeReference() {}).getType(); + public static Object toPrimitive(Class c, Object value) { + if (boolean.class == c) return ((Boolean) value).booleanValue(); + if (byte.class == c) return ((Byte) value).byteValue(); + if (short.class == c) return ((Short) value).shortValue(); + if (int.class == c) return ((Integer) value).intValue(); + if (long.class == c) return ((Long) value).longValue(); + if (float.class == c) return ((Float) value).floatValue(); + if (double.class == c) return ((Double) value).doubleValue(); + if (char.class == c) return ((Character) value).charValue(); + return value; } + + public static Class typeToClass(Type type) { + if (type instanceof Class) { + return (Class) type; + } else if (type instanceof GenericArrayType) { + // Instantiate an array of the same type as this type, then return its class + return Array.newInstance(typeToClass(((GenericArrayType)type).getGenericComponentType()), 0).getClass(); + } else if (type instanceof ParameterizedType) { + return typeToClass(((ParameterizedType) type).getRawType()); + } else if (type instanceof TypeVariable) { + Type[] bounds = ((TypeVariable) type).getBounds(); + return bounds.length == 0 ? Object.class : typeToClass(bounds[0]); + } else if (type instanceof WildcardType) { + Type[] bounds = ((WildcardType) type).getUpperBounds(); + return bounds.length == 0 ? Object.class : typeToClass(bounds[0]); + } else { + throw new UnsupportedOperationException("Cannot handle type class: " + type.getClass()); + } + } } \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index 611d4c02963c..04e166f077b6 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -28,7 +28,7 @@ class HubConnectionTest { private static final String RECORD_SEPARATOR = "\u001e"; - + @Test public void checkHubConnectionState() { HubConnection hubConnection = TestUtils.createHubConnection("http://example.com"); @@ -1286,7 +1286,7 @@ public void sendWithNoParamsTriggersOnHandler() { assertEquals(Integer.valueOf(1), value.get()); } - @Test + /*@Test public void sendWithParamTriggersOnHandler() { AtomicReference value = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); @@ -1303,7 +1303,7 @@ public void sendWithParamTriggersOnHandler() { // Confirming that our handler was called and the correct message was passed in. assertEquals("Hello World", value.get()); - } + } */ @Test public void sendWithTwoParamsTriggersOnHandler() { @@ -1558,7 +1558,7 @@ public void sendWithEightParamsTriggersOnHandler() { assertEquals("F", value8.get()); } - private class Custom { + private class Custom { public int number; public String str; public boolean[] bools; diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java index 73ff89210077..3a8a0406c575 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java @@ -5,6 +5,7 @@ import static org.junit.jupiter.api.Assertions.*; +import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.util.List; @@ -40,7 +41,7 @@ public void verifyWriteMessage() { public void parsePingMessage() { String stringifiedMessage = "{\"type\":6}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(PingMessage.getInstance()); + TestBinder binder = new TestBinder(null, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -54,7 +55,7 @@ public void parsePingMessage() { public void parseCloseMessage() { String stringifiedMessage = "{\"type\":7}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new CloseMessage()); + TestBinder binder = new TestBinder(null, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -74,7 +75,7 @@ public void parseCloseMessage() { public void parseCloseMessageWithError() { String stringifiedMessage = "{\"type\":7,\"error\": \"There was an error\"}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new CloseMessage("There was an error")); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -94,7 +95,7 @@ public void parseCloseMessageWithError() { public void parseSingleMessage() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -118,7 +119,7 @@ public void parseSingleMessage() { public void parseSingleUnsupportedStreamInvocationMessage() { String stringifiedMessage = "{\"type\":4,\"Id\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new StreamInvocationMessage(null, "1", "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); Throwable exception = assertThrows(UnsupportedOperationException.class, () -> jsonHubProtocol.parseMessages(message, binder)); assertEquals("The message type STREAM_INVOCATION is not supported yet.", exception.getMessage()); @@ -128,7 +129,7 @@ public void parseSingleUnsupportedStreamInvocationMessage() { public void parseSingleUnsupportedCancelInvocationMessage() { String stringifiedMessage = "{\"type\":5,\"invocationId\":123}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(null); + TestBinder binder = new TestBinder(null, null); Throwable exception = assertThrows(UnsupportedOperationException.class, () -> jsonHubProtocol.parseMessages(message, binder)); assertEquals("The message type CANCEL_INVOCATION is not supported yet.", exception.getMessage()); @@ -138,7 +139,7 @@ public void parseSingleUnsupportedCancelInvocationMessage() { public void parseTwoMessages() { String stringifiedMessage = "{\"type\":1,\"target\":\"one\",\"arguments\":[42]}\u001E{\"type\":1,\"target\":\"two\",\"arguments\":[43]}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "one", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -172,7 +173,7 @@ public void parseTwoMessages() { public void parseSingleMessageMutipleArgs() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42, 24 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class, int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -195,7 +196,7 @@ public void parseSingleMessageMutipleArgs() { public void parseMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42, 24 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class, int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -218,7 +219,7 @@ public void parseMessageWithOutOfOrderProperties() { public void parseCompletionMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"type\":3,\"result\":42,\"invocationId\":\"1\"}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new CompletionMessage(null, "1", 42, null)); + TestBinder binder = new TestBinder(null, int.class); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -237,7 +238,7 @@ public void parseCompletionMessageWithOutOfOrderProperties() { public void invocationBindingFailureWhileParsingTooManyArgumentsWithOutOfOrderProperties() { String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -253,7 +254,7 @@ public void invocationBindingFailureWhileParsingTooManyArgumentsWithOutOfOrderPr public void invocationBindingFailureWhileParsingTooManyArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -269,7 +270,7 @@ public void invocationBindingFailureWhileParsingTooManyArguments() { public void invocationBindingFailureWhileParsingTooFewArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class, int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -285,7 +286,7 @@ public void invocationBindingFailureWhileParsingTooFewArguments() { public void invocationBindingFailureWhenParsingIncorrectType() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"]}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -301,7 +302,7 @@ public void invocationBindingFailureWhenParsingIncorrectType() { public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"],\"invocationId\":\"123\"}\u001E"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -318,7 +319,7 @@ public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() { public void errorWhileParsingIncompleteMessage() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":"; ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class, int.class }, null); RuntimeException exception = assertThrows(RuntimeException.class, () -> jsonHubProtocol.parseMessages(message, binder)); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index 4c938754a335..96423b90ca23 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -3,8 +3,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import java.lang.reflect.Type; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; @@ -145,7 +145,7 @@ public void verifyWriteCloseMessageWithError() { public void parsePingMessage() { byte[] messageBytes = {0x02, (byte) 0x91, 0x06}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(PingMessage.getInstance()); + TestBinder binder = new TestBinder(null, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -159,7 +159,7 @@ public void parsePingMessage() { public void parseCloseMessage() { byte[] messageBytes = {0x04, (byte) 0x93, 0x07, (byte) 0xC0, (byte) 0xC2}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new CloseMessage()); + TestBinder binder = new TestBinder(null, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -179,7 +179,7 @@ public void parseCloseMessage() { public void parseCloseMessageWithError() { byte[] messageBytes = {0x09, (byte) 0x93, 0x07, (byte) 0xA5, 0x45, 0x72, 0x72, 0x6F, 0x72, (byte) 0xC2}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new CloseMessage("Error")); + TestBinder binder = new TestBinder(null, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -200,7 +200,7 @@ public void parseSingleInvocationMessage() { byte[] messageBytes = {0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, "1", "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -227,7 +227,7 @@ public void parseSingleInvocationMessageWithHeaders() { byte[] messageBytes = {0x14, (byte) 0x96, 0x01, (byte) 0x82, (byte) 0xA1, 0x61, (byte) 0xA1, 0x62, (byte) 0xA1, 0x63, (byte) 0xA1, 0x64, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -259,7 +259,7 @@ public void parseSingleStreamInvocationMessage() { byte[] messageBytes = {0x12, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA6, 0x6D, 0x65, 0x74, 0x68, 0x6F, 0x64, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new StreamInvocationMessage(null, "method", "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -285,7 +285,7 @@ public void parseSingleStreamInvocationMessage() { public void parseSingleCancelInvocationMessage() { byte[] messageBytes = {0x0A, (byte) 0x93, 0x05, (byte) 0x80, (byte) 0xA6, 0x6D, 0x65, 0x74, 0x68, 0x6F, 0x64}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new CancelInvocationMessage(null, "method")); + TestBinder binder = new TestBinder(null, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -307,7 +307,7 @@ public void parseTwoMessages() { byte[] messageBytes = {0x0B, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x6F, 0x6E, 0x65, (byte) 0x91, 0x2A, (byte) 0x90, 0x0B, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x74, 0x77, 0x6F, (byte) 0x91, 0x2B, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "one", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -348,7 +348,7 @@ public void parseSingleMessageMutipleArgs() { byte[] messageBytes = {0x0F, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x92, 0x2A, (byte) 0xA2, 0x34, 0x32, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, "42" }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class, String.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -372,7 +372,7 @@ public void invocationBindingFailureWhileParsingTooManyArguments() { byte[] messageBytes = {0x0F, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x92, 0x2A, (byte) 0xA2, 0x34, 0x32, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -389,7 +389,7 @@ public void invocationBindingFailureWhileParsingTooFewArguments() { byte[] messageBytes = {0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class, int.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -406,7 +406,7 @@ public void invocationBindingFailureWhenParsingIncorrectType() { byte[] messageBytes = {0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, (byte) 0xC3, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -415,8 +415,9 @@ public void invocationBindingFailureWhenParsingIncorrectType() { assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); - assertTrue(invocationBindingFailureMessage.getException().getMessage().startsWith - ("Cannot deserialize instance of `java.lang.Integer` out of VALUE_TRUE token")); + assertEquals(invocationBindingFailureMessage.getException().getMessage(), + "class java.lang.Boolean cannot be cast to class java.lang.Integer (java.lang.Boolean and java.lang.Integer " + + "are in module java.base of loader 'bootstrap')"); } @Test @@ -425,7 +426,7 @@ public void invocationBindingFailureReadsNextMessageAfterTooManyArguments() { 0x2A, (byte) 0xA2, 0x34, 0x32, (byte) 0x90, 0x0B, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x74, 0x77, 0x6F, (byte) 0x91, 0x2B, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -456,7 +457,7 @@ public void invocationBindingFailureReadsNextMessageAfterTooFewArguments() { byte[] messageBytes = {0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, (byte) 0x90, 0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x74, 0x77, 0x6F, (byte) 0x92, 0x2A, 0x2B, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42, 24 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class, int.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -490,7 +491,7 @@ public void invocationBindingFailureReadsNextMessageAfterIncorrectArgument() { (byte) 0xC3, (byte) 0x90, 0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -499,8 +500,9 @@ public void invocationBindingFailureReadsNextMessageAfterIncorrectArgument() { assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); - assertTrue(invocationBindingFailureMessage.getException().getMessage().startsWith - ("Cannot deserialize instance of `java.lang.Integer` out of VALUE_TRUE token")); + assertEquals(invocationBindingFailureMessage.getException().getMessage(), + "class java.lang.Boolean cannot be cast to class java.lang.Integer (java.lang.Boolean and java.lang.Integer " + + "are in module java.base of loader 'bootstrap')"); // Check the second message assertEquals(HubMessageType.INVOCATION, messages.get(1).getMessageType()); @@ -522,7 +524,7 @@ public void errorWhenLengthHeaderTooLong() { byte[] messageBytes = {0x0D, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); RuntimeException exception = assertThrows(RuntimeException.class, () -> messagePackHubProtocol.parseMessages(message, binder)); @@ -534,7 +536,7 @@ public void errorWhenLengthHeaderTooShort() { byte[] messageBytes = {0x0B, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, 0x2A, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { 42 }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class }, null); RuntimeException exception = assertThrows(RuntimeException.class, () -> messagePackHubProtocol.parseMessages(message, binder)); @@ -553,7 +555,7 @@ public void parseMessageWithTwoByteLengthHeader() { 0x6E, 0x65, 0x65, 0x64, 0x20, 0x61, 0x20, 0x66, 0x65, 0x77, 0x20, 0x6D, 0x6F, 0x72, 0x65, 0x20, 0x63, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, 0x73, 0x2E, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { "arg" }, null)); + TestBinder binder = new TestBinder(new Type[] { String.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -605,8 +607,7 @@ public void parseInvocationMessageWithPrimitiveArgs() { byte bite = 0x11; char c = 'c'; long l = 3333333333l; - - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { i, d, bool, bite, c, l }, null)); + TestBinder binder = new TestBinder(new Type[] { int.class, double.class, boolean.class, byte.class, char.class, long.class }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); @@ -658,8 +659,8 @@ public void parseInvocationMessageWithArrayArg() { 0x02, 0x03, 0x04, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder arrayBinder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { new int[] {} }, null)); - TestBinder listBinder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { new ArrayList() }, null)); + TestBinder arrayBinder = new TestBinder(new Type[] { int[].class }, null); + TestBinder listBinder = new TestBinder(new Type[] { (new TypeReference>() { }).getType() }, null); List arrayMessages = messagePackHubProtocol.parseMessages(message, arrayBinder); message.flip(); @@ -718,7 +719,7 @@ public void parseInvocationMessageWithMapArg() { 0x65, (byte) 0x90}; ByteBuffer message = ByteBuffer.wrap(messageBytes); - TestBinder binder = new TestBinder(new InvocationMessage(null, null, "test", new Object[] { new HashMap() }, null)); + TestBinder binder = new TestBinder(new Type[] { (new TypeReference>() { }).getType() }, null); List messages = messagePackHubProtocol.parseMessages(message, binder); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java index 9dbb59dc1393..e830b0eb6fba 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java @@ -6,57 +6,24 @@ import java.util.List; public class TestBinder implements InvocationBinder { - private TypeAndClass[] paramTypes = null; - private TypeAndClass returnType = null; + private Type[] paramTypes = null; + private Type returnType = null; - public TestBinder(HubMessage expectedMessage) { - if (expectedMessage == null) { - return; - } - - switch (expectedMessage.getMessageType()) { - case STREAM_INVOCATION: - ArrayList streamTypes = new ArrayList<>(); - for (Object obj : ((StreamInvocationMessage) expectedMessage).getArguments()) { - Type type = getType(obj.getClass()); - streamTypes.add(new TypeAndClass(type, obj.getClass())); - } - paramTypes = streamTypes.toArray(new TypeAndClass[streamTypes.size()]); - break; - case INVOCATION: - ArrayList types = new ArrayList<>(); - for (Object obj : ((InvocationMessage) expectedMessage).getArguments()) { - Type type = getType(obj.getClass()); - types.add(new TypeAndClass(type, obj.getClass())); - } - paramTypes = types.toArray(new TypeAndClass[types.size()]); - break; - case STREAM_ITEM: - break; - case COMPLETION: - Object obj = ((CompletionMessage)expectedMessage).getResult().getClass(); - returnType = new TypeAndClass(getType(((CompletionMessage)expectedMessage).getResult().getClass()), - ((CompletionMessage)expectedMessage).getResult().getClass()); - break; - default: - break; - } - } - - private Type getType(Class param) { - return (new TypeReference() {}).getType(); + public TestBinder(Type[] paramTypes, Type returnType) { + this.paramTypes = paramTypes; + this.returnType = returnType; } @Override - public TypeAndClass getReturnType(String invocationId) { + public Type getReturnType(String invocationId) { return returnType; } @Override - public List getParameterTypes(String methodName) { + public List getParameterTypes(String methodName) { if (paramTypes == null) { return new ArrayList<>(); } - return new ArrayList(Arrays.asList(paramTypes)); + return new ArrayList(Arrays.asList(paramTypes)); } } \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java index 7f9dd5ca87e2..ef375cea3d57 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java @@ -7,6 +7,7 @@ import java.nio.charset.StandardCharsets; class TestUtils { + static HubConnection createHubConnection(String url) { return createHubConnection(url, new MockTransport(true), true, new TestHttpClient()); } From 43711b1fff5bb00341492aca0be71803f021c44e Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 12 Aug 2020 15:36:21 -0700 Subject: [PATCH 51/64] Apply feedback, add some tests --- .../com/microsoft/signalr/HubConnection.java | 92 +++------------ .../signalr/MessagePackHubProtocol.java | 8 +- .../microsoft/signalr/HubConnectionTest.java | 6 +- .../signalr/MessagePackHubProtocolTest.java | 107 +++++++++++++++++- 4 files changed, 126 insertions(+), 87 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index bd7c7ea3712f..d4692efaec08 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -700,39 +700,8 @@ public Completable invoke(String method, Object... args) { * @param The expected return type. * @return A Single that yields the return value when the invocation has completed. */ - @SuppressWarnings("unchecked") public Single invoke(Class returnType, String method, Object... args) { - hubConnectionStateLock.lock(); - try { - if (hubConnectionState != HubConnectionState.CONNECTED) { - throw new RuntimeException("The 'invoke' method cannot be called if the connection is not active."); - } - - String id = connectionState.getNextInvocationId(); - InvocationRequest irq = new InvocationRequest(returnType, id); - connectionState.addInvocation(irq); - - SingleSubject subject = SingleSubject.create(); - - // forward the invocation result or error to the user - // run continuations on a separate thread - Subject pendingCall = irq.getPendingCall(); - pendingCall.subscribe(result -> { - // Primitive types can't be cast with the Class cast function - if (returnType.isPrimitive()) { - subject.onSuccess((T)result); - } else { - subject.onSuccess(returnType.cast(result)); - } - }, error -> subject.onError(error)); - - // Make sure the actual send is after setting up the callbacks otherwise there is a race - // where the map doesn't have the callbacks yet when the response is returned - sendInvocationMessage(method, args, id, false); - return subject; - } finally { - hubConnectionStateLock.unlock(); - } + return this.invoke(returnType, returnType, method, args); } @@ -745,15 +714,19 @@ public Single invoke(Class returnType, String method, Object... args) * @param The expected return type. * @return A Single that yields the return value when the invocation has completed. */ - @SuppressWarnings("unchecked") public Single invoke(Type returnType, String method, Object... args) { + Class returnClass = Utils.typeToClass(returnType); + return this.invoke(returnType, returnClass, method, args); + } + + @SuppressWarnings("unchecked") + private Single invoke(Type returnType, Class returnClass, String method, Object... args) { hubConnectionStateLock.lock(); try { if (hubConnectionState != HubConnectionState.CONNECTED) { throw new RuntimeException("The 'invoke' method cannot be called if the connection is not active."); } - Class returnClass = Utils.typeToClass(returnType); String id = connectionState.getNextInvocationId(); InvocationRequest irq = new InvocationRequest(returnType, id); connectionState.addInvocation(irq); @@ -790,48 +763,8 @@ public Single invoke(Type returnType, String method, Object... args) { * @param The expected return type. * @return An observable that yields the streaming results from the server. */ - @SuppressWarnings("unchecked") public Observable stream(Class returnType, String method, Object ... args) { - String invocationId; - InvocationRequest irq; - hubConnectionStateLock.lock(); - try { - if (hubConnectionState != HubConnectionState.CONNECTED) { - throw new RuntimeException("The 'stream' method cannot be called if the connection is not active."); - } - - invocationId = connectionState.getNextInvocationId(); - irq = new InvocationRequest(returnType, invocationId); - connectionState.addInvocation(irq); - - AtomicInteger subscriptionCount = new AtomicInteger(); - ReplaySubject subject = ReplaySubject.create(); - Subject pendingCall = irq.getPendingCall(); - pendingCall.subscribe(result -> { - // Primitive types can't be cast with the Class cast function - if (returnType.isPrimitive()) { - subject.onNext((T)result); - } else { - subject.onNext(returnType.cast(result)); - } - }, error -> subject.onError(error), - () -> subject.onComplete()); - - Observable observable = subject.doOnSubscribe((subscriber) -> subscriptionCount.incrementAndGet()); - sendInvocationMessage(method, args, invocationId, true); - return observable.doOnDispose(() -> { - if (subscriptionCount.decrementAndGet() == 0) { - CancelInvocationMessage cancelInvocationMessage = new CancelInvocationMessage(null, invocationId); - sendHubMessage(cancelInvocationMessage); - if (connectionState != null) { - connectionState.tryRemoveInvocation(invocationId); - } - subject.onComplete(); - } - }); - } finally { - hubConnectionStateLock.unlock(); - } + return this.stream(returnType, returnType, method, args); } /** @@ -843,8 +776,13 @@ public Observable stream(Class returnType, String method, Object ... a * @param The expected return type. * @return An observable that yields the streaming results from the server. */ - @SuppressWarnings("unchecked") public Observable stream(Type returnType, String method, Object ... args) { + Class returnClass = Utils.typeToClass(returnType); + return this.stream(returnType, returnClass, method, args); + } + + @SuppressWarnings("unchecked") + private Observable stream(Type returnType, Class returnClass, String method, Object ... args) { String invocationId; InvocationRequest irq; hubConnectionStateLock.lock(); @@ -853,7 +791,6 @@ public Observable stream(Type returnType, String method, Object ... args) throw new RuntimeException("The 'stream' method cannot be called if the connection is not active."); } - Class returnClass = Utils.typeToClass(returnType); invocationId = connectionState.getNextInvocationId(); irq = new InvocationRequest(returnType, invocationId); connectionState.addInvocation(irq); @@ -1149,7 +1086,6 @@ public Subscription on(String target, Action8 Subscription on(String target, Action1 callback, Type param1) { - Class class1 = Utils.typeToClass(param1); ActionBase action = params -> callback.invoke((T1)Utils.typeToClass(param1).cast(params[0])); return registerHandler(target, action, param1); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 8cd42448999c..08184b41e999 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -566,9 +566,8 @@ private Object readValue(MessageUnpacker unpacker, Type itemType, ByteBuffer pay if (outermostCall) { // Check how many bytes we've read, grab that from the payload, and deserialize with objectMapper byte[] payloadBytes = payload.array(); - byte[] arrayBytes = Arrays.copyOfRange(payloadBytes, payload.position() + (int) readBytesStart, - payload.position() + (int) unpacker.getTotalReadBytes()); - return objectMapper.readValue(arrayBytes, typeFactory.constructType(itemType)); + return objectMapper.readValue(payloadBytes, payload.position() + (int) readBytesStart, (int) (unpacker.getTotalReadBytes() - readBytesStart), + typeFactory.constructType(itemType)); } else { // This is an inner call to readValue - we just need to read the right number of bytes // We can return null, and the outermost call will know how many bytes to give to objectMapper. @@ -586,7 +585,8 @@ private Object readValue(MessageUnpacker unpacker, Type itemType, ByteBuffer pay byte[] payloadBytes = payload.array(); byte[] mapBytes = Arrays.copyOfRange(payloadBytes, payload.position() + (int) readBytesStart, payload.position() + (int) unpacker.getTotalReadBytes()); - return objectMapper.readValue(mapBytes, typeFactory.constructType(itemType)); + return objectMapper.readValue(payloadBytes, payload.position() + (int) readBytesStart, (int) (unpacker.getTotalReadBytes() - readBytesStart), + typeFactory.constructType(itemType)); } else { // This is an inner call to readValue - we just need to read the right number of bytes // We can return null, and the outermost call will know how many bytes to give to objectMapper. diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index 04e166f077b6..b21f1a7e6f4d 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -1286,7 +1286,7 @@ public void sendWithNoParamsTriggersOnHandler() { assertEquals(Integer.valueOf(1), value.get()); } - /*@Test + @Test public void sendWithParamTriggersOnHandler() { AtomicReference value = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); @@ -1303,7 +1303,7 @@ public void sendWithParamTriggersOnHandler() { // Confirming that our handler was called and the correct message was passed in. assertEquals("Hello World", value.get()); - } */ + } @Test public void sendWithTwoParamsTriggersOnHandler() { @@ -1558,7 +1558,7 @@ public void sendWithEightParamsTriggersOnHandler() { assertEquals("F", value8.get()); } - private class Custom { + private class Custom { public int number; public String str; public boolean[] bools; diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index 96423b90ca23..bf15f447d538 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -752,8 +752,111 @@ public void verifyWriteInvocationMessageWithMapArg() { InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] { argument }, null); ByteBuffer result = messagePackHubProtocol.writeMessage(invocationMessage); byte[] expectedBytes = {0x23, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, (byte) 0x82, (byte) 0xA5, - 0x61, 0x70, 0x70, 0x6C, 0x65, (byte) 0xA6, 0x62, 0x61, 0x6E, 0x61, 0x6E, 0x61, (byte) 0xA3, 0x6B, 0x65, 0x79, (byte) 0xA5, 0x76, 0x61, 0x6C, 0x75, - 0x65, (byte) 0x90}; + 0x61, 0x70, 0x70, 0x6C, 0x65, (byte) 0xA6, 0x62, 0x61, 0x6E, 0x61, 0x6E, 0x61, (byte) 0xA3, 0x6B, 0x65, 0x79, (byte) 0xA5, 0x76, 0x61, 0x6C, 0x75, + 0x65, (byte) 0x90}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } + + @Test + public void parseInvocationMessageWithNestedCollection() { + byte[] messageBytes = {0x39, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, (byte) 0x92, + (byte) 0x82, (byte) 0xA3, 0x6F, 0x6E, 0x65, (byte) 0x92, (byte) 0xA1, 0x61, (byte) 0xA1, 0x62, (byte) 0xA3, 0x74, 0x77, 0x6F, (byte) 0x92, + (byte) 0xA3, (byte) 0xEB, (byte) 0xBB, (byte) 0xAF, (byte) 0xA3, (byte) 0xEA, (byte) 0xAF, (byte) 0x8D, (byte) 0x82, (byte) 0xA4, 0x66, + 0x6F, 0x75, 0x72, (byte) 0x92, (byte) 0xA1, 0x5E, (byte) 0xA1, 0x2A, (byte) 0xA5, 0x74, 0x68, 0x72, 0x65, 0x65, (byte) 0x92, (byte) 0xA1, + 0x35, (byte) 0xA1, 0x39, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + + TestBinder binder = new TestBinder(new Type[] { (new TypeReference>>>() { }).getType() }, null); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's an invocation message. + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); + + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + assertEquals(null, invocationMessage.getHeaders()); + assertEquals(null, invocationMessage.getStreamIds()); + + @SuppressWarnings("unchecked") + ArrayList>> result = (ArrayList>>)invocationMessage.getArguments()[0]; + assertEquals(2, result.size()); + + HashMap> firstMap = result.get(0); + HashMap> secondMap = result.get(1); + + assertEquals(2, firstMap.keySet().size()); + assertEquals(2, secondMap.keySet().size()); + + ArrayList firstList = firstMap.get("one"); + ArrayList secondList = firstMap.get("two"); + + ArrayList thirdList = secondMap.get("three"); + ArrayList fourthList = secondMap.get("four"); + + assertEquals(2, firstList.size()); + assertEquals(2, secondList.size()); + assertEquals(2, thirdList.size()); + assertEquals(2, fourthList.size()); + + assertEquals('a', (char) firstList.get(0)); + assertEquals('b', (char) firstList.get(1)); + + assertEquals('\ubeef', (char) secondList.get(0)); + assertEquals('\uabcd', (char) secondList.get(1)); + + assertEquals('5', (char) thirdList.get(0)); + assertEquals('9', (char) thirdList.get(1)); + + assertEquals('^', (char) fourthList.get(0)); + assertEquals('*', (char) fourthList.get(1)); + } + + @Test + public void verifyWriteInvocationMessageWithNestedCollection() { + ArrayList clist1 = new ArrayList(); + ArrayList clist2 = new ArrayList(); + ArrayList clist3 = new ArrayList(); + ArrayList clist4 = new ArrayList(); + + clist1.add('a'); + clist1.add('b'); + + clist2.add('\ubeef'); + clist2.add('\uabcd'); + + clist3.add('5'); + clist3.add('9'); + + clist4.add('^'); + clist4.add('*'); + + TreeMap> map1 = new TreeMap>(); + TreeMap> map2 = new TreeMap>(); + + map1.put("one", clist1); + map1.put("two", clist2); + + map2.put("three", clist3); + map2.put("four", clist4); + + ArrayList>> argument = new ArrayList>>(); + argument.add(map1); + argument.add(map2); + InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] { argument }, null); + ByteBuffer result = messagePackHubProtocol.writeMessage(invocationMessage); + byte[] expectedBytes = {0x39, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, (byte) 0x92, + (byte) 0x82, (byte) 0xA3, 0x6F, 0x6E, 0x65, (byte) 0x92, (byte) 0xA1, 0x61, (byte) 0xA1, 0x62, (byte) 0xA3, 0x74, 0x77, 0x6F, (byte) 0x92, + (byte) 0xA3, (byte) 0xEB, (byte) 0xBB, (byte) 0xAF, (byte) 0xA3, (byte) 0xEA, (byte) 0xAF, (byte) 0x8D, (byte) 0x82, (byte) 0xA4, 0x66, + 0x6F, 0x75, 0x72, (byte) 0x92, (byte) 0xA1, 0x5E, (byte) 0xA1, 0x2A, (byte) 0xA5, 0x74, 0x68, 0x72, 0x65, 0x65, (byte) 0x92, (byte) 0xA1, + 0x35, (byte) 0xA1, 0x39, (byte) 0x90}; ByteString expectedResult = ByteString.of(expectedBytes); assertEquals(expectedResult, ByteString.of(result)); } From 77cb968cd118502a7b95e7e13583c0ca6e8470be Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 13 Aug 2020 10:11:55 -0700 Subject: [PATCH 52/64] Add some tests, fix some tests --- .../signalr/MessagePackHubProtocolTest.java | 65 +++++++++++++++++-- .../com/microsoft/signalr/PersonPojo.java | 40 ++++++++++++ 2 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/PersonPojo.java diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index bf15f447d538..9c306aec12f2 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.lang.reflect.Type; import java.nio.ByteBuffer; @@ -415,9 +416,10 @@ public void invocationBindingFailureWhenParsingIncorrectType() { assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); - assertEquals(invocationBindingFailureMessage.getException().getMessage(), + assertTrue(invocationBindingFailureMessage.getException().getMessage().equals( "class java.lang.Boolean cannot be cast to class java.lang.Integer (java.lang.Boolean and java.lang.Integer " - + "are in module java.base of loader 'bootstrap')"); + + "are in module java.base of loader 'bootstrap')") || invocationBindingFailureMessage.getException().getMessage().equals( + "java.base/java.lang.Boolean cannot be cast to java.base/java.lang.Integer")); } @Test @@ -500,9 +502,10 @@ public void invocationBindingFailureReadsNextMessageAfterIncorrectArgument() { assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); - assertEquals(invocationBindingFailureMessage.getException().getMessage(), + assertTrue(invocationBindingFailureMessage.getException().getMessage().equals( "class java.lang.Boolean cannot be cast to class java.lang.Integer (java.lang.Boolean and java.lang.Integer " - + "are in module java.base of loader 'bootstrap')"); + + "are in module java.base of loader 'bootstrap')") || invocationBindingFailureMessage.getException().getMessage().equals( + "java.base/java.lang.Boolean cannot be cast to java.base/java.lang.Integer")); // Check the second message assertEquals(HubMessageType.INVOCATION, messages.get(1).getMessageType()); @@ -860,4 +863,58 @@ public void verifyWriteInvocationMessageWithNestedCollection() { ByteString expectedResult = ByteString.of(expectedBytes); assertEquals(expectedResult, ByteString.of(result)); } + + @Test + public void parseInvocationMessageWithCustomPojoArg() { + byte[] messageBytes = {0x32, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, (byte) 0x84, (byte) 0xA9, + 0x66, 0x69, 0x72, 0x73, 0x74, 0x4E, 0x61, 0x6D, 0x65, (byte) 0xA4, 0x4A, 0x6F, 0x68, 0x6E, (byte) 0xA8, 0x6C, 0x61, 0x73, 0x74, 0x4E, 0x61, + 0x6D, 0x65, (byte) 0xA3, 0x44, 0x6F, 0x65, (byte) 0xA3, 0x61, 0x67, 0x65, 0x1E, (byte) 0xA1, 0x74, (byte) 0x92, 0x05, 0x08, (byte) 0x90}; + ByteBuffer message = ByteBuffer.wrap(messageBytes); + + TestBinder binder = new TestBinder(new Type[] { (new TypeReference>>() { }).getType() }, null); + + List messages = messagePackHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's an invocation message. + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); + + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + assertEquals(null, invocationMessage.getHeaders()); + assertEquals(null, invocationMessage.getStreamIds()); + + @SuppressWarnings("unchecked") + PersonPojo> result = (PersonPojo>)invocationMessage.getArguments()[0]; + assertEquals("John", result.getFirstName()); + assertEquals("Doe", result.getLastName()); + assertEquals(30, result.getAge()); + + ArrayList generic = result.getT(); + assertEquals(2, generic.size()); + assertEquals((short)5, (short)generic.get(0)); + assertEquals((short)8, (short)generic.get(1)); + } + + @Test + public void verifyWriteInvocationMessageWithCustomPojoArg() { + ArrayList shorts = new ArrayList(); + shorts.add((short) 5); + shorts.add((short) 8); + + PersonPojo> person = new PersonPojo>("John", "Doe", 30, shorts); + + InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] { person }, null); + ByteBuffer result = messagePackHubProtocol.writeMessage(invocationMessage); + byte[] expectedBytes = {0x32, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, (byte) 0x91, (byte) 0x84, (byte) 0xA9, + 0x66, 0x69, 0x72, 0x73, 0x74, 0x4E, 0x61, 0x6D, 0x65, (byte) 0xA4, 0x4A, 0x6F, 0x68, 0x6E, (byte) 0xA8, 0x6C, 0x61, 0x73, 0x74, 0x4E, 0x61, + 0x6D, 0x65, (byte) 0xA3, 0x44, 0x6F, 0x65, (byte) 0xA3, 0x61, 0x67, 0x65, 0x1E, (byte) 0xA1, 0x74, (byte) 0x92, 0x05, 0x08, (byte) 0x90}; + ByteString expectedResult = ByteString.of(expectedBytes); + assertEquals(expectedResult, ByteString.of(result)); + } } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/PersonPojo.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/PersonPojo.java new file mode 100644 index 000000000000..294f8c10366e --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/PersonPojo.java @@ -0,0 +1,40 @@ +package com.microsoft.signalr; + +public class PersonPojo implements Comparable> { + public String firstName; + public String lastName; + public int age; + public T t; + + public PersonPojo() { + super(); + } + + public PersonPojo(String firstName, String lastName, int age, T t) { + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + this.t = t; + } + + public String getFirstName() { + return this.firstName; + } + + public String getLastName() { + return this.lastName; + } + + public int getAge() { + return this.age; + } + + public T getT() { + return t; + } + + @Override + public int compareTo(PersonPojo ep) { + return 0; + } +} From c4d1fec8c0a1701e05f19556e133bf90cd32b87e Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 13 Aug 2020 10:15:40 -0700 Subject: [PATCH 53/64] Fix tests for real --- .../microsoft/signalr/MessagePackHubProtocolTest.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index 9c306aec12f2..4a161577ae21 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -416,10 +416,7 @@ public void invocationBindingFailureWhenParsingIncorrectType() { assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); - assertTrue(invocationBindingFailureMessage.getException().getMessage().equals( - "class java.lang.Boolean cannot be cast to class java.lang.Integer (java.lang.Boolean and java.lang.Integer " - + "are in module java.base of loader 'bootstrap')") || invocationBindingFailureMessage.getException().getMessage().equals( - "java.base/java.lang.Boolean cannot be cast to java.base/java.lang.Integer")); + assertTrue(invocationBindingFailureMessage.getException().getMessage().matches("^.*Boolean.*cannot be cast to.*Integer.*")); } @Test @@ -502,10 +499,7 @@ public void invocationBindingFailureReadsNextMessageAfterIncorrectArgument() { assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); - assertTrue(invocationBindingFailureMessage.getException().getMessage().equals( - "class java.lang.Boolean cannot be cast to class java.lang.Integer (java.lang.Boolean and java.lang.Integer " - + "are in module java.base of loader 'bootstrap')") || invocationBindingFailureMessage.getException().getMessage().equals( - "java.base/java.lang.Boolean cannot be cast to java.base/java.lang.Integer")); + assertTrue(invocationBindingFailureMessage.getException().getMessage().matches("^.*Boolean.*cannot be cast to.*Integer.*")); // Check the second message assertEquals(HubMessageType.INVOCATION, messages.get(1).getMessageType()); From 602ef3339f5793df64fea8de22163927da0e0a16 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 13 Aug 2020 16:30:37 -0700 Subject: [PATCH 54/64] Add a whole buncha tests --- .../signalr/MessagePackHubProtocol.java | 32 +- .../java/com/microsoft/signalr/Utils.java | 3 + .../microsoft/signalr/HubConnectionTest.java | 992 +++++++++++++++++- .../signalr/MessagePackHubProtocolTest.java | 2 + .../com/microsoft/signalr/MockTransport.java | 11 +- 5 files changed, 1021 insertions(+), 19 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 08184b41e999..d706cb7ff3c6 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -501,10 +501,6 @@ private Object[] bindArguments(MessageUnpacker unpacker, List paramTypes, } private Object readValue(MessageUnpacker unpacker, Type itemType, ByteBuffer payload, boolean outermostCall) throws IOException { - // This shouldn't ever get called with itemType == null, but we return anyways to avoid NullPointerExceptions - if (itemType == null) { - return null; - } Class itemClass = Utils.typeToClass(itemType); MessageFormat messageFormat = unpacker.getNextFormat(); ValueType valueType = messageFormat.getValueType(); @@ -531,12 +527,14 @@ private Object readValue(MessageUnpacker unpacker, Type itemType, ByteBuffer pay default: item = unpacker.unpackInt(); // unpackInt could correspond to an int, short, char, or byte - cast those literally here - if (itemClass.equals(Short.class) || itemClass.equals(short.class)) { - item = ((Integer) item).shortValue(); - } else if (itemClass.equals(Character.class) || itemClass.equals(char.class)) { - item = (char) ((Integer) item).shortValue(); - } else if (itemClass.equals(Byte.class) || itemClass.equals(byte.class)) { - item = ((Integer) item).byteValue(); + if (itemClass != null) { + if (itemClass.equals(Short.class) || itemClass.equals(short.class)) { + item = ((Integer) item).shortValue(); + } else if (itemClass.equals(Character.class) || itemClass.equals(char.class)) { + item = (char) ((Integer) item).shortValue(); + } else if (itemClass.equals(Byte.class) || itemClass.equals(byte.class)) { + item = ((Integer) item).byteValue(); + } } break; } @@ -547,7 +545,7 @@ private Object readValue(MessageUnpacker unpacker, Type itemType, ByteBuffer pay case STRING: item = unpacker.unpackString(); // ObjectMapper packs chars as Strings - correct back to char while unpacking if necessary - if (itemClass.equals(char.class) || itemClass.equals(Character.class)) { + if (itemClass != null && (itemClass.equals(char.class) || itemClass.equals(Character.class))) { item = ((String) item).charAt(0); } break; @@ -566,6 +564,10 @@ private Object readValue(MessageUnpacker unpacker, Type itemType, ByteBuffer pay if (outermostCall) { // Check how many bytes we've read, grab that from the payload, and deserialize with objectMapper byte[] payloadBytes = payload.array(); + // If itemType was null, we were just in this method to advance the buffer. return null. + if (itemType == null) { + return null; + } return objectMapper.readValue(payloadBytes, payload.position() + (int) readBytesStart, (int) (unpacker.getTotalReadBytes() - readBytesStart), typeFactory.constructType(itemType)); } else { @@ -585,6 +587,10 @@ private Object readValue(MessageUnpacker unpacker, Type itemType, ByteBuffer pay byte[] payloadBytes = payload.array(); byte[] mapBytes = Arrays.copyOfRange(payloadBytes, payload.position() + (int) readBytesStart, payload.position() + (int) unpacker.getTotalReadBytes()); + // If itemType was null, we were just in this method to advance the buffer. return null. + if (itemType == null) { + return null; + } return objectMapper.readValue(payloadBytes, payload.position() + (int) readBytesStart, (int) (unpacker.getTotalReadBytes() - readBytesStart), typeFactory.constructType(itemType)); } else { @@ -604,6 +610,10 @@ private Object readValue(MessageUnpacker unpacker, Type itemType, ByteBuffer pay default: return null; } + // If itemType was null, we were just in this method to advance the buffer. return null. + if (itemType == null) { + return null; + } // If we get here, the item isn't a map or a collection/array, so we use the Class to cast it if (itemClass.isPrimitive()) { return Utils.toPrimitive(itemClass, item); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java index f3747a5156c9..88d589a93683 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/Utils.java @@ -89,6 +89,9 @@ public static Object toPrimitive(Class c, Object value) { } public static Class typeToClass(Type type) { + if (type == null) { + return null; + } if (type instanceof Class) { return (Class) type; } else if (type instanceof GenericArrayType) { diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index b21f1a7e6f4d..1b769c02b91a 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -5,7 +5,9 @@ import static org.junit.jupiter.api.Assertions.*; +import java.lang.reflect.Type; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.CancellationException; @@ -25,9 +27,15 @@ import io.reactivex.subjects.PublishSubject; import io.reactivex.subjects.ReplaySubject; import io.reactivex.subjects.SingleSubject; +import okio.ByteString; class HubConnectionTest { private static final String RECORD_SEPARATOR = "\u001e"; + private static final Type booleanType = (new TypeReference() { }).getType(); + private static final Type doubleType = (new TypeReference() { }).getType(); + private static final Type integerType = (new TypeReference() { }).getType(); + private static final Type stringType = (new TypeReference() { }).getType(); + private static final MessagePackHubProtocol messagePackHubProtocol = new MessagePackHubProtocol(); @Test public void checkHubConnectionState() { @@ -621,6 +629,9 @@ public void checkStreamUploadSingleItemThroughInvoke() { stream.onNext("FirstItem"); ByteBuffer[] messages = mockTransport.getSentMessages(); + for (ByteBuffer bb: messages) { + System.out.println(TestUtils.ByteBufferToString(bb)); + } assertEquals(3, messages.length); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"2\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); @@ -630,6 +641,38 @@ public void checkStreamUploadSingleItemThroughInvoke() { messages = mockTransport.getSentMessages(); assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[3])); } + + @Test + public void checkStreamUploadSingleItemThroughInvokeWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + ReplaySubject stream = ReplaySubject.create(); + hubConnection.invoke(stringType, "UploadStream", stream); + + stream.onNext("FirstItem"); + ByteBuffer[] messages = mockTransport.getSentMessages(); + for (ByteBuffer bb: messages) { + System.out.println(TestUtils.ByteBufferToString(bb)); + } + assertEquals(3, messages.length); + + byte[] firstMessageExpectedBytes = new byte[] { 0x16, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xAC, 0x55, 0x70, 0x6C, 0x6F, + 0x61, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, (byte) 0x90, (byte) 0x91, (byte) 0xA1, 0x32 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(messages[1])); + + byte[] secondMessageExpectedBytes = new byte[] { 0x0F, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x32, (byte) 0xA9, 0x46, 0x69, 0x72, 0x73, + 0x74, 0x49, 0x74, 0x65, 0x6D }; + assertEquals(ByteString.of(secondMessageExpectedBytes), ByteString.of(messages[2])); + + stream.onComplete(); + messages = mockTransport.getSentMessages(); + + byte[] thirdMessageExpectedBytes = new byte[] { 0x06, (byte) 0x94, 0x03, (byte) 0x80, (byte) 0xA1, 0x32, 0x02 }; + assertEquals(ByteString.of(thirdMessageExpectedBytes), ByteString.of(messages[3])); + } @Test public void checkStreamUploadSingleItemThroughStream() { @@ -654,6 +697,37 @@ public void checkStreamUploadSingleItemThroughStream() { assertEquals(4, messages.length); assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[3])); } + + @Test + public void checkStreamUploadSingleItemThroughStreamWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + ReplaySubject stream = ReplaySubject.create(); + hubConnection.stream(stringType, "UploadStream", stream); + + stream.onNext("FirstItem"); + + ByteBuffer[] messages = mockTransport.getSentMessages(); + assertEquals(3, messages.length); + + byte[] firstMessageExpectedBytes = new byte[] { 0x16, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xAC, 0x55, 0x70, 0x6C, 0x6F, + 0x61, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, (byte) 0x90, (byte) 0x91, (byte) 0xA1, 0x32 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(messages[1])); + + byte[] secondMessageExpectedBytes = new byte[] { 0x0F, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x32, (byte) 0xA9, 0x46, 0x69, 0x72, 0x73, + 0x74, 0x49, 0x74, 0x65, 0x6D }; + assertEquals(ByteString.of(secondMessageExpectedBytes), ByteString.of(messages[2])); + + stream.onComplete(); + messages = mockTransport.getSentMessages(); + assertEquals(4, messages.length); + + byte[] thirdMessageExpectedBytes = new byte[] { 0x06, (byte) 0x94, 0x03, (byte) 0x80, (byte) 0xA1, 0x32, 0x02 }; + assertEquals(ByteString.of(thirdMessageExpectedBytes), ByteString.of(messages[3])); + } @Test public void useSameSubjectInMutlipleStreamsFromDifferentMethods() { @@ -690,6 +764,64 @@ public void useSameSubjectInMutlipleStreamsFromDifferentMethods() { assertEquals("{\"type\":3,\"invocationId\":\"3\"}\u001E", TestUtils.ByteBufferToString(messages[8])); assertEquals("{\"type\":3,\"invocationId\":\"5\"}\u001E", TestUtils.ByteBufferToString(messages[9])); } + + @Test + public void useSameSubjectInMutlipleStreamsFromDifferentMethodsWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + ReplaySubject stream = ReplaySubject.create(); + hubConnection.send("UploadStream", stream); + hubConnection.invoke(stringType, "UploadStream", stream); + hubConnection.stream(stringType, "UploadStream", stream); + + ByteBuffer[] messages = mockTransport.getSentMessages(); + assertEquals(4, messages.length); + + byte[] firstMessageExpectedBytes = new byte[] { 0x15, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xAC, 0x55, 0x70, 0x6C, 0x6F, 0x61, + 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, (byte) 0x90, (byte) 0x91, (byte) 0xA1, 0x31 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(messages[1])); + + byte[] secondMessageExpectedBytes = new byte[] { 0x16, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xA1, 0x32, (byte) 0xAC, 0x55, 0x70, 0x6C, 0x6F, + 0x61, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, (byte) 0x90, (byte) 0x91, (byte) 0xA1, 0x33 }; + assertEquals(ByteString.of(secondMessageExpectedBytes), ByteString.of(messages[2])); + + byte[] thirdMessageExpectedBytes = new byte[] { 0x16, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA1, 0x34, (byte) 0xAC, 0x55, 0x70, 0x6C, 0x6F, + 0x61, 0x64, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6D, (byte) 0x90, (byte) 0x91, (byte) 0xA1, 0x35 }; + assertEquals(ByteString.of(thirdMessageExpectedBytes), ByteString.of(messages[3])); + + stream.onNext("FirstItem"); + + messages = mockTransport.getSentMessages(); + assertEquals(7, messages.length); + + byte[] fourthMessageExpectedBytes = new byte[] { 0x0F, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA9, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x49, 0x74, 0x65, 0x6D }; + assertEquals(ByteString.of(fourthMessageExpectedBytes), ByteString.of(messages[4])); + + byte[] fifthMessageExpectedBytes = new byte[] { 0x0F, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x33, (byte) 0xA9, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x49, 0x74, 0x65, 0x6D }; + assertEquals(ByteString.of(fifthMessageExpectedBytes), ByteString.of(messages[5])); + + byte[] sixthMessageExpectedBytes = new byte[] { 0x0F, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x35, (byte) 0xA9, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x49, 0x74, 0x65, 0x6D }; + assertEquals(ByteString.of(sixthMessageExpectedBytes), ByteString.of(messages[6])); + + stream.onComplete(); + messages = mockTransport.getSentMessages(); + assertEquals(10, messages.length); + + byte[] seventhMessageExpectedBytes = new byte[] { 0x06, (byte) 0x94, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x02 }; + assertEquals(ByteString.of(seventhMessageExpectedBytes), ByteString.of(messages[7])); + + byte[] eighthMessageExpectedBytes = new byte[] { 0x06, (byte) 0x94, 0x03, (byte) 0x80, (byte) 0xA1, 0x33, 0x02 }; + assertEquals(ByteString.of(eighthMessageExpectedBytes), ByteString.of(messages[8])); + + byte[] ninthMessageExpectedBytes = new byte[] { 0x06, (byte) 0x94, 0x03, (byte) 0x80, (byte) 0xA1, 0x35, 0x02 }; + assertEquals(ByteString.of(ninthMessageExpectedBytes), ByteString.of(messages[9])); + } @Test public void streamUploadCallOnError() { @@ -764,6 +896,38 @@ public void checkStreamUploadMultipleItemsThroughInvoke() { assertEquals(5, messages.length); assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[4])); } + + @Test + public void checkStreamUploadMultipleItemsThroughInvokeWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + ReplaySubject stream = ReplaySubject.create(); + hubConnection.invoke(stringType, "UploadStream", stream); + + stream.onNext("FirstItem"); + stream.onNext("SecondItem"); + + ByteBuffer[] messages = mockTransport.getSentMessages(); + assertEquals(4, messages.length); + + byte[] firstMessageExpectedBytes = new byte[] { 0x0F, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x32, (byte) 0xA9, 0x46, 0x69, 0x72, 0x73, 0x74, + 0x49, 0x74, 0x65, 0x6D }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(messages[2])); + + byte[] secondMessageExpectedBytes = new byte[] { 0x10, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x32, (byte) 0xAA, 0x53, 0x65, 0x63, 0x6F, 0x6E, + 0x64, 0x49, 0x74, 0x65, 0x6D }; + assertEquals(ByteString.of(secondMessageExpectedBytes), ByteString.of(messages[3])); + + stream.onComplete(); + messages = mockTransport.getSentMessages(); + assertEquals(5, messages.length); + + byte[] thirdMessageExpectedBytes = new byte[] { 0x06, (byte) 0x94, 0x03, (byte) 0x80, (byte) 0xA1, 0x32, 0x02 }; + assertEquals(ByteString.of(thirdMessageExpectedBytes), ByteString.of(messages[4])); + } @Test public void canStartAndStopMultipleStreams() { @@ -825,7 +989,39 @@ public void checkStreamSingleItem() { assertEquals("First", result.timeout(1000, TimeUnit.MILLISECONDS).blockingFirst()); } + + @Test + public void checkStreamSingleItemWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean completed = new AtomicBoolean(); + AtomicBoolean onNextCalled = new AtomicBoolean(); + Observable result = hubConnection.stream(stringType, "echo", "message"); + result.subscribe((item) -> onNextCalled.set(true), + (error) -> {}, + () -> completed.set(true)); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x65, 0x63, 0x68, 0x6F, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + assertFalse(completed.get()); + assertFalse(onNextCalled.get()); + + byte[] secondMessageExpectedBytes = new byte[] { 0x0B, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA5, 0x46, 0x69, 0x72, 0x73, 0x74 }; + mockTransport.receiveMessage(ByteBuffer.wrap(secondMessageExpectedBytes)); + assertTrue(onNextCalled.get()); + + byte[] thirdMessageExpectedBytes = new byte[] { 0x0C, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x03, (byte) 0xA5, 0x68, 0x65, 0x6C, 0x6C, 0x6F }; + mockTransport.receiveMessage(ByteBuffer.wrap(thirdMessageExpectedBytes)); + assertTrue(completed.get()); + + assertEquals("First", result.timeout(1000, TimeUnit.MILLISECONDS).blockingFirst()); + } + @Test public void checkStreamCompletionResult() { MockTransport mockTransport = new MockTransport(); @@ -855,6 +1051,40 @@ public void checkStreamCompletionResult() { assertEquals("First", result.timeout(1000, TimeUnit.MILLISECONDS).blockingFirst()); assertEquals("COMPLETED", result.timeout(1000, TimeUnit.MILLISECONDS).blockingLast()); } + + @Test + public void checkStreamCompletionResultWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean completed = new AtomicBoolean(); + AtomicBoolean onNextCalled = new AtomicBoolean(); + Observable result = hubConnection.stream(stringType, "echo", "message"); + result.subscribe((item) -> onNextCalled.set(true), + (error) -> {}, + () -> completed.set(true)); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x65, 0x63, 0x68, 0x6F, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + assertFalse(completed.get()); + assertFalse(onNextCalled.get()); + + byte[] secondMessageExpectedBytes = new byte[] { 0x0B, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA5, 0x46, 0x69, 0x72, 0x73, 0x74 }; + mockTransport.receiveMessage(ByteBuffer.wrap(secondMessageExpectedBytes)); + + assertTrue(onNextCalled.get()); + + byte[] thirdMessageExpectedBytes = new byte[] { 0x10, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x03, (byte) 0xA9, 0x43, 0x4F, 0x4D, 0x50, + 0x4C, 0x45, 0x54, 0x45, 0x44 }; + mockTransport.receiveMessage(ByteBuffer.wrap(thirdMessageExpectedBytes)); + assertTrue(completed.get()); + + assertEquals("First", result.timeout(1000, TimeUnit.MILLISECONDS).blockingFirst()); + assertEquals("COMPLETED", result.timeout(1000, TimeUnit.MILLISECONDS).blockingLast()); + } @Test public void checkStreamCompletionError() { @@ -886,6 +1116,40 @@ public void checkStreamCompletionError() { Throwable exception = assertThrows(HubException.class, () -> result.timeout(1000, TimeUnit.MILLISECONDS).blockingLast()); assertEquals("There was an error", exception.getMessage()); } + + @Test + public void checkStreamCompletionErrorWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean onErrorCalled = new AtomicBoolean(); + AtomicBoolean onNextCalled = new AtomicBoolean(); + Observable result = hubConnection.stream(stringType, "echo", "message"); + result.subscribe((item) -> onNextCalled.set(true), + (error) -> onErrorCalled.set(true), + () -> {}); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x65, 0x63, 0x68, 0x6F, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + assertFalse(onErrorCalled.get()); + assertFalse(onNextCalled.get()); + + byte[] secondMessageExpectedBytes = new byte[] { 0x0B, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA5, 0x46, 0x69, 0x72, 0x73, 0x74 }; + mockTransport.receiveMessage(ByteBuffer.wrap(secondMessageExpectedBytes)); + + assertTrue(onNextCalled.get()); + + byte[] thirdMessageExpectedBytes = new byte[] { 0x0C, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x01, (byte) 0xA5, 0x45, 0x72, 0x72, 0x6F, 0x72 }; + mockTransport.receiveMessage(ByteBuffer.wrap(thirdMessageExpectedBytes)); + assertTrue(onErrorCalled.get()); + + assertEquals("First", result.timeout(1000, TimeUnit.MILLISECONDS).blockingFirst()); + Throwable exception = assertThrows(HubException.class, () -> result.timeout(1000, TimeUnit.MILLISECONDS).blockingLast()); + assertEquals("Error", exception.getMessage()); + } @Test public void checkStreamMultipleItems() { @@ -913,6 +1177,39 @@ public void checkStreamMultipleItems() { assertEquals("Second", resultIterator.next()); assertTrue(completed.get()); } + + @Test + public void checkStreamMultipleItemsWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean completed = new AtomicBoolean(); + Observable result = hubConnection.stream(stringType, "echo", "message"); + result.subscribe((item) -> {/*OnNext*/ }, + (error) -> {/*OnError*/}, + () -> {/*OnCompleted*/completed.set(true);}); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x65, 0x63, 0x68, 0x6F, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + assertFalse(completed.get()); + + byte[] secondMessageExpectedBytes = new byte[] { 0x0B, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA5, 0x46, 0x69, 0x72, 0x73, 0x74 }; + mockTransport.receiveMessage(ByteBuffer.wrap(secondMessageExpectedBytes)); + + byte[] thirdMessageExpectedBytes = new byte[] { 0x0C, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA6, 0x53, 0x65, 0x63, 0x6F, 0x6E, 0x64 }; + mockTransport.receiveMessage(ByteBuffer.wrap(thirdMessageExpectedBytes)); + + byte[] fourthMessageExpectedBytes = new byte[] { 0x06, (byte) 0x94, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x02 }; + mockTransport.receiveMessage(ByteBuffer.wrap(fourthMessageExpectedBytes)); + + Iterator resultIterator = result.timeout(1000, TimeUnit.MILLISECONDS).blockingIterable().iterator(); + assertEquals("First", resultIterator.next()); + assertEquals("Second", resultIterator.next()); + assertTrue(completed.get()); + } @Test public void checkCancelIsSentAfterDispose() { @@ -934,6 +1231,29 @@ public void checkCancelIsSentAfterDispose() { subscription.dispose(); assertEquals("{\"type\":5,\"invocationId\":\"1\"}" + RECORD_SEPARATOR, TestUtils.ByteBufferToString(mockTransport.getSentMessages()[2])); } + + @Test + public void checkCancelIsSentAfterDisposeWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean completed = new AtomicBoolean(); + Observable result = hubConnection.stream(stringType, "echo", "message"); + Disposable subscription = result.subscribe((item) -> {/*OnNext*/ }, + (error) -> {/*OnError*/}, + () -> {/*OnCompleted*/completed.set(true);}); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x65, 0x63, 0x68, 0x6F, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + assertFalse(completed.get()); + + subscription.dispose(); + assertEquals(ByteString.of(new byte[] { 0x05, (byte) 0x93, 0x05, (byte) 0x80, (byte) 0xA1, 0x31 }), + ByteString.of(mockTransport.getSentMessages()[2])); + } @Test public void checkCancelIsSentAfterAllSubscriptionsAreDisposed() { @@ -961,6 +1281,35 @@ public void checkCancelIsSentAfterAllSubscriptionsAreDisposed() { assertEquals("{\"type\":5,\"invocationId\":\"1\"}" + RECORD_SEPARATOR, TestUtils.ByteBufferToString(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); } + + @Test + public void checkCancelIsSentAfterAllSubscriptionsAreDisposedWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + Observable result = hubConnection.stream(stringType, "echo", "message"); + Disposable subscription = result.subscribe((item) -> {/*OnNext*/ }, + (error) -> {/*OnError*/}, + () -> {/*OnCompleted*/}); + + Disposable secondSubscription = result.subscribe((item) -> {/*OnNext*/ }, + (error) -> {/*OnError*/}, + () -> {/*OnCompleted*/}); + + subscription.dispose(); + assertEquals(2, mockTransport.getSentMessages().length); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x65, 0x63, 0x68, 0x6F, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); + + secondSubscription.dispose(); + assertEquals(3, mockTransport.getSentMessages().length); + assertEquals(ByteString.of(new byte[] { 0x05, (byte) 0x93, 0x05, (byte) 0x80, (byte) 0xA1, 0x31 }), + ByteString.of(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); + } @Test public void checkStreamWithDispose() { @@ -984,6 +1333,32 @@ public void checkStreamWithDispose() { assertEquals("First", result.timeout(1000, TimeUnit.MILLISECONDS).blockingLast()); } + + @Test + public void checkStreamWithDisposeWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + Observable result = hubConnection.stream(stringType, "echo", "message"); + Disposable subscription = result.subscribe((item) -> {/*OnNext*/}, + (error) -> {/*OnError*/}, + () -> {/*OnCompleted*/}); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x65, 0x63, 0x68, 0x6F, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + + byte[] secondMessageExpectedBytes = new byte[] { 0x0B, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA5, 0x46, 0x69, 0x72, 0x73, 0x74 }; + mockTransport.receiveMessage(ByteBuffer.wrap(secondMessageExpectedBytes)); + + subscription.dispose(); + byte[] thirdMessageExpectedBytes = new byte[] { 0x0C, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA6, 0x53, 0x65, 0x63, 0x6F, 0x6E, 0x64 }; + mockTransport.receiveMessage(ByteBuffer.wrap(thirdMessageExpectedBytes)); + + assertEquals("First", result.timeout(1000, TimeUnit.MILLISECONDS).blockingLast()); + } @Test public void checkStreamWithDisposeWithMultipleSubscriptions() { @@ -1018,6 +1393,45 @@ public void checkStreamWithDisposeWithMultipleSubscriptions() { subscription2.dispose(); assertEquals("Second", result.timeout(1000, TimeUnit.MILLISECONDS).blockingLast()); } + + @Test + public void checkStreamWithDisposeWithMultipleSubscriptionsWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean completed = new AtomicBoolean(); + Observable result = hubConnection.stream(stringType, "echo", "message"); + Disposable subscription = result.subscribe((item) -> {/*OnNext*/}, + (error) -> {/*OnError*/}, + () -> {/*OnCompleted*/}); + + Disposable subscription2 = result.subscribe((item) -> {/*OnNext*/}, + (error) -> {/*OnError*/}, + () -> {/*OnCompleted*/completed.set(true);}); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x04, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x65, 0x63, 0x68, 0x6F, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + assertFalse(completed.get()); + + byte[] secondMessageExpectedBytes = new byte[] { 0x0B, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA5, 0x46, 0x69, 0x72, 0x73, 0x74 }; + mockTransport.receiveMessage(ByteBuffer.wrap(secondMessageExpectedBytes)); + + subscription.dispose(); + byte[] thirdMessageExpectedBytes = new byte[] { 0x0C, (byte) 0x94, 0x02, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA6, 0x53, 0x65, 0x63, 0x6F, 0x6E, 0x64 }; + mockTransport.receiveMessage(ByteBuffer.wrap(thirdMessageExpectedBytes)); + + byte[] fourthMessageExpectedBytes = new byte[] { 0x06, (byte) 0x94, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x02 }; + mockTransport.receiveMessage(ByteBuffer.wrap(fourthMessageExpectedBytes)); + + assertTrue(completed.get()); + assertEquals("First", result.timeout(1000, TimeUnit.MILLISECONDS).blockingFirst()); + + subscription2.dispose(); + assertEquals("Second", result.timeout(1000, TimeUnit.MILLISECONDS).blockingLast()); + } @Test public void invokeWaitsForCompletionMessage() { @@ -1038,6 +1452,28 @@ public void invokeWaitsForCompletionMessage() { assertEquals(Integer.valueOf(42), result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); assertTrue(done.get()); } + + @Test + public void invokeWaitsForCompletionMessageWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + Single result = hubConnection.invoke(integerType, "echo", "message"); + result.doOnSuccess(value -> done.set(true)).subscribe(); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x65, 0x63, 0x68, 0x6F, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + assertFalse(done.get()); + + mockTransport.receiveMessage(ByteBuffer.wrap(new byte[] { 0x07, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x03, 0x2A })); + + assertEquals(Integer.valueOf(42), result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); + } @Test public void invokeNoReturnValueWaitsForCompletion() { @@ -1059,11 +1495,11 @@ public void invokeNoReturnValueWaitsForCompletion() { assertNull(result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); assertTrue(done.get()); } - + @Test - public void invokeCompletedByCompletionMessageWithResult() { + public void invokeNoReturnValueWaitsForCompletionWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1071,18 +1507,62 @@ public void invokeCompletedByCompletionMessageWithResult() { Completable result = hubConnection.invoke("test", "message"); result.doOnComplete(() -> done.set(true)).subscribe(); - assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); assertFalse(done.get()); - mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); + mockTransport.receiveMessage(ByteBuffer.wrap(new byte[] { 0x06, (byte) 0x94, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x02 })); assertNull(result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); assertTrue(done.get()); } @Test - public void completionWithResultAndErrorHandlesError() { + public void invokeCompletedByCompletionMessageWithResult() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + Completable result = hubConnection.invoke("test", "message"); + result.doOnComplete(() -> done.set(true)).subscribe(); + + assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, + TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + assertFalse(done.get()); + + mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); + + assertNull(result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); + } + + @Test + public void invokeCompletedByCompletionMessageWithResultWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + Completable result = hubConnection.invoke("test", "message"); + result.doOnComplete(() -> done.set(true)).subscribe(); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + assertFalse(done.get()); + + mockTransport.receiveMessage(ByteBuffer.wrap(new byte[] { 0x07, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x03, 0x2A })); + + assertNull(result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); + } + + @Test + public void completionWithResultAndErrorHandlesError() { MockTransport mockTransport = new MockTransport(); HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport); @@ -1127,6 +1607,36 @@ public void invokeNoReturnValueHandlesError() { assertEquals("There was an error", errorMessage.get()); } + + @Test + public void invokeNoReturnValueHandlesErrorWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + Completable result = hubConnection.invoke("test", "message"); + result.doOnComplete(() -> done.set(true)).subscribe(() -> {}, (error) -> {}); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x74, 0x65, 0x73, 0x74, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + assertFalse(done.get()); + + byte[] completionMessageErrorBytes = new byte[] { 0x19, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x01, (byte) 0xB2, 0x54, 0x68, 0x65, + 0x72, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x61, 0x6E, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72 }; + mockTransport.receiveMessage(ByteBuffer.wrap(completionMessageErrorBytes)); + + result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet(); + + AtomicReference errorMessage = new AtomicReference<>(); + result.doOnError(error -> { + errorMessage.set(error.getMessage()); + }).subscribe(() -> {}, (error) -> {}); + + assertEquals("There was an error", errorMessage.get()); + } @Test public void canSendNullArgInInvocation() { @@ -1147,6 +1657,30 @@ public void canSendNullArgInInvocation() { assertEquals("Hello World", result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); assertTrue(done.get()); } + + @Test + public void canSendNullArgInInvocationWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + Single result = hubConnection.invoke(stringType, "fixedMessage", (Object)null); + result.doOnSuccess(value -> done.set(true)).subscribe(); + + byte[] firstMessageExpectedBytes = new byte[] { 0x15, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xAC, 0x66, 0x69, 0x78, 0x65, 0x64, + 0x4D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x91, (byte) 0xC0, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + assertFalse(done.get()); + + byte[] completionMessageBytes = new byte[] { 0x12, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x03, (byte) 0xAB, 0x48, 0x65, 0x6C, 0x6C, + 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64 }; + mockTransport.receiveMessage(ByteBuffer.wrap(completionMessageBytes)); + + assertEquals("Hello World", result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); + } @Test public void canSendMultipleNullArgsInInvocation() { @@ -1167,6 +1701,30 @@ public void canSendMultipleNullArgsInInvocation() { assertEquals("Hello World", result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); assertTrue(done.get()); } + + @Test + public void canSendMultipleNullArgsInInvocationWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + Single result = hubConnection.invoke(String.class, "fixedMessage", null, null); + result.doOnSuccess(value -> done.set(true)).subscribe(); + + byte[] firstMessageExpectedBytes = new byte[] { 0x16, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xAC, 0x66, 0x69, 0x78, 0x65, 0x64, 0x4D, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x92, (byte) 0xC0, (byte) 0xC0, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + assertFalse(done.get()); + + byte[] completionMessageBytes = new byte[] { 0x12, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x03, (byte) 0xAB, 0x48, 0x65, 0x6C, 0x6C, + 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64 }; + mockTransport.receiveMessage(ByteBuffer.wrap(completionMessageBytes)); + + assertEquals("Hello World", result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); + } @Test public void multipleInvokesWaitForOwnCompletionMessage() { @@ -1197,6 +1755,43 @@ public void multipleInvokesWaitForOwnCompletionMessage() { assertEquals(Integer.valueOf(42), result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); assertTrue(doneFirst.get()); } + + @Test + public void multipleInvokesWaitForOwnCompletionMessageWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean doneFirst = new AtomicBoolean(); + AtomicBoolean doneSecond = new AtomicBoolean(); + Single result = hubConnection.invoke(integerType, "echo", "message"); + Single result2 = hubConnection.invoke(stringType, "echo", "message"); + result.doOnSuccess(value -> doneFirst.set(true)).subscribe(); + result2.doOnSuccess(value -> doneSecond.set(true)).subscribe(); + + byte[] firstMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xA1, 0x31, (byte) 0xA4, 0x65, 0x63, 0x68, 0x6F, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(firstMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[1])); + + byte[] secondMessageExpectedBytes = new byte[] { 0x14, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xA1, 0x32, (byte) 0xA4, 0x65, 0x63, 0x68, 0x6F, + (byte) 0x91, (byte) 0xA7, 0x6D, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, (byte) 0x90 }; + assertEquals(ByteString.of(secondMessageExpectedBytes), ByteString.of(mockTransport.getSentMessages()[2])); + assertFalse(doneFirst.get()); + assertFalse(doneSecond.get()); + + byte[] firstCompletionMessageBytes = new byte[] { 0x0E, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x32, 0x03, (byte) 0xA7, 0x6D, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65 }; + mockTransport.receiveMessage(ByteBuffer.wrap(firstCompletionMessageBytes)); + assertEquals("message", result2.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertFalse(doneFirst.get()); + assertTrue(doneSecond.get()); + + byte[] secondCompletionMessageBytes = new byte[] { 0x07, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x03, 0x2A }; + mockTransport.receiveMessage(ByteBuffer.wrap(secondCompletionMessageBytes)); + assertEquals(Integer.valueOf(42), result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(doneFirst.get()); + } @Test public void invokeWorksForPrimitiveTypes() { @@ -1217,6 +1812,26 @@ public void invokeWorksForPrimitiveTypes() { assertEquals(Integer.valueOf(42), result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); assertTrue(done.get()); } + + @Test + public void invokeWorksForPrimitiveTypesWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + // int.class is a primitive type and since we use Class.cast to cast an Object to the expected return type + // which does not work for primitives we have to write special logic for that case. + Single result = hubConnection.invoke(int.class, "echo", "message"); + result.doOnSuccess(value -> done.set(true)).subscribe(); + assertFalse(done.get()); + + mockTransport.receiveMessage(ByteBuffer.wrap(new byte[] { 0x07, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x03, 0x2A })); + + assertEquals(Integer.valueOf(42), result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); + assertTrue(done.get()); + } @Test public void completionMessageCanHaveError() { @@ -1242,6 +1857,33 @@ public void completionMessageCanHaveError() { assertEquals("There was an error", exceptionMessage); } + + @Test + public void completionMessageCanHaveErrorWithMessagePack() { + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + + AtomicBoolean done = new AtomicBoolean(); + Single result = hubConnection.invoke(int.class, "echo", "message"); + result.doOnSuccess(value -> done.set(true)); + assertFalse(done.get()); + + byte[] completionMessageErrorBytes = new byte[] { 0x19, (byte) 0x95, 0x03, (byte) 0x80, (byte) 0xA1, 0x31, 0x01, (byte) 0xB2, 0x54, 0x68, 0x65, + 0x72, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x61, 0x6E, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72 }; + mockTransport.receiveMessage(ByteBuffer.wrap(completionMessageErrorBytes)); + + String exceptionMessage = null; + try { + result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet(); + assertFalse(true); + } catch (HubException ex) { + exceptionMessage = ex.getMessage(); + } + + assertEquals("There was an error", exceptionMessage); + } @Test public void stopCancelsActiveInvokes() { @@ -1557,6 +2199,315 @@ public void sendWithEightParamsTriggersOnHandler() { assertEquals("E", value7.get()); assertEquals("F", value8.get()); } + + @Test + public void sendWithNoParamsTriggersOnHandlerWithMessagePack() { + AtomicReference value = new AtomicReference<>(0); + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.on("inc", () ->{ + assertEquals(Integer.valueOf(0), value.get()); + value.getAndUpdate((val) -> val + 1); + }); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + byte[] messageBytes = new byte[] { 0x0A, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x69, 0x6E, 0x63, (byte) 0x90, (byte) 0x90 }; + mockTransport.receiveMessage(ByteBuffer.wrap(messageBytes)); + + // Confirming that our handler was called and that the counter property was incremented. + assertEquals(Integer.valueOf(1), value.get()); + } + + @Test + public void sendWithParamTriggersOnHandlerWithMessagePack() { + AtomicReference value = new AtomicReference<>(); + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.on("inc", (param) ->{ + assertNull(value.get()); + value.set(param); + }, stringType); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + byte[] messageBytes = new byte[] { 0x0C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x69, 0x6E, 0x63, (byte) 0x91, (byte) 0xA1, + 0x41, (byte) 0x90 }; + mockTransport.receiveMessage(ByteBuffer.wrap(messageBytes)); + hubConnection.send("inc", "A"); + + // Confirming that our handler was called and the correct message was passed in. + assertEquals("A", value.get()); + } + + @Test + public void sendWithTwoParamsTriggersOnHandlerWithMessagePack() { + AtomicReference value1 = new AtomicReference<>(); + AtomicReference value2 = new AtomicReference<>(); + + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.on("inc", (param1, param2) ->{ + assertNull(value1.get()); + assertNull((value2.get())); + + value1.set(param1); + value2.set(param2); + }, stringType, doubleType); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + byte[] messageBytes = new byte[] { 0x15, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x69, 0x6E, 0x63, (byte) 0x92, (byte) 0xA1, 0x41, + (byte) 0xCB, 0x40, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x90 }; + mockTransport.receiveMessage(ByteBuffer.wrap(messageBytes)); + hubConnection.send("inc", "A", 12); + + // Confirming that our handler was called and the correct message was passed in. + assertEquals("A", value1.get()); + assertEquals(Double.valueOf(12), value2.get()); + } + + @Test + public void sendWithThreeParamsTriggersOnHandlerWithMessagePack() { + AtomicReference value1 = new AtomicReference<>(); + AtomicReference value2 = new AtomicReference<>(); + AtomicReference value3 = new AtomicReference<>(); + + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.on("inc", (param1, param2, param3) ->{ + assertNull(value1.get()); + assertNull(value2.get()); + assertNull(value3.get()); + + value1.set(param1); + value2.set(param2); + value3.set(param3); + }, stringType, stringType, stringType); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + byte[] messageBytes = new byte[] { 0x10, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x69, 0x6E, 0x63, (byte) 0x93, (byte) 0xA1, 0x41, + (byte) 0xA1, 0x42, (byte) 0xA1, 0x43, (byte) 0x90 }; + mockTransport.receiveMessage(ByteBuffer.wrap(messageBytes)); + hubConnection.send("inc", "A", "B", "C"); + + // Confirming that our handler was called and the correct message was passed in. + assertEquals("A", value1.get()); + assertEquals("B", value2.get()); + assertEquals("C", value3.get()); + } + + @Test + public void sendWithFourParamsTriggersOnHandlerWithMessagePack() { + AtomicReference value1 = new AtomicReference<>(); + AtomicReference value2 = new AtomicReference<>(); + AtomicReference value3 = new AtomicReference<>(); + AtomicReference value4 = new AtomicReference<>(); + + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.on("inc", (param1, param2, param3, param4) ->{ + assertNull(value1.get()); + assertNull(value2.get()); + assertNull(value3.get()); + assertNull(value4.get()); + + value1.set(param1); + value2.set(param2); + value3.set(param3); + value4.set(param4); + }, stringType, stringType, stringType, stringType); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + byte[] messageBytes = new byte[] { 0x12, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x69, 0x6E, 0x63, (byte) 0x94, (byte) 0xA1, 0x41, + (byte) 0xA1, 0x42, (byte) 0xA1, 0x43, (byte) 0xA1, 0x44, (byte) 0x90 }; + mockTransport.receiveMessage(ByteBuffer.wrap(messageBytes)); + + // Confirming that our handler was called and the correct message was passed in. + assertEquals("A", value1.get()); + assertEquals("B", value2.get()); + assertEquals("C", value3.get()); + assertEquals("D", value4.get()); + } + + @Test + public void sendWithFiveParamsTriggersOnHandlerWithMessagePack() { + AtomicReference value1 = new AtomicReference<>(); + AtomicReference value2 = new AtomicReference<>(); + AtomicReference value3 = new AtomicReference<>(); + AtomicReference value4 = new AtomicReference<>(); + AtomicReference value5 = new AtomicReference<>(); + + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.on("inc", (param1, param2, param3, param4, param5) ->{ + assertNull(value1.get()); + assertNull(value2.get()); + assertNull(value3.get()); + assertNull(value4.get()); + assertNull(value5.get()); + + value1.set(param1); + value2.set(param2); + value3.set(param3); + value4.set(param4); + value5.set(param5); + }, stringType, stringType, stringType, booleanType, doubleType); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + byte[] messageBytes = new byte[] { 0x1A, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x69, 0x6E, 0x63, (byte) 0x95, (byte) 0xA1, 0x41, + (byte) 0xA1, 0x42, (byte) 0xA1, 0x43, (byte) 0xC3, (byte) 0xCB, 0x40, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x90 }; + mockTransport.receiveMessage(ByteBuffer.wrap(messageBytes)); + + // Confirming that our handler was called and the correct message was passed in. + assertEquals("A", value1.get()); + assertEquals("B", value2.get()); + assertEquals("C", value3.get()); + assertTrue(value4.get()); + assertEquals(Double.valueOf(12), value5.get()); + } + + @Test + public void sendWithSixParamsTriggersOnHandlerWithMessagePack() { + AtomicReference value1 = new AtomicReference<>(); + AtomicReference value2 = new AtomicReference<>(); + AtomicReference value3 = new AtomicReference<>(); + AtomicReference value4 = new AtomicReference<>(); + AtomicReference value5 = new AtomicReference<>(); + AtomicReference value6 = new AtomicReference<>(); + + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.on("inc", (param1, param2, param3, param4, param5, param6) -> { + assertNull(value1.get()); + assertNull(value2.get()); + assertNull(value3.get()); + assertNull(value4.get()); + assertNull(value5.get()); + assertNull(value6.get()); + + value1.set(param1); + value2.set(param2); + value3.set(param3); + value4.set(param4); + value5.set(param5); + value6.set(param6); + }, stringType, stringType, stringType, booleanType, doubleType, stringType); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + byte[] messageBytes = new byte[] { 0x1C, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x69, 0x6E, 0x63, (byte) 0x96, (byte) 0xA1, 0x41, + (byte) 0xA1, 0x42, (byte) 0xA1, 0x43, (byte) 0xC3, (byte) 0xCB, 0x40, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xA1, 0x44, (byte) 0x90 }; + mockTransport.receiveMessage(ByteBuffer.wrap(messageBytes)); + + // Confirming that our handler was called and the correct message was passed in. + assertEquals("A", value1.get()); + assertEquals("B", value2.get()); + assertEquals("C", value3.get()); + assertTrue(value4.get()); + assertEquals(Double.valueOf(12), value5.get()); + assertEquals("D", value6.get()); + } + + @Test + public void sendWithSevenParamsTriggersOnHandlerWithMessagePack() { + AtomicReference value1 = new AtomicReference<>(); + AtomicReference value2 = new AtomicReference<>(); + AtomicReference value3 = new AtomicReference<>(); + AtomicReference value4 = new AtomicReference<>(); + AtomicReference value5 = new AtomicReference<>(); + AtomicReference value6 = new AtomicReference<>(); + AtomicReference value7 = new AtomicReference<>(); + + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.on("inc", (param1, param2, param3, param4, param5, param6, param7) -> { + assertNull(value1.get()); + assertNull(value2.get()); + assertNull(value3.get()); + assertNull(value4.get()); + assertNull(value5.get()); + assertNull(value6.get()); + assertNull(value7.get()); + + value1.set(param1); + value2.set(param2); + value3.set(param3); + value4.set(param4); + value5.set(param5); + value6.set(param6); + value7.set(param7); + }, stringType, stringType, stringType, booleanType, doubleType, stringType, stringType); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + byte[] messageBytes = new byte[] { 0x1E, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x69, 0x6E, 0x63, (byte) 0x97, (byte) 0xA1, 0x41, + (byte) 0xA1, 0x42, (byte) 0xA1, 0x43, (byte) 0xC3, (byte) 0xCB, 0x40, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xA1, 0x44, (byte) 0xA1, + 0x45, (byte) 0x90 }; + mockTransport.receiveMessage(ByteBuffer.wrap(messageBytes)); + + // Confirming that our handler was called and the correct message was passed in. + assertEquals("A", value1.get()); + assertEquals("B", value2.get()); + assertEquals("C", value3.get()); + assertTrue(value4.get()); + assertEquals(Double.valueOf(12), value5.get()); + assertEquals("D", value6.get()); + assertEquals("E", value7.get()); + } + + @Test + public void sendWithEightParamsTriggersOnHandlerWithMessagePack() { + AtomicReference value1 = new AtomicReference<>(); + AtomicReference value2 = new AtomicReference<>(); + AtomicReference value3 = new AtomicReference<>(); + AtomicReference value4 = new AtomicReference<>(); + AtomicReference value5 = new AtomicReference<>(); + AtomicReference value6 = new AtomicReference<>(); + AtomicReference value7 = new AtomicReference<>(); + AtomicReference value8 = new AtomicReference<>(); + + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.on("inc", (param1, param2, param3, param4, param5, param6, param7, param8) -> { + assertNull(value1.get()); + assertNull(value2.get()); + assertNull(value3.get()); + assertNull(value4.get()); + assertNull(value5.get()); + assertNull(value6.get()); + assertNull(value7.get()); + assertNull(value8.get()); + + value1.set(param1); + value2.set(param2); + value3.set(param3); + value4.set(param4); + value5.set(param5); + value6.set(param6); + value7.set(param7); + value8.set(param8); + }, stringType, stringType, stringType, booleanType, doubleType, stringType, stringType, stringType); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + byte[] messageBytes = new byte[] { 0x20, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x69, 0x6E, 0x63, (byte) 0x98, (byte) 0xA1, 0x41, + (byte) 0xA1, 0x42, (byte) 0xA1, 0x43, (byte) 0xC3, (byte) 0xCB, 0x40, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xA1, 0x44, (byte) 0xA1, + 0x45, (byte) 0xA1, 0x46, (byte) 0x90 }; + mockTransport.receiveMessage(ByteBuffer.wrap(messageBytes)); + // Confirming that our handler was called and the correct message was passed in. + assertEquals("A", value1.get()); + assertEquals("B", value2.get()); + assertEquals("C", value3.get()); + assertTrue(value4.get()); + assertEquals(Double.valueOf(12), value5.get()); + assertEquals("D", value6.get()); + assertEquals("E", value7.get()); + assertEquals("F", value8.get()); + } private class Custom { public int number; @@ -1588,6 +2539,33 @@ public void sendWithCustomObjectTriggersOnHandler() { assertEquals(true, custom.bools[0]); assertEquals(false, custom.bools[1]); } + + @Test + public void sendWithCustomObjectTriggersOnHandlerWithMessagePack() { + AtomicReference> value1 = new AtomicReference<>(); + + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + + hubConnection.>on("inc", (param1) -> { + assertNull(value1.get()); + + value1.set(param1); + }, (new TypeReference>() { }).getType()); + + hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); + byte[] messageBytes = new byte[] { 0x2F, (byte) 0x96, 0x01, (byte) 0x80, (byte) 0xC0, (byte) 0xA3, 0x69, 0x6E, 0x63, (byte) 0x91, (byte) 0x84, + (byte) 0xA9, 0x66, 0x69, 0x72, 0x73, 0x74, 0x4E, 0x61, 0x6D, 0x65, (byte) 0xA4, 0x4A, 0x6F, 0x68, 0x6E, (byte) 0xA8, 0x6C, 0x61, 0x73, 0x74, + 0x4E, 0x61, 0x6D, 0x65, (byte) 0xA3, 0x44, 0x6F, 0x65, (byte) 0xA3, 0x61, 0x67, 0x65, 0x1E, (byte) 0xA1, 0x74, 0x05, (byte) 0x90 }; + mockTransport.receiveMessage(ByteBuffer.wrap(messageBytes)); + + // Confirming that our handler was called and the correct message was passed in. + PersonPojo person = value1.get(); + assertEquals("John", person.getFirstName()); + assertEquals("Doe", person.getLastName()); + assertEquals(30, person.getAge()); + assertEquals((short) 5, (short) person.getT()); + } @Test public void receiveHandshakeResponseAndMessage() { diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index 4a161577ae21..c2d0f6e060d4 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -416,6 +416,7 @@ public void invocationBindingFailureWhenParsingIncorrectType() { assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); + // We get different exception messages on different platforms, so use a regex assertTrue(invocationBindingFailureMessage.getException().getMessage().matches("^.*Boolean.*cannot be cast to.*Integer.*")); } @@ -499,6 +500,7 @@ public void invocationBindingFailureReadsNextMessageAfterIncorrectArgument() { assertEquals(HubMessageType.INVOCATION_BINDING_FAILURE, messages.get(0).getMessageType()); InvocationBindingFailureMessage invocationBindingFailureMessage = (InvocationBindingFailureMessage) messages.get(0); + // We get different exception messages on different platforms, so use a regex assertTrue(invocationBindingFailureMessage.getException().getMessage().matches("^.*Boolean.*cannot be cast to.*Integer.*")); // Check the second message diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java index 9b7762b28f9f..4aaefb018a91 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java @@ -52,7 +52,7 @@ public Completable start(String url) { @Override public Completable send(ByteBuffer message) { - if (!(ignorePings && TestUtils.ByteBufferToString(message).equals("{\"type\":6}" + RECORD_SEPARATOR))) { + if (!(ignorePings && isPing(message))) { sentMessages.add(message); sendSubject.onSuccess(message); sendSubject = SingleSubject.create(); @@ -89,6 +89,10 @@ public void stopWithError(String errorMessage) { public void receiveMessage(String message) { this.onReceive(TestUtils.StringToByteBuffer(message)); } + + public void receiveMessage(ByteBuffer message) { + this.onReceive(message); + } public ByteBuffer[] getSentMessages() { return sentMessages.toArray(new ByteBuffer[sentMessages.size()]); @@ -109,4 +113,9 @@ public Completable getStartTask() { public Completable getStopTask() { return stopSubject; } + + private boolean isPing(ByteBuffer message) { + return (TestUtils.ByteBufferToString(message).equals("{\"type\":6}" + RECORD_SEPARATOR) || + (message.array()[0] == 2 && message.array()[1] == -111 && message.array()[2] == 6)); + } } From 02a83a569a0f1c8e83a8a7b6dff5cf3e618f4866 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 14 Aug 2020 09:19:45 -0700 Subject: [PATCH 55/64] Add TestUtils change that I didn't commit yesterday --- .../java/com/microsoft/signalr/TestUtils.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java index ef375cea3d57..cf186efa8a93 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java @@ -9,17 +9,26 @@ class TestUtils { static HubConnection createHubConnection(String url) { - return createHubConnection(url, new MockTransport(true), true, new TestHttpClient()); + return createHubConnection(url, new MockTransport(true), true, new TestHttpClient(), new JsonHubProtocol()); } static HubConnection createHubConnection(String url, Transport transport) { - return createHubConnection(url, transport, true, new TestHttpClient()); + return createHubConnection(url, transport, true, new TestHttpClient(), new JsonHubProtocol()); + } + + static HubConnection createHubConnection(String url, HubProtocol protocol) { + return createHubConnection(url, new MockTransport(true), true, new TestHttpClient(), protocol); + } + + static HubConnection createHubConnection(String url, Transport transport, HubProtocol protocol) { + return createHubConnection(url, transport, true, new TestHttpClient(), protocol); } - static HubConnection createHubConnection(String url, Transport transport, boolean skipNegotiate, HttpClient client) { + static HubConnection createHubConnection(String url, Transport transport, boolean skipNegotiate, HttpClient client, HubProtocol protocol) { HttpHubConnectionBuilder builder = HubConnectionBuilder.create(url) .withTransportImplementation(transport) .withHttpClient(client) + .withProtocol(protocol) .shouldSkipNegotiate(skipNegotiate); return builder.build(); From 057a3ae80e932814b3ee83bc92ed99f30a8223ce Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 14 Aug 2020 10:26:59 -0700 Subject: [PATCH 56/64] Respond to some feedback --- .../com/microsoft/signalr/CloseMessage.java | 2 +- .../com/microsoft/signalr/HubConnection.java | 28 ++++++++++++++++- .../com/microsoft/signalr/HubProtocol.java | 4 +-- .../microsoft/signalr/InvocationBinder.java | 4 +++ .../microsoft/signalr/JsonHubProtocol.java | 15 ++------- .../signalr/LongPollingTransport.java | 2 +- .../signalr/MessagePackHubProtocol.java | 23 ++++++++------ .../com/microsoft/signalr/TypeReference.java | 3 ++ .../microsoft/signalr/WebSocketTransport.java | 1 - .../microsoft/signalr/HubConnectionTest.java | 4 +-- .../signalr/MessagePackHubProtocolTest.java | 2 +- .../com/microsoft/signalr/PersonPojo.java | 31 ++++++++++--------- .../com/microsoft/signalr/TestBinder.java | 5 ++- 13 files changed, 78 insertions(+), 46 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java index 45ce7aa5f6d6..cfebb5a3aacd 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java @@ -15,7 +15,7 @@ public HubMessageType getMessageType() { public CloseMessage() { this(null, false); } - + public CloseMessage(String error) { this (error, false); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index d4692efaec08..383678b75b40 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -881,9 +881,10 @@ public Subscription on(String target, Action callback) { ActionBase action = args -> callback.invoke(); return registerHandler(target, action); } - + /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * Takes a Class. Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -899,6 +900,7 @@ public Subscription on(String target, Action1 callback, Class param /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * Takes a Class. Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -917,6 +919,7 @@ public Subscription on(String target, Action2 callback, Class. Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -938,6 +941,7 @@ public Subscription on(String target, Action3 callback, /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * Takes a Class. Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -961,6 +965,7 @@ public Subscription on(String target, Action4 c /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * Takes a Class. Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -987,6 +992,7 @@ public Subscription on(String target, Action5. Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1015,6 +1021,7 @@ public Subscription on(String target, Action6. Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1045,6 +1052,7 @@ public Subscription on(String target, Action7. Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1077,6 +1085,10 @@ public Subscription on(String target, Action8. Should be used for generic classes and Parameterized Collections, + * like List or Map. + * + * Should be invoked with the syntax * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1092,6 +1104,8 @@ public Subscription on(String target, Action1 callback, Type param1) { /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * Takes a Type, which is more descriptive than a Class. Should be used for generic classes and Parameterized Collections, + * like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1111,6 +1125,8 @@ public Subscription on(String target, Action2 callback, Type pa /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * Takes a Type, which is more descriptive than a Class. Should be used for generic classes and Parameterized Collections, + * like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1134,6 +1150,8 @@ public Subscription on(String target, Action3 callback, /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * Takes a Type, which is more descriptive than a Class. Should be used for generic classes and Parameterized Collections, + * like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1159,6 +1177,8 @@ public Subscription on(String target, Action4 c /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. + * Takes a Type, which is more descriptive than a Class. Should be used for generic classes and Parameterized Collections, + * like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1187,6 +1207,8 @@ public Subscription on(String target, Action5. Should be used for generic classes and Parameterized Collections, + * like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1217,6 +1239,8 @@ public Subscription on(String target, Action6. Should be used for generic classes and Parameterized Collections, + * like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1250,6 +1274,8 @@ public Subscription on(String target, Action7. Should be used for generic classes and Parameterized Collections, + * like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java index 909cd1601157..3096bf719c90 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java @@ -16,7 +16,7 @@ public interface HubProtocol { /** * Creates a new list of {@link HubMessage}s. - * @param message A string representation of one or more {@link HubMessage}s. + * @param message A ByteBuffer representation of one or more {@link HubMessage}s. * @return A list of {@link HubMessage}s. */ List parseMessages(ByteBuffer message, InvocationBinder binder); @@ -24,7 +24,7 @@ public interface HubProtocol { /** * Writes the specified {@link HubMessage} to a String. * @param message The message to write. - * @return A string representation of the message. + * @return A ByteBuffer representation of the message. */ ByteBuffer writeMessage(HubMessage message); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java index 4fd2e3ecb1e4..cdbe27556bf1 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java @@ -6,6 +6,10 @@ import java.lang.reflect.Type; import java.util.List; +/** + * An abstraction for passing around information about method signatures + */ + public interface InvocationBinder { Type getReturnType(String invocationId); List getParameterTypes(String methodName); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index f111ede1fac5..6a688a681ea4 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -150,22 +150,14 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde case COMPLETION: if (resultToken != null) { Type returnType = binder.getReturnType(invocationId); - Type completionReturnType = Object.class; - if (returnType != null) { - completionReturnType = returnType; - } - result = gson.fromJson(resultToken, completionReturnType); + result = gson.fromJson(resultToken, returnType != null ? returnType : Object.class); } hubMessages.add(new CompletionMessage(null, invocationId, result, error)); break; case STREAM_ITEM: if (resultToken != null) { Type returnType = binder.getReturnType(invocationId); - Type streamReturnType = Object.class; - if (returnType != null) { - streamReturnType = returnType; - } - result = gson.fromJson(resultToken, streamReturnType); + result = gson.fromJson(resultToken, returnType != null ? returnType : Object.class); } hubMessages.add(new StreamItem(null, invocationId, result)); break; @@ -221,8 +213,7 @@ private ArrayList bindArguments(JsonReader reader, List paramTypes ArrayList arguments = new ArrayList<>(); while (reader.peek() != JsonToken.END_ARRAY) { if (argCount < paramCount) { - Object o = gson.fromJson(reader, paramTypes.get(argCount)); - arguments.add(o); + arguments.add(gson.fromJson(reader, paramTypes.get(argCount))); } else { reader.skipValue(); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java index 316dc4a611df..4bcc2ede5e7f 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java @@ -102,7 +102,7 @@ private Completable poll(String url) { } else { if (response.getContent() != null) { logger.debug("Message received."); - onReceiveThread.submit(() ->this.onReceive(ByteBuffer.wrap(response.getContent().getBytes(StandardCharsets.UTF_8)))); + onReceiveThread.submit(() -> this.onReceive(ByteBuffer.wrap(response.getContent().getBytes(StandardCharsets.UTF_8)))); } else { logger.debug("Poll timed out, reissuing."); } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index d706cb7ff3c6..8f2de2014af8 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -57,19 +57,23 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde List hubMessages = new ArrayList<>(); - try { - while (payload.hasRemaining()) { - int length = Utils.readLengthHeader(payload); + while (payload.hasRemaining()) { + int length; + try { + length = Utils.readLengthHeader(payload); // Throw if remaining buffer is shorter than length header if (payload.remaining() < length) { throw new RuntimeException(String.format("MessagePack message was length %d but claimed to be length %d.", payload.remaining(), length)); } - // Instantiate MessageUnpacker - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payload); - + } catch (IOException ex) { + throw new RuntimeException("Error reading length header.", ex); + } + // Instantiate MessageUnpacker + try(MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(payload)) { + int itemCount = unpacker.unpackArrayHeader(); HubMessageType messageType = HubMessageType.values()[unpacker.unpackInt() - 1]; - + switch (messageType) { case INVOCATION: hubMessages.add(createInvocationMessage(unpacker, binder, itemCount, payload)); @@ -108,11 +112,10 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde } unpacker.close(); payload.position(payload.position() + readBytes); + } catch (MessagePackException | IOException ex) { + throw new RuntimeException("Error reading MessagePack data.", ex); } - } catch (MessagePackException | IOException ex) { - throw new RuntimeException("Error reading MessagePack data.", ex); } - return hubMessages; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java index 4bebe9542cb2..592def53ff07 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java @@ -1,3 +1,6 @@ +// 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. + package com.microsoft.signalr; import java.lang.reflect.Type; diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java index 113ced03f89b..42ef00231f11 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/WebSocketTransport.java @@ -10,7 +10,6 @@ import org.slf4j.LoggerFactory; import io.reactivex.Completable; -import okio.ByteString; class WebSocketTransport implements Transport { private WebSocketWrapper webSocketClient; diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index 1b769c02b91a..9cf2aed4d066 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -36,7 +36,7 @@ class HubConnectionTest { private static final Type integerType = (new TypeReference() { }).getType(); private static final Type stringType = (new TypeReference() { }).getType(); private static final MessagePackHubProtocol messagePackHubProtocol = new MessagePackHubProtocol(); - + @Test public void checkHubConnectionState() { HubConnection hubConnection = TestUtils.createHubConnection("http://example.com"); @@ -1538,7 +1538,7 @@ public void invokeCompletedByCompletionMessageWithResult() { assertNull(result.timeout(1000, TimeUnit.MILLISECONDS).blockingGet()); assertTrue(done.get()); } - + @Test public void invokeCompletedByCompletionMessageWithResultWithMessagePack() { MockTransport mockTransport = new MockTransport(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index c2d0f6e060d4..9169b89d6621 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -693,7 +693,7 @@ public void parseInvocationMessageWithArrayArg() { @SuppressWarnings("unchecked") List listArg = (ArrayList)listInvocationMessage.getArguments()[0]; - //assertEquals(4, arrayArg.length); + assertEquals(4, arrayArg.length); assertEquals(4, listArg.size()); for (int i = 0; i < arrayArg.length; i++) { assertEquals(i + 1, arrayArg[i]); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/PersonPojo.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/PersonPojo.java index 294f8c10366e..446977351292 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/PersonPojo.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/PersonPojo.java @@ -1,40 +1,43 @@ +// 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. + package com.microsoft.signalr; -public class PersonPojo implements Comparable> { +class PersonPojo implements Comparable> { public String firstName; public String lastName; public int age; public T t; public PersonPojo() { - super(); + super(); } public PersonPojo(String firstName, String lastName, int age, T t) { - this.firstName = firstName; - this.lastName = lastName; - this.age = age; - this.t = t; + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + this.t = t; } public String getFirstName() { - return this.firstName; + return this.firstName; } public String getLastName() { - return this.lastName; + return this.lastName; } public int getAge() { - return this.age; + return this.age; } public T getT() { - return t; + return t; } - @Override - public int compareTo(PersonPojo ep) { - return 0; - } + @Override + public int compareTo(PersonPojo ep) { + return 0; + } } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java index e830b0eb6fba..b27ebb4154e4 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestBinder.java @@ -1,3 +1,6 @@ +// 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. + package com.microsoft.signalr; import java.lang.reflect.Type; @@ -5,7 +8,7 @@ import java.util.Arrays; import java.util.List; -public class TestBinder implements InvocationBinder { +class TestBinder implements InvocationBinder { private Type[] paramTypes = null; private Type returnType = null; From 58a539dc21151a92e2e33d1ce04d24322632a94e Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 14 Aug 2020 10:49:44 -0700 Subject: [PATCH 57/64] Add a couple Json tests --- .../signalr/JsonHubProtocolTest.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java index 3a8a0406c575..e54c7db1e073 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java @@ -7,6 +7,8 @@ import java.lang.reflect.Type; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import org.junit.jupiter.api.Test; @@ -191,6 +193,96 @@ public void parseSingleMessageMutipleArgs() { assertEquals(42, messageResult); assertEquals(24, messageResult2); } + + @Test + public void parseSingleMessageNestedCollection() { + String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[[{\"one\":[\"a\",\"b\"],\"two\":[\"\uBEEF\",\"\uABCD\"]},{\"four\":[\"^\",\"*\"],\"three\":[\"5\",\"9\"]}]]}\u001E"; + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + TestBinder binder = new TestBinder(new Type[] { (new TypeReference>>>() { }).getType() }, null); + + List messages = jsonHubProtocol.parseMessages(message, binder); + + assertNotNull(messages); + assertEquals(1, messages.size()); + + //We know it's only one message + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); + + InvocationMessage invocationMessage = (InvocationMessage)messages.get(0); + + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + assertEquals(null, invocationMessage.getHeaders()); + assertEquals(null, invocationMessage.getStreamIds()); + + @SuppressWarnings("unchecked") + ArrayList>> result = (ArrayList>>)invocationMessage.getArguments()[0]; + assertEquals(2, result.size()); + + HashMap> firstMap = result.get(0); + HashMap> secondMap = result.get(1); + + assertEquals(2, firstMap.keySet().size()); + assertEquals(2, secondMap.keySet().size()); + + ArrayList firstList = firstMap.get("one"); + ArrayList secondList = firstMap.get("two"); + + ArrayList thirdList = secondMap.get("three"); + ArrayList fourthList = secondMap.get("four"); + + assertEquals(2, firstList.size()); + assertEquals(2, secondList.size()); + assertEquals(2, thirdList.size()); + assertEquals(2, fourthList.size()); + + assertEquals('a', (char) firstList.get(0)); + assertEquals('b', (char) firstList.get(1)); + + assertEquals('\ubeef', (char) secondList.get(0)); + assertEquals('\uabcd', (char) secondList.get(1)); + + assertEquals('5', (char) thirdList.get(0)); + assertEquals('9', (char) thirdList.get(1)); + + assertEquals('^', (char) fourthList.get(0)); + assertEquals('*', (char) fourthList.get(1)); + } + + @Test + public void parseSingleMessageCustomPojoArg() { + String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":30,\"t\":[5,8]}]}\u001E"; + ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + + TestBinder binder = new TestBinder(new Type[] { (new TypeReference>>() { }).getType() }, null); + + List messages = jsonHubProtocol.parseMessages(message, binder); + + //We know it's only one message + assertNotNull(messages); + assertEquals(1, messages.size()); + + assertEquals(HubMessageType.INVOCATION, messages.get(0).getMessageType()); + + //We can safely cast here because we know that it's an invocation message. + InvocationMessage invocationMessage = (InvocationMessage) messages.get(0); + + assertEquals("test", invocationMessage.getTarget()); + assertEquals(null, invocationMessage.getInvocationId()); + assertEquals(null, invocationMessage.getHeaders()); + assertEquals(null, invocationMessage.getStreamIds()); + + @SuppressWarnings("unchecked") + PersonPojo> result = (PersonPojo>)invocationMessage.getArguments()[0]; + assertEquals("John", result.getFirstName()); + assertEquals("Doe", result.getLastName()); + assertEquals(30, result.getAge()); + + ArrayList generic = result.getT(); + assertEquals(2, generic.size()); + assertEquals((short)5, (short)generic.get(0)); + assertEquals((short)8, (short)generic.get(1)); + } @Test public void parseMessageWithOutOfOrderProperties() { From ebd634449333584d2607e8c3a4242a8e0f223aff Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 14 Aug 2020 12:19:27 -0700 Subject: [PATCH 58/64] Apply more feedback --- .../microsoft/signalr/DefaultHttpClient.java | 2 +- .../com/microsoft/signalr/HttpClient.java | 8 +- .../com/microsoft/signalr/HubConnection.java | 2 +- .../signalr/LongPollingTransport.java | 2 +- .../microsoft/signalr/HubConnectionTest.java | 141 +++++++++--------- .../signalr/LongPollingTransportTest.java | 58 +++---- .../java/com/microsoft/signalr/TestUtils.java | 2 + 7 files changed, 108 insertions(+), 107 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/DefaultHttpClient.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/DefaultHttpClient.java index 2eb1cc203069..f8ddc42c6b8e 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/DefaultHttpClient.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/DefaultHttpClient.java @@ -152,7 +152,7 @@ public void onFailure(Call call, IOException e) { @Override public void onResponse(Call call, Response response) throws IOException { try (ResponseBody body = response.body()) { - HttpResponse httpResponse = new HttpResponse(response.code(), response.message(), body.string()); + HttpResponse httpResponse = new HttpResponse(response.code(), response.message(), ByteBuffer.wrap(body.bytes())); responseSubject.onSuccess(httpResponse); } } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpClient.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpClient.java index 597c3d04011e..9a5ecf352be5 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpClient.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpClient.java @@ -46,23 +46,23 @@ public Map getHeaders() { class HttpResponse { private final int statusCode; private final String statusText; - private final String content; + private final ByteBuffer content; public HttpResponse(int statusCode) { this(statusCode, ""); } public HttpResponse(int statusCode, String statusText) { - this(statusCode, statusText, ""); + this(statusCode, statusText, ByteBuffer.wrap(new byte[] {})); } - public HttpResponse(int statusCode, String statusText, String content) { + public HttpResponse(int statusCode, String statusText, ByteBuffer content) { this.statusCode = statusCode; this.statusText = statusText; this.content = content; } - public String getContent() { + public ByteBuffer getContent() { return content; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index 383678b75b40..56f93139d3ff 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -293,7 +293,7 @@ private Single handleNegotiate(String url) { throw new RuntimeException(String.format("Unexpected status code returned from negotiate: %d %s.", response.getStatusCode(), response.getStatusText())); } - JsonReader reader = new JsonReader(new StringReader(response.getContent())); + JsonReader reader = new JsonReader(new StringReader(new String(response.getContent().array(), StandardCharsets.UTF_8))); NegotiateResponse negotiateResponse = new NegotiateResponse(reader); if (negotiateResponse.getError() != null) { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java index 4bcc2ede5e7f..d17c20214820 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/LongPollingTransport.java @@ -102,7 +102,7 @@ private Completable poll(String url) { } else { if (response.getContent() != null) { logger.debug("Message received."); - onReceiveThread.submit(() -> this.onReceive(ByteBuffer.wrap(response.getContent().getBytes(StandardCharsets.UTF_8)))); + onReceiveThread.submit(() -> this.onReceive(response.getContent())); } else { logger.debug("Poll timed out, reissuing."); } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index 9cf2aed4d066..1853d29e738c 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -7,7 +7,6 @@ import java.lang.reflect.Type; import java.nio.ByteBuffer; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.concurrent.CancellationException; @@ -2704,7 +2703,7 @@ public void doesNotErrorWhenReceivingInvokeWithIncorrectArgumentLength() { @Test public void negotiateSentOnStart() { TestHttpClient client = new TestHttpClient() - .on("POST", (req) -> Single.just(new HttpResponse(404, "", ""))); + .on("POST", (req) -> Single.just(new HttpResponse(404, "", TestUtils.emptyByteBuffer))); HubConnection hubConnection = HubConnectionBuilder .create("http://example.com") @@ -2722,7 +2721,7 @@ public void negotiateSentOnStart() { @Test public void negotiateThatRedirectsForeverFailsAfter100Tries() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", - (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://example.com\"}"))); + (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://example.com\"}")))); HubConnection hubConnection = HubConnectionBuilder .create("http://example.com") @@ -2756,8 +2755,8 @@ public void noConnectionIdWhenSkippingNegotiate() { public void connectionIdIsAvailableAfterStart() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); MockTransport transport = new MockTransport(true); HubConnection hubConnection = HubConnectionBuilder @@ -2781,10 +2780,10 @@ public void connectionIdIsAvailableAfterStart() { public void connectionTokenAppearsInQSConnectionIdIsOnConnectionInstance() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + "\"negotiateVersion\": 1," + "\"connectionToken\":\"connection-token-value\"," + - "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); MockTransport transport = new MockTransport(true); HubConnection hubConnection = HubConnectionBuilder @@ -2808,9 +2807,9 @@ public void connectionTokenAppearsInQSConnectionIdIsOnConnectionInstance() { public void connectionTokenIsIgnoredIfNegotiateVersionIsNotPresentInNegotiateResponse() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + "\"connectionToken\":\"connection-token-value\"," + - "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); MockTransport transport = new MockTransport(true); HubConnection hubConnection = HubConnectionBuilder @@ -2834,9 +2833,9 @@ public void connectionTokenIsIgnoredIfNegotiateVersionIsNotPresentInNegotiateRes public void negotiateVersionIsNotAddedIfAlreadyPresent() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=42", (req) -> Single.just(new HttpResponse(200, "", - "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + "\"connectionToken\":\"connection-token-value\"," + - "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); MockTransport transport = new MockTransport(true); HubConnection hubConnection = HubConnectionBuilder @@ -2860,8 +2859,8 @@ public void negotiateVersionIsNotAddedIfAlreadyPresent() { public void afterSuccessfulNegotiateConnectsWithWebsocketsTransport() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); MockTransport transport = new MockTransport(true); HubConnection hubConnection = HubConnectionBuilder @@ -2881,8 +2880,8 @@ public void afterSuccessfulNegotiateConnectsWithWebsocketsTransport() { public void afterSuccessfulNegotiateConnectsWithLongPollingTransport() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); MockTransport transport = new MockTransport(true); HubConnection hubConnection = HubConnectionBuilder @@ -2905,15 +2904,15 @@ public void TransportAllUsesLongPollingWhenServerOnlySupportLongPolling() { TestHttpClient client = new TestHttpClient() .on("POST", (req) -> Single.just(new HttpResponse(200, "", - "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))) + TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))) .on("GET", (req) -> { if (requestCount.get() < 2) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", "{}" + RECORD_SEPARATOR)); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{}" + RECORD_SEPARATOR))); } assertTrue(close.blockingAwait(5, TimeUnit.SECONDS)); - return Single.just(new HttpResponse(204, "", "")); + return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); }); HubConnection hubConnection = HubConnectionBuilder @@ -2932,10 +2931,10 @@ public void TransportAllUsesLongPollingWhenServerOnlySupportLongPolling() { public void ClientThatSelectsWebsocketsThrowsWhenWebsocketsAreNotAvailable() { TestHttpClient client = new TestHttpClient().on("POST", (req) -> Single.just(new HttpResponse(200, "", - "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))) + TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))) .on("GET", (req) -> { - return Single.just(new HttpResponse(204, "", "")); + return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); }); HubConnection hubConnection = HubConnectionBuilder @@ -2954,8 +2953,8 @@ public void ClientThatSelectsWebsocketsThrowsWhenWebsocketsAreNotAvailable() { @Test public void ClientThatSelectsLongPollingThrowsWhenLongPollingIsNotAvailable() { TestHttpClient client = new TestHttpClient().on("POST", - (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); HubConnection hubConnection = HubConnectionBuilder .create("http://example.com") @@ -2974,8 +2973,8 @@ public void ClientThatSelectsLongPollingThrowsWhenLongPollingIsNotAvailable() { public void receivingServerSentEventsTransportFromNegotiateFails() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"ServerSentEvents\",\"transferFormats\":[\"Text\"]}]}"))); + TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"ServerSentEvents\",\"transferFormats\":[\"Text\"]}]}")))); MockTransport transport = new MockTransport(true); HubConnection hubConnection = HubConnectionBuilder @@ -2993,7 +2992,7 @@ public void receivingServerSentEventsTransportFromNegotiateFails() { @Test public void negotiateThatReturnsErrorThrowsFromStart() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", - (req) -> Single.just(new HttpResponse(200, "", "{\"error\":\"Test error.\"}"))); + (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"error\":\"Test error.\"}")))); MockTransport transport = new MockTransport(true); HubConnection hubConnection = HubConnectionBuilder @@ -3010,7 +3009,7 @@ public void negotiateThatReturnsErrorThrowsFromStart() { @Test public void DetectWhenTryingToConnectToClassicSignalRServer() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", - (req) -> Single.just(new HttpResponse(200, "", "{\"Url\":\"/signalr\"," + + (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"Url\":\"/signalr\"," + "\"ConnectionToken\":\"X97dw3uxW4NPPggQsYVcNcyQcuz4w2\"," + "\"ConnectionId\":\"05265228-1e2c-46c5-82a1-6a5bcc3f0143\"," + "\"KeepAliveTimeout\":10.0," + @@ -3018,7 +3017,7 @@ public void DetectWhenTryingToConnectToClassicSignalRServer() { "\"TryWebSockets\":true," + "\"ProtocolVersion\":\"1.5\"," + "\"TransportConnectTimeout\":30.0," + - "\"LongPollDelay\":0.0}"))); + "\"LongPollDelay\":0.0}")))); MockTransport transport = new MockTransport(true); HubConnection hubConnection = HubConnectionBuilder @@ -3036,10 +3035,10 @@ public void DetectWhenTryingToConnectToClassicSignalRServer() { @Test public void negotiateRedirectIsFollowed() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", - (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\"}"))) + (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\"}")))) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", - (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); MockTransport transport = new MockTransport(true); HubConnection hubConnection = HubConnectionBuilder @@ -3061,12 +3060,12 @@ public void accessTokenProviderReferenceIsKeptAfterNegotiateRedirect() { TestHttpClient client = new TestHttpClient() .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { beforeRedirectToken.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))); }) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); MockTransport transport = new MockTransport(true); @@ -3102,8 +3101,8 @@ public void accessTokenProviderIsUsedForNegotiate() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); MockTransport transport = new MockTransport(true); @@ -3127,8 +3126,8 @@ public void AccessTokenProviderCanProvideDifferentValues() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); AtomicInteger i = new AtomicInteger(0); @@ -3156,13 +3155,13 @@ public void accessTokenProviderIsOverriddenFromRedirectNegotiate() { AtomicReference token = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - "{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))) + TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}")))) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + "\"connectionToken\":\"connection-token-value\"," + "\"negotiateVersion\":1," - + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); MockTransport transport = new MockTransport(true); @@ -3188,12 +3187,12 @@ public void authorizationHeaderFromNegotiateGetsClearedAfterStopping() { TestHttpClient client = new TestHttpClient() .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { beforeRedirectToken.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))); }) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," - + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); MockTransport transport = new MockTransport(true); @@ -3231,16 +3230,16 @@ public void authorizationHeaderFromNegotiateGetsSetToNewValue() { if (redirectCount.get() == 0) { redirectCount.incrementAndGet(); redirectToken.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"firstRedirectToken\"}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"firstRedirectToken\"}"))); } else { redirectToken.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"secondRedirectToken\"}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"secondRedirectToken\"}"))); } }) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); MockTransport transport = new MockTransport(true); @@ -3305,8 +3304,8 @@ public void userAgentHeaderIsSet() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("User-Agent")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); MockTransport transport = new MockTransport(); @@ -3329,8 +3328,8 @@ public void userAgentHeaderCanBeOverwritten() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("User-Agent")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); MockTransport transport = new MockTransport(); @@ -3353,8 +3352,8 @@ public void userAgentCanBeCleared() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("User-Agent")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); MockTransport transport = new MockTransport(); @@ -3376,8 +3375,8 @@ public void headersAreSetAndSentThroughBuilder() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("ExampleHeader")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3401,8 +3400,8 @@ public void headersAreNotClearedWhenConnectionIsRestarted() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); MockTransport transport = new MockTransport(); @@ -3431,13 +3430,13 @@ public void userSetAuthHeaderIsNotClearedAfterRedirect() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { beforeRedirectHeader.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\",\"accessToken\":\"redirectToken\"}\"}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"redirectToken\"}\"}"))); }) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { afterRedirectHeader.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); MockTransport transport = new MockTransport(); @@ -3474,8 +3473,8 @@ public void sameHeaderSetTwiceGetsOverwritten() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("ExampleHeader")); - return Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3516,9 +3515,9 @@ public void hubConnectionCanBeStartedAfterBeingStopped() { public void hubConnectionCanBeStartedAfterBeingStoppedAndRedirected() { MockTransport mockTransport = new MockTransport(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\"}"))) - .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\"}")))) + .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", + TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); HubConnection hubConnection = HubConnectionBuilder .create("http://example.com") @@ -3540,7 +3539,7 @@ public void hubConnectionCanBeStartedAfterBeingStoppedAndRedirected() { public void non200FromNegotiateThrowsError() { TestHttpClient client = new TestHttpClient() .on("POST", "http://example.com/negotiate?negotiateVersion=1", - (req) -> Single.just(new HttpResponse(500, "Internal server error", ""))); + (req) -> Single.just(new HttpResponse(500, "Internal server error", TestUtils.emptyByteBuffer))); MockTransport transport = new MockTransport(); HubConnection hubConnection = HubConnectionBuilder @@ -3558,9 +3557,9 @@ public void non200FromNegotiateThrowsError() { public void hubConnectionCloseCallsStop() throws Exception { MockTransport mockTransport = new MockTransport(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"url\":\"http://testexample.com/\"}"))) - .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" - + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\"}")))) + .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", + TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); CompletableSubject close = CompletableSubject.create(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java index 54c2936f16d0..a84998d6d8c2 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java @@ -23,7 +23,7 @@ public class LongPollingTransportTest { @Test public void LongPollingFailsToConnectWith404Response() { TestHttpClient client = new TestHttpClient() - .on("GET", (req) -> Single.just(new HttpResponse(404, "", ""))); + .on("GET", (req) -> Single.just(new HttpResponse(404, "", TestUtils.emptyByteBuffer))); Map headers = new HashMap<>(); LongPollingTransport transport = new LongPollingTransport(headers, client, Single.just("")); @@ -36,7 +36,7 @@ public void LongPollingFailsToConnectWith404Response() { @Test public void LongPollingTransportCantSendBeforeStart() { TestHttpClient client = new TestHttpClient() - .on("GET", (req) -> Single.just(new HttpResponse(404, "", ""))); + .on("GET", (req) -> Single.just(new HttpResponse(404, "", TestUtils.emptyByteBuffer))); Map headers = new HashMap<>(); LongPollingTransport transport = new LongPollingTransport(headers, client, Single.just("")); @@ -55,9 +55,9 @@ public void StatusCode204StopsLongPollingTriggersOnClosed() { .on("GET", (req) -> { if (firstPoll.get()) { firstPoll.set(false); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); } - return Single.just(new HttpResponse(204, "", "")); + return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); }); Map headers = new HashMap<>(); @@ -83,9 +83,9 @@ public void LongPollingFailsWhenReceivingUnexpectedErrorCode() { .on("GET", (req) -> { if (firstPoll.get()) { firstPoll.set(false); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); } - return Single.just(new HttpResponse(999, "", "")); + return Single.just(new HttpResponse(999, "", TestUtils.emptyByteBuffer)); }); Map headers = new HashMap<>(); @@ -106,7 +106,7 @@ public void LongPollingFailsWhenReceivingUnexpectedErrorCode() { @Test public void CanSetAndTriggerOnReceive() { TestHttpClient client = new TestHttpClient() - .on("GET", (req) -> Single.just(new HttpResponse(200, "", ""))); + .on("GET", (req) -> Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer))); Map headers = new HashMap<>(); LongPollingTransport transport = new LongPollingTransport(headers, client, Single.just("")); @@ -132,13 +132,13 @@ public void LongPollingTransportOnReceiveGetsCalled() { .on("GET", (req) -> { if (requestCount.get() == 0) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); } else if (requestCount.get() == 1) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", "TEST")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("TEST"))); } - return Single.just(new HttpResponse(204, "", "")); + return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); }); Map headers = new HashMap<>(); @@ -168,19 +168,19 @@ public void LongPollingTransportOnReceiveGetsCalledMultipleTimes() { .on("GET", (req) -> { if (requestCount.get() == 0) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); } else if (requestCount.get() == 1) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", "FIRST")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("FIRST"))); } else if (requestCount.get() == 2) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", "SECOND")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("SECOND"))); } else if (requestCount.get() == 3) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", "THIRD")); + return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("THIRD"))); } - return Single.just(new HttpResponse(204, "", "")); + return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); }); Map headers = new HashMap<>(); @@ -214,14 +214,14 @@ public void LongPollingTransportSendsHeaders() { .on("GET", (req) -> { if (requestCount.get() == 0) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); } assertTrue(close.blockingAwait(1, TimeUnit.SECONDS)); - return Single.just(new HttpResponse(204, "", "")); + return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); }).on("POST", (req) -> { assertFalse(req.getHeaders().isEmpty()); headerValue.set(req.getHeaders().get("KEY")); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); }); Map headers = new HashMap<>(); @@ -245,15 +245,15 @@ public void LongPollingTransportSetsAuthorizationHeader() { .on("GET", (req) -> { if (requestCount.get() == 0) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); } assertTrue(close.blockingAwait(1, TimeUnit.SECONDS)); - return Single.just(new HttpResponse(204, "", "")); + return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); }) .on("POST", (req) -> { assertFalse(req.getHeaders().isEmpty()); headerValue.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); }); Map headers = new HashMap<>(); @@ -278,17 +278,17 @@ public void LongPollingTransportRunsAccessTokenProviderEveryRequest() { .on("GET", (req) -> { if (requestCount.get() == 0) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); } assertEquals("Bearer TOKEN1", req.getHeaders().get("Authorization")); secondGet.onComplete(); assertTrue(close.blockingAwait(1, TimeUnit.SECONDS)); - return Single.just(new HttpResponse(204, "", "")); + return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); }) .on("POST", (req) -> { assertFalse(req.getHeaders().isEmpty()); headerValue.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); }); AtomicInteger i = new AtomicInteger(0); @@ -313,9 +313,9 @@ public void After204StopDoesNotTriggerOnClose() { .on("GET", (req) -> { if (firstPoll.get()) { firstPoll.set(false); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); } - return Single.just(new HttpResponse(204, "", "")); + return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); }); Map headers = new HashMap<>(); @@ -347,16 +347,16 @@ public void StoppingTransportRunsCloseHandlersOnce() { .on("GET", (req) -> { if (firstPoll.get()) { firstPoll.set(false); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); } else { assertTrue(block.blockingAwait(1, TimeUnit.SECONDS)); - return Single.just(new HttpResponse(204, "", "")); + return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); } }) .on("DELETE", (req) ->{ //Unblock the last poll when we sent the DELETE request. block.onComplete(); - return Single.just(new HttpResponse(200, "", "")); + return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); }); Map headers = new HashMap<>(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java index cf186efa8a93..c38e7a89c809 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java @@ -7,6 +7,8 @@ import java.nio.charset.StandardCharsets; class TestUtils { + + static ByteBuffer emptyByteBuffer = StringToByteBuffer(""); static HubConnection createHubConnection(String url) { return createHubConnection(url, new MockTransport(true), true, new TestHttpClient(), new JsonHubProtocol()); From 3476f8950a07fff52a37665d822789c42c8b4151 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 14 Aug 2020 12:42:32 -0700 Subject: [PATCH 59/64] Move readonly fix to msgpack --- .../src/main/java/com/microsoft/signalr/HubConnection.java | 7 ------- .../main/java/com/microsoft/signalr/JsonHubProtocol.java | 4 +++- .../java/com/microsoft/signalr/MessagePackHubProtocol.java | 7 +++++++ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index 56f93139d3ff..62612fb04331 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -206,13 +206,6 @@ Transport getTransport() { return; } } - - // MessagePack library can't handle read-only ByteBuffer - copy into an array-backed ByteBuffer if this is the case - if (payload.isReadOnly()) { - byte[] payloadBytes = new byte[payload.remaining()]; - payload.get(payloadBytes, 0, payloadBytes.length); - payload = ByteBuffer.wrap(payloadBytes); - } List messages = protocol.parseMessages(payload, connectionState); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index 6a688a681ea4..a29ab9375b5b 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -41,8 +41,10 @@ public TransferFormat getTransferFormat() { @Override public List parseMessages(ByteBuffer payload, InvocationBinder binder) { + byte[] payloadBytes = new byte[payload.remaining()]; + payload.get(payloadBytes, 0, payloadBytes.length); // The position of the ByteBuffer may have been incremented - make sure we only grab the remaining bytes - String payloadStr = new String(payload.array(), payload.position(), payload.remaining(), StandardCharsets.UTF_8); + String payloadStr = new String(payloadBytes, StandardCharsets.UTF_8); if (payloadStr.length() == 0) { return null; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index 8f2de2014af8..bacc28080c1f 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -54,6 +54,13 @@ public List parseMessages(ByteBuffer payload, InvocationBinder binde if (payload.remaining() == 0) { return null; } + + // MessagePack library can't handle read-only ByteBuffer - copy into an array-backed ByteBuffer if this is the case + if (payload.isReadOnly()) { + byte[] payloadBytes = new byte[payload.remaining()]; + payload.get(payloadBytes, 0, payloadBytes.length); + payload = ByteBuffer.wrap(payloadBytes); + } List hubMessages = new ArrayList<>(); From 5dc8c5cda9b92533e4a6522dab4abb981a88c21b Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 14 Aug 2020 12:44:48 -0700 Subject: [PATCH 60/64] Minor optimization --- .../com/microsoft/signalr/JsonHubProtocol.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index a29ab9375b5b..3db95c0fcdb9 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -41,10 +41,17 @@ public TransferFormat getTransferFormat() { @Override public List parseMessages(ByteBuffer payload, InvocationBinder binder) { - byte[] payloadBytes = new byte[payload.remaining()]; - payload.get(payloadBytes, 0, payloadBytes.length); - // The position of the ByteBuffer may have been incremented - make sure we only grab the remaining bytes - String payloadStr = new String(payloadBytes, StandardCharsets.UTF_8); + String payloadStr; + // If the payload is readOnly, we have to copy the bytes from its array to make the payload string + if (payload.isReadOnly()) { + byte[] payloadBytes = new byte[payload.remaining()]; + payload.get(payloadBytes, 0, payloadBytes.length); + payloadStr = new String(payloadBytes, StandardCharsets.UTF_8); + // Otherwise we can allocate directly from its array + } else { + // The position of the ByteBuffer may have been incremented - make sure we only grab the remaining bytes + payloadStr = new String(payload.array(), payload.position(), payload.remaining(), StandardCharsets.UTF_8); + } if (payloadStr.length() == 0) { return null; } From 9746105a877aa243236e7c7dac832449097f6942 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Fri, 14 Aug 2020 14:29:08 -0700 Subject: [PATCH 61/64] Fixup some javadocs --- .../com/microsoft/signalr/HubConnection.java | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index 62612fb04331..bcdd1a0d6b43 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -877,7 +877,7 @@ public Subscription on(String target, Action callback) { /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. - * Takes a Class. Should be used for primitives and non-generic classes. + * Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -893,7 +893,7 @@ public Subscription on(String target, Action1 callback, Class param /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. - * Takes a Class. Should be used for primitives and non-generic classes. + * Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -912,7 +912,7 @@ public Subscription on(String target, Action2 callback, Class. Should be used for primitives and non-generic classes. + * Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -934,7 +934,7 @@ public Subscription on(String target, Action3 callback, /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. - * Takes a Class. Should be used for primitives and non-generic classes. + * Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -958,7 +958,7 @@ public Subscription on(String target, Action4 c /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. - * Takes a Class. Should be used for primitives and non-generic classes. + * Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -985,7 +985,7 @@ public Subscription on(String target, Action5. Should be used for primitives and non-generic classes. + * Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1014,7 +1014,7 @@ public Subscription on(String target, Action6. Should be used for primitives and non-generic classes. + * Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1045,7 +1045,7 @@ public Subscription on(String target, Action7. Should be used for primitives and non-generic classes. + * Should be used for primitives and non-generic classes. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1078,8 +1078,7 @@ public Subscription on(String target, Action8. Should be used for generic classes and Parameterized Collections, - * like List or Map. + * Should be used for generic classes and Parameterized Collections, like List or Map. * * Should be invoked with the syntax * @@ -1097,8 +1096,7 @@ public Subscription on(String target, Action1 callback, Type param1) { /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. - * Takes a Type, which is more descriptive than a Class. Should be used for generic classes and Parameterized Collections, - * like List or Map. + * Should be used for generic classes and Parameterized Collections, like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1118,8 +1116,7 @@ public Subscription on(String target, Action2 callback, Type pa /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. - * Takes a Type, which is more descriptive than a Class. Should be used for generic classes and Parameterized Collections, - * like List or Map. + * Should be used for generic classes and Parameterized Collections, like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1143,8 +1140,7 @@ public Subscription on(String target, Action3 callback, /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. - * Takes a Type, which is more descriptive than a Class. Should be used for generic classes and Parameterized Collections, - * like List or Map. + * Should be used for generic classes and Parameterized Collections, like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1170,8 +1166,7 @@ public Subscription on(String target, Action4 c /** * Registers a handler that will be invoked when the hub method with the specified method name is invoked. - * Takes a Type, which is more descriptive than a Class. Should be used for generic classes and Parameterized Collections, - * like List or Map. + * Should be used for generic classes and Parameterized Collections, like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1200,8 +1195,7 @@ public Subscription on(String target, Action5. Should be used for generic classes and Parameterized Collections, - * like List or Map. + * Should be used for generic classes and Parameterized Collections, like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1232,8 +1226,7 @@ public Subscription on(String target, Action6. Should be used for generic classes and Parameterized Collections, - * like List or Map. + * Should be used for generic classes and Parameterized Collections, like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. @@ -1267,8 +1260,7 @@ public Subscription on(String target, Action7. Should be used for generic classes and Parameterized Collections, - * like List or Map. + * Should be used for generic classes and Parameterized Collections, like List or Map. * * @param target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. From 5a2357e18a45621dbfb61a8f8d67c2099eff87d3 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Tue, 18 Aug 2020 10:12:49 -0700 Subject: [PATCH 62/64] Respond to feedback --- .../com/microsoft/signalr/CloseMessage.java | 4 +- .../com/microsoft/signalr/HubConnection.java | 3 - .../microsoft/signalr/InvocationBinder.java | 3 +- .../microsoft/signalr/JsonHubProtocol.java | 10 +- .../signalr/MessagePackHubProtocol.java | 2 +- .../com/microsoft/signalr/ByteString.java | 53 ++++ .../signalr/HandshakeProtocolTest.java | 2 +- .../microsoft/signalr/HubConnectionTest.java | 251 +++++++++--------- .../signalr/JsonHubProtocolTest.java | 38 +-- .../signalr/LongPollingTransportTest.java | 24 +- .../signalr/MessagePackHubProtocolTest.java | 5 +- .../com/microsoft/signalr/MockTransport.java | 6 +- .../java/com/microsoft/signalr/TestUtils.java | 8 +- 13 files changed, 229 insertions(+), 180 deletions(-) create mode 100644 src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/ByteString.java diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java index cfebb5a3aacd..1d896950579c 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/CloseMessage.java @@ -17,11 +17,11 @@ public CloseMessage() { } public CloseMessage(String error) { - this (error, false); + this(error, false); } public CloseMessage(boolean allowReconnect) { - this (null, allowReconnect); + this(null, allowReconnect); } public CloseMessage(String error, boolean allowReconnect) { diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java index bcdd1a0d6b43..54f92c906454 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubConnection.java @@ -696,7 +696,6 @@ public Completable invoke(String method, Object... args) { public Single invoke(Class returnType, String method, Object... args) { return this.invoke(returnType, returnType, method, args); } - /** * Invokes a hub method on the server using the specified method name and arguments. @@ -1079,8 +1078,6 @@ public Subscription on(String target, Action8 getParameterTypes(String methodName); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index 3db95c0fcdb9..6899f097b3d1 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -41,16 +41,16 @@ public TransferFormat getTransferFormat() { @Override public List parseMessages(ByteBuffer payload, InvocationBinder binder) { - String payloadStr; - // If the payload is readOnly, we have to copy the bytes from its array to make the payload string + String payloadStr; + // If the payload is readOnly, we have to copy the bytes from its array to make the payload string if (payload.isReadOnly()) { - byte[] payloadBytes = new byte[payload.remaining()]; + byte[] payloadBytes = new byte[payload.remaining()]; payload.get(payloadBytes, 0, payloadBytes.length); payloadStr = new String(payloadBytes, StandardCharsets.UTF_8); // Otherwise we can allocate directly from its array } else { - // The position of the ByteBuffer may have been incremented - make sure we only grab the remaining bytes - payloadStr = new String(payload.array(), payload.position(), payload.remaining(), StandardCharsets.UTF_8); + // The position of the ByteBuffer may have been incremented - make sure we only grab the remaining bytes + payloadStr = new String(payload.array(), payload.position(), payload.remaining(), StandardCharsets.UTF_8); } if (payloadStr.length() == 0) { return null; diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index bacc28080c1f..aeff7e7c58c9 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -616,7 +616,7 @@ private Object readValue(MessageUnpacker unpacker, Type itemType, ByteBuffer pay //Convert this to an object? item = extensionValue; */ - throw new RuntimeException("Extension types are not supported yet"); + throw new RuntimeException("Extension types are not supported"); default: return null; } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/ByteString.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/ByteString.java new file mode 100644 index 000000000000..2546d9245487 --- /dev/null +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/ByteString.java @@ -0,0 +1,53 @@ +// 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. + +package com.microsoft.signalr; + +import java.nio.ByteBuffer; + +class ByteString{ + + private byte[] src; + + private ByteString(byte[] src) { + this.src = src; + } + + public static ByteString of(byte[] src) { + return new ByteString(src); + } + + public static ByteString of(ByteBuffer src) { + return new ByteString(src.array()); + } + + public byte[] array() { + return src; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ByteString)) { + return false; + } + byte[] otherSrc = ((ByteString) obj).array(); + if (otherSrc.length != src.length) { + return false; + } + for (int i = 0; i < src.length; i++) { + if (src[i] != otherSrc[i]) { + return false; + } + } + return true; + } + + @Override + public String toString() { + String str = ""; + for (byte b: src) { + str += String.format("%02X", b); + } + return str; + } +} diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HandshakeProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HandshakeProtocolTest.java index 352a5d5d1725..e98b55ba0112 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HandshakeProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HandshakeProtocolTest.java @@ -14,7 +14,7 @@ public void VerifyCreateHandshakerequestMessage() { HandshakeRequestMessage handshakeRequest = new HandshakeRequestMessage("json", 1); ByteBuffer result = HandshakeProtocol.createHandshakeRequestMessage(handshakeRequest); String expectedResult = "{\"protocol\":\"json\",\"version\":1}\u001E"; - assertEquals(expectedResult, TestUtils.ByteBufferToString(result)); + assertEquals(expectedResult, TestUtils.byteBufferToString(result)); } @Test diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index 1853d29e738c..ababeb940ae3 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -26,7 +26,6 @@ import io.reactivex.subjects.PublishSubject; import io.reactivex.subjects.ReplaySubject; import io.reactivex.subjects.SingleSubject; -import okio.ByteString; class HubConnectionTest { private static final String RECORD_SEPARATOR = "\u001e"; @@ -229,7 +228,7 @@ public void registeringMultipleHandlersAndBothGetTriggered() { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); + String message = TestUtils.byteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -252,7 +251,7 @@ public void removeHandlerByName() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); + String message = TestUtils.byteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -279,7 +278,7 @@ public void addAndRemoveHandlerImmediately() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); + String message = TestUtils.byteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -304,7 +303,7 @@ public void removingMultipleHandlersWithOneCallToRemove() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); + String message = TestUtils.byteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -333,7 +332,7 @@ public void removeHandlerWithUnsubscribe() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); + String message = TestUtils.byteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -365,7 +364,7 @@ public void unsubscribeTwice() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); + String message = TestUtils.byteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -400,7 +399,7 @@ public void removeSingleHandlerWithUnsubscribe() { assertEquals(Double.valueOf(0), value.get()); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0]); + String message = TestUtils.byteBufferToString(mockTransport.getSentMessages()[0]); String expectedHanshakeRequest = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; assertEquals(expectedHanshakeRequest, message); @@ -470,11 +469,11 @@ public void checkStreamUploadSingleItemThroughSend() { stream.onNext("FirstItem"); ByteBuffer[] messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[2])); stream.onComplete(); messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[3])); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.byteBufferToString(messages[3])); } @Test @@ -493,15 +492,15 @@ public void checkStreamUploadMultipleStreamsThroughSend() { secondStream.onNext("Second Stream 1"); ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First Stream 1\"}\u001E", TestUtils.ByteBufferToString(messages[2])); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Second Stream 1\"}\u001E", TestUtils.ByteBufferToString(messages[3])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First Stream 1\"}\u001E", TestUtils.byteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Second Stream 1\"}\u001E", TestUtils.byteBufferToString(messages[3])); firstStream.onComplete(); secondStream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(6, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[4])); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[5])); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.byteBufferToString(messages[4])); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.byteBufferToString(messages[5])); } @Test @@ -516,12 +515,12 @@ public void checkStreamUploadThroughSendWithArgs() { stream.onNext("FirstItem"); ByteBuffer[] messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); + assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", TestUtils.byteBufferToString(messages[1])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[2])); stream.onComplete(); messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[3])); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.byteBufferToString(messages[3])); } @Test @@ -536,12 +535,12 @@ public void streamMapIsClearedOnClose() { stream.onNext("FirstItem"); ByteBuffer[] messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); + assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", TestUtils.byteBufferToString(messages[1])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[2])); stream.onComplete(); messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[3])); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.byteBufferToString(messages[3])); hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -565,10 +564,10 @@ public void streamMapEntriesRemovedOnStreamClose() { stream.onNext("FirstItem"); secondStream.onNext("SecondItem"); ByteBuffer[] messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); - assertEquals("{\"type\":1,\"target\":\"SecondUploadStream\",\"arguments\":[13],\"streamIds\":[\"2\"]}\u001E", TestUtils.ByteBufferToString(messages[2])); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[3])); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"SecondItem\"}\u001E", TestUtils.ByteBufferToString(messages[4])); + assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[12],\"streamIds\":[\"1\"]}\u001E", TestUtils.byteBufferToString(messages[1])); + assertEquals("{\"type\":1,\"target\":\"SecondUploadStream\",\"arguments\":[13],\"streamIds\":[\"2\"]}\u001E", TestUtils.byteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[3])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"SecondItem\"}\u001E", TestUtils.byteBufferToString(messages[4])); assertEquals(2, hubConnection.getStreamMap().size()); @@ -585,8 +584,8 @@ public void streamMapEntriesRemovedOnStreamClose() { assertTrue(hubConnection.getStreamMap().isEmpty()); messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[5])); - assertEquals("{\"type\":3,\"invocationId\":\"2\",\"error\":\"java.lang.Exception: Exception\"}\u001E", TestUtils.ByteBufferToString(messages[6])); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.byteBufferToString(messages[5])); + assertEquals("{\"type\":3,\"invocationId\":\"2\",\"error\":\"java.lang.Exception: Exception\"}\u001E", TestUtils.byteBufferToString(messages[6])); hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); assertTrue(hubConnection.getStreamMap().isEmpty()); @@ -605,15 +604,15 @@ public void useSameSubjectMultipleTimes() { stream.onNext("FirstItem"); ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"1\",\"2\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[3])); + assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"1\",\"2\"]}\u001E", TestUtils.byteBufferToString(messages[1])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[3])); stream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(6, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[4])); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[5])); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.byteBufferToString(messages[4])); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.byteBufferToString(messages[5])); } @Test @@ -629,16 +628,16 @@ public void checkStreamUploadSingleItemThroughInvoke() { stream.onNext("FirstItem"); ByteBuffer[] messages = mockTransport.getSentMessages(); for (ByteBuffer bb: messages) { - System.out.println(TestUtils.ByteBufferToString(bb)); + System.out.println(TestUtils.byteBufferToString(bb)); } assertEquals(3, messages.length); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"2\"]}\u001E", - TestUtils.ByteBufferToString(messages[1])); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); + TestUtils.byteBufferToString(messages[1])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[2])); stream.onComplete(); messages = mockTransport.getSentMessages(); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[3])); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.byteBufferToString(messages[3])); } @Test @@ -654,7 +653,7 @@ public void checkStreamUploadSingleItemThroughInvokeWithMessagePack() { stream.onNext("FirstItem"); ByteBuffer[] messages = mockTransport.getSentMessages(); for (ByteBuffer bb: messages) { - System.out.println(TestUtils.ByteBufferToString(bb)); + System.out.println(TestUtils.byteBufferToString(bb)); } assertEquals(3, messages.length); @@ -688,13 +687,13 @@ public void checkStreamUploadSingleItemThroughStream() { ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(3, messages.length); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"2\"]}\u001E", - TestUtils.ByteBufferToString(messages[1])); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); + TestUtils.byteBufferToString(messages[1])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[2])); stream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[3])); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.byteBufferToString(messages[3])); } @Test @@ -742,26 +741,26 @@ public void useSameSubjectInMutlipleStreamsFromDifferentMethods() { ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"1\"]}\u001E", TestUtils.ByteBufferToString(messages[1])); + assertEquals("{\"type\":1,\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"1\"]}\u001E", TestUtils.byteBufferToString(messages[1])); assertEquals("{\"type\":1,\"invocationId\":\"2\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"3\"]}\u001E", - TestUtils.ByteBufferToString(messages[2])); + TestUtils.byteBufferToString(messages[2])); assertEquals("{\"type\":4,\"invocationId\":\"4\",\"target\":\"UploadStream\",\"arguments\":[],\"streamIds\":[\"5\"]}\u001E", - TestUtils.ByteBufferToString(messages[3])); + TestUtils.byteBufferToString(messages[3])); stream.onNext("FirstItem"); messages = mockTransport.getSentMessages(); assertEquals(7, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[4])); - assertEquals("{\"type\":2,\"invocationId\":\"3\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[5])); - assertEquals("{\"type\":2,\"invocationId\":\"5\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[6])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[4])); + assertEquals("{\"type\":2,\"invocationId\":\"3\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[5])); + assertEquals("{\"type\":2,\"invocationId\":\"5\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[6])); stream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(10, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[7])); - assertEquals("{\"type\":3,\"invocationId\":\"3\"}\u001E", TestUtils.ByteBufferToString(messages[8])); - assertEquals("{\"type\":3,\"invocationId\":\"5\"}\u001E", TestUtils.ByteBufferToString(messages[9])); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.byteBufferToString(messages[7])); + assertEquals("{\"type\":3,\"invocationId\":\"3\"}\u001E", TestUtils.byteBufferToString(messages[8])); + assertEquals("{\"type\":3,\"invocationId\":\"5\"}\u001E", TestUtils.byteBufferToString(messages[9])); } @Test @@ -836,9 +835,9 @@ public void streamUploadCallOnError() { stream.onError(new RuntimeException("onError called")); ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[2])); assertEquals("{\"type\":3,\"invocationId\":\"1\",\"error\":\"java.lang.RuntimeException: onError called\"}\u001E", - TestUtils.ByteBufferToString(messages[3])); + TestUtils.byteBufferToString(messages[3])); // onComplete doesn't send a completion message after onError. stream.onComplete(); @@ -862,14 +861,14 @@ public void checkStreamUploadMultipleItemsThroughSend() { ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(5, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"SecondItem\"}\u001E", TestUtils.ByteBufferToString(messages[3])); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"ThirdItem\"}\u001E", TestUtils.ByteBufferToString(messages[4])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"SecondItem\"}\u001E", TestUtils.byteBufferToString(messages[3])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"ThirdItem\"}\u001E", TestUtils.byteBufferToString(messages[4])); stream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(6, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[5])); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.byteBufferToString(messages[5])); } @Test @@ -887,13 +886,13 @@ public void checkStreamUploadMultipleItemsThroughInvoke() { ByteBuffer[] messages = mockTransport.getSentMessages(); assertEquals(4, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.ByteBufferToString(messages[2])); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"SecondItem\"}\u001E", TestUtils.ByteBufferToString(messages[3])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"FirstItem\"}\u001E", TestUtils.byteBufferToString(messages[2])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"SecondItem\"}\u001E", TestUtils.byteBufferToString(messages[3])); stream.onComplete(); messages = mockTransport.getSentMessages(); assertEquals(5, messages.length); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[4])); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.byteBufferToString(messages[4])); } @Test @@ -952,12 +951,12 @@ public void canStartAndStopMultipleStreams() { // Handshake message + 2 calls to send + 4 calls to onNext + 2 calls to onComplete = 9 assertEquals(9, messages.length); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"Stream One First Item\"}\u001E", TestUtils.ByteBufferToString(messages[3])); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Stream Two First Item\"}\u001E", TestUtils.ByteBufferToString(messages[4])); - assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"Stream One Second Item\"}\u001E", TestUtils.ByteBufferToString(messages[5])); - assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Stream Two Second Item\"}\u001E", TestUtils.ByteBufferToString(messages[6])); - assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.ByteBufferToString(messages[7])); - assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.ByteBufferToString(messages[8])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"Stream One First Item\"}\u001E", TestUtils.byteBufferToString(messages[3])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Stream Two First Item\"}\u001E", TestUtils.byteBufferToString(messages[4])); + assertEquals("{\"type\":2,\"invocationId\":\"1\",\"item\":\"Stream One Second Item\"}\u001E", TestUtils.byteBufferToString(messages[5])); + assertEquals("{\"type\":2,\"invocationId\":\"2\",\"item\":\"Stream Two Second Item\"}\u001E", TestUtils.byteBufferToString(messages[6])); + assertEquals("{\"type\":3,\"invocationId\":\"1\"}\u001E", TestUtils.byteBufferToString(messages[7])); + assertEquals("{\"type\":3,\"invocationId\":\"2\"}\u001E", TestUtils.byteBufferToString(messages[8])); } @Test @@ -975,7 +974,7 @@ public void checkStreamSingleItem() { () -> completed.set(true)); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); assertFalse(onNextCalled.get()); @@ -1036,7 +1035,7 @@ public void checkStreamCompletionResult() { () -> completed.set(true)); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); assertFalse(onNextCalled.get()); @@ -1100,7 +1099,7 @@ public void checkStreamCompletionError() { () -> {}); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(onErrorCalled.get()); assertFalse(onNextCalled.get()); @@ -1164,7 +1163,7 @@ public void checkStreamMultipleItems() { () -> {/*OnCompleted*/completed.set(true);}); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); mockTransport.receiveMessage("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First\"}" + RECORD_SEPARATOR); @@ -1224,11 +1223,11 @@ public void checkCancelIsSentAfterDispose() { () -> {/*OnCompleted*/completed.set(true);}); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); subscription.dispose(); - assertEquals("{\"type\":5,\"invocationId\":\"1\"}" + RECORD_SEPARATOR, TestUtils.ByteBufferToString(mockTransport.getSentMessages()[2])); + assertEquals("{\"type\":5,\"invocationId\":\"1\"}" + RECORD_SEPARATOR, TestUtils.byteBufferToString(mockTransport.getSentMessages()[2])); } @Test @@ -1273,12 +1272,12 @@ public void checkCancelIsSentAfterAllSubscriptionsAreDisposed() { subscription.dispose(); assertEquals(2, mockTransport.getSentMessages().length); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); secondSubscription.dispose(); assertEquals(3, mockTransport.getSentMessages().length); assertEquals("{\"type\":5,\"invocationId\":\"1\"}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[mockTransport.getSentMessages().length - 1])); } @Test @@ -1323,7 +1322,7 @@ public void checkStreamWithDispose() { () -> {/*OnCompleted*/}); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); mockTransport.receiveMessage("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First\"}" + RECORD_SEPARATOR); @@ -1377,7 +1376,7 @@ public void checkStreamWithDisposeWithMultipleSubscriptions() { () -> {/*OnCompleted*/completed.set(true);}); assertEquals("{\"type\":4,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(completed.get()); mockTransport.receiveMessage("{\"type\":2,\"invocationId\":\"1\",\"item\":\"First\"}" + RECORD_SEPARATOR); @@ -1443,7 +1442,7 @@ public void invokeWaitsForCompletionMessage() { Single result = hubConnection.invoke(Integer.class, "echo", "message"); result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); @@ -1486,7 +1485,7 @@ public void invokeNoReturnValueWaitsForCompletion() { result.doOnComplete(() -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\"}" + RECORD_SEPARATOR); @@ -1529,7 +1528,7 @@ public void invokeCompletedByCompletionMessageWithResult() { result.doOnComplete(() -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":42}" + RECORD_SEPARATOR); @@ -1572,7 +1571,7 @@ public void completionWithResultAndErrorHandlesError() { result.doOnComplete(() -> done.set(true)).subscribe(() -> {}, (error) -> {}); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); Throwable exception = assertThrows(IllegalArgumentException.class, () -> @@ -1592,7 +1591,7 @@ public void invokeNoReturnValueHandlesError() { result.doOnComplete(() -> done.set(true)).subscribe(() -> {}, (error) -> {}); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"test\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"error\":\"There was an error\"}" + RECORD_SEPARATOR); @@ -1648,7 +1647,7 @@ public void canSendNullArgInInvocation() { Single result = hubConnection.invoke(String.class, "fixedMessage", (Object)null); result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":\"Hello World\"}" + RECORD_SEPARATOR); @@ -1692,7 +1691,7 @@ public void canSendMultipleNullArgsInInvocation() { Single result = hubConnection.invoke(String.class, "fixedMessage", null, null); result.doOnSuccess(value -> done.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"fixedMessage\",\"arguments\":[null,null]}"+ RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertFalse(done.get()); mockTransport.receiveMessage("{\"type\":3,\"invocationId\":\"1\",\"result\":\"Hello World\"}" + RECORD_SEPARATOR); @@ -1739,9 +1738,9 @@ public void multipleInvokesWaitForOwnCompletionMessage() { result.doOnSuccess(value -> doneFirst.set(true)).subscribe(); result2.doOnSuccess(value -> doneSecond.set(true)).subscribe(); assertEquals("{\"type\":1,\"invocationId\":\"1\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[1])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[1])); assertEquals("{\"type\":1,\"invocationId\":\"2\",\"target\":\"echo\",\"arguments\":[\"message\"]}" + RECORD_SEPARATOR, - TestUtils.ByteBufferToString(mockTransport.getSentMessages()[2])); + TestUtils.byteBufferToString(mockTransport.getSentMessages()[2])); assertFalse(doneFirst.get()); assertFalse(doneSecond.get()); @@ -2581,7 +2580,7 @@ public void receiveHandshakeResponseAndMessage() { hubConnection.start(); mockTransport.getStartTask().timeout(1, TimeUnit.SECONDS).blockingAwait(); String expectedSentMessage = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; - assertEquals(expectedSentMessage, TestUtils.ByteBufferToString(mockTransport.getSentMessages()[0])); + assertEquals(expectedSentMessage, TestUtils.byteBufferToString(mockTransport.getSentMessages()[0])); mockTransport.receiveMessage("{}" + RECORD_SEPARATOR + "{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR); @@ -2721,7 +2720,7 @@ public void negotiateSentOnStart() { @Test public void negotiateThatRedirectsForeverFailsAfter100Tries() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", - (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://example.com\"}")))); + (req) -> Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"url\":\"http://example.com\"}")))); HubConnection hubConnection = HubConnectionBuilder .create("http://example.com") @@ -2755,7 +2754,7 @@ public void noConnectionIdWhenSkippingNegotiate() { public void connectionIdIsAvailableAfterStart() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); MockTransport transport = new MockTransport(true); @@ -2780,7 +2779,7 @@ public void connectionIdIsAvailableAfterStart() { public void connectionTokenAppearsInQSConnectionIdIsOnConnectionInstance() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + "\"negotiateVersion\": 1," + "\"connectionToken\":\"connection-token-value\"," + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); @@ -2807,7 +2806,7 @@ public void connectionTokenAppearsInQSConnectionIdIsOnConnectionInstance() { public void connectionTokenIsIgnoredIfNegotiateVersionIsNotPresentInNegotiateResponse() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + "\"connectionToken\":\"connection-token-value\"," + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); @@ -2833,7 +2832,7 @@ public void connectionTokenIsIgnoredIfNegotiateVersionIsNotPresentInNegotiateRes public void negotiateVersionIsNotAddedIfAlreadyPresent() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=42", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + + TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + "\"connectionToken\":\"connection-token-value\"," + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); @@ -2859,7 +2858,7 @@ public void negotiateVersionIsNotAddedIfAlreadyPresent() { public void afterSuccessfulNegotiateConnectsWithWebsocketsTransport() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); MockTransport transport = new MockTransport(true); @@ -2873,14 +2872,14 @@ public void afterSuccessfulNegotiateConnectsWithWebsocketsTransport() { ByteBuffer[] sentMessages = transport.getSentMessages(); assertEquals(1, sentMessages.length); - assertEquals("{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR, TestUtils.ByteBufferToString(sentMessages[0])); + assertEquals("{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR, TestUtils.byteBufferToString(sentMessages[0])); } @Test public void afterSuccessfulNegotiateConnectsWithLongPollingTransport() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); MockTransport transport = new MockTransport(true); @@ -2894,7 +2893,7 @@ public void afterSuccessfulNegotiateConnectsWithLongPollingTransport() { ByteBuffer[] sentMessages = transport.getSentMessages(); assertEquals(1, sentMessages.length); - assertEquals("{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR, TestUtils.ByteBufferToString(sentMessages[0])); + assertEquals("{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR, TestUtils.byteBufferToString(sentMessages[0])); } @Test @@ -2904,12 +2903,12 @@ public void TransportAllUsesLongPollingWhenServerOnlySupportLongPolling() { TestHttpClient client = new TestHttpClient() .on("POST", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))) .on("GET", (req) -> { if (requestCount.get() < 2) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{}" + RECORD_SEPARATOR))); + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{}" + RECORD_SEPARATOR))); } assertTrue(close.blockingAwait(5, TimeUnit.SECONDS)); return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); @@ -2931,7 +2930,7 @@ public void TransportAllUsesLongPollingWhenServerOnlySupportLongPolling() { public void ClientThatSelectsWebsocketsThrowsWhenWebsocketsAreNotAvailable() { TestHttpClient client = new TestHttpClient().on("POST", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))) .on("GET", (req) -> { return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); @@ -2953,7 +2952,7 @@ public void ClientThatSelectsWebsocketsThrowsWhenWebsocketsAreNotAvailable() { @Test public void ClientThatSelectsLongPollingThrowsWhenLongPollingIsNotAvailable() { TestHttpClient client = new TestHttpClient().on("POST", - (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + (req) -> Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); HubConnection hubConnection = HubConnectionBuilder @@ -2973,7 +2972,7 @@ public void ClientThatSelectsLongPollingThrowsWhenLongPollingIsNotAvailable() { public void receivingServerSentEventsTransportFromNegotiateFails() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"ServerSentEvents\",\"transferFormats\":[\"Text\"]}]}")))); MockTransport transport = new MockTransport(true); @@ -2992,7 +2991,7 @@ public void receivingServerSentEventsTransportFromNegotiateFails() { @Test public void negotiateThatReturnsErrorThrowsFromStart() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", - (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"error\":\"Test error.\"}")))); + (req) -> Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"error\":\"Test error.\"}")))); MockTransport transport = new MockTransport(true); HubConnection hubConnection = HubConnectionBuilder @@ -3009,7 +3008,7 @@ public void negotiateThatReturnsErrorThrowsFromStart() { @Test public void DetectWhenTryingToConnectToClassicSignalRServer() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", - (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"Url\":\"/signalr\"," + + (req) -> Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"Url\":\"/signalr\"," + "\"ConnectionToken\":\"X97dw3uxW4NPPggQsYVcNcyQcuz4w2\"," + "\"ConnectionId\":\"05265228-1e2c-46c5-82a1-6a5bcc3f0143\"," + "\"KeepAliveTimeout\":10.0," + @@ -3035,9 +3034,9 @@ public void DetectWhenTryingToConnectToClassicSignalRServer() { @Test public void negotiateRedirectIsFollowed() { TestHttpClient client = new TestHttpClient().on("POST", "http://example.com/negotiate?negotiateVersion=1", - (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\"}")))) + (req) -> Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"url\":\"http://testexample.com/\"}")))) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", - (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + (req) -> Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); MockTransport transport = new MockTransport(true); @@ -3060,11 +3059,11 @@ public void accessTokenProviderReferenceIsKeptAfterNegotiateRedirect() { TestHttpClient client = new TestHttpClient() .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { beforeRedirectToken.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))); + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))); }) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3101,7 +3100,7 @@ public void accessTokenProviderIsUsedForNegotiate() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3126,7 +3125,7 @@ public void AccessTokenProviderCanProvideDifferentValues() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3155,10 +3154,10 @@ public void accessTokenProviderIsOverriddenFromRedirectNegotiate() { AtomicReference token = new AtomicReference<>(); TestHttpClient client = new TestHttpClient() .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}")))) + TestUtils.stringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}")))) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + "\"connectionToken\":\"connection-token-value\"," + "\"negotiateVersion\":1," + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); @@ -3187,11 +3186,11 @@ public void authorizationHeaderFromNegotiateGetsClearedAfterStopping() { TestHttpClient client = new TestHttpClient() .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { beforeRedirectToken.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))); + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"newToken\"}"))); }) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\"," + "\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3230,15 +3229,15 @@ public void authorizationHeaderFromNegotiateGetsSetToNewValue() { if (redirectCount.get() == 0) { redirectCount.incrementAndGet(); redirectToken.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"firstRedirectToken\"}"))); + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"firstRedirectToken\"}"))); } else { redirectToken.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"secondRedirectToken\"}"))); + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"secondRedirectToken\"}"))); } }) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { token.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3289,9 +3288,9 @@ public void connectionSendsPingsRegularly() throws InterruptedException { hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); - String message = TestUtils.ByteBufferToString(mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet()); + String message = TestUtils.byteBufferToString(mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet()); assertEquals("{\"type\":6}" + RECORD_SEPARATOR, message); - message = TestUtils.ByteBufferToString(mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet()); + message = TestUtils.byteBufferToString(mockTransport.getNextSentMessage().timeout(1, TimeUnit.SECONDS).blockingGet()); assertEquals("{\"type\":6}" + RECORD_SEPARATOR, message); hubConnection.stop().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -3304,7 +3303,7 @@ public void userAgentHeaderIsSet() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("User-Agent")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3328,7 +3327,7 @@ public void userAgentHeaderCanBeOverwritten() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("User-Agent")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3352,7 +3351,7 @@ public void userAgentCanBeCleared() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("User-Agent")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3375,7 +3374,7 @@ public void headersAreSetAndSentThroughBuilder() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("ExampleHeader")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3400,7 +3399,7 @@ public void headersAreNotClearedWhenConnectionIsRestarted() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3430,12 +3429,12 @@ public void userSetAuthHeaderIsNotClearedAfterRedirect() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { beforeRedirectHeader.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"redirectToken\"}\"}"))); + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"url\":\"http://testexample.com/\",\"accessToken\":\"redirectToken\"}\"}"))); }) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> { afterRedirectHeader.set(req.getHeaders().get("Authorization")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3473,7 +3472,7 @@ public void sameHeaderSetTwiceGetsOverwritten() { .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> { header.set(req.getHeaders().get("ExampleHeader")); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"))); }); @@ -3515,9 +3514,9 @@ public void hubConnectionCanBeStartedAfterBeingStopped() { public void hubConnectionCanBeStartedAfterBeingStoppedAndRedirected() { MockTransport mockTransport = new MockTransport(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\"}")))) + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"url\":\"http://testexample.com/\"}")))) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); + TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); HubConnection hubConnection = HubConnectionBuilder .create("http://example.com") @@ -3557,9 +3556,9 @@ public void non200FromNegotiateThrowsError() { public void hubConnectionCloseCallsStop() throws Exception { MockTransport mockTransport = new MockTransport(); TestHttpClient client = new TestHttpClient() - .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("{\"url\":\"http://testexample.com/\"}")))) + .on("POST", "http://example.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("{\"url\":\"http://testexample.com/\"}")))) .on("POST", "http://testexample.com/negotiate?negotiateVersion=1", (req) -> Single.just(new HttpResponse(200, "", - TestUtils.StringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); + TestUtils.stringToByteBuffer("{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}]}")))); CompletableSubject close = CompletableSubject.create(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java index e54c7db1e073..1b707d6a53e6 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java @@ -34,7 +34,7 @@ public void checkTransferFormat() { @Test public void verifyWriteMessage() { InvocationMessage invocationMessage = new InvocationMessage(null, null, "test", new Object[] {"42"}, null); - String result = TestUtils.ByteBufferToString(jsonHubProtocol.writeMessage(invocationMessage)); + String result = TestUtils.byteBufferToString(jsonHubProtocol.writeMessage(invocationMessage)); String expectedResult = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"42\"]}\u001E"; assertEquals(expectedResult, result); } @@ -42,7 +42,7 @@ public void verifyWriteMessage() { @Test public void parsePingMessage() { String stringifiedMessage = "{\"type\":6}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(null, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -56,7 +56,7 @@ public void parsePingMessage() { @Test public void parseCloseMessage() { String stringifiedMessage = "{\"type\":7}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(null, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -76,7 +76,7 @@ public void parseCloseMessage() { @Test public void parseCloseMessageWithError() { String stringifiedMessage = "{\"type\":7,\"error\": \"There was an error\"}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -96,7 +96,7 @@ public void parseCloseMessageWithError() { @Test public void parseSingleMessage() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -120,7 +120,7 @@ public void parseSingleMessage() { @Test public void parseSingleUnsupportedStreamInvocationMessage() { String stringifiedMessage = "{\"type\":4,\"Id\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class }, null); Throwable exception = assertThrows(UnsupportedOperationException.class, () -> jsonHubProtocol.parseMessages(message, binder)); @@ -130,7 +130,7 @@ public void parseSingleUnsupportedStreamInvocationMessage() { @Test public void parseSingleUnsupportedCancelInvocationMessage() { String stringifiedMessage = "{\"type\":5,\"invocationId\":123}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(null, null); Throwable exception = assertThrows(UnsupportedOperationException.class, () -> jsonHubProtocol.parseMessages(message, binder)); @@ -140,7 +140,7 @@ public void parseSingleUnsupportedCancelInvocationMessage() { @Test public void parseTwoMessages() { String stringifiedMessage = "{\"type\":1,\"target\":\"one\",\"arguments\":[42]}\u001E{\"type\":1,\"target\":\"two\",\"arguments\":[43]}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -174,7 +174,7 @@ public void parseTwoMessages() { @Test public void parseSingleMessageMutipleArgs() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class, int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -197,7 +197,7 @@ public void parseSingleMessageMutipleArgs() { @Test public void parseSingleMessageNestedCollection() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[[{\"one\":[\"a\",\"b\"],\"two\":[\"\uBEEF\",\"\uABCD\"]},{\"four\":[\"^\",\"*\"],\"three\":[\"5\",\"9\"]}]]}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { (new TypeReference>>>() { }).getType() }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -252,7 +252,7 @@ public void parseSingleMessageNestedCollection() { @Test public void parseSingleMessageCustomPojoArg() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[{\"firstName\":\"John\",\"lastName\":\"Doe\",\"age\":30,\"t\":[5,8]}]}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { (new TypeReference>>() { }).getType() }, null); @@ -287,7 +287,7 @@ public void parseSingleMessageCustomPojoArg() { @Test public void parseMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class, int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -310,7 +310,7 @@ public void parseMessageWithOutOfOrderProperties() { @Test public void parseCompletionMessageWithOutOfOrderProperties() { String stringifiedMessage = "{\"type\":3,\"result\":42,\"invocationId\":\"1\"}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(null, int.class); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -329,7 +329,7 @@ public void parseCompletionMessageWithOutOfOrderProperties() { @Test public void invocationBindingFailureWhileParsingTooManyArgumentsWithOutOfOrderProperties() { String stringifiedMessage = "{\"arguments\":[42, 24],\"type\":1,\"target\":\"test\"}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -345,7 +345,7 @@ public void invocationBindingFailureWhileParsingTooManyArgumentsWithOutOfOrderPr @Test public void invocationBindingFailureWhileParsingTooManyArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42, 24]}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -361,7 +361,7 @@ public void invocationBindingFailureWhileParsingTooManyArguments() { @Test public void invocationBindingFailureWhileParsingTooFewArguments() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[42]}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class, int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -377,7 +377,7 @@ public void invocationBindingFailureWhileParsingTooFewArguments() { @Test public void invocationBindingFailureWhenParsingIncorrectType() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"]}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -393,7 +393,7 @@ public void invocationBindingFailureWhenParsingIncorrectType() { @Test public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":[\"true\"],\"invocationId\":\"123\"}\u001E"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class }, null); List messages = jsonHubProtocol.parseMessages(message, binder); @@ -410,7 +410,7 @@ public void invocationBindingFailureStillReadsJsonPayloadAfterFailure() { @Test public void errorWhileParsingIncompleteMessage() { String stringifiedMessage = "{\"type\":1,\"target\":\"test\",\"arguments\":"; - ByteBuffer message = TestUtils.StringToByteBuffer(stringifiedMessage); + ByteBuffer message = TestUtils.stringToByteBuffer(stringifiedMessage); TestBinder binder = new TestBinder(new Type[] { int.class, int.class }, null); RuntimeException exception = assertThrows(RuntimeException.class, diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java index a84998d6d8c2..ae9cb177597e 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/LongPollingTransportTest.java @@ -40,7 +40,7 @@ public void LongPollingTransportCantSendBeforeStart() { Map headers = new HashMap<>(); LongPollingTransport transport = new LongPollingTransport(headers, client, Single.just("")); - ByteBuffer sendBuffer = TestUtils.StringToByteBuffer("First"); + ByteBuffer sendBuffer = TestUtils.stringToByteBuffer("First"); Throwable exception = assertThrows(RuntimeException.class, () -> transport.send(sendBuffer).timeout(1, TimeUnit.SECONDS).blockingAwait()); assertEquals(Exception.class, exception.getCause().getClass()); assertEquals("Cannot send unless the transport is active.", exception.getCause().getMessage()); @@ -114,12 +114,12 @@ public void CanSetAndTriggerOnReceive() { AtomicBoolean onReceivedRan = new AtomicBoolean(false); transport.setOnReceive((message) -> { onReceivedRan.set(true); - assertEquals("TEST", TestUtils.ByteBufferToString(message)); + assertEquals("TEST", TestUtils.byteBufferToString(message)); }); // The transport doesn't need to be active to trigger onReceive for the case // when we are handling the last outstanding poll. - ByteBuffer onReceiveBuffer = TestUtils.StringToByteBuffer("TEST"); + ByteBuffer onReceiveBuffer = TestUtils.stringToByteBuffer("TEST"); transport.onReceive(onReceiveBuffer); assertTrue(onReceivedRan.get()); } @@ -135,7 +135,7 @@ public void LongPollingTransportOnReceiveGetsCalled() { return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); } else if (requestCount.get() == 1) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("TEST"))); + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("TEST"))); } return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); @@ -148,7 +148,7 @@ public void LongPollingTransportOnReceiveGetsCalled() { AtomicReference message = new AtomicReference<>(); transport.setOnReceive((msg -> { onReceiveCalled.set(true); - message.set(TestUtils.ByteBufferToString(msg)); + message.set(TestUtils.byteBufferToString(msg)); block.onComplete(); }) ); @@ -171,13 +171,13 @@ public void LongPollingTransportOnReceiveGetsCalledMultipleTimes() { return Single.just(new HttpResponse(200, "", TestUtils.emptyByteBuffer)); } else if (requestCount.get() == 1) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("FIRST"))); + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("FIRST"))); } else if (requestCount.get() == 2) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("SECOND"))); + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("SECOND"))); } else if (requestCount.get() == 3) { requestCount.incrementAndGet(); - return Single.just(new HttpResponse(200, "", TestUtils.StringToByteBuffer("THIRD"))); + return Single.just(new HttpResponse(200, "", TestUtils.stringToByteBuffer("THIRD"))); } return Single.just(new HttpResponse(204, "", TestUtils.emptyByteBuffer)); @@ -191,7 +191,7 @@ public void LongPollingTransportOnReceiveGetsCalledMultipleTimes() { AtomicInteger messageCount = new AtomicInteger(); transport.setOnReceive((msg) -> { onReceiveCalled.set(true); - message.set(message.get() + TestUtils.ByteBufferToString(msg)); + message.set(message.get() + TestUtils.byteBufferToString(msg)); if (messageCount.incrementAndGet() == 3) { blocker.onComplete(); } @@ -230,7 +230,7 @@ public void LongPollingTransportSendsHeaders() { transport.setOnClose((error) -> {}); transport.start("http://example.com").timeout(1, TimeUnit.SECONDS).blockingAwait(); - ByteBuffer sendBuffer = TestUtils.StringToByteBuffer("TEST"); + ByteBuffer sendBuffer = TestUtils.stringToByteBuffer("TEST"); assertTrue(transport.send(sendBuffer).blockingAwait(1, TimeUnit.SECONDS)); close.onComplete(); assertEquals(headerValue.get(), "VALUE"); @@ -262,7 +262,7 @@ public void LongPollingTransportSetsAuthorizationHeader() { transport.setOnClose((error) -> {}); transport.start("http://example.com").timeout(1, TimeUnit.SECONDS).blockingAwait(); - ByteBuffer sendBuffer = TestUtils.StringToByteBuffer("TEST"); + ByteBuffer sendBuffer = TestUtils.stringToByteBuffer("TEST"); assertTrue(transport.send(sendBuffer).blockingAwait(1, TimeUnit.SECONDS)); assertEquals(headerValue.get(), "Bearer TOKEN"); close.onComplete(); @@ -299,7 +299,7 @@ public void LongPollingTransportRunsAccessTokenProviderEveryRequest() { transport.start("http://example.com").timeout(1, TimeUnit.SECONDS).blockingAwait(); secondGet.blockingAwait(1, TimeUnit.SECONDS); - ByteBuffer sendBuffer = TestUtils.StringToByteBuffer("TEST"); + ByteBuffer sendBuffer = TestUtils.stringToByteBuffer("TEST"); assertTrue(transport.send(sendBuffer).blockingAwait(1, TimeUnit.SECONDS)); assertEquals("Bearer TOKEN2", headerValue.get()); close.onComplete(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index 9169b89d6621..37df89fabfb2 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -1,3 +1,6 @@ +// 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. + package com.microsoft.signalr; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -14,8 +17,6 @@ import java.util.SortedMap; import java.util.TreeMap; -import okio.ByteString; - import org.junit.jupiter.api.Test; class MessagePackHubProtocolTest { diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java index 4aaefb018a91..919ba49e7443 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MockTransport.java @@ -41,7 +41,7 @@ public Completable start(String url) { this.url = url; if (autoHandshake) { try { - onReceiveCallBack.invoke(TestUtils.StringToByteBuffer("{}" + RECORD_SEPARATOR)); + onReceiveCallBack.invoke(TestUtils.stringToByteBuffer("{}" + RECORD_SEPARATOR)); } catch (Exception e) { throw new RuntimeException(e); } @@ -87,7 +87,7 @@ public void stopWithError(String errorMessage) { } public void receiveMessage(String message) { - this.onReceive(TestUtils.StringToByteBuffer(message)); + this.onReceive(TestUtils.stringToByteBuffer(message)); } public void receiveMessage(ByteBuffer message) { @@ -115,7 +115,7 @@ public Completable getStopTask() { } private boolean isPing(ByteBuffer message) { - return (TestUtils.ByteBufferToString(message).equals("{\"type\":6}" + RECORD_SEPARATOR) || + return (TestUtils.byteBufferToString(message).equals("{\"type\":6}" + RECORD_SEPARATOR) || (message.array()[0] == 2 && message.array()[1] == -111 && message.array()[2] == 6)); } } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java index c38e7a89c809..8c3cfe0d1ecf 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java @@ -7,8 +7,8 @@ import java.nio.charset.StandardCharsets; class TestUtils { - - static ByteBuffer emptyByteBuffer = StringToByteBuffer(""); + + static ByteBuffer emptyByteBuffer = stringToByteBuffer(""); static HubConnection createHubConnection(String url) { return createHubConnection(url, new MockTransport(true), true, new TestHttpClient(), new JsonHubProtocol()); @@ -36,11 +36,11 @@ static HubConnection createHubConnection(String url, Transport transport, boolea return builder.build(); } - static String ByteBufferToString(ByteBuffer buffer) { + static String byteBufferToString(ByteBuffer buffer) { return new String(buffer.array(), StandardCharsets.UTF_8); } - static ByteBuffer StringToByteBuffer(String s) { + static ByteBuffer stringToByteBuffer(String s) { return ByteBuffer.wrap(s.getBytes(StandardCharsets.UTF_8)); } } From e2bcd967d2c23e189a6d18051ce86fae6298e178 Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Wed, 19 Aug 2020 16:17:50 -0700 Subject: [PATCH 63/64] Remove TypeReference, make Protocols private again --- .../signalr/HttpHubConnectionBuilder.java | 17 +++-- .../com/microsoft/signalr/HubProtocol.java | 2 +- .../microsoft/signalr/InvocationBinder.java | 2 +- .../microsoft/signalr/JsonHubProtocol.java | 2 +- .../signalr/MessagePackHubProtocol.java | 2 +- .../com/microsoft/signalr/TransferFormat.java | 2 +- .../com/microsoft/signalr/TypeReference.java | 27 -------- .../microsoft/signalr/HubConnectionTest.java | 65 ++++++++++--------- .../signalr/JsonHubProtocolTest.java | 2 + .../signalr/MessagePackHubProtocolTest.java | 2 + .../java/com/microsoft/signalr/TestUtils.java | 19 +++--- 11 files changed, 66 insertions(+), 76 deletions(-) delete mode 100644 src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java index 435194bfce13..d09ecdc8ca75 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java @@ -57,13 +57,22 @@ HttpHubConnectionBuilder withHttpClient(HttpClient httpClient) { } /** - * Sets the {@link HubProtocol} to be used by the {@link HubConnection}. + * Sets Json as the {@link HubProtocol} to be used by the {@link HubConnection}. * - * @param protocol The {@link HubProtocol} to be used by the {@link HubConnection}. * @return This instance of the HttpHubConnectionBuilder. */ - public HttpHubConnectionBuilder withProtocol(HubProtocol protocol) { - this.protocol = protocol; + public HttpHubConnectionBuilder withJson() { + this.protocol = new JsonHubProtocol(); + return this; + } + + /** + * Sets MessagePack as the {@link HubProtocol} to be used by the {@link HubConnection}. + * + * @return This instance of the HttpHubConnectionBuilder. + */ + public HttpHubConnectionBuilder withMessagePack() { + this.protocol = new MessagePackHubProtocol(); return this; } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java index 3096bf719c90..844a458c8cde 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HubProtocol.java @@ -9,7 +9,7 @@ /** * A protocol abstraction for communicating with SignalR hubs. */ -public interface HubProtocol { +interface HubProtocol { String getName(); int getVersion(); TransferFormat getTransferFormat(); diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java index f09035f4f968..40767a7a985f 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/InvocationBinder.java @@ -9,7 +9,7 @@ /** * An abstraction for passing around information about method signatures. */ -public interface InvocationBinder { +interface InvocationBinder { Type getReturnType(String invocationId); List getParameterTypes(String methodName); } \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java index 6899f097b3d1..892f600ea71a 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/JsonHubProtocol.java @@ -19,7 +19,7 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; -public class JsonHubProtocol implements HubProtocol { +class JsonHubProtocol implements HubProtocol { private final JsonParser jsonParser = new JsonParser(); private final Gson gson = new Gson(); private static final String RECORD_SEPARATOR = "\u001e"; diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java index aeff7e7c58c9..bd7f9cd7b2e5 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/MessagePackHubProtocol.java @@ -25,7 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; -public class MessagePackHubProtocol implements HubProtocol { +class MessagePackHubProtocol implements HubProtocol { private static final int ERROR_RESULT = 1; private static final int VOID_RESULT = 2; diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TransferFormat.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TransferFormat.java index 413404bea612..59a9dd588967 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TransferFormat.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TransferFormat.java @@ -3,7 +3,7 @@ package com.microsoft.signalr; -public enum TransferFormat { +enum TransferFormat { TEXT, BINARY } diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java deleted file mode 100644 index 592def53ff07..000000000000 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/TypeReference.java +++ /dev/null @@ -1,27 +0,0 @@ -// 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. - -package com.microsoft.signalr; - -import java.lang.reflect.Type; -import java.lang.reflect.ParameterizedType; - -public class TypeReference { - - private final Type type; - - protected TypeReference() { - Type superclass = getClass().getGenericSuperclass(); - if (superclass instanceof Class) { - throw new RuntimeException("Missing type parameter."); - } - this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0]; - } - - /** - * Gets the referenced type. - */ - public Type getType() { - return this.type; - } -} \ No newline at end of file diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java index ababeb940ae3..8bc833239e94 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/HubConnectionTest.java @@ -18,6 +18,8 @@ import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.core.type.TypeReference; + import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.Single; @@ -33,7 +35,6 @@ class HubConnectionTest { private static final Type doubleType = (new TypeReference() { }).getType(); private static final Type integerType = (new TypeReference() { }).getType(); private static final Type stringType = (new TypeReference() { }).getType(); - private static final MessagePackHubProtocol messagePackHubProtocol = new MessagePackHubProtocol(); @Test public void checkHubConnectionState() { @@ -643,7 +644,7 @@ public void checkStreamUploadSingleItemThroughInvoke() { @Test public void checkStreamUploadSingleItemThroughInvokeWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -699,7 +700,7 @@ public void checkStreamUploadSingleItemThroughStream() { @Test public void checkStreamUploadSingleItemThroughStreamWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -766,7 +767,7 @@ public void useSameSubjectInMutlipleStreamsFromDifferentMethods() { @Test public void useSameSubjectInMutlipleStreamsFromDifferentMethodsWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -898,7 +899,7 @@ public void checkStreamUploadMultipleItemsThroughInvoke() { @Test public void checkStreamUploadMultipleItemsThroughInvokeWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -991,7 +992,7 @@ public void checkStreamSingleItem() { @Test public void checkStreamSingleItemWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1053,7 +1054,7 @@ public void checkStreamCompletionResult() { @Test public void checkStreamCompletionResultWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1118,7 +1119,7 @@ public void checkStreamCompletionError() { @Test public void checkStreamCompletionErrorWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1179,7 +1180,7 @@ public void checkStreamMultipleItems() { @Test public void checkStreamMultipleItemsWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1233,7 +1234,7 @@ public void checkCancelIsSentAfterDispose() { @Test public void checkCancelIsSentAfterDisposeWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1283,7 +1284,7 @@ public void checkCancelIsSentAfterAllSubscriptionsAreDisposed() { @Test public void checkCancelIsSentAfterAllSubscriptionsAreDisposedWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1335,7 +1336,7 @@ public void checkStreamWithDispose() { @Test public void checkStreamWithDisposeWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1395,7 +1396,7 @@ public void checkStreamWithDisposeWithMultipleSubscriptions() { @Test public void checkStreamWithDisposeWithMultipleSubscriptionsWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1454,7 +1455,7 @@ public void invokeWaitsForCompletionMessage() { @Test public void invokeWaitsForCompletionMessageWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1497,7 +1498,7 @@ public void invokeNoReturnValueWaitsForCompletion() { @Test public void invokeNoReturnValueWaitsForCompletionWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1540,7 +1541,7 @@ public void invokeCompletedByCompletionMessageWithResult() { @Test public void invokeCompletedByCompletionMessageWithResultWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1609,7 +1610,7 @@ public void invokeNoReturnValueHandlesError() { @Test public void invokeNoReturnValueHandlesErrorWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1659,7 +1660,7 @@ public void canSendNullArgInInvocation() { @Test public void canSendNullArgInInvocationWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1703,7 +1704,7 @@ public void canSendMultipleNullArgsInInvocation() { @Test public void canSendMultipleNullArgsInInvocationWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1757,7 +1758,7 @@ public void multipleInvokesWaitForOwnCompletionMessage() { @Test public void multipleInvokesWaitForOwnCompletionMessageWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1814,7 +1815,7 @@ public void invokeWorksForPrimitiveTypes() { @Test public void invokeWorksForPrimitiveTypesWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -1859,7 +1860,7 @@ public void completionMessageCanHaveError() { @Test public void completionMessageCanHaveErrorWithMessagePack() { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.start().timeout(1, TimeUnit.SECONDS).blockingAwait(); @@ -2202,7 +2203,7 @@ public void sendWithEightParamsTriggersOnHandler() { public void sendWithNoParamsTriggersOnHandlerWithMessagePack() { AtomicReference value = new AtomicReference<>(0); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", () ->{ assertEquals(Integer.valueOf(0), value.get()); @@ -2221,7 +2222,7 @@ public void sendWithNoParamsTriggersOnHandlerWithMessagePack() { public void sendWithParamTriggersOnHandlerWithMessagePack() { AtomicReference value = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param) ->{ assertNull(value.get()); @@ -2244,7 +2245,7 @@ public void sendWithTwoParamsTriggersOnHandlerWithMessagePack() { AtomicReference value2 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2) ->{ assertNull(value1.get()); @@ -2272,7 +2273,7 @@ public void sendWithThreeParamsTriggersOnHandlerWithMessagePack() { AtomicReference value3 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3) ->{ assertNull(value1.get()); @@ -2304,7 +2305,7 @@ public void sendWithFourParamsTriggersOnHandlerWithMessagePack() { AtomicReference value4 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3, param4) ->{ assertNull(value1.get()); @@ -2339,7 +2340,7 @@ public void sendWithFiveParamsTriggersOnHandlerWithMessagePack() { AtomicReference value5 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3, param4, param5) ->{ assertNull(value1.get()); @@ -2378,7 +2379,7 @@ public void sendWithSixParamsTriggersOnHandlerWithMessagePack() { AtomicReference value6 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3, param4, param5, param6) -> { assertNull(value1.get()); @@ -2421,7 +2422,7 @@ public void sendWithSevenParamsTriggersOnHandlerWithMessagePack() { AtomicReference value7 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3, param4, param5, param6, param7) -> { assertNull(value1.get()); @@ -2469,7 +2470,7 @@ public void sendWithEightParamsTriggersOnHandlerWithMessagePack() { AtomicReference value8 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3, param4, param5, param6, param7, param8) -> { assertNull(value1.get()); @@ -2543,7 +2544,7 @@ public void sendWithCustomObjectTriggersOnHandlerWithMessagePack() { AtomicReference> value1 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, messagePackHubProtocol); + HubConnection hubConnection = TestUtils.createHubConnection("http://example.com", mockTransport, true); hubConnection.>on("inc", (param1) -> { assertNull(value1.get()); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java index 1b707d6a53e6..92febe6ad0bd 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/JsonHubProtocolTest.java @@ -13,6 +13,8 @@ import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.core.type.TypeReference; + class JsonHubProtocolTest { private JsonHubProtocol jsonHubProtocol = new JsonHubProtocol(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java index 37df89fabfb2..0b508d6e210f 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/MessagePackHubProtocolTest.java @@ -19,6 +19,8 @@ import org.junit.jupiter.api.Test; +import com.fasterxml.jackson.core.type.TypeReference; + class MessagePackHubProtocolTest { private MessagePackHubProtocol messagePackHubProtocol = new MessagePackHubProtocol(); diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java index 8c3cfe0d1ecf..2f22ccbd3bfd 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java @@ -11,27 +11,30 @@ class TestUtils { static ByteBuffer emptyByteBuffer = stringToByteBuffer(""); static HubConnection createHubConnection(String url) { - return createHubConnection(url, new MockTransport(true), true, new TestHttpClient(), new JsonHubProtocol()); + return createHubConnection(url, new MockTransport(true), true, new TestHttpClient(), false); } static HubConnection createHubConnection(String url, Transport transport) { - return createHubConnection(url, transport, true, new TestHttpClient(), new JsonHubProtocol()); + return createHubConnection(url, transport, true, new TestHttpClient(), false); } - static HubConnection createHubConnection(String url, HubProtocol protocol) { - return createHubConnection(url, new MockTransport(true), true, new TestHttpClient(), protocol); + static HubConnection createHubConnection(String url, boolean withMessagePack) { + return createHubConnection(url, new MockTransport(true), true, new TestHttpClient(), withMessagePack); } - static HubConnection createHubConnection(String url, Transport transport, HubProtocol protocol) { - return createHubConnection(url, transport, true, new TestHttpClient(), protocol); + static HubConnection createHubConnection(String url, Transport transport, boolean withMessagePack) { + return createHubConnection(url, transport, true, new TestHttpClient(), withMessagePack); } - static HubConnection createHubConnection(String url, Transport transport, boolean skipNegotiate, HttpClient client, HubProtocol protocol) { + static HubConnection createHubConnection(String url, Transport transport, boolean skipNegotiate, HttpClient client, boolean withMessagePack) { HttpHubConnectionBuilder builder = HubConnectionBuilder.create(url) .withTransportImplementation(transport) .withHttpClient(client) - .withProtocol(protocol) .shouldSkipNegotiate(skipNegotiate); + + if (withMessagePack) { + builder = builder.withMessagePack(); + } return builder.build(); } From 6e53284a188e8d68e509f2f3b11986a2865ae32f Mon Sep 17 00:00:00 2001 From: Will Godbe Date: Thu, 20 Aug 2020 09:21:16 -0700 Subject: [PATCH 64/64] Feedback --- .../microsoft/signalr/HttpHubConnectionBuilder.java | 12 +----------- .../test/java/com/microsoft/signalr/TestUtils.java | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java index d09ecdc8ca75..ce29e1a2cecf 100644 --- a/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java +++ b/src/SignalR/clients/java/signalr/src/main/java/com/microsoft/signalr/HttpHubConnectionBuilder.java @@ -56,22 +56,12 @@ HttpHubConnectionBuilder withHttpClient(HttpClient httpClient) { return this; } - /** - * Sets Json as the {@link HubProtocol} to be used by the {@link HubConnection}. - * - * @return This instance of the HttpHubConnectionBuilder. - */ - public HttpHubConnectionBuilder withJson() { - this.protocol = new JsonHubProtocol(); - return this; - } - /** * Sets MessagePack as the {@link HubProtocol} to be used by the {@link HubConnection}. * * @return This instance of the HttpHubConnectionBuilder. */ - public HttpHubConnectionBuilder withMessagePack() { + public HttpHubConnectionBuilder withMessagePackHubProtocol() { this.protocol = new MessagePackHubProtocol(); return this; } diff --git a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java index 2f22ccbd3bfd..29b08709883b 100644 --- a/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java +++ b/src/SignalR/clients/java/signalr/src/test/java/com/microsoft/signalr/TestUtils.java @@ -33,7 +33,7 @@ static HubConnection createHubConnection(String url, Transport transport, boolea .shouldSkipNegotiate(skipNegotiate); if (withMessagePack) { - builder = builder.withMessagePack(); + builder = builder.withMessagePackHubProtocol(); } return builder.build();