From dc6088bf21307df7c10c6eab4f9beaf3ffc91cc7 Mon Sep 17 00:00:00 2001 From: Mikael Mengistu Date: Wed, 5 Sep 2018 16:53:07 -0700 Subject: [PATCH] Negotiate + SignalR Service Support for the Java client (#2882) --- clients/java/signalr/build.gradle | 1 + .../com/microsoft/aspnet/signalr/Chat.java | 49 +-- .../aspnet/signalr/HubConnection.java | 278 +++++++++++------- .../microsoft/aspnet/signalr/Negotiate.java | 63 ++++ .../aspnet/signalr/NegotiateResponse.java | 52 ++++ .../aspnet/signalr/WebSocketTransport.java | 64 ++-- .../src/test/java/HubConnectionTest.java | 56 ++-- .../src/test/java/NegotiateResponseTest.java | 38 +++ .../test/java/ResolveNegotiateUrlTest.java | 39 +++ 9 files changed, 452 insertions(+), 188 deletions(-) create mode 100644 clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Negotiate.java create mode 100644 clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/NegotiateResponse.java create mode 100644 clients/java/signalr/src/test/java/NegotiateResponseTest.java create mode 100644 clients/java/signalr/src/test/java/ResolveNegotiateUrlTest.java diff --git a/clients/java/signalr/build.gradle b/clients/java/signalr/build.gradle index 0c8a4d6d1a..daf0c42b12 100644 --- a/clients/java/signalr/build.gradle +++ b/clients/java/signalr/build.gradle @@ -18,6 +18,7 @@ dependencies { testImplementation group: 'junit', name: 'junit', version: '4.12' implementation "org.java-websocket:Java-WebSocket:1.3.8" implementation 'com.google.code.gson:gson:2.8.5' + implementation 'com.squareup.okhttp3:okhttp:3.11.0' } task sourceJar(type: Jar) { diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Chat.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Chat.java index 5f9345519b..c306c26eef 100644 --- a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Chat.java +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Chat.java @@ -7,34 +7,37 @@ import java.util.Scanner; public class Chat { public static void main(String[] args) throws Exception { - System.out.println("Enter the URL of the SignalR Chat you want to join"); - Scanner reader = new Scanner(System.in); // Reading from System.in - String input; - input = reader.nextLine(); + System.out.println("Enter the URL of the SignalR Chat you want to join"); + Scanner reader = new Scanner(System.in); // Reading from System.in + String input = reader.nextLine(); - HubConnection hubConnection = new HubConnectionBuilder() - .withUrl(input) - .configureLogging(LogLevel.Information).build(); + System.out.print("Enter your name:"); + String enteredName = reader.nextLine(); - hubConnection.on("Send", (message) -> { - System.out.println("REGISTERED HANDLER: " + message); - }, String.class); + HubConnection hubConnection = new HubConnectionBuilder() + .withUrl(input) + .configureLogging(LogLevel.Information).build(); - hubConnection.onClosed((ex) -> { - if(ex.getMessage() != null){ - System.out.printf("There was an error: %s", ex.getMessage()); - } - }); + hubConnection.on("Send", (name, message) -> { + System.out.println(name + ": " + message); + }, String.class, String.class); - //This is a blocking call - hubConnection.start(); - - while (!input.equals("leave")){ - // Scans the next token of the input as an int. - input = reader.nextLine(); - hubConnection.send("Send", input); + hubConnection.onClosed((ex) -> { + if (ex.getMessage() != null) { + System.out.printf("There was an error: %s", ex.getMessage()); } + }); - hubConnection.stop(); + //This is a blocking call + hubConnection.start(); + + String message = ""; + while (!message.equals("leave")) { + // Scans the next token of the input as an int. + message = reader.nextLine(); + hubConnection.send("Send", enteredName, message); + } + + hubConnection.stop(); } } diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubConnection.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubConnection.java index 056a6661ba..34c9619fff 100644 --- a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubConnection.java +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/HubConnection.java @@ -5,9 +5,11 @@ package com.microsoft.aspnet.signalr; import com.google.gson.Gson; import com.google.gson.JsonArray; -import java.net.URISyntaxException; + import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Consumer; public class HubConnection { @@ -22,8 +24,14 @@ public class HubConnection { private HubConnectionState connectionState = HubConnectionState.DISCONNECTED; private Logger logger; private List> onClosedCallbackList; + private boolean skipNegotiate = false; + private NegotiateResponse negotiateResponse; + private String accessToken; + private Map headers = new HashMap<>(); - public HubConnection(String url, Transport transport, Logger logger){ + private static int MAX_NEGOTIATE_ATTEMPTS = 100; + + public HubConnection(String url, Transport transport, Logger logger, boolean skipNegotiate) { this.url = url; this.protocol = new JsonHubProtocol(); @@ -32,7 +40,7 @@ public class HubConnection { } else { this.logger = new NullLogger(); } - + this.skipNegotiate = skipNegotiate; this.callback = (payload) -> { if (!handshakeReceived) { @@ -56,16 +64,16 @@ public class HubConnection { HubMessage[] messages = protocol.parseMessages(payload); for (HubMessage message : messages) { - logger.log(LogLevel.Debug,"Received message of type %s", message.getMessageType()); + logger.log(LogLevel.Debug, "Received message of type %s", message.getMessageType()); switch (message.getMessageType()) { case INVOCATION: - InvocationMessage invocationMessage = (InvocationMessage)message; + InvocationMessage invocationMessage = (InvocationMessage) message; if (handlers.containsKey(invocationMessage.target)) { - ArrayList args = gson.fromJson((JsonArray)invocationMessage.arguments[0], (new ArrayList<>()).getClass()); + ArrayList args = gson.fromJson((JsonArray) invocationMessage.arguments[0], (new ArrayList<>()).getClass()); List actions = handlers.get(invocationMessage.target); if (actions != null) { logger.log(LogLevel.Debug, "Invoking handlers for target %s", invocationMessage.target); - for (ActionBase action: actions) { + for (ActionBase action : actions) { action.invoke(args.toArray()); } } @@ -75,7 +83,7 @@ public class HubConnection { break; case CLOSE: logger.log(LogLevel.Information, "Close message received from server."); - CloseMessage closeMessage = (CloseMessage)message; + CloseMessage closeMessage = (CloseMessage) message; stop(closeMessage.getError()); break; case PING: @@ -92,20 +100,23 @@ public class HubConnection { } }; - if (transport == null){ - try { - this.transport = new WebSocketTransport(this.url, this.logger); - } catch (URISyntaxException e) { - e.printStackTrace(); - } - } else { + if (transport != null) { this.transport = transport; } } + public HubConnection(String url, Transport transport, Logger logger) { + this(url, transport, logger, false); + } + + public HubConnection(String url, Transport transport, boolean skipNegotiate) { + this(url, transport, new NullLogger(), skipNegotiate); + } + /** * Initializes a new instance of the {@link HubConnection} class. - * @param url The url of the SignalR server to connect to. + * + * @param url The url of the SignalR server to connect to. * @param transport The {@link Transport} that the client will use to communicate with the server. */ public HubConnection(String url, Transport transport) { @@ -114,6 +125,7 @@ public class HubConnection { /** * Initializes a new instance of the {@link HubConnection} class. + * * @param url The url of the SignalR server to connect to. */ public HubConnection(String url) { @@ -122,15 +134,17 @@ public class HubConnection { /** * Initializes a new instance of the {@link HubConnection} class. - * @param url The url of the SignalR server to connect to. + * + * @param url The url of the SignalR server to connect to. * @param logLevel The minimum level of messages to log. */ - public HubConnection(String url, LogLevel logLevel){ + public HubConnection(String url, LogLevel logLevel) { this(url, null, new ConsoleLogger(logLevel)); } /** * Indicates the state of the {@link HubConnection} to the server. + * * @return HubConnection state enum. */ public HubConnectionState getConnectionState() { @@ -139,14 +153,47 @@ public class HubConnection { /** * Starts a connection to the server. + * * @throws Exception An error occurred while connecting. */ public void start() throws Exception { if (connectionState != HubConnectionState.DISCONNECTED) { return; } + if (!skipNegotiate) { + int negotiateAttempts = 0; + do { + accessToken = (negotiateResponse == null) ? null : negotiateResponse.getAccessToken(); + negotiateResponse = Negotiate.processNegotiate(url, accessToken); + + if (negotiateResponse.getConnectionId() != null) { + if (url.contains("?")) { + url = url + "&id=" + negotiateResponse.getConnectionId(); + } else { + url = url + "?id=" + negotiateResponse.getConnectionId(); + } + } + + if (negotiateResponse.getAccessToken() != null) { + this.headers.put("Authorization", "Bearer " + negotiateResponse.getAccessToken()); + } + + if (negotiateResponse.getRedirectUrl() != null) { + url = this.negotiateResponse.getRedirectUrl(); + } + + negotiateAttempts++; + } while (negotiateResponse.getRedirectUrl() != null && negotiateAttempts < MAX_NEGOTIATE_ATTEMPTS); + if (!negotiateResponse.getAvailableTransports().contains("WebSockets")) { + throw new HubException("There were no compatible transports on the server."); + } + } logger.log(LogLevel.Debug, "Starting HubConnection"); + if (transport == null) { + transport = new WebSocketTransport(url, logger, headers); + } + transport.setOnReceive(this.callback); transport.start(); String handshake = HandshakeProtocol.createHandshakeRequestMessage(new HandshakeRequestMessage(protocol.getName(), protocol.getVersion())); @@ -163,8 +210,8 @@ public class HubConnection { return; } - if(errorMessage != null) { - logger.log(LogLevel.Error , "HubConnection disconnected with an error %s.", errorMessage); + if (errorMessage != null) { + logger.log(LogLevel.Error, "HubConnection disconnected with an error %s.", errorMessage); } else { logger.log(LogLevel.Debug, "Stopping HubConnection."); } @@ -172,9 +219,9 @@ public class HubConnection { transport.stop(); connectionState = HubConnectionState.DISCONNECTED; logger.log(LogLevel.Information, "HubConnection stopped."); - if (onClosedCallbackList != null){ + if (onClosedCallbackList != null) { HubException hubException = new HubException(errorMessage); - for (Consumer callback: onClosedCallbackList) { + for (Consumer callback : onClosedCallbackList) { callback.accept(hubException); } } @@ -190,8 +237,9 @@ public class HubConnection { /** * Invokes a hub method on the server using the specified method name. * Does not wait for a response from the receiver. + * * @param method The name of the server method to invoke. - * @param args The arguments to be passed to the method. + * @param args The arguments to be passed to the method. * @throws Exception If there was an error while sending. */ public void send(String method, Object... args) throws Exception { @@ -207,7 +255,8 @@ public class HubConnection { /** * 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 target The name of the hub method to define. * @param callback The handler that will be raised when the hub method is invoked. * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. */ @@ -220,10 +269,11 @@ public class HubConnection { /** * 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 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. + * @param param1 The first parameter. + * @param The first argument type. * @return A {@link Subscription} that can be disposed to unsubscribe from the hub method. */ public Subscription on(String target, Action1 callback, Class param1) { @@ -235,12 +285,13 @@ public class HubConnection { /** * 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 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. + * @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. */ public Subscription on(String target, Action2 callback, Class param1, Class param2) { @@ -254,14 +305,15 @@ public class HubConnection { /** * 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 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. + * @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. */ public Subscription on(String target, Action3 callback, @@ -276,16 +328,17 @@ public class HubConnection { /** * 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 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. + * @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. */ public Subscription on(String target, Action4 callback, @@ -300,18 +353,19 @@ public class HubConnection { /** * 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 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. + * @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. */ public Subscription on(String target, Action5 callback, @@ -327,27 +381,28 @@ public class HubConnection { /** * 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 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. + * @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. */ public Subscription on(String target, Action6 callback, Class param1, Class param2, Class param3, Class param4, Class param5, Class 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])); + param5.cast(params[4]), param6.cast(params[5])); }; handlers.put(target, action); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); @@ -356,29 +411,30 @@ public class HubConnection { /** * 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 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. + * @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. */ public Subscription on(String target, Action7 callback, Class param1, Class param2, Class param3, Class param4, Class param5, Class param6, Class 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])); + param5.cast(params[4]), param6.cast(params[5]), param7.cast(params[6])); }; handlers.put(target, action); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); @@ -387,31 +443,32 @@ public class HubConnection { /** * 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 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. + * @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. */ public Subscription on(String target, Action8 callback, - Class param1, Class param2, Class param3, Class param4, Class param5, Class param6, Class param7, Class param8) { + Class param1, Class param2, Class param3, Class param4, Class param5, Class param6, Class param7, Class 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])); + param5.cast(params[4]), param6.cast(params[5]), param7.cast(params[6]), param8.cast(params[7])); }; handlers.put(target, action); logger.log(LogLevel.Trace, "Registering handler for client method: %s", target); @@ -420,15 +477,16 @@ public class HubConnection { /** * Removes all handlers associated with the method with the specified method name. + * * @param name The name of the hub method from which handlers are being removed. */ public void remove(String name) { handlers.remove(name); - logger.log(LogLevel.Trace, "Removing handlers for client method %s" , name); + logger.log(LogLevel.Trace, "Removing handlers for client method %s", name); } public void onClosed(Consumer callback) { - if (onClosedCallbackList == null){ + if (onClosedCallbackList == null) { onClosedCallbackList = new ArrayList<>(); } diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Negotiate.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Negotiate.java new file mode 100644 index 0000000000..1de9637a3d --- /dev/null +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/Negotiate.java @@ -0,0 +1,63 @@ +// 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.aspnet.signalr; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +import java.io.IOException; + +public class Negotiate { + + public static NegotiateResponse processNegotiate(String url) throws IOException { + return processNegotiate(url, null); + } + + public static NegotiateResponse processNegotiate(String url, String accessTokenHeader) throws IOException { + url = resolveNegotiateUrl(url); + OkHttpClient client = new OkHttpClient(); + RequestBody body = RequestBody.create(null, new byte[]{}); + Request.Builder requestBuilder = new Request.Builder() + .url(url) + .post(body); + + if (accessTokenHeader != null) { + requestBuilder.addHeader("Authorization", "Bearer " + accessTokenHeader); + } + + Request request = requestBuilder.build(); + + Response response = client.newCall(request).execute(); + String result = response.body().string(); + return new NegotiateResponse(result); + } + + public static String resolveNegotiateUrl(String url) { + String negotiateUrl = ""; + + // Check if we have a query string. If we do then we ignore it for now. + int queryStringIndex = url.indexOf('?'); + if (queryStringIndex > 0) { + negotiateUrl = url.substring(0, url.indexOf('?')); + } else { + negotiateUrl = url; + } + + //Check if the url ends in a / + if (negotiateUrl.charAt(negotiateUrl.length() - 1) != '/') { + negotiateUrl += "/"; + } + + negotiateUrl += "negotiate"; + + // Add the query string back if it existed. + if (queryStringIndex > 0) { + negotiateUrl += url.substring(url.indexOf('?')); + } + + return negotiateUrl; + } +} diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/NegotiateResponse.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/NegotiateResponse.java new file mode 100644 index 0000000000..a46fb18887 --- /dev/null +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/NegotiateResponse.java @@ -0,0 +1,52 @@ +// 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.aspnet.signalr; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.util.HashSet; +import java.util.Set; + +public class NegotiateResponse { + private String connectionId; + private Set availableTransports = new HashSet<>(); + private String redirectUrl; + private String accessToken; + private JsonParser jsonParser = new JsonParser(); + + public NegotiateResponse(String negotiatePayload) { + JsonObject negotiateResponse = jsonParser.parse(negotiatePayload).getAsJsonObject(); + if (negotiateResponse.has("url")) { + this.redirectUrl = negotiateResponse.get("url").getAsString(); + if (negotiateResponse.has("accessToken")) { + this.accessToken = negotiateResponse.get("accessToken").getAsString(); + } + return; + } + this.connectionId = negotiateResponse.get("connectionId").getAsString(); + JsonArray transports = (JsonArray) negotiateResponse.get("availableTransports"); + for (int i = 0; i < transports.size(); i++) { + availableTransports.add(transports.get(i).getAsJsonObject().get("transport").getAsString()); + } + } + + public String getConnectionId() { + return connectionId; + } + + public Set getAvailableTransports() { + return availableTransports; + } + + public String getRedirectUrl() { + return redirectUrl; + } + + public String getAccessToken() { + return accessToken; + } +} + diff --git a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/WebSocketTransport.java b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/WebSocketTransport.java index 4bc7f5556b..6feb7b9ffe 100644 --- a/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/WebSocketTransport.java +++ b/clients/java/signalr/src/main/java/com/microsoft/aspnet/signalr/WebSocketTransport.java @@ -5,6 +5,9 @@ package com.microsoft.aspnet.signalr; import java.net.URI; import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; @@ -13,35 +16,42 @@ public class WebSocketTransport implements Transport { private OnReceiveCallBack onReceiveCallBack; private URI url; private Logger logger; + private Map headers; private static final String HTTP = "http"; private static final String HTTPS = "https"; private static final String WS = "ws"; private static final String WSS = "wss"; - public WebSocketTransport(String url, Logger logger) throws URISyntaxException { + public WebSocketTransport(String url, Logger logger, Map headers) throws URISyntaxException { this.url = formatUrl(url); this.logger = logger; + this.headers = headers; } - public URI getUrl(){ + public WebSocketTransport(String url, Logger logger) throws URISyntaxException { + this(url, logger, null); + } + + public URI getUrl() { return url; } private URI formatUrl(String url) throws URISyntaxException { - if(url.startsWith(HTTPS)){ + if (url.startsWith(HTTPS)) { url = WSS + url.substring(HTTPS.length()); - } - else if(url.startsWith(HTTP)){ + } else if (url.startsWith(HTTP)) { url = WS + url.substring(HTTP.length()); } + return new URI(url); } @Override public void start() throws Exception { logger.log(LogLevel.Debug, "Starting Websocket connection."); - webSocketClient = createWebSocket(); + webSocketClient = createWebSocket(headers); + if (!webSocketClient.connectBlocking()) { String errorMessage = "There was an error starting the Websockets transport."; logger.log(LogLevel.Debug, errorMessage); @@ -72,31 +82,31 @@ public class WebSocketTransport implements Transport { logger.log(LogLevel.Information, "WebSocket connection stopped"); } - private WebSocketClient createWebSocket() { - return new WebSocketClient(url) { - @Override - public void onOpen(ServerHandshake handshakedata) { - System.out.println("Connected to " + url); - } + private WebSocketClient createWebSocket(Map headers) { + return new WebSocketClient(url, headers) { + @Override + public void onOpen(ServerHandshake handshakedata) { + System.out.println("Connected to " + url); + } - @Override - public void onMessage(String message) { - try { - onReceive(message); - } catch (Exception e) { - e.printStackTrace(); - } - } + @Override + public void onMessage(String message) { + try { + onReceive(message); + } catch (Exception e) { + e.printStackTrace(); + } + } - @Override - public void onClose(int code, String reason, boolean remote) { + @Override + public void onClose(int code, String reason, boolean remote) { System.out.println("Connection Closed"); - } + } - @Override - public void onError(Exception ex) { + @Override + public void onError(Exception ex) { System.out.println("Error: " + ex.getMessage()); - } - }; + } + }; } } diff --git a/clients/java/signalr/src/test/java/HubConnectionTest.java b/clients/java/signalr/src/test/java/HubConnectionTest.java index 867078cc82..d3780afa76 100644 --- a/clients/java/signalr/src/test/java/HubConnectionTest.java +++ b/clients/java/signalr/src/test/java/HubConnectionTest.java @@ -17,7 +17,7 @@ public class HubConnectionTest { @Test public void checkHubConnectionState() throws Exception { Transport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.start(); assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); @@ -28,7 +28,7 @@ public class HubConnectionTest { @Test public void HubConnectionClosesAfterCloseMessage() throws Exception { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.start(); mockTransport.receiveMessage("{}" + RECORD_SEPARATOR); @@ -44,7 +44,7 @@ public class HubConnectionTest { public void RegisteringMultipleHandlersAndBothGetTriggered() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); Action action = () -> value.getAndUpdate((val) -> val + 1); hubConnection.on("inc", action); @@ -70,7 +70,7 @@ public class HubConnectionTest { public void RemoveHandlerByName() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); Action action = () -> value.getAndUpdate((val) -> val + 1); hubConnection.on("inc", action); @@ -97,7 +97,7 @@ public class HubConnectionTest { public void AddAndRemoveHandlerImmediately() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); Action action = () -> value.getAndUpdate((val) -> val + 1); hubConnection.on("inc", action); @@ -122,7 +122,7 @@ public class HubConnectionTest { public void RemovingMultipleHandlersWithOneCallToRemove() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); Action action = () -> value.getAndUpdate((val) -> val + 1); Action secondAction = () -> value.getAndUpdate((val) -> val + 2); @@ -154,7 +154,7 @@ public class HubConnectionTest { public void RemoveHandlerWithUnsubscribe() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); Action action = () -> value.getAndUpdate((val) -> val + 1); Subscription subscription = hubConnection.on("inc", action); @@ -182,7 +182,7 @@ public class HubConnectionTest { public void UnsubscribeTwice() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); Action action = () -> value.getAndUpdate((val) -> val + 1); Subscription subscription = hubConnection.on("inc", action); @@ -211,7 +211,7 @@ public class HubConnectionTest { public void RemoveSingleHandlerWithUnsubscribe() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); Action action = () -> value.getAndUpdate((val) -> val + 1); Action secondAction = () -> value.getAndUpdate((val) -> val + 2); @@ -241,7 +241,7 @@ public class HubConnectionTest { public void AddAndRemoveHandlerImmediatelyWithSubscribe() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); Action action = () -> value.getAndUpdate((val) -> val + 1); Subscription sub = hubConnection.on("inc", action); @@ -260,7 +260,7 @@ public class HubConnectionTest { public void RegisteringMultipleHandlersThatTakeParamsAndBothGetTriggered() throws Exception { AtomicReference value = new AtomicReference<>(0.0); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); Action1 action = (number) -> value.getAndUpdate((val) -> val + number); @@ -282,7 +282,7 @@ public class HubConnectionTest { public void SendWithNoParamsTriggersOnHandler() throws Exception { AtomicReference value = new AtomicReference(0.0); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", () ->{ assertEquals(0.0, value.get(), 0); @@ -301,7 +301,7 @@ public class HubConnectionTest { public void SendWithParamTriggersOnHandler() throws Exception { AtomicReference value = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param) ->{ assertNull(value.get()); @@ -323,7 +323,7 @@ public class HubConnectionTest { AtomicReference value2 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2) ->{ assertNull(value1.get()); @@ -350,7 +350,7 @@ public class HubConnectionTest { AtomicReference value3 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3) ->{ assertNull(value1.get()); @@ -381,7 +381,7 @@ public class HubConnectionTest { AtomicReference value4 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3, param4) ->{ assertNull(value1.get()); @@ -415,7 +415,7 @@ public class HubConnectionTest { AtomicReference value5 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3, param4, param5) ->{ assertNull(value1.get()); @@ -453,7 +453,7 @@ public class HubConnectionTest { AtomicReference value6 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3, param4, param5, param6) -> { assertNull(value1.get()); @@ -495,7 +495,7 @@ public class HubConnectionTest { AtomicReference value7 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3, param4, param5, param6, param7) -> { assertNull(value1.get()); @@ -541,7 +541,7 @@ public class HubConnectionTest { AtomicReference value8 = new AtomicReference<>(); MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", (param1, param2, param3, param4, param5, param6, param7, param8) -> { assertNull(value1.get()); @@ -580,8 +580,8 @@ public class HubConnectionTest { @Test public void ReceiveHandshakeResponseAndMessage() throws Exception { AtomicReference value = new AtomicReference(0.0); - MockTransport transport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", transport); + MockTransport mockTransport = new MockTransport(); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.on("inc", () ->{ assertEquals(0.0, value.get(), 0); @@ -591,9 +591,9 @@ public class HubConnectionTest { // On start we're going to receive the handshake response and also an invocation in the same payload. hubConnection.start(); String expectedSentMessage = "{\"protocol\":\"json\",\"version\":1}" + RECORD_SEPARATOR; - assertEquals(expectedSentMessage, transport.getSentMessages()[0]); + assertEquals(expectedSentMessage, mockTransport.getSentMessages()[0]); - transport.receiveMessage("{}" + RECORD_SEPARATOR + "{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR); + mockTransport.receiveMessage("{}" + RECORD_SEPARATOR + "{\"type\":1,\"target\":\"inc\",\"arguments\":[]}" + RECORD_SEPARATOR); // Confirming that our handler was called and that the counter property was incremented. assertEquals(1, value.get(), 0); @@ -603,7 +603,7 @@ public class HubConnectionTest { public void onClosedCallbackRunsWhenStopIsCalled() throws Exception { AtomicReference value1 = new AtomicReference<>(); Transport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.start(); hubConnection.onClosed((ex) -> { assertNull(value1.get()); @@ -620,7 +620,7 @@ public class HubConnectionTest { AtomicReference value1 = new AtomicReference<>(); AtomicReference value2 = new AtomicReference<>(); Transport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.start(); hubConnection.onClosed((ex) -> { @@ -645,7 +645,7 @@ public class HubConnectionTest { @Test public void HubConnectionClosesAndRunsOnClosedCallbackAfterCloseMessageWithError() throws Exception { MockTransport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.onClosed((ex) -> { assertEquals(ex.getMessage(), "There was an error"); }); @@ -662,7 +662,7 @@ public class HubConnectionTest { @Test public void CallingStartOnStartedHubConnectionNoOps() throws Exception { Transport mockTransport = new MockTransport(); - HubConnection hubConnection = new HubConnection("http://example.com", mockTransport); + HubConnection hubConnection = new HubConnection("http://example.com", mockTransport, true); hubConnection.start(); assertEquals(HubConnectionState.CONNECTED, hubConnection.getConnectionState()); diff --git a/clients/java/signalr/src/test/java/NegotiateResponseTest.java b/clients/java/signalr/src/test/java/NegotiateResponseTest.java new file mode 100644 index 0000000000..df5e18f8f1 --- /dev/null +++ b/clients/java/signalr/src/test/java/NegotiateResponseTest.java @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +import com.microsoft.aspnet.signalr.NegotiateResponse; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class NegotiateResponseTest { + + @Test + public void VerifyNegotiateResponse() { + String stringNegotiateResponse = "{\"connectionId\":\"bVOiRPG8-6YiJ6d7ZcTOVQ\",\"" + + "availableTransports\":[{\"transport\":\"WebSockets\",\"transferFormats\":[\"Text\",\"Binary\"]}," + + "{\"transport\":\"ServerSentEvents\",\"transferFormats\":[\"Text\"]}," + + "{\"transport\":\"LongPolling\",\"transferFormats\":[\"Text\",\"Binary\"]}]}"; + NegotiateResponse negotiateResponse = new NegotiateResponse(stringNegotiateResponse); + assertTrue(negotiateResponse.getAvailableTransports().contains("WebSockets")); + assertTrue(negotiateResponse.getAvailableTransports().contains("ServerSentEvents")); + assertTrue(negotiateResponse.getAvailableTransports().contains("LongPolling")); + assertNull(negotiateResponse.getAccessToken()); + assertNull(negotiateResponse.getRedirectUrl()); + assertEquals("bVOiRPG8-6YiJ6d7ZcTOVQ", negotiateResponse.getConnectionId()); + } + + @Test + public void VerifyRedirectNegotiateResponse() { + String stringNegotiateResponse = "{\"url\":\"www.example.com\"," + + "\"accessToken\":\"some_access_token\"," + + "\"availableTransports\":[]}"; + NegotiateResponse negotiateResponse = new NegotiateResponse(stringNegotiateResponse); + assertTrue(negotiateResponse.getAvailableTransports().isEmpty()); + assertNull(negotiateResponse.getConnectionId()); + assertEquals("some_access_token", negotiateResponse.getAccessToken()); + assertEquals("www.example.com", negotiateResponse.getRedirectUrl()); + assertNull(negotiateResponse.getConnectionId()); + } +} diff --git a/clients/java/signalr/src/test/java/ResolveNegotiateUrlTest.java b/clients/java/signalr/src/test/java/ResolveNegotiateUrlTest.java new file mode 100644 index 0000000000..c9964db532 --- /dev/null +++ b/clients/java/signalr/src/test/java/ResolveNegotiateUrlTest.java @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +import com.microsoft.aspnet.signalr.Negotiate; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; + +@RunWith(Parameterized.class) +public class ResolveNegotiateUrlTest { + private String url; + private String resolvedUrl; + + public ResolveNegotiateUrlTest(String url, String resolvedUrl) { + this.url = url; + this.resolvedUrl = resolvedUrl; + } + + @Parameterized.Parameters + public static Collection protocols() { + return Arrays.asList(new String[][]{ + {"http://example.com/hub/", "http://example.com/hub/negotiate"}, + {"http://example.com/hub", "http://example.com/hub/negotiate"}, + {"http://example.com/endpoint?q=my/Data", "http://example.com/endpoint/negotiate?q=my/Data"}, + {"http://example.com/endpoint/?q=my/Data", "http://example.com/endpoint/negotiate?q=my/Data"}, + {"http://example.com/endpoint/path/more?q=my/Data", "http://example.com/endpoint/path/more/negotiate?q=my/Data"},}); + } + + @Test + public void checkNegotiateUrl() { + String urlResult = Negotiate.resolveNegotiateUrl(this.url); + assertEquals(this.resolvedUrl, urlResult); + } +} \ No newline at end of file